diff --git a/.github/data/autoupdate/kernel-base.config b/.github/data/autoupdate/kernel-base.config new file mode 120000 index 0000000000..af2da35b4b --- /dev/null +++ b/.github/data/autoupdate/kernel-base.config @@ -0,0 +1 @@ +../../../pkg/arch/kernel/config \ No newline at end of file diff --git a/.github/data/autoupdate/kernel-overrides.config b/.github/data/autoupdate/kernel-overrides.config new file mode 100644 index 0000000000..e18e444ab7 --- /dev/null +++ b/.github/data/autoupdate/kernel-overrides.config @@ -0,0 +1,26 @@ +## +## Forcefully disable debug symbols +## + +CONFIG_DEBUG_INFO_NONE=y +# CONFIG_DEBUG_INFO is not set +# CONFIG_DEBUG_INFO_BTF is not set +# CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT is not set +# CONFIG_DEBUG_INFO_DWARF4 is not set +# CONFIG_DEBUG_INFO_DWARF5 is not set + +## +## Prevent BPF stuff from comlaining during boot because we have debug info disabled +## + +# CONFIG_BPF_PRELOAD is not set + +## +## ASHMEM / BINDERFS +## + +CONFIG_ASHMEM=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_BINDERFS=y +CONFIG_ANDROID_BINDER_DEVICES="" diff --git a/.github/data/autoupdate/maintained-branches.conf b/.github/data/autoupdate/maintained-branches.conf new file mode 100644 index 0000000000..7435574f39 --- /dev/null +++ b/.github/data/autoupdate/maintained-branches.conf @@ -0,0 +1,10 @@ +# Maintained kernel branches configuration +# Format: branch:version +# Example: v6.18-surface:6.18 +# +# The workflow will automatically trigger rebases when new stable tags +# matching the version are found (e.g., v6.18.5 for version 6.18) + +v6.18-surface:6.18 +v6.17-surface:6.17 +v6.12-surface:6.12 diff --git a/.github/scripts/autoupdate/build-kernel.sh b/.github/scripts/autoupdate/build-kernel.sh new file mode 100755 index 0000000000..f36aa3062d --- /dev/null +++ b/.github/scripts/autoupdate/build-kernel.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# +# Build kernel to validate rebase +# +# This script must be run from within a linux-surface/kernel git repository. +# It will: +# 1. Validate the kernel source directory and version +# 2. Collect and merge kernel configs (base + surface + overrides) +# 3. Build the kernel and modules +# 4. Output metadata for subsequent steps +# +# Environment variables: +# KERNEL_VERSION: (required) Major.minor version (e.g., "6.18") +# LINUX_SURFACE_DIR: (required) Path to linux-surface repository + +set -euo pipefail + +# Enable debug mode if DEBUG is set +if [ "${DEBUG:-}" = "1" ]; then + set -x +fi + +# ============================================================================ +# Phase 1: Validation +# ============================================================================ + +echo "============================================" +echo "Kernel Build Validation Script" +echo "============================================" +echo "" + +if [ -z "${KERNEL_VERSION:-}" ]; then + echo "ERROR: KERNEL_VERSION environment variable is required" + echo "Example: KERNEL_VERSION=6.18" + exit 1 +fi + +if [ -z "${LINUX_SURFACE_DIR:-}" ]; then + echo "ERROR: LINUX_SURFACE_DIR environment variable is required" + echo "Example: LINUX_SURFACE_DIR=/path/to/linux-surface" + exit 1 +fi + +echo "[1/5] Validating environment..." + +# Verify we're in a git repository with kernel source +if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "ERROR: Not in a git repository" + echo "This script must be run from within the kernel repository" + exit 1 +fi + +# Verify this looks like a kernel source tree +if [ ! -f "Makefile" ] || [ ! -d "kernel" ] || [ ! -d "drivers" ]; then + echo "ERROR: Current directory doesn't appear to be a kernel source tree" + echo "Expected files: Makefile, kernel/, drivers/" + exit 1 +fi + +# Validate KERNEL_VERSION format +if ! echo "${KERNEL_VERSION}" | grep -qE '^[0-9]+\.[0-9]+$'; then + echo "ERROR: Invalid KERNEL_VERSION format: ${KERNEL_VERSION}" + echo "Expected format: X.Y (e.g., 6.18)" + exit 1 +fi + +# Validate LINUX_SURFACE_DIR exists +if [ ! -d "${LINUX_SURFACE_DIR}" ]; then + echo "ERROR: LINUX_SURFACE_DIR does not exist: ${LINUX_SURFACE_DIR}" + exit 1 +fi + +echo "Kernel version: ${KERNEL_VERSION}" +echo "Linux-surface dir: ${LINUX_SURFACE_DIR}" +echo "Validation successful" + +# ============================================================================ +# Phase 2: Config Setup +# ============================================================================ + +echo "[2/5] Setting up kernel configs..." + +# Define config paths +BASE_CONFIG="${LINUX_SURFACE_DIR}/.github/data/autoupdate/kernel-base.config" +SURFACE_CONFIG="${LINUX_SURFACE_DIR}/configs/surface-${KERNEL_VERSION}.config" +OVERRIDES_CONFIG="${LINUX_SURFACE_DIR}/.github/data/autoupdate/kernel-overrides.config" + +# Verify all required configs exist +if [ ! -f "${BASE_CONFIG}" ]; then + echo "ERROR: Base config not found: ${BASE_CONFIG}" + exit 1 +fi + +if [ ! -f "${SURFACE_CONFIG}" ]; then + echo "ERROR: Surface config not found for version ${KERNEL_VERSION}: ${SURFACE_CONFIG}" + echo "Available surface configs:" + ls -1 "${LINUX_SURFACE_DIR}/configs"/surface-*.config 2>/dev/null || echo "(none found)" + exit 1 +fi + +if [ ! -f "${OVERRIDES_CONFIG}" ]; then + echo "ERROR: Overrides config not found: ${OVERRIDES_CONFIG}" + exit 1 +fi + +echo "Base config: ${BASE_CONFIG}" +echo "Surface config: ${SURFACE_CONFIG}" +echo "Overrides config: ${OVERRIDES_CONFIG}" + +# Copy configs to kernel directory with simplified names +cp "${BASE_CONFIG}" base.config +cp "${SURFACE_CONFIG}" surface.config +cp "${OVERRIDES_CONFIG}" overrides.config + +echo "Configs copied successfully" + +# ============================================================================ +# Phase 3: Config Merge (Three-Stage) +# ============================================================================ + +echo "[3/5] Merging kernel configs..." + +# Merge configs using kernel's merge_config.sh script +# Order is critical: base → surface → overrides +if ! ./scripts/kconfig/merge_config.sh -m base.config surface.config overrides.config; then + echo "" + echo "ERROR: Config merge failed" + echo "Check the output above for details" + exit 1 +fi + +echo "Running olddefconfig to validate merged config..." + +if ! make olddefconfig; then + echo "" + echo "ERROR: olddefconfig failed" + echo "The merged config may have invalid options" + exit 1 +fi + +echo "Config merge successful" +echo "Final config: $(pwd)/.config" + +# ============================================================================ +# Phase 4: Build Kernel +# ============================================================================ + +echo "[4/5] Building kernel..." + +# Set KBUILD environment variables +export KBUILD_BUILD_HOST=github-actions +export KBUILD_BUILD_USER=linux-surface + +# Determine number of parallel jobs +NPROC=$(nproc) +echo "Building with ${NPROC} parallel jobs..." + +# Build kernel and modules +# Using 'all' target builds both vmlinux and modules +echo "Running: make -j${NPROC} all" + +if ! make -j"${NPROC}" all; then + echo "" + echo "============================================" + echo "BUILD FAILED" + echo "============================================" + echo "" + echo "The kernel build failed. This indicates the rebase introduced" + echo "compilation errors or config incompatibilities." + echo "" + echo "Check the build output above for error details." + echo "" + exit 1 +fi + +echo "" +echo "============================================" +echo "Build successful!" +echo "============================================" +echo "" + +# ============================================================================ +# Phase 5: Output Metadata +# ============================================================================ + +echo "[5/5] Setting outputs..." + +# Set outputs for GitHub Actions +if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "build_success=true" >> "${GITHUB_OUTPUT}" + echo "config_path=$(pwd)/.config" >> "${GITHUB_OUTPUT}" +fi + +echo "Build validation complete!" +echo "Config saved at: $(pwd)/.config" +echo "" + +echo "Done!" diff --git a/.github/scripts/autoupdate/generate-patches.sh b/.github/scripts/autoupdate/generate-patches.sh new file mode 100755 index 0000000000..25102aa52b --- /dev/null +++ b/.github/scripts/autoupdate/generate-patches.sh @@ -0,0 +1,252 @@ +#!/usr/bin/env bash +# +# Generate kernel patches from linux-surface/kernel repository +# +# This script: +# 1. Clones the linux-surface/kernel repository +# 2. Detects the base kernel version using git describe +# 3. Uses git-format-patchsets to generate patches +# 4. Outputs patches to the appropriate directory +# +# Environment variables: +# KERNEL_BRANCH: The kernel branch to process (e.g., "v6.18-surface") +# BASE_VERSION_TAG: (optional) Base version tag to use (e.g., "v6.18.4") +# If not specified, auto-detected using git describe +# FORCE_UPDATE: (optional) Force update even if no changes + +set -euo pipefail + +# Enable debug mode if DEBUG is set +if [ "${DEBUG:-}" = "1" ]; then + set -x +fi + +# ============================================================================ +# Configuration and validation +# ============================================================================ + +if [ -z "${KERNEL_BRANCH:-}" ]; then + echo "ERROR: KERNEL_BRANCH environment variable is required" + echo "Example: KERNEL_BRANCH=v6.18-surface" + exit 1 +fi + +# Get absolute paths +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +WORK_DIR="${REPO_ROOT}/.tmp-patch-gen" + +# Configuration +KERNEL_REPO="https://github.com/linux-surface/kernel" +GIT_TOOLS_REPO="https://github.com/qzed/git-tools" + +echo "============================================" +echo "Generating patches for kernel branch ${KERNEL_BRANCH}" +echo "============================================" +echo "Kernel repo: ${KERNEL_REPO}" +echo "Branch: ${KERNEL_BRANCH}" +echo "" + +# ============================================================================ +# Setup working directory +# ============================================================================ + +echo "[1/6] Setting up working directory..." +rm -rf "${WORK_DIR}" +mkdir -p "${WORK_DIR}" +cd "${WORK_DIR}" + +# ============================================================================ +# Clone kernel repository +# ============================================================================ + +echo "[2/6] Cloning kernel repository..." + +# Clone with limited history depth to get all our added patches. +# Depth 250 should be sufficient to reach the base tag in most cases. +if ! git clone --depth=250 --branch="${KERNEL_BRANCH}" "${KERNEL_REPO}" kernel; then + echo "ERROR: Failed to clone kernel repository" + echo "Check that branch '${KERNEL_BRANCH}' exists" + exit 1 +fi + +cd kernel + +echo "Successfully cloned ${KERNEL_BRANCH}" + +# ============================================================================ +# Detect base kernel version +# ============================================================================ + +echo "[3/6] Detecting base kernel version..." + +# Check if BASE_VERSION_TAG was provided as input +if [ -n "${BASE_VERSION_TAG:-}" ]; then + echo "Using provided base version tag: ${BASE_VERSION_TAG}" + + # Validate format + if ! echo "${BASE_VERSION_TAG}" | grep -qE '^v[0-9]+\.[0-9]+(\.[0-9]+)?$'; then + echo "ERROR: Invalid base version tag format: ${BASE_VERSION_TAG}" + echo "Expected format: vX.Y or vX.Y.Z (e.g., v6.18, v6.18.4)" + exit 1 + fi + + # Verify the provided tag exists + if ! git rev-parse "${BASE_VERSION_TAG}" >/dev/null 2>&1; then + echo "ERROR: Provided base version tag '${BASE_VERSION_TAG}' not found in repository" + exit 1 + fi +else + echo "Auto-detecting base version using git describe..." + + # Use git describe to find the base version tag + # Pattern: v[0-9]*.[0-9]* matches vX.Y or vX.Y.Z (e.g., v6.18, v6.18.4, v7.0, v7.0.1) + # This requires at least one digit after 'v' and after the first dot + BASE_VERSION=$(git describe --tags --match='v[0-9]*.[0-9]*' 2>/dev/null || true) + + if [ -z "${BASE_VERSION}" ]; then + echo "ERROR: Could not detect base kernel version using git describe" + echo "Make sure the kernel branch has proper version tags" + exit 1 + fi + + # Extract just the version tag using grep + # This handles formats like: v6.18, v6.18.4, v6.18.4-123-gabcdef + BASE_VERSION_TAG=$(echo "${BASE_VERSION}" | grep -oE 'v[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1) + + if [ -z "${BASE_VERSION_TAG}" ]; then + echo "ERROR: Could not extract version tag from: ${BASE_VERSION}" + exit 1 + fi + + echo "Detected base version: ${BASE_VERSION}" +fi + +# Extract kernel major.minor version from base tag (e.g., v6.18.4 → 6.18, v6.18 → 6.18) +KERNEL_VERSION=$(echo "${BASE_VERSION_TAG}" | sed -E 's/^v([0-9]+\.[0-9]+)(\.[0-9]+)?$/\1/') + +if [ -z "${KERNEL_VERSION}" ]; then + echo "ERROR: Could not extract kernel version from tag: ${BASE_VERSION_TAG}" + exit 1 +fi + +echo "Base version tag: ${BASE_VERSION_TAG}" +echo "Kernel version: ${KERNEL_VERSION}" + +# Set patches directory based on detected version +PATCHES_DIR="${REPO_ROOT}/patches/${KERNEL_VERSION}" +echo "Patches directory: ${PATCHES_DIR}" +echo "" + +# ============================================================================ +# Install git-format-patchsets tool +# ============================================================================ + +echo "[4/6] Installing git-format-patchsets..." + +cd "${WORK_DIR}" +git clone --depth=1 "${GIT_TOOLS_REPO}" git-tools + +# git-format-patchsets is a bash script, make it executable and add to PATH +chmod +x git-tools/git-format-patchsets +export PATH="${WORK_DIR}/git-tools:${PATH}" + +# Verify tool is available +if ! command -v git-format-patchsets >/dev/null 2>&1; then + echo "ERROR: git-format-patchsets not found in PATH" + exit 1 +fi + +echo "Successfully installed git-format-patchsets" + +# ============================================================================ +# Generate patches +# ============================================================================ + +echo "[5/6] Generating patches..." + +cd "${WORK_DIR}/kernel" + +# Generate patches using git-format-patchsets +# Flags: +# -t: Extract patchset assignments from "Patchset:" tags +# --no-confirm: Don't ask for confirmation (non-interactive) +# Note: Patches are generated in the current directory (kernel dir) +PATCH_RANGE="${BASE_VERSION_TAG}" + +echo "Generating patches from range: ${PATCH_RANGE}" + +# Run git-format-patchsets with proper error handling +if ! git-format-patchsets -t --no-confirm "${PATCH_RANGE}"; then + echo "ERROR: git-format-patchsets failed" + exit 1 +fi + +# Count generated patches in current directory +PATCH_COUNT=$(find . -maxdepth 1 -name "*.patch" 2>/dev/null | wc -l) +echo "Generated ${PATCH_COUNT} patch files" + +if [ "${PATCH_COUNT}" -eq 0 ]; then + echo "WARNING: No patches were generated" + if [ "${FORCE_UPDATE:-false}" != "true" ]; then + echo "Skipping update (use FORCE_UPDATE=true to force)" + echo "has_changes=false" >> "${GITHUB_OUTPUT:-/dev/null}" + exit 0 + fi +fi + +# ============================================================================ +# Update patches directory +# ============================================================================ + +echo "[6/6] Updating patches directory..." + +# Create/clear the patches directory +mkdir -p "${PATCHES_DIR}" +rm -f "${PATCHES_DIR}"/*.patch + +# Copy generated patches from kernel directory +if [ "${PATCH_COUNT}" -gt 0 ]; then + mv "${WORK_DIR}/kernel"/*.patch "${PATCHES_DIR}/" + echo "Copied ${PATCH_COUNT} patches to ${PATCHES_DIR}" +fi + +# ============================================================================ +# Verify and report +# ============================================================================ + +echo "" +echo "============================================" +echo "Patch generation complete!" +echo "============================================" +echo "Branch: ${KERNEL_BRANCH}" +echo "Base version: ${BASE_VERSION_TAG}" +echo "Kernel version: ${KERNEL_VERSION}" +echo "Patches generated: ${PATCH_COUNT}" +echo "Output directory: ${PATCHES_DIR}" +echo "" + +# List generated patches +if [ "${PATCH_COUNT}" -gt 0 ]; then + echo "Generated patches:" + ls -1 "${PATCHES_DIR}"/*.patch | sort + echo "" +fi + +# Set output for GitHub Actions +if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "has_changes=true" >> "${GITHUB_OUTPUT}" + echo "patch_count=${PATCH_COUNT}" >> "${GITHUB_OUTPUT}" + echo "base_version=${BASE_VERSION_TAG}" >> "${GITHUB_OUTPUT}" + echo "kernel_version=${KERNEL_VERSION}" >> "${GITHUB_OUTPUT}" +fi + +# ============================================================================ +# Cleanup +# ============================================================================ + +echo "Cleaning up temporary files..." +cd "${REPO_ROOT}" +rm -rf "${WORK_DIR}" + +echo "Done!" diff --git a/.github/scripts/autoupdate/rebase-kernel.sh b/.github/scripts/autoupdate/rebase-kernel.sh new file mode 100755 index 0000000000..4ca33e88d2 --- /dev/null +++ b/.github/scripts/autoupdate/rebase-kernel.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash +# +# Rebase kernel branch onto latest stable tag +# +# This script must be run from within a linux-surface/kernel git repository. +# It will: +# 1. Detect the current base version using git describe +# 2. Detect the latest stable tag for the same kernel version +# 3. Check if already up-to-date using git merge-base +# 4. Perform rebase onto the target tag +# 5. Output metadata for subsequent build/tag/push steps +# +# Environment variables: +# TARGET_TAG: (optional) Explicit target tag (e.g., "v6.18.5") +# If not specified, auto-detects latest stable for detected version + +set -euo pipefail + +# Enable debug mode if DEBUG is set +if [ "${DEBUG:-}" = "1" ]; then + set -x +fi + +# ============================================================================ +# Phase 1: Setup +# ============================================================================ + +echo "============================================" +echo "Kernel Branch Rebase Script" +echo "============================================" +echo "" + +# Verify we're in a git repository +if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "ERROR: Not in a git repository" + echo "This script must be run from within the kernel repository" + exit 1 +fi + +echo "[1/6] Configuring environment..." + +# Configure git identity for rebase operations +git config user.name "surfacebot" +git config user.email "surfacebot@users.noreply.github.com" + +echo "Environment configured" + +# ============================================================================ +# Phase 2: Detect Current Base Version +# ============================================================================ + +echo "[2/6] Detecting current base version..." + +# Use git describe to find the current base version tag +# Pattern: v[0-9]*.[0-9]* matches vX.Y or vX.Y.Z (e.g., v6.18, v6.18.4, v7.0, v7.0.1) +CURRENT_BASE_VERSION=$(git describe --tags --match='v[0-9]*.[0-9]*' 2>/dev/null || true) + +if [ -z "${CURRENT_BASE_VERSION}" ]; then + echo "ERROR: Could not detect current base kernel version using git describe" + echo "Make sure the branch has proper version tags in its history" + exit 1 +fi + +echo "Current base version (git describe): ${CURRENT_BASE_VERSION}" + +# Extract just the version tag using grep +# This handles formats like: v6.18, v6.18.4, v6.18.4-123-gabcdef +CURRENT_BASE_TAG=$(echo "${CURRENT_BASE_VERSION}" | grep -oE 'v[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1) + +if [ -z "${CURRENT_BASE_TAG}" ]; then + echo "ERROR: Could not extract version tag from: ${CURRENT_BASE_VERSION}" + exit 1 +fi + +echo "Current base tag: ${CURRENT_BASE_TAG}" + +# Extract kernel major.minor version from base tag (e.g., v6.18.4 → 6.18, v6.18 → 6.18) +KERNEL_VERSION=$(echo "${CURRENT_BASE_TAG}" | sed -E 's/^v([0-9]+\.[0-9]+)(\.[0-9]+)?$/\1/') + +if [ -z "${KERNEL_VERSION}" ]; then + echo "ERROR: Could not extract kernel version from tag: ${CURRENT_BASE_TAG}" + exit 1 +fi + +echo "Kernel version: ${KERNEL_VERSION}" + +# ============================================================================ +# Phase 3: Target Tag Detection +# ============================================================================ + +echo "[3/6] Detecting target tag..." + +# Check if TARGET_TAG was provided as input +if [ -n "${TARGET_TAG:-}" ]; then + echo "Using provided target tag: ${TARGET_TAG}" + + # Validate format (vX.Y or vX.Y.Z) + if ! echo "${TARGET_TAG}" | grep -qE '^v[0-9]+\.[0-9]+(\.[0-9]+)?$'; then + echo "ERROR: Invalid target tag format: ${TARGET_TAG}" + echo "Expected format: vX.Y or vX.Y.Z (e.g., v6.18, v6.18.4)" + exit 1 + fi + + # Extract version from provided tag to validate it matches detected kernel version + TAG_VERSION=$(echo "${TARGET_TAG}" | sed -E 's/^v([0-9]+\.[0-9]+)(\..*)?$/\1/') + + if [ "${TAG_VERSION}" != "${KERNEL_VERSION}" ]; then + echo "ERROR: Target tag version '${TAG_VERSION}' doesn't match detected kernel version '${KERNEL_VERSION}'" + exit 1 + fi + + # Verify the provided tag exists + if ! git rev-parse "${TARGET_TAG}" >/dev/null 2>&1; then + echo "ERROR: Provided target tag '${TARGET_TAG}' not found in repository" + exit 1 + fi +else + echo "Auto-detecting latest stable tag for version ${KERNEL_VERSION}..." + + # Find latest stable tag for same version + # Pattern: v${KERNEL_VERSION}.* matches v6.18.1, v6.18.4, etc. + # Filter out RC releases and sort by version + TARGET_TAG=$(git tag --list "v${KERNEL_VERSION}.*" \ + --sort=-version:refname | \ + grep -v '\-rc' | \ + head -1) + + if [ -z "${TARGET_TAG}" ]; then + echo "ERROR: No stable tags found for version ${KERNEL_VERSION}" + echo "Available tags:" + git tag --list "v${KERNEL_VERSION}.*" | head -10 + exit 1 + fi + + echo "Detected target tag: ${TARGET_TAG}" +fi + +# Extract full version from target tag (v6.18.4 → 6.18.4) +FULL_VERSION=$(echo "${TARGET_TAG}" | sed -E 's/^v(.*)$/\1/') + +if [ -z "${FULL_VERSION}" ]; then + echo "ERROR: Could not extract full version from target tag: ${TARGET_TAG}" + exit 1 +fi + +echo "Full version: ${FULL_VERSION}" + +# ============================================================================ +# Phase 4: Ancestor Check (Up-to-Date) +# ============================================================================ + +echo "[4/6] Checking if already up-to-date..." + +# Check if target tag is already an ancestor of HEAD +if git merge-base --is-ancestor "${TARGET_TAG}" HEAD; then + echo "" + echo "============================================" + echo "Already up-to-date!" + echo "============================================" + echo "Current branch already contains ${TARGET_TAG}" + echo "No rebase needed." + echo "" + + # Set outputs for GitHub Actions + if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "success=true" >> "${GITHUB_OUTPUT}" + echo "already_uptodate=true" >> "${GITHUB_OUTPUT}" + echo "target_tag=${TARGET_TAG}" >> "${GITHUB_OUTPUT}" + echo "kernel_version=${KERNEL_VERSION}" >> "${GITHUB_OUTPUT}" + echo "full_version=${FULL_VERSION}" >> "${GITHUB_OUTPUT}" + fi + + exit 0 +fi + +echo "Branch needs rebasing onto ${TARGET_TAG}" + +# ============================================================================ +# Phase 6: Perform Rebase +# ============================================================================ + +echo "[5/6] Performing rebase..." + +echo "Rebasing onto ${TARGET_TAG}..." + +# Perform the rebase +if ! git rebase "${TARGET_TAG}"; then + echo "" + echo "============================================" + echo "REBASE FAILED - CONFLICTS DETECTED" + echo "============================================" + echo "" + echo "The rebase encountered conflicts. Manual intervention required." + echo "" + echo "Conflicted files:" + git status --short | grep '^[UAD][UAD]' || echo "(see git status for details)" + echo "" + + # Abort the rebase + git rebase --abort + + echo "Rebase has been aborted." + echo "" + echo "To resolve manually:" + echo " 1. Clone the repository locally" + echo " 2. git checkout " + echo " 3. git rebase ${TARGET_TAG}" + echo " 4. Resolve conflicts" + echo " 5. git rebase --continue" + echo " 6. Force-push the result" + echo "" + + exit 1 +fi + +echo "Rebase completed successfully!" + +# ============================================================================ +# Phase 6: Output Metadata +# ============================================================================ + +echo "[6/6] Setting outputs..." + +# Set outputs for GitHub Actions +if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "success=true" >> "${GITHUB_OUTPUT}" + echo "already_uptodate=false" >> "${GITHUB_OUTPUT}" + echo "target_tag=${TARGET_TAG}" >> "${GITHUB_OUTPUT}" + echo "kernel_version=${KERNEL_VERSION}" >> "${GITHUB_OUTPUT}" + echo "full_version=${FULL_VERSION}" >> "${GITHUB_OUTPUT}" +fi + +echo "" +echo "============================================" +echo "Rebase complete!" +echo "============================================" +echo "Target tag: ${TARGET_TAG}" +echo "Kernel version: ${KERNEL_VERSION}" +echo "Full version: ${FULL_VERSION}" diff --git a/.github/scripts/package/arch.sh b/.github/scripts/package/arch.sh index 21ab798cb7..972883fe8b 100644 --- a/.github/scripts/package/arch.sh +++ b/.github/scripts/package/arch.sh @@ -25,6 +25,14 @@ setup-builddeps) # Install tools for singing the kernel for secureboot pacman -S sbsigntools + + # Install actual build dependencies from PKGBUILD + pushd pkg/arch/kernel || exit 1 + + deps=$(bash -c 'source PKGBUILD && echo "${makedepends[@]}"') + pacman --noconfirm -S ${deps} + + popd || exit 1 ;; setup-secureboot) if [ -z "${SB_KEY:-}" ]; then @@ -40,7 +48,6 @@ build-packages) pushd pkg/arch/kernel || exit 1 # Fix permissions (can't makepkg as root) - echo "nobody ALL=(ALL) NOPASSWD: /usr/bin/pacman" >> /etc/sudoers chown -R nobody . # Package compression settings (Matches latest Arch) @@ -49,7 +56,7 @@ build-packages) export MAKEFLAGS="-j2" # Build - su nobody --pty -p -s /bin/bash -c 'makepkg -sf --skippgpcheck --noconfirm' + runuser -u nobody -- makepkg -sf --skippgpcheck --noconfirm # Prepare release mkdir release diff --git a/.github/scripts/package/debian.sh b/.github/scripts/package/debian.sh index 16964bd1ef..9548345764 100644 --- a/.github/scripts/package/debian.sh +++ b/.github/scripts/package/debian.sh @@ -20,6 +20,8 @@ MAINLINE_BRANCH="cod/mainline" case "${1:-}" in setup-builddeps) + export PATH="$HOME/.cargo/bin:$PATH" + SOURCES="$(sed 's/^deb /deb-src /' /etc/apt/sources.list)" echo "${SOURCES}" >> /etc/apt/sources.list @@ -29,7 +31,8 @@ setup-builddeps) apt-get update apt-get upgrade apt-get install build-essential fakeroot rsync git wget software-properties-common \ - zstd lz4 sbsigntool debhelper dpkg-dev dpkg-sig + zstd lz4 sbsigntool debhelper dpkg-dev dpkg-sig pkg-config \ + llvm-15 clang-15 libclang-15-dev apt-get build-dep linux # install python 3.11, required for configuring the kernel via Ubuntu's annotation format @@ -43,6 +46,11 @@ setup-builddeps) rm -f /usr/bin/python3 ln -s /usr/bin/python3.11 /usr/bin/python ln -s /usr/bin/python3.11 /usr/bin/python3 + + # install rust dependencies (required for now because of minimum version requirements) + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + rustup component add rust-src rustfmt clippy + cargo install --locked bindgen-cli ;; setup-secureboot) if [ -z "${SB_KEY:-}" ]; then @@ -57,6 +65,8 @@ setup-secureboot) cp pkg/keys/surface.crt pkg/debian/kernel/keys/MOK.crt ;; build-packages) + export PATH="$HOME/.cargo/bin:$PATH" + pushd pkg/debian/kernel || exit 1 . version.conf @@ -110,6 +120,9 @@ build-packages) # Set kernel localversion export LOCALVERSION="${KERNEL_LOCALVERSION}-${KERNEL_REVISION}" + # Get debug info for rust + make rustavailable + make bindeb-pkg -j "$(nproc)" popd || exit 1 diff --git a/.github/workflows/arch.yml b/.github/workflows/arch.yml index ff9b15e58c..87d3e269b8 100644 --- a/.github/workflows/arch.yml +++ b/.github/workflows/arch.yml @@ -25,7 +25,7 @@ jobs: remove-docker-images: true - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize containers run: | @@ -64,7 +64,7 @@ jobs: bash ./.github/scripts/package/arch.sh sign-packages - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: arch-latest path: pkg/arch/kernel/release @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: arch-latest path: arch-latest @@ -96,10 +96,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: arch-latest path: arch-latest diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 19a0ebc731..92e703c94c 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -25,12 +25,12 @@ jobs: remove-docker-images: true - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize containers run: | bash ./.github/scripts/container/create.sh \ - ubuntu:20.04 + ubuntu:22.04 - name: Install build dependencies run: | @@ -64,7 +64,7 @@ jobs: bash ./.github/scripts/package/debian.sh sign-packages - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debian-latest path: pkg/debian/release @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: debian-latest path: debian-latest @@ -96,10 +96,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: debian-latest path: debian-latest diff --git a/.github/workflows/fedora-40.yml b/.github/workflows/fedora-40.yml index aca862de0f..7026a2f8c3 100644 --- a/.github/workflows/fedora-40.yml +++ b/.github/workflows/fedora-40.yml @@ -26,7 +26,7 @@ jobs: remove-docker-images: true - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize containers run: | @@ -65,7 +65,7 @@ jobs: bash ./.github/scripts/package/fedora.sh sign-packages - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: pkg/fedora/kernel-surface/out/x86_64 @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: fedora-${{ env.FEDORA }}-latest @@ -97,10 +97,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: fedora-${{ env.FEDORA }}-latest diff --git a/.github/workflows/fedora-39.yml b/.github/workflows/fedora-41.yml similarity index 92% rename from .github/workflows/fedora-39.yml rename to .github/workflows/fedora-41.yml index b029a59b76..81232c6b53 100644 --- a/.github/workflows/fedora-39.yml +++ b/.github/workflows/fedora-41.yml @@ -1,13 +1,13 @@ -name: Fedora 39 +name: Fedora 41 env: - FEDORA: 39 + FEDORA: 41 GPG_KEY_ID: 56C464BAAC421453 on: push: tags: - - 'fedora-39-*' + - 'fedora-41-*' repository_dispatch: workflow_dispatch: @@ -26,7 +26,7 @@ jobs: remove-docker-images: true - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize containers run: | @@ -65,7 +65,7 @@ jobs: bash ./.github/scripts/package/fedora.sh sign-packages - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: pkg/fedora/kernel-surface/out/x86_64 @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: fedora-${{ env.FEDORA }}-latest @@ -97,10 +97,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: fedora-${{ env.FEDORA }}-latest diff --git a/.github/workflows/fedora-38.yml b/.github/workflows/fedora-42.yml similarity index 92% rename from .github/workflows/fedora-38.yml rename to .github/workflows/fedora-42.yml index 9b151aa7a3..8df6f1ab64 100644 --- a/.github/workflows/fedora-38.yml +++ b/.github/workflows/fedora-42.yml @@ -1,13 +1,13 @@ -name: Fedora 38 +name: Fedora 42 env: - FEDORA: 38 + FEDORA: 42 GPG_KEY_ID: 56C464BAAC421453 on: push: tags: - - 'fedora-38-*' + - 'fedora-42-*' repository_dispatch: workflow_dispatch: @@ -26,7 +26,7 @@ jobs: remove-docker-images: true - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize containers run: | @@ -65,7 +65,7 @@ jobs: bash ./.github/scripts/package/fedora.sh sign-packages - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: pkg/fedora/kernel-surface/out/x86_64 @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: fedora-${{ env.FEDORA }}-latest @@ -97,10 +97,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fedora-${{ env.FEDORA }}-latest path: fedora-${{ env.FEDORA }}-latest @@ -122,4 +122,4 @@ jobs: -e BRANCH_STAGING \ -e GITHUB_REPOSITORY \ -- \ - bash ./.github/scripts/repository/fedora.sh ${{ env.FEDORA }} \ No newline at end of file + bash ./.github/scripts/repository/fedora.sh ${{ env.FEDORA }} diff --git a/.github/workflows/fedora-43.yml b/.github/workflows/fedora-43.yml new file mode 100644 index 0000000000..70061087c5 --- /dev/null +++ b/.github/workflows/fedora-43.yml @@ -0,0 +1,125 @@ +name: Fedora 43 + +env: + FEDORA: 43 + GPG_KEY_ID: 56C464BAAC421453 + +on: + push: + tags: + - 'fedora-43-*' + + repository_dispatch: + workflow_dispatch: + +jobs: + build: + name: Build Kernel + runs-on: ubuntu-latest + steps: + - name: Maximize disk space + uses: easimon/maximize-build-space@master + with: + root-reserve-mb: 5120 + remove-dotnet: true + remove-android: true + remove-docker-images: true + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Initialize containers + run: | + bash ./.github/scripts/container/create.sh \ + registry.fedoraproject.org/fedora:${{ env.FEDORA }} + + - name: Install build dependencies + run: | + bash ./.github/scripts/container/exec.sh \ + -- \ + bash ./.github/scripts/package/fedora.sh setup-builddeps + + - name: Setup secureboot certificate + env: + SB_KEY: ${{ secrets.SURFACE_SB_KEY }} + run: | + bash ./.github/scripts/container/exec.sh \ + -e SB_KEY \ + -- \ + bash ./.github/scripts/package/fedora.sh setup-secureboot + + - name: Build packages + run: | + bash ./.github/scripts/container/exec.sh \ + -- \ + bash ./.github/scripts/package/fedora.sh build-packages + + - name: Sign packages + env: + GPG_KEY: ${{ secrets.LINUX_SURFACE_GPG_KEY }} + run: | + bash ./.github/scripts/container/exec.sh \ + -e GPG_KEY \ + -e GPG_KEY_ID \ + -- \ + bash ./.github/scripts/package/fedora.sh sign-packages + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: fedora-${{ env.FEDORA }}-latest + path: pkg/fedora/kernel-surface/out/x86_64 + + release: + name: Publish release + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') + needs: [build] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: fedora-${{ env.FEDORA }}-latest + path: fedora-${{ env.FEDORA }}-latest + + - name: Upload assets + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + file: ./*-latest/* + tag: ${{ github.ref }} + overwrite: true + file_glob: true + + repo: + name: Update package repository + needs: [release] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: fedora-${{ env.FEDORA }}-latest + path: fedora-${{ env.FEDORA }}-latest + + - name: Initialize containers + run: | + bash ./.github/scripts/container/create.sh \ + registry.fedoraproject.org/fedora:${{ env.FEDORA }} + + - name: Update repository + env: + SURFACEBOT_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + GIT_REF: ${{ github.ref }} + BRANCH_STAGING: u/staging + run: | + bash ./.github/scripts/container/exec.sh \ + -e SURFACEBOT_TOKEN \ + -e GIT_REF \ + -e BRANCH_STAGING \ + -e GITHUB_REPOSITORY \ + -- \ + bash ./.github/scripts/repository/fedora.sh ${{ env.FEDORA }} diff --git a/.github/workflows/generate-patches.yml b/.github/workflows/generate-patches.yml new file mode 100644 index 0000000000..e648bf7059 --- /dev/null +++ b/.github/workflows/generate-patches.yml @@ -0,0 +1,151 @@ +name: Generate Kernel Patches + +on: + # Manual trigger with inputs + workflow_dispatch: + inputs: + branch: + description: 'Kernel branch to update (e.g., "v6.18-surface")' + required: true + type: string + base_version: + description: 'Base version tag (optional, e.g., "v6.18.4"). If not specified, auto-detected with git describe' + required: false + type: string + force: + description: 'Force update even if no changes detected' + required: false + type: boolean + default: false + +jobs: + generate: + name: Generate patches for ${{ inputs.branch }} + runs-on: ubuntu-latest + + steps: + - name: Checkout linux-surface repo + uses: actions/checkout@v4 + with: + token: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + fetch-depth: 0 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y git + + - name: Generate patches + id: generate + env: + KERNEL_BRANCH: ${{ inputs.branch }} + BASE_VERSION_TAG: ${{ inputs.base_version }} + FORCE_UPDATE: ${{ inputs.force }} + run: | + bash .github/scripts/autoupdate/generate-patches.sh + + - name: Check if changes exist + id: check + run: | + if [ "${{ steps.generate.outputs.has_changes }}" = "true" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "No changes detected, skipping PR creation" + fi + + - name: Setup git identity + if: steps.check.outputs.has_changes == 'true' + run: | + git config user.name "surfacebot" + git config user.email "surfacebot@users.noreply.github.com" + + - name: Create branch and commit changes + if: steps.check.outputs.has_changes == 'true' + id: commit + run: | + # Extract kernel version and base version from script outputs + KERNEL_VERSION="${{ steps.generate.outputs.kernel_version }}" + BASE_VERSION="${{ steps.generate.outputs.base_version }}" + PATCH_COUNT="${{ steps.generate.outputs.patch_count }}" + + # Create unique branch name with timestamp + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + BRANCH="autoupdate/patches-v${KERNEL_VERSION}-${TIMESTAMP}" + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "kernel_version=${KERNEL_VERSION}" >> $GITHUB_OUTPUT + echo "base_version=${BASE_VERSION}" >> $GITHUB_OUTPUT + + # Create and switch to new branch + git checkout -b "${BRANCH}" + + # Stage changes in patches directory + git add patches/${KERNEL_VERSION}/ + + # Check if there are actual changes to commit + if git diff --cached --quiet; then + echo "No changes to commit" + echo "committed=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Create commit message + cat > /tmp/commit-msg <> $GITHUB_OUTPUT + + - name: Push branch + if: steps.check.outputs.has_changes == 'true' && steps.commit.outputs.committed == 'true' + run: | + git push -u origin "${{ steps.commit.outputs.branch }}" + + - name: Create Pull Request + if: steps.check.outputs.has_changes == 'true' && steps.commit.outputs.committed == 'true' + env: + GH_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + run: | + KERNEL_VERSION="${{ steps.commit.outputs.kernel_version }}" + BASE_VERSION="${{ steps.commit.outputs.base_version }}" + PATCH_COUNT="${{ steps.generate.outputs.patch_count }}" + BRANCH="${{ inputs.branch }}" + + gh pr create \ + --title "patches/v${KERNEL_VERSION}: Automatic update to ${BASE_VERSION}" \ + --body "$(cat <> "${GITHUB_OUTPUT}" + + - name: Push changes + if: steps.rebase.outputs.success == 'true' && steps.rebase.outputs.already_uptodate != 'true' && (inputs.build == false || steps.build.outputs.build_success == 'true') && inputs.dry_run == false + env: + KERNEL_BRANCH: ${{ inputs.branch }} + SURFACE_TAG: ${{ steps.create_tag.outputs.surface_tag }} + working-directory: kernel + run: | + echo "Force-pushing branch ${KERNEL_BRANCH}..." + git push --force-with-lease origin "${KERNEL_BRANCH}" + + echo "Pushing tag ${SURFACE_TAG}..." + git push origin "${SURFACE_TAG}" + + echo "Pushing new upstream tags..." + git push origin "refs/tags/v*" + + - name: Upload kernel config artifact + if: steps.build.outputs.build_success == 'true' + uses: actions/upload-artifact@v4 + with: + name: kernel-config-${{ inputs.branch }}-${{ steps.rebase.outputs.target_tag }} + path: kernel/.config + retention-days: 30 + + - name: Trigger patch generation + if: steps.rebase.outputs.success == 'true' && steps.rebase.outputs.already_uptodate != 'true' && (inputs.build == false || steps.build.outputs.build_success == 'true') && inputs.dry_run == false + env: + GH_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + KERNEL_BRANCH: ${{ inputs.branch }} + TARGET_TAG: ${{ steps.rebase.outputs.target_tag }} + run: | + echo "Triggering patch generation for ${KERNEL_BRANCH}..." + + gh workflow run generate-patches.yml \ + --repo ${{ github.repository }} \ + --field branch="${KERNEL_BRANCH}" \ + --field base_version="${TARGET_TAG}" + + echo "✓ Patch generation workflow triggered" + + - name: Generate summary + if: always() + env: + REBASE_SUCCESS: ${{ steps.rebase.outputs.success }} + ALREADY_UPTODATE: ${{ steps.rebase.outputs.already_uptodate }} + BUILD_ENABLED: ${{ inputs.build }} + BUILD_SUCCESS: ${{ steps.build.outputs.build_success }} + KERNEL_BRANCH: ${{ inputs.branch }} + TARGET_TAG: ${{ steps.rebase.outputs.target_tag }} + FULL_VERSION: ${{ steps.rebase.outputs.full_version }} + DRY_RUN: ${{ inputs.dry_run }} + run: | + echo "## Kernel Rebase and Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${REBASE_SUCCESS}" = "true" ]; then + if [ "${ALREADY_UPTODATE}" = "true" ]; then + echo "✅ **Status:** Already up-to-date" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Branch \`${KERNEL_BRANCH}\` already contains target tag \`${TARGET_TAG}\`" >> $GITHUB_STEP_SUMMARY + echo "No rebase was necessary." >> $GITHUB_STEP_SUMMARY + elif [ "${BUILD_ENABLED}" = "false" ] || [ "${BUILD_SUCCESS}" = "true" ]; then + echo "✅ **Status:** Success" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Branch:** \`${KERNEL_BRANCH}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Target tag:** \`${TARGET_TAG}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Full version:** \`${FULL_VERSION}\`" >> $GITHUB_STEP_SUMMARY + + if [ "${BUILD_ENABLED}" = "false" ]; then + echo "- **Build:** Skipped" >> $GITHUB_STEP_SUMMARY + else + echo "- **Build:** Passed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${DRY_RUN}" = "true" ]; then + echo "⚠️ **Dry run mode** - Changes were not pushed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "To push the changes, run again with dry_run=false" >> $GITHUB_STEP_SUMMARY + else + echo "✅ Changes pushed successfully" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Automatic Patch Generation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Patch generation workflow has been automatically triggered." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Check the [Generate Kernel Patches workflow runs](../../actions/workflows/generate-patches.yml) for progress." >> $GITHUB_STEP_SUMMARY + fi + else + echo "❌ **Status:** Build failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Rebase succeeded but kernel build failed." >> $GITHUB_STEP_SUMMARY + echo "Check the build logs for errors." >> $GITHUB_STEP_SUMMARY + fi + else + echo "❌ **Status:** Rebase failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Check the rebase logs for conflict details." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/sync-kernel-upstream.yml b/.github/workflows/sync-kernel-upstream.yml new file mode 100644 index 0000000000..4a5e1ab9fc --- /dev/null +++ b/.github/workflows/sync-kernel-upstream.yml @@ -0,0 +1,214 @@ +name: Sync Kernel with Upstream + +on: + # Manual trigger + workflow_dispatch: + + # Automatic daily sync at 7:30 AM UTC + schedule: + - cron: '30 7 * * *' + +jobs: + sync: + name: Sync with upstream + runs-on: ubuntu-latest + + steps: + - name: Cache kernel repository + uses: actions/cache@v4 + with: + path: kernel/.git + key: kernel-git-${{ github.run_number }} + restore-keys: | + kernel-git- + + - name: Clean up old caches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Cleaning up old kernel-git caches (keeping only the latest)..." + gh cache list --repo ${{ github.repository }} --key kernel-git- --json id,key,createdAt --limit 100 | \ + jq -r 'sort_by(.createdAt) | reverse | .[1:] | .[].id' | \ + xargs -I {} gh cache delete {} --repo ${{ github.repository }} || true + + - name: Checkout linux-surface repo + uses: actions/checkout@v6 + with: + repository: linux-surface/linux-surface + path: linux-surface + token: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + + - name: Checkout kernel repository + uses: actions/checkout@v6 + with: + repository: linux-surface/kernel + path: kernel + fetch-depth: 0 + token: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + + - name: Setup git identity + run: | + git config --global user.name "surfacebot" + git config --global user.email "surfacebot@users.noreply.github.com" + + - name: Sync with upstream + id: sync + working-directory: kernel + run: | + echo "Getting current origin tags..." + git ls-remote --tags origin | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}' | sort > /tmp/origin-tags.txt + + echo "Adding upstream remote..." + git remote add upstream https://github.com/gregkh/linux || true + + echo "Fetching from upstream..." + git fetch upstream --tags + + echo "Counting new tags..." + # Get tags that exist in upstream but not in origin + git ls-remote --tags upstream | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}' | sort > /tmp/upstream-tags.txt + NEW_TAGS=$(comm -13 /tmp/origin-tags.txt /tmp/upstream-tags.txt | wc -l) + + echo "new_tags=${NEW_TAGS}" >> "${GITHUB_OUTPUT}" + echo "Found ${NEW_TAGS} new tags" + + # Save new tags list for later processing + comm -13 /tmp/origin-tags.txt /tmp/upstream-tags.txt > /tmp/new-tags.txt + + # Show some of the new tags (first 10) + echo "Sample of new tags:" + cat /tmp/new-tags.txt | head -10 + + echo "Updating master branch..." + git checkout -B master origin/master + git merge --ff-only upstream/master || { + echo "ERROR: Cannot fast-forward master branch" + echo "Manual intervention may be required" + exit 1 + } + + echo "master_updated=true" >> "${GITHUB_OUTPUT}" + + - name: Push changes + if: steps.sync.outputs.new_tags != '0' || steps.sync.outputs.master_updated == 'true' + working-directory: kernel + run: | + echo "Pushing new tags..." + git push origin --tags + + echo "Pushing master branch..." + git push origin master + + - name: Check for maintained branch updates + id: check_maintained + if: steps.sync.outputs.new_tags != '0' + run: | + CONFIG_FILE="${GITHUB_WORKSPACE}/linux-surface/.github/data/autoupdate/maintained-branches.conf" + + echo "Reading maintained branches from ${CONFIG_FILE}..." + + # Read config file, skip comments and empty lines + BRANCHES_TO_REBASE="" + + while IFS=: read -r BRANCH VERSION; do + # Skip comments and empty lines + [[ "$BRANCH" =~ ^#.*$ ]] && continue + [[ -z "$BRANCH" ]] && continue + + echo "Checking for version ${VERSION} (branch: ${BRANCH})..." + + # Check if any new tags match this version (e.g., v6.18.*, excluding -rc) + MATCHING_TAGS=$(grep -E "^v${VERSION}\.[0-9]+$" /tmp/new-tags.txt || true) + + if [ -n "$MATCHING_TAGS" ]; then + LATEST_TAG=$(echo "$MATCHING_TAGS" | sort -V | tail -1) + echo " ✓ Found new release for ${BRANCH}: ${LATEST_TAG}" + BRANCHES_TO_REBASE="${BRANCHES_TO_REBASE}${BRANCH}:${LATEST_TAG}," + else + echo " - No new releases for ${BRANCH}" + fi + done < "$CONFIG_FILE" + + # Remove trailing comma + BRANCHES_TO_REBASE="${BRANCHES_TO_REBASE%,}" + + echo "branches_to_rebase=${BRANCHES_TO_REBASE}" >> "${GITHUB_OUTPUT}" + + if [ -n "$BRANCHES_TO_REBASE" ]; then + echo "" + echo "Branches to rebase: ${BRANCHES_TO_REBASE}" + else + echo "" + echo "No maintained branches need rebasing" + fi + + - name: Trigger rebase workflows + if: steps.check_maintained.outputs.branches_to_rebase != '' + env: + GH_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} + BRANCHES_TO_REBASE: ${{ steps.check_maintained.outputs.branches_to_rebase }} + run: | + echo "Triggering rebase workflows for updated branches..." + echo "" + + IFS=',' read -ra BRANCH_PAIRS <<< "$BRANCHES_TO_REBASE" + for pair in "${BRANCH_PAIRS[@]}"; do + BRANCH=$(echo "$pair" | cut -d: -f1) + TAG=$(echo "$pair" | cut -d: -f2) + + echo "Triggering rebase for ${BRANCH} to ${TAG}..." + gh workflow run rebase-kernel.yml \ + --repo ${{ github.repository }} \ + --field branch="${BRANCH}" \ + --field target_tag="${TAG}" \ + --field build=true \ + --field dry_run=false + + echo " ✓ Triggered" + echo "" + done + + - name: Generate summary + if: always() + env: + NEW_TAGS: ${{ steps.sync.outputs.new_tags }} + MASTER_UPDATED: ${{ steps.sync.outputs.master_updated }} + BRANCHES_TO_REBASE: ${{ steps.check_maintained.outputs.branches_to_rebase }} + run: | + echo "## Kernel Upstream Sync Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${MASTER_UPDATED}" = "true" ]; then + echo "✅ **Status:** Success" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **New tags:** ${NEW_TAGS:-0}" >> $GITHUB_STEP_SUMMARY + echo "- **Master branch:** Updated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ -n "${BRANCHES_TO_REBASE}" ]; then + echo "### Automatic Rebases Triggered" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The following branches will be automatically rebased:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + IFS=',' read -ra BRANCH_PAIRS <<< "$BRANCHES_TO_REBASE" + for pair in "${BRANCH_PAIRS[@]}"; do + BRANCH=$(echo "$pair" | cut -d: -f1) + TAG=$(echo "$pair" | cut -d: -f2) + echo "- **${BRANCH}** → ${TAG}" >> $GITHUB_STEP_SUMMARY + done + + echo "" >> $GITHUB_STEP_SUMMARY + echo "Check the [Rebase and Build Kernel workflow runs](../../actions/workflows/rebase-kernel.yml) for progress." >> $GITHUB_STEP_SUMMARY + elif [ "${NEW_TAGS:-0}" -gt 0 ]; then + echo "### New Tags" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "New tags were synced, but none match maintained branch versions." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "To update maintained branches, edit \`.github/data/autoupdate/maintained-branches.txt\`" >> $GITHUB_STEP_SUMMARY + fi + else + echo "ℹ️ **Status:** No changes" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Repository is already up-to-date with upstream." >> $GITHUB_STEP_SUMMARY + fi diff --git a/README.md b/README.md index c5d841493e..ecdedb00a9 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,12 @@ If your device is not listed as supported yet, feel free to open an issue. * Surface Laptop 3 * Surface Laptop 4 * Surface Laptop 5 +* Surface Laptop 6 * Surface Laptop Go * Surface Laptop Go 2 +* Surface Laptop Go 3 * Surface Laptop Studio +* Surface Laptop Studio 2 * Surface Pro 1 * Surface Pro 3 * Surface Pro 4 @@ -60,6 +63,7 @@ If your device is not listed as supported yet, feel free to open an issue. * Surface Pro 7+ * Surface Pro 8 * Surface Pro 9 +* Surface Pro 10 * Surface Studio ### Features / What's Working @@ -96,7 +100,6 @@ If you have questions or need support, please join our [Matrix Space][matrix-spa This space contains - a [support channel][matrix-support] for general support and - a [development channel][matrix-development] for all development related questions and discussions. - If you prefer IRC, you can also join in via our channel at [`libera.chat/#linux-surface`][liberachat], to which the matrix room is bridged to. ## License This repository contains patches, which are either derivative work targeting a specific already licensed source, i.e. parts of the Linux kernel, or introduce new parts to the Linux kernel. @@ -112,7 +115,6 @@ License texts can be obtained at https://github.com/torvalds/linux/tree/master/L [matrix-space]: https://matrix.to/#/#linux-surface:matrix.org [matrix-support]: https://matrix.to/#/#linux-surface-support:matrix.org [matrix-development]: https://matrix.to/#/#linux-surface-development:matrix.org -[liberachat]: https://web.libera.chat/#linux-surface [hibernate-setup]: https://fitzcarraldoblog.wordpress.com/2018/07/14/configuring-lubuntu-18-04-to-enable-hibernation-using-a-swap-file [releases]: https://github.com/linux-surface/linux-surface/releases diff --git a/configs/surface-6.8.config b/configs/surface-6.10.config similarity index 92% rename from configs/surface-6.8.config rename to configs/surface-6.10.config index fedb251cf3..05628a9957 100644 --- a/configs/surface-6.8.config +++ b/configs/surface-6.10.config @@ -65,6 +65,12 @@ CONFIG_VIDEO_OV8865=m ## CONFIG_APDS9960=m +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + ## ## Other Drivers ## diff --git a/configs/surface-6.11.config b/configs/surface-6.11.config new file mode 100644 index 0000000000..05628a9957 --- /dev/null +++ b/configs/surface-6.11.config @@ -0,0 +1,81 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/configs/surface-6.12.config b/configs/surface-6.12.config new file mode 100644 index 0000000000..05628a9957 --- /dev/null +++ b/configs/surface-6.12.config @@ -0,0 +1,81 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/configs/surface-6.13.config b/configs/surface-6.13.config new file mode 100644 index 0000000000..d58d539727 --- /dev/null +++ b/configs/surface-6.13.config @@ -0,0 +1,83 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +CONFIG_RTC_DRV_SURFACE=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/configs/surface-6.14.config b/configs/surface-6.14.config new file mode 100644 index 0000000000..a026c661b5 --- /dev/null +++ b/configs/surface-6.14.config @@ -0,0 +1,85 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +CONFIG_RTC_DRV_SURFACE=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m +CONFIG_INTEL_THC_HID=m +CONFIG_INTEL_QUICKSPI=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/configs/surface-6.15.config b/configs/surface-6.15.config new file mode 100644 index 0000000000..a026c661b5 --- /dev/null +++ b/configs/surface-6.15.config @@ -0,0 +1,85 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +CONFIG_RTC_DRV_SURFACE=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m +CONFIG_INTEL_THC_HID=m +CONFIG_INTEL_QUICKSPI=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/configs/surface-6.16.config b/configs/surface-6.16.config new file mode 100644 index 0000000000..a026c661b5 --- /dev/null +++ b/configs/surface-6.16.config @@ -0,0 +1,85 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +CONFIG_RTC_DRV_SURFACE=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m +CONFIG_INTEL_THC_HID=m +CONFIG_INTEL_QUICKSPI=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/configs/surface-6.17.config b/configs/surface-6.17.config new file mode 100644 index 0000000000..016efc4198 --- /dev/null +++ b/configs/surface-6.17.config @@ -0,0 +1,86 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +CONFIG_RTC_DRV_SURFACE=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m +CONFIG_INTEL_THC_HID=m +CONFIG_INTEL_QUICKSPI=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m +CONFIG_HID_SURFACE=m diff --git a/configs/surface-6.18.config b/configs/surface-6.18.config new file mode 100644 index 0000000000..016efc4198 --- /dev/null +++ b/configs/surface-6.18.config @@ -0,0 +1,86 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +CONFIG_RTC_DRV_SURFACE=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m +CONFIG_INTEL_THC_HID=m +CONFIG_INTEL_QUICKSPI=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m +CONFIG_HID_SURFACE=m diff --git a/configs/surface-6.6.config b/configs/surface-6.6.config index fedb251cf3..05628a9957 100644 --- a/configs/surface-6.6.config +++ b/configs/surface-6.6.config @@ -65,6 +65,12 @@ CONFIG_VIDEO_OV8865=m ## CONFIG_APDS9960=m +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + ## ## Other Drivers ## diff --git a/configs/surface-6.9.config b/configs/surface-6.9.config new file mode 100644 index 0000000000..05628a9957 --- /dev/null +++ b/configs/surface-6.9.config @@ -0,0 +1,81 @@ +## +## Surface Aggregator Module +## +CONFIG_SURFACE_AGGREGATOR=m +# CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +CONFIG_SENSORS_SURFACE_TEMP=m +CONFIG_SENSORS_SURFACE_FAN=m + +## +## Surface Hotplug +## +CONFIG_SURFACE_HOTPLUG=m + +## +## IPTS and ITHC touchscreen +## +## This only enables the user interface for IPTS/ITHC data. +## For the touchscreen to work, you need to install iptsd. +## +CONFIG_HID_IPTS=m +CONFIG_HID_ITHC=m + +## +## Cameras: IPU3 +## +CONFIG_VIDEO_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_IPU_BRIDGE=m +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=m +CONFIG_LEDS_TPS68470=m + +## +## Cameras: Sensor drivers +## +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV8865=m + +## +## Surface 3: atomisp causes problems (see issue #1095). Disable it for now. +## +# CONFIG_INTEL_ATOMISP is not set + +## +## ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +## +CONFIG_APDS9960=m + +## +## Build-in UFS support (required for some Surface Go devices) +## +CONFIG_SCSI_UFSHCD=m +CONFIG_SCSI_UFSHCD_PCI=m + +## +## Other Drivers +## +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/contrib/thermald/surface_laptop_4a/README.md b/contrib/thermald/surface_laptop_4a/README.md index 7fc9e99653..d9734fd5dd 100644 --- a/contrib/thermald/surface_laptop_4a/README.md +++ b/contrib/thermald/surface_laptop_4a/README.md @@ -30,7 +30,7 @@ sudo systemctl stop thermald.service Place thermal-conf.xml into the following directory: ``` -\etc\thermald\ +/etc/thermald/ ``` Restart thermald: diff --git a/patches/6.10/0001-secureboot.patch b/patches/6.10/0001-secureboot.patch new file mode 100644 index 0000000000..a84252cca5 --- /dev/null +++ b/patches/6.10/0001-secureboot.patch @@ -0,0 +1,112 @@ +From 2dd7c57b604b7cae49efcebc5fb6e46f7401a69b Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index b5c79f43359b..a1bbedd989e4 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.46.1 + +From 8c63d9f68dff804be41fcf71b725a1e28c78118f Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index c82446cef8e2..2ae1a6fda7f9 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3000,6 +3000,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index 0a213f69a9e4..8e4f9dcc9f4c 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -37,6 +37,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -92,7 +93,7 @@ void hibernate_release(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1422,6 +1423,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1480,3 +1487,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.46.1 + diff --git a/patches/6.8/0001-surface3-oemb.patch b/patches/6.10/0002-surface3-oemb.patch similarity index 95% rename from patches/6.8/0001-surface3-oemb.patch rename to patches/6.10/0002-surface3-oemb.patch index 05577d4579..4370ef10fb 100644 --- a/patches/6.8/0001-surface3-oemb.patch +++ b/patches/6.10/0002-surface3-oemb.patch @@ -1,4 +1,4 @@ -From 45a9e7f97fc36942e3d70a78fe5313fa78733933 Mon Sep 17 00:00:00 2001 +From 3d3aec0cd78fd07d406486dd140daa7baf1fba7c Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 18 Oct 2020 16:42:44 +0900 Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI @@ -58,10 +58,10 @@ index c15ed7a12784..1ec8edb5aafa 100644 { } }; diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 20191a4473c2..5ba599b5aba6 100644 +index 51187b1e0ed2..bfb83ce8d8f8 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c -@@ -3768,6 +3768,15 @@ static const struct dmi_system_id dmi_platform_data[] = { +@@ -3790,6 +3790,15 @@ static const struct dmi_system_id dmi_platform_data[] = { }, .driver_data = (void *)&intel_braswell_platform_data, }, @@ -97,5 +97,5 @@ index 5e2ec60e2954..207868c699f2 100644 }; -- -2.44.0 +2.46.1 diff --git a/patches/6.8/0002-mwifiex.patch b/patches/6.10/0003-mwifiex.patch similarity index 98% rename from patches/6.8/0002-mwifiex.patch rename to patches/6.10/0003-mwifiex.patch index dd5fa2315a..97ba5fcfe9 100644 --- a/patches/6.8/0002-mwifiex.patch +++ b/patches/6.10/0003-mwifiex.patch @@ -1,4 +1,4 @@ -From 2c7ff35a85341dcd8fa2ea575088881df9dea874 Mon Sep 17 00:00:00 2001 +From a50739a5e7b0ca9f6bca85606f2b9389a9dd6bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Tue, 3 Nov 2020 13:28:04 +0100 Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface @@ -163,9 +163,9 @@ index d6ff964aec5b..5d30ae39d65e 100644 void mwifiex_initialize_quirks(struct pcie_service_card *card); int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -- -2.44.0 +2.46.1 -From 4a326d9e87d1dc4945903560d3d22fbd69a8962c Mon Sep 17 00:00:00 2001 +From 883ef69235ff8daa9653576dd6104460f60b7bde Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 4 Oct 2020 00:11:49 +0900 Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ @@ -318,9 +318,9 @@ index 5d30ae39d65e..c14eb56eb911 100644 void mwifiex_initialize_quirks(struct pcie_service_card *card); int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -- -2.44.0 +2.46.1 -From dddda6f9c25716dea1265f71b8286936afa192b5 Mon Sep 17 00:00:00 2001 +From 9e1720f0132e61bc6f738284080c6925941e1a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Thu, 25 Mar 2021 11:33:02 +0100 Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell @@ -356,7 +356,7 @@ Patchset: mwifiex 1 file changed, 15 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index d31edad7a056..fc08e0c51c87 100644 +index 0927f51867c2..3d3573829631 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -65,6 +65,7 @@ static struct usb_driver btusb_driver; @@ -375,7 +375,7 @@ index d31edad7a056..fc08e0c51c87 100644 /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, -@@ -4401,6 +4403,19 @@ static int btusb_probe(struct usb_interface *intf, +@@ -4448,6 +4450,19 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_MARVELL) hdev->set_bdaddr = btusb_set_bdaddr_marvell; @@ -396,5 +396,5 @@ index d31edad7a056..fc08e0c51c87 100644 (id->driver_info & BTUSB_MEDIATEK)) { hdev->setup = btusb_mtk_setup; -- -2.44.0 +2.46.1 diff --git a/patches/6.10/0004-ath10k.patch b/patches/6.10/0004-ath10k.patch new file mode 100644 index 0000000000..79ea37ce13 --- /dev/null +++ b/patches/6.10/0004-ath10k.patch @@ -0,0 +1,120 @@ +From 68efac8fe5bd9af5e3c3e1d3cf7d9d2a3a5f4248 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index bdf0552cd1c3..e062cc687689 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -39,6 +39,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -51,6 +54,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -60,6 +66,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -914,6 +923,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -928,6 +973,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.46.1 + diff --git a/patches/6.8/0004-ipts.patch b/patches/6.10/0005-ipts.patch similarity index 98% rename from patches/6.8/0004-ipts.patch rename to patches/6.10/0005-ipts.patch index 383a872937..817b65d4db 100644 --- a/patches/6.8/0004-ipts.patch +++ b/patches/6.10/0005-ipts.patch @@ -1,4 +1,4 @@ -From 4a1fdfebd3e84fe581b512f73bbc551ec9e2d0e8 Mon Sep 17 00:00:00 2001 +From 7e608ebdf188a298503111e503a4cc2fc3880f7c Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Thu, 30 Jul 2020 13:21:53 +0200 Subject: [PATCH] mei: me: Add Icelake device ID for iTouch @@ -11,7 +11,7 @@ Patchset: ipts 2 files changed, 2 insertions(+) diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index aac36750d2c5..2ba8db8a9583 100644 +index c3a6657dcd4a..82eef2f4eb0a 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -92,6 +92,7 @@ @@ -23,7 +23,7 @@ index aac36750d2c5..2ba8db8a9583 100644 #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index 8cf636c54032..078d3e773cda 100644 +index 6589635f8ba3..a1df48a434e2 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { @@ -35,9 +35,9 @@ index 8cf636c54032..078d3e773cda 100644 {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, -- -2.44.0 +2.46.1 -From b1e22125c8f241f49cc3a6b7eaa0b6430bb1f1ce Mon Sep 17 00:00:00 2001 +From 8b12cf7a1fe199057c7a39087d233f62fd4d6a93 Mon Sep 17 00:00:00 2001 From: Liban Hannan Date: Tue, 12 Apr 2022 23:31:12 +0100 Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS @@ -61,7 +61,7 @@ Patchset: ipts 1 file changed, 29 insertions(+) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index 11652e0bcab3..6c01b1aebf27 100644 +index e9bea0305c26..6ee97bf7b6a9 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -40,6 +40,11 @@ @@ -76,24 +76,24 @@ index 11652e0bcab3..6c01b1aebf27 100644 #define IOAPIC_RANGE_START (0xfee00000) #define IOAPIC_RANGE_END (0xfeefffff) #define IOVA_START_ADDR (0x1000) -@@ -148,12 +153,14 @@ int intel_iommu_enabled = 0; +@@ -217,12 +222,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; EXPORT_SYMBOL_GPL(intel_iommu_enabled); - static int dmar_map_gfx = 1; +static int dmar_map_ipts = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; + static int disable_igfx_iommu; - #define IDENTMAP_GFX 2 #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; static const struct iommu_dirty_ops intel_dirty_ops; -@@ -2412,6 +2419,9 @@ static int device_def_domain_type(struct device *dev) +@@ -2195,6 +2202,9 @@ static int device_def_domain_type(struct device *dev) - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) return IOMMU_DOMAIN_IDENTITY; + + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) @@ -101,9 +101,9 @@ index 11652e0bcab3..6c01b1aebf27 100644 } return 0; -@@ -2719,6 +2729,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; +@@ -2495,6 +2505,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; @@ -111,8 +111,8 @@ index 11652e0bcab3..6c01b1aebf27 100644 check_tylersburg_isoch(); ret = si_domain_init(hw_pass_through); -@@ -4896,6 +4909,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; +@@ -4617,6 +4630,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; } +static void quirk_iommu_ipts(struct pci_dev *dev) @@ -130,7 +130,7 @@ index 11652e0bcab3..6c01b1aebf27 100644 /* G4x/GM45 integrated gfx dmar support is totally busted. */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -4931,6 +4956,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); +@@ -4652,6 +4677,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); @@ -142,9 +142,9 @@ index 11652e0bcab3..6c01b1aebf27 100644 { if (risky_device(dev)) -- -2.44.0 +2.46.1 -From fa7796bc06659b87f47d8921d0441314612870b9 Mon Sep 17 00:00:00 2001 +From fd63fc042c6554c75134e4dab8e4a1d74c495d05 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Sun, 11 Dec 2022 12:00:59 +0100 Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus @@ -164,16 +164,16 @@ Patchset: ipts drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ drivers/hid/ipts/control.h | 126 +++++++++ drivers/hid/ipts/desc.h | 80 ++++++ - drivers/hid/ipts/eds1.c | 103 +++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ drivers/hid/ipts/eds1.h | 35 +++ - drivers/hid/ipts/eds2.c | 144 ++++++++++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ drivers/hid/ipts/eds2.h | 35 +++ drivers/hid/ipts/hid.c | 225 +++++++++++++++ drivers/hid/ipts/hid.h | 24 ++ drivers/hid/ipts/main.c | 126 +++++++++ drivers/hid/ipts/mei.c | 188 +++++++++++++ drivers/hid/ipts/mei.h | 66 +++++ - drivers/hid/ipts/receiver.c | 250 +++++++++++++++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ drivers/hid/ipts/receiver.h | 16 ++ drivers/hid/ipts/resources.c | 131 +++++++++ drivers/hid/ipts/resources.h | 41 +++ @@ -182,7 +182,7 @@ Patchset: ipts drivers/hid/ipts/spec-hid.h | 34 +++ drivers/hid/ipts/thread.c | 84 ++++++ drivers/hid/ipts/thread.h | 59 ++++ - 28 files changed, 2850 insertions(+) + 28 files changed, 2853 insertions(+) create mode 100644 drivers/hid/ipts/Kconfig create mode 100644 drivers/hid/ipts/Makefile create mode 100644 drivers/hid/ipts/cmd.c @@ -211,10 +211,10 @@ Patchset: ipts create mode 100644 drivers/hid/ipts/thread.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig -index 4c682c650704..a263e49b2ae2 100644 +index 08446c89eff6..ccddfba86004 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig -@@ -1351,4 +1351,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" +@@ -1367,4 +1367,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" source "drivers/hid/surface-hid/Kconfig" @@ -222,10 +222,10 @@ index 4c682c650704..a263e49b2ae2 100644 + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile -index 082a728eac60..f4bad1b8d813 100644 +index ce71b53ea6c5..de41081b6a5a 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile -@@ -170,3 +170,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ +@@ -171,3 +171,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ @@ -1176,10 +1176,10 @@ index 000000000000..307438c7c80c +#endif /* IPTS_DESC_H */ diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c new file mode 100644 -index 000000000000..ecbb3a8bdaf6 +index 000000000000..7b9f54388a9f --- /dev/null +++ b/drivers/hid/ipts/eds1.c -@@ -0,0 +1,103 @@ +@@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll @@ -1196,6 +1196,7 @@ index 000000000000..ecbb3a8bdaf6 +#include "context.h" +#include "control.h" +#include "desc.h" ++#include "eds1.h" +#include "spec-device.h" + +int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) @@ -1326,10 +1327,10 @@ index 000000000000..eeeb6575e3e8 + enum hid_report_type report_type, enum hid_class_request request_type); diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c new file mode 100644 -index 000000000000..198dc65d7887 +index 000000000000..639940794615 --- /dev/null +++ b/drivers/hid/ipts/eds2.c -@@ -0,0 +1,144 @@ +@@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll @@ -1347,6 +1348,7 @@ index 000000000000..198dc65d7887 +#include "context.h" +#include "control.h" +#include "desc.h" ++#include "eds2.h" +#include "spec-data.h" + +int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) @@ -2176,10 +2178,10 @@ index 000000000000..973bade6b0fd +#endif /* IPTS_MEI_H */ diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c new file mode 100644 -index 000000000000..ef66c3c9db80 +index 000000000000..977724c728c3 --- /dev/null +++ b/drivers/hid/ipts/receiver.c -@@ -0,0 +1,250 @@ +@@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll @@ -2198,6 +2200,7 @@ index 000000000000..ef66c3c9db80 +#include "context.h" +#include "control.h" +#include "hid.h" ++#include "receiver.h" +#include "resources.h" +#include "spec-device.h" +#include "thread.h" @@ -3234,5 +3237,5 @@ index 000000000000..1f966b8b32c4 + +#endif /* IPTS_THREAD_H */ -- -2.44.0 +2.46.1 diff --git a/patches/6.10/0006-ithc.patch b/patches/6.10/0006-ithc.patch new file mode 100644 index 0000000000..1bebd1a50d --- /dev/null +++ b/patches/6.10/0006-ithc.patch @@ -0,0 +1,2771 @@ +From dc65c76763207057a6797b0ef844c3f58e6a6e15 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index e4a70886678c..961a33b87c24 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -389,6 +389,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.46.1 + +From c3ddc02f7b0e27c09233022d7bbfd6b72ca12d2e Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 431 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2568 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index ccddfba86004..8e2ea8175bfb 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1369,4 +1369,6 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index de41081b6a5a..9d156f1b3910 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -173,3 +173,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..4937ba131297 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..2d8c6afe9966 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..bf4eab33062b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..1749a5819b3e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..8883987fb352 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..ac56c253674b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,431 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_ANY_ID, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .class = PCI_CLASS_INPUT_PEN << 8, ++ .class_mask = ~0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ del_timer_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..e2d1690b8cf8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..c0f13506af20 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..4f541fe533fa +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..aec320d4e945 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.46.1 + diff --git a/patches/6.10/0007-surface-sam.patch b/patches/6.10/0007-surface-sam.patch new file mode 100644 index 0000000000..51f60eb415 --- /dev/null +++ b/patches/6.10/0007-surface-sam.patch @@ -0,0 +1,423 @@ +From 3e6733db885b3a91339039897cbf0f3e05cda2a5 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 30 Dec 2023 18:07:54 +0100 +Subject: [PATCH] hwmon: Add thermal sensor driver for Surface Aggregator + Module + +Some of the newer Microsoft Surface devices (such as the Surface Book +3 and Pro 9) have thermal sensors connected via the Surface Aggregator +Module (the embedded controller on those devices). Add a basic driver +to read out the temperature values of those sensors. + +Link: https://github.com/linux-surface/surface-aggregator-module/issues/59 +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/hwmon/Kconfig | 10 +++ + drivers/hwmon/Makefile | 1 + + drivers/hwmon/surface_temp.c | 165 +++++++++++++++++++++++++++++++++++ + 3 files changed, 176 insertions(+) + create mode 100644 drivers/hwmon/surface_temp.c + +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index e14ae18a973b..76eabe3e4435 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -2093,6 +2093,16 @@ config SENSORS_SURFACE_FAN + + Select M or Y here, if you want to be able to read the fan's speed. + ++config SENSORS_SURFACE_TEMP ++ tristate "Microsoft Surface Thermal Sensor Driver" ++ depends on SURFACE_AGGREGATOR ++ help ++ Driver for monitoring thermal sensors connected via the Surface ++ Aggregator Module (embedded controller) on Microsoft Surface devices. ++ ++ This driver can also be built as a module. If so, the module ++ will be called surface_temp. ++ + config SENSORS_ADC128D818 + tristate "Texas Instruments ADC128D818" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index e3f25475d1f0..eff74ab7f720 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -209,6 +209,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o + obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o + obj-$(CONFIG_SENSORS_STTS751) += stts751.o + obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o ++obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o + obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o + obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o + obj-$(CONFIG_SENSORS_TC74) += tc74.o +diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c +new file mode 100644 +index 000000000000..48c3e826713f +--- /dev/null ++++ b/drivers/hwmon/surface_temp.c +@@ -0,0 +1,165 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2022-2023 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++/* -- SAM interface. -------------------------------------------------------- */ ++ ++SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x04, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x01, ++}); ++ ++static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) ++{ ++ __le16 sensors_le; ++ int status; ++ ++ status = __ssam_tmp_get_available_sensors(sdev, &sensors_le); ++ if (status) ++ return status; ++ ++ *sensors = le16_to_cpu(sensors_le); ++ return 0; ++} ++ ++static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temperature) ++{ ++ __le16 temp_le; ++ int status; ++ ++ status = __ssam_tmp_get_temperature(sdev->ctrl, sdev->uid.target, iid, &temp_le); ++ if (status) ++ return status; ++ ++ /* Convert 1/10 °K to 1/1000 °C */ ++ *temperature = (le16_to_cpu(temp_le) - 2731) * 100L; ++ return 0; ++} ++ ++ ++/* -- Driver.---------------------------------------------------------------- */ ++ ++struct ssam_temp { ++ struct ssam_device *sdev; ++ s16 sensors; ++}; ++ ++static umode_t ssam_temp_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ const struct ssam_temp *ssam_temp = data; ++ ++ if (!(ssam_temp->sensors & BIT(channel))) ++ return 0; ++ ++ return 0444; ++} ++ ++static int ssam_temp_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, long *value) ++{ ++ const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); ++ ++ return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); ++} ++ ++static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ), ++ /* We have at most 16 thermal sensor channels. */ ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT), ++ NULL ++}; ++ ++static const struct hwmon_ops ssam_temp_hwmon_ops = { ++ .is_visible = ssam_temp_hwmon_is_visible, ++ .read = ssam_temp_hwmon_read, ++}; ++ ++static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { ++ .ops = &ssam_temp_hwmon_ops, ++ .info = ssam_temp_hwmon_info, ++}; ++ ++static int ssam_temp_probe(struct ssam_device *sdev) ++{ ++ struct ssam_temp *ssam_temp; ++ struct device *hwmon_dev; ++ s16 sensors; ++ int status; ++ ++ status = ssam_tmp_get_available_sensors(sdev, &sensors); ++ if (status) ++ return status; ++ ++ ssam_temp = devm_kzalloc(&sdev->dev, sizeof(*ssam_temp), GFP_KERNEL); ++ if (!ssam_temp) ++ return -ENOMEM; ++ ++ ssam_temp->sdev = sdev; ++ ssam_temp->sensors = sensors; ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, ++ "surface_thermal", ssam_temp, &ssam_temp_hwmon_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++ return 0; ++} ++ ++static const struct ssam_device_id ssam_temp_match[] = { ++ { SSAM_SDEV(TMP, SAM, 0x00, 0x02) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_temp_match); ++ ++static struct ssam_device_driver ssam_temp = { ++ .probe = ssam_temp_probe, ++ .match_table = ssam_temp_match, ++ .driver = { ++ .name = "surface_temp", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(ssam_temp); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.46.1 + +From 3e4479ac556ff21b45485822edb0980bbb27fdba Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 30 Dec 2023 18:12:23 +0100 +Subject: [PATCH] hwmon: surface_temp: Add support for sensor names + +The thermal subsystem of the Surface Aggregator Module allows us to +query the names of the respective thermal sensors. Forward those to +userspace. + +Signed-off-by: Ivor Wanders +Co-Developed-by: Maximilian Luz +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/hwmon/surface_temp.c | 113 +++++++++++++++++++++++++++++------ + 1 file changed, 96 insertions(+), 17 deletions(-) + +diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c +index 48c3e826713f..4c08926139db 100644 +--- a/drivers/hwmon/surface_temp.c ++++ b/drivers/hwmon/surface_temp.c +@@ -17,6 +17,27 @@ + + /* -- SAM interface. -------------------------------------------------------- */ + ++/* ++ * Available sensors are indicated by a 16-bit bitfield, where a 1 marks the ++ * presence of a sensor. So we have at most 16 possible sensors/channels. ++ */ ++#define SSAM_TMP_SENSOR_MAX_COUNT 16 ++ ++/* ++ * All names observed so far are 6 characters long, but there's only ++ * zeros after the name, so perhaps they can be longer. This number reflects ++ * the maximum zero-padded space observed in the returned buffer. ++ */ ++#define SSAM_TMP_SENSOR_NAME_LENGTH 18 ++ ++struct ssam_tmp_get_name_rsp { ++ __le16 unknown1; ++ char unknown2; ++ char name[SSAM_TMP_SENSOR_NAME_LENGTH]; ++} __packed; ++ ++static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21); ++ + SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x04, +@@ -27,6 +48,11 @@ SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { + .command_id = 0x01, + }); + ++SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name, struct ssam_tmp_get_name_rsp, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x0e, ++}); ++ + static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) + { + __le16 sensors_le; +@@ -54,12 +80,37 @@ static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temp + return 0; + } + ++static int ssam_tmp_get_name(struct ssam_device *sdev, u8 iid, char *buf, size_t buf_len) ++{ ++ struct ssam_tmp_get_name_rsp name_rsp; ++ int status; ++ ++ status = __ssam_tmp_get_name(sdev->ctrl, sdev->uid.target, iid, &name_rsp); ++ if (status) ++ return status; ++ ++ /* ++ * This should not fail unless the name in the returned struct is not ++ * null-terminated or someone changed something in the struct ++ * definitions above, since our buffer and struct have the same ++ * capacity by design. So if this fails blow this up with a warning. ++ * Since the more likely cause is that the returned string isn't ++ * null-terminated, we might have received garbage (as opposed to just ++ * an incomplete string), so also fail the function. ++ */ ++ status = strscpy(buf, name_rsp.name, buf_len); ++ WARN_ON(status < 0); ++ ++ return status < 0 ? status : 0; ++} ++ + + /* -- Driver.---------------------------------------------------------------- */ + + struct ssam_temp { + struct ssam_device *sdev; + s16 sensors; ++ char names[SSAM_TMP_SENSOR_MAX_COUNT][SSAM_TMP_SENSOR_NAME_LENGTH]; + }; + + static umode_t ssam_temp_hwmon_is_visible(const void *data, +@@ -83,33 +134,47 @@ static int ssam_temp_hwmon_read(struct device *dev, + return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); + } + ++static int ssam_temp_hwmon_read_string(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); ++ ++ *str = ssam_temp->names[channel]; ++ return 0; ++} ++ + static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), +- /* We have at most 16 thermal sensor channels. */ ++ /* ++ * We have at most SSAM_TMP_SENSOR_MAX_COUNT = 16 thermal sensor ++ * channels. ++ */ + HWMON_CHANNEL_INFO(temp, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT), ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL), + NULL + }; + + static const struct hwmon_ops ssam_temp_hwmon_ops = { + .is_visible = ssam_temp_hwmon_is_visible, + .read = ssam_temp_hwmon_read, ++ .read_string = ssam_temp_hwmon_read_string, + }; + + static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { +@@ -122,6 +187,7 @@ static int ssam_temp_probe(struct ssam_device *sdev) + struct ssam_temp *ssam_temp; + struct device *hwmon_dev; + s16 sensors; ++ int channel; + int status; + + status = ssam_tmp_get_available_sensors(sdev, &sensors); +@@ -135,6 +201,19 @@ static int ssam_temp_probe(struct ssam_device *sdev) + ssam_temp->sdev = sdev; + ssam_temp->sensors = sensors; + ++ /* Retrieve the name for each available sensor. */ ++ for (channel = 0; channel < SSAM_TMP_SENSOR_MAX_COUNT; channel++) ++ { ++ if (!(sensors & BIT(channel))) ++ continue; ++ ++ status = ssam_tmp_get_name(sdev, channel + 1, ++ ssam_temp->names[channel], ++ SSAM_TMP_SENSOR_NAME_LENGTH); ++ if (status) ++ return status; ++ } ++ + hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, + "surface_thermal", ssam_temp, &ssam_temp_hwmon_chip_info, + NULL); +-- +2.46.1 + diff --git a/patches/6.10/0008-surface-sam-over-hid.patch b/patches/6.10/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..2916ab4a70 --- /dev/null +++ b/patches/6.10/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From 134830c02674d93c2433bb898de34d1b84467f86 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index 14ae0cfc325e..6197c5252d2a 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -639,6 +639,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -740,6 +761,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.46.1 + +From 61af2c864646d5582463f9e5fbf06a96e0c4300d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b629e82af97c..68656e8f309e 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..68db237734a1 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.46.1 + diff --git a/patches/6.8/0008-surface-button.patch b/patches/6.10/0009-surface-button.patch similarity index 97% rename from patches/6.8/0008-surface-button.patch rename to patches/6.10/0009-surface-button.patch index af5086f37e..4c84ec181e 100644 --- a/patches/6.8/0008-surface-button.patch +++ b/patches/6.10/0009-surface-button.patch @@ -1,4 +1,4 @@ -From 3584a6c1791dc9c9b9c3ee846621571cbfabe37e Mon Sep 17 00:00:00 2001 +From 02f4deefe4c0ab63cd6771198cd05d5798fcc80f Mon Sep 17 00:00:00 2001 From: Sachi King Date: Tue, 5 Oct 2021 00:05:09 +1100 Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices @@ -73,9 +73,9 @@ index f6d060377d18..b8603f74eb28 100644 /* -- -2.44.0 +2.46.1 -From c26bb1d0af0fe40be270d203d6aaeab28dd04a10 Mon Sep 17 00:00:00 2001 +From fbdda31b4a3ce2a7113fa831056830b6b46cf0df Mon Sep 17 00:00:00 2001 From: Sachi King Date: Tue, 5 Oct 2021 00:22:57 +1100 Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd @@ -145,5 +145,5 @@ index 2755601f979c..4240c98ca226 100644 -- -2.44.0 +2.46.1 diff --git a/patches/6.8/0009-surface-typecover.patch b/patches/6.10/0010-surface-typecover.patch similarity index 91% rename from patches/6.8/0009-surface-typecover.patch rename to patches/6.10/0010-surface-typecover.patch index bd6e4df977..72d6d86a56 100644 --- a/patches/6.8/0009-surface-typecover.patch +++ b/patches/6.10/0010-surface-typecover.patch @@ -1,4 +1,4 @@ -From b3fac417611f5bb4ae2a9bc9e828dacfe4418fbf Mon Sep 17 00:00:00 2001 +From edc26a9d993aca624b164989945d6bbcc5c7a856 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 18 Feb 2023 01:02:49 +0100 Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 @@ -23,7 +23,7 @@ Patchset: surface-typecover 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c -index b4783574b8e6..360970620589 100644 +index 13171454f959..a83beefd25f3 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { @@ -37,9 +37,9 @@ index b4783574b8e6..360970620589 100644 { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, -- -2.44.0 +2.46.1 -From bf5167d418b660e321368222505a97d9f1ed68b4 Mon Sep 17 00:00:00 2001 +From 43bac5cac64c45b423509196acdb2261c3f0a7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Thu, 5 Nov 2020 13:09:45 +0100 Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when @@ -75,7 +75,7 @@ Patchset: surface-typecover 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 3e91e4d6ba6f..45b7884c97f0 100644 +index 99812c0f830b..0b3b51e37149 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -34,7 +34,10 @@ @@ -113,7 +113,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 enum latency_mode { HID_LATENCY_NORMAL = 0, HID_LATENCY_HIGH = 1, -@@ -169,6 +176,8 @@ struct mt_device { +@@ -168,6 +175,8 @@ struct mt_device { struct list_head applications; struct list_head reports; @@ -122,7 +122,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 }; static void mt_post_parse_default_settings(struct mt_device *td, -@@ -213,6 +222,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); +@@ -212,6 +221,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_GOOGLE 0x0111 #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 #define MT_CLS_SMART_TECH 0x0113 @@ -130,7 +130,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 #define MT_DEFAULT_MAXCONTACT 10 #define MT_MAX_MAXCONTACT 250 -@@ -397,6 +407,16 @@ static const struct mt_class mt_classes[] = { +@@ -396,6 +406,16 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_SEPARATE_APP_REPORT, }, @@ -147,7 +147,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 { } }; -@@ -1721,6 +1741,69 @@ static void mt_expired_timeout(struct timer_list *t) +@@ -1744,6 +1764,69 @@ static void mt_expired_timeout(struct timer_list *t) clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); } @@ -217,7 +217,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, i; -@@ -1744,6 +1827,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) +@@ -1767,6 +1850,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; hid_set_drvdata(hdev, td); @@ -227,7 +227,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 INIT_LIST_HEAD(&td->applications); INIT_LIST_HEAD(&td->reports); -@@ -1782,15 +1868,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) +@@ -1805,15 +1891,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) timer_setup(&td->release_timer, mt_expired_timeout, 0); ret = hid_parse(hdev); @@ -249,7 +249,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); if (ret) -@@ -1840,6 +1930,7 @@ static void mt_remove(struct hid_device *hdev) +@@ -1863,6 +1953,7 @@ static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); @@ -257,7 +257,7 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 del_timer_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2230,6 +2321,11 @@ static const struct hid_device_id mt_devices[] = { +@@ -2267,6 +2358,11 @@ static const struct hid_device_id mt_devices[] = { MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_CSR2) }, @@ -270,9 +270,9 @@ index 3e91e4d6ba6f..45b7884c97f0 100644 { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, -- -2.44.0 +2.46.1 -From dda4ca6e0248ae5acf3aaf78ee00e613f4e04bad Mon Sep 17 00:00:00 2001 +From 41478ba3be0680c18610cb66f5344abadff1b82d Mon Sep 17 00:00:00 2001 From: PJungkamp Date: Fri, 25 Feb 2022 12:04:25 +0100 Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet @@ -301,7 +301,7 @@ Patchset: surface-typecover 1 file changed, 122 insertions(+), 26 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 45b7884c97f0..f8978b405aca 100644 +index 0b3b51e37149..481b97dce830 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -77,6 +77,7 @@ MODULE_LICENSE("GPL"); @@ -321,7 +321,7 @@ index 45b7884c97f0..f8978b405aca 100644 enum latency_mode { HID_LATENCY_NORMAL = 0, -@@ -409,6 +412,7 @@ static const struct mt_class mt_classes[] = { +@@ -408,6 +411,7 @@ static const struct mt_class mt_classes[] = { }, { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | @@ -329,7 +329,7 @@ index 45b7884c97f0..f8978b405aca 100644 MT_QUIRK_ALWAYS_VALID | MT_QUIRK_IGNORE_DUPLICATES | MT_QUIRK_HOVERING | -@@ -1390,6 +1394,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, +@@ -1389,6 +1393,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, field->application != HID_CP_CONSUMER_CONTROL && field->application != HID_GD_WIRELESS_RADIO_CTLS && field->application != HID_GD_SYSTEM_MULTIAXIS && @@ -339,7 +339,7 @@ index 45b7884c97f0..f8978b405aca 100644 !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) return -1; -@@ -1417,6 +1424,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, +@@ -1416,6 +1423,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 1; } @@ -361,7 +361,7 @@ index 45b7884c97f0..f8978b405aca 100644 if (rdata->is_mt_collection) return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, application); -@@ -1438,6 +1460,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, +@@ -1437,6 +1459,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, { struct mt_device *td = hid_get_drvdata(hdev); struct mt_report_data *rdata; @@ -369,7 +369,7 @@ index 45b7884c97f0..f8978b405aca 100644 rdata = mt_find_report_data(td, field->report); if (rdata && rdata->is_mt_collection) { -@@ -1445,6 +1468,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, +@@ -1444,6 +1467,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, return -1; } @@ -389,7 +389,7 @@ index 45b7884c97f0..f8978b405aca 100644 /* let hid-core decide for the others */ return 0; } -@@ -1454,11 +1490,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, +@@ -1453,11 +1489,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, { struct mt_device *td = hid_get_drvdata(hid); struct mt_report_data *rdata; @@ -411,7 +411,7 @@ index 45b7884c97f0..f8978b405aca 100644 return 0; } -@@ -1611,6 +1657,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) +@@ -1634,6 +1680,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; } @@ -454,7 +454,7 @@ index 45b7884c97f0..f8978b405aca 100644 static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct mt_device *td = hid_get_drvdata(hdev); -@@ -1659,6 +1741,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) +@@ -1682,6 +1764,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) /* force BTN_STYLUS to allow tablet matching in udev */ __set_bit(BTN_STYLUS, hi->input->keybit); break; @@ -468,7 +468,7 @@ index 45b7884c97f0..f8978b405aca 100644 default: suffix = "UNKNOWN"; break; -@@ -1741,30 +1830,6 @@ static void mt_expired_timeout(struct timer_list *t) +@@ -1764,30 +1853,6 @@ static void mt_expired_timeout(struct timer_list *t) clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); } @@ -499,7 +499,7 @@ index 45b7884c97f0..f8978b405aca 100644 static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) { struct usb_device *udev = hid_to_usb_dev(hdev); -@@ -1773,8 +1838,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) +@@ -1796,8 +1861,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) /* Wake up the device in case it's already suspended */ pm_runtime_get_sync(&udev->dev); @@ -511,7 +511,7 @@ index 45b7884c97f0..f8978b405aca 100644 hid_err(hdev, "couldn't find backlight field\n"); goto out; } -@@ -1908,13 +1974,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) +@@ -1931,13 +1997,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) static int mt_reset_resume(struct hid_device *hdev) { @@ -536,7 +536,7 @@ index 45b7884c97f0..f8978b405aca 100644 /* Some Elan legacy devices require SET_IDLE to be set on resume. * It should be safe to send it to other devices too. * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ -@@ -1923,12 +2000,31 @@ static int mt_resume(struct hid_device *hdev) +@@ -1946,12 +2023,31 @@ static int mt_resume(struct hid_device *hdev) mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); @@ -569,5 +569,5 @@ index 45b7884c97f0..f8978b405aca 100644 unregister_pm_notifier(&td->pm_notifier); del_timer_sync(&td->release_timer); -- -2.44.0 +2.46.1 diff --git a/patches/6.10/0011-surface-shutdown.patch b/patches/6.10/0011-surface-shutdown.patch new file mode 100644 index 0000000000..6b4bfe3bf5 --- /dev/null +++ b/patches/6.10/0011-surface-shutdown.patch @@ -0,0 +1,97 @@ +From fe6c6b6e8d581aeca114902ec5f962a44a0ee3ff Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index af2996d0d17f..3ce0fb61257d 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 568410e64ce6..f59d8fb36335 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6273,3 +6273,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index cafc5ab1cbcb..64bb5aca2c13 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -465,6 +465,7 @@ struct pci_dev { + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.46.1 + diff --git a/patches/6.8/0011-surface-gpe.patch b/patches/6.10/0012-surface-gpe.patch similarity index 95% rename from patches/6.8/0011-surface-gpe.patch rename to patches/6.10/0012-surface-gpe.patch index 871f0b0071..8a66e178be 100644 --- a/patches/6.8/0011-surface-gpe.patch +++ b/patches/6.10/0012-surface-gpe.patch @@ -1,4 +1,4 @@ -From 36cf5399fd0f16f10f97c21131d29ebda13607f5 Mon Sep 17 00:00:00 2001 +From d0443743ccc9c4d76192f7bbf0152990e461945e Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 12 Mar 2023 01:41:57 +0100 Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 @@ -47,5 +47,5 @@ index 62fd4004db31..103fc4468262 100644 .ident = "Surface Book 1", .matches = { -- -2.44.0 +2.46.1 diff --git a/patches/6.10/0013-cameras.patch b/patches/6.10/0013-cameras.patch new file mode 100644 index 0000000000..54620c7313 --- /dev/null +++ b/patches/6.10/0013-cameras.patch @@ -0,0 +1,781 @@ +From e7b32369a9dc01221dc44a1250c425d8ebc9b28b Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index cdc5a74092c7..e3a16c14a29e 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2176,6 +2176,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.46.1 + +From aa71d469b9000984bfbd4ee4f596517db89bcb46 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 6ee97bf7b6a9..a1f86bde277a 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -45,6 +45,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -223,12 +230,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -2203,6 +2212,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2505,6 +2517,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4630,6 +4645,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4677,6 +4704,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.46.1 + +From fb1981ea8427c311f08860ddbb2ea1b7e1ddd313 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 1e107fd49f82..e3e1696e7f0e 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.46.1 + +From ae05619486ea72603af63628e7f7a0c3b4f045ac Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 2 Mar 2023 12:59:39 +0000 +Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E + +ACPI _HID INT347E represents the OmniVision 7251 camera sensor. The +driver for this sensor expects a single pin named "enable", but on +some Microsoft Surface platforms the sensor is assigned a single +GPIO who's type flag is INT3472_GPIO_TYPE_RESET. + +Remap the GPIO pin's function from "reset" to "enable". This is done +outside of the existing remap table since it is a more widespread +discrepancy than that method is designed for. Additionally swap the +polarity of the pin to match the driver's expectation. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/discrete.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index 07b302e09340..baad1e50ca81 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -83,12 +83,27 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + const char *func, u32 polarity) + { + int ret; ++ const struct acpi_device_id ov7251_ids[] = { ++ { "INT347E" }, ++ { } ++ }; + + if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { + dev_warn(int3472->dev, "Too many GPIOs mapped\n"); + return -EINVAL; + } + ++ /* ++ * In addition to the function remap table we need to bulk remap the ++ * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that ++ * expects its only GPIO pin to be called "enable" (and to have the ++ * opposite polarity). ++ */ ++ if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { ++ func = "enable"; ++ polarity ^= GPIO_ACTIVE_LOW; ++ } ++ + ret = skl_int3472_fill_gpiod_lookup(&int3472->gpios.table[int3472->n_sensor_gpios], + agpio, func, polarity); + if (ret) +-- +2.46.1 + +From 7066acd9423a7eecc94d22397095e04f4a47aba8 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 30f61e04ecaf..9c1292ca8552 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1051,7 +1051,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1572,7 +1572,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.46.1 + +From 9feb537c1c69e0d01ad3ed713959fcf371dc2db6 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index c477723c07bf..83413048eb35 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -795,6 +795,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index 89c7192148df..44eca113e772 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1219,10 +1219,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.46.1 + +From 580ca92681c7c6bafa132b81043fbd0857b5da6b Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index e3e1696e7f0e..423dc555093f 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -200,7 +200,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.46.1 + +From ef863c4385da78967765505ccd3702371a32d70e Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db0..2d2abb25b944 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.46.1 + +From e81fa0ec83149cf15d779fea0ba7435f8fd28898 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 05e6af88b88c..c120eb0b5aa8 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -909,6 +909,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index effdfc6f1e95..6ce609b2cdac 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -86,6 +86,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o + obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.46.1 + +From afce9fb83bf7355d5ceb4a6958023dd07e00cfdf Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Sat, 3 Feb 2024 12:59:53 +0900 +Subject: [PATCH] media: staging: ipu3-imgu: Fix multiple calls of s_stream on + stream stop + +Adapt to 009905e "media: v4l2-subdev: Document and enforce .s_stream() requirements" + +Patchset: cameras +--- + drivers/staging/media/ipu3/ipu3-v4l2.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c +index 3df58eb3e882..81aff2d5d898 100644 +--- a/drivers/staging/media/ipu3/ipu3-v4l2.c ++++ b/drivers/staging/media/ipu3/ipu3-v4l2.c +@@ -538,18 +538,18 @@ static void imgu_vb2_stop_streaming(struct vb2_queue *vq) + + WARN_ON(!node->enabled); + +- pipe = node->pipe; +- dev_dbg(dev, "Try to stream off node [%u][%u]", pipe, node->id); +- imgu_pipe = &imgu->imgu_pipe[pipe]; +- r = v4l2_subdev_call(&imgu_pipe->imgu_sd.subdev, video, s_stream, 0); +- if (r) +- dev_err(&imgu->pci_dev->dev, +- "failed to stop subdev streaming\n"); +- + mutex_lock(&imgu->streaming_lock); + /* Was this the first node with streaming disabled? */ + if (imgu->streaming && imgu_all_nodes_streaming(imgu, node)) { + /* Yes, really stop streaming now */ ++ pipe = node->pipe; ++ dev_dbg(dev, "Try to stream off node [%u][%u]", pipe, node->id); ++ imgu_pipe = &imgu->imgu_pipe[pipe]; ++ r = v4l2_subdev_call(&imgu_pipe->imgu_sd.subdev, video, s_stream, 0); ++ if (r) ++ dev_err(&imgu->pci_dev->dev, ++ "failed to stop subdev streaming\n"); ++ + dev_dbg(dev, "IMGU streaming is ready to stop"); + r = imgu_s_stream(imgu, false); + if (!r) +-- +2.46.1 + +From 10cc9e54fa6067a0c08dc4f2c0f5bf029e3a4ed1 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index c626ed845928..0094cfda57ea 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -82,6 +82,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); + +-- +2.46.1 + diff --git a/patches/6.8/0013-amd-gpio.patch b/patches/6.10/0014-amd-gpio.patch similarity index 87% rename from patches/6.8/0013-amd-gpio.patch rename to patches/6.10/0014-amd-gpio.patch index 9b12bd1a75..21d3ccb2d8 100644 --- a/patches/6.8/0013-amd-gpio.patch +++ b/patches/6.10/0014-amd-gpio.patch @@ -1,4 +1,4 @@ -From 302d8dc26283bc10ba22bc549c41292d00125e60 Mon Sep 17 00:00:00 2001 +From 04f1ce03393e660c6275004e0ccda0f1e4481f8b Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sat, 29 May 2021 17:47:38 +1000 Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 @@ -21,7 +21,7 @@ Patchset: amd-gpio 1 file changed, 17 insertions(+) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 85a3ce2a3666..2c0e04a3a697 100644 +index 4bf82dbd2a6b..7a8cb090c656 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -22,6 +22,7 @@ @@ -32,7 +32,7 @@ index 85a3ce2a3666..2c0e04a3a697 100644 #include #include -@@ -1251,6 +1252,17 @@ static void __init mp_config_acpi_legacy_irqs(void) +@@ -1216,6 +1217,17 @@ static void __init mp_config_acpi_legacy_irqs(void) } } @@ -50,7 +50,7 @@ index 85a3ce2a3666..2c0e04a3a697 100644 /* * Parse IOAPIC related entries in MADT * returns 0 on success, < 0 on error -@@ -1306,6 +1318,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) +@@ -1271,6 +1283,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, acpi_gbl_FADT.sci_interrupt); @@ -63,9 +63,9 @@ index 85a3ce2a3666..2c0e04a3a697 100644 mp_config_acpi_legacy_irqs(); -- -2.44.0 +2.46.1 -From d2a793e4fd47cd1cba2847915e7078671d9e9ea5 Mon Sep 17 00:00:00 2001 +From a05e3240a282702699117da0e657a46bc960a944 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 3 Jun 2021 14:04:26 +0200 Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override @@ -80,10 +80,10 @@ Patchset: amd-gpio 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 2c0e04a3a697..b0e1dab3d2ec 100644 +index 7a8cb090c656..0faafc323e67 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c -@@ -1254,12 +1254,19 @@ static void __init mp_config_acpi_legacy_irqs(void) +@@ -1219,12 +1219,19 @@ static void __init mp_config_acpi_legacy_irqs(void) static const struct dmi_system_id surface_quirk[] __initconst = { { @@ -105,5 +105,5 @@ index 2c0e04a3a697..b0e1dab3d2ec 100644 }; -- -2.44.0 +2.46.1 diff --git a/patches/6.8/0014-rtc.patch b/patches/6.10/0015-rtc.patch similarity index 91% rename from patches/6.8/0014-rtc.patch rename to patches/6.10/0015-rtc.patch index 21ba633f8f..38ca064ee6 100644 --- a/patches/6.8/0014-rtc.patch +++ b/patches/6.10/0015-rtc.patch @@ -1,4 +1,4 @@ -From 34ad5b493b00c944ed68d2436cad96785fe37a33 Mon Sep 17 00:00:00 2001 +From 4e5c87c48625f80729705e6ca756b289dace0452 Mon Sep 17 00:00:00 2001 From: "Bart Groeneveld | GPX Solutions B.V" Date: Mon, 5 Dec 2022 16:08:46 +0100 Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms @@ -21,7 +21,7 @@ Patchset: rtc 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c -index 33c3b16af556..900445d06623 100644 +index 1d670dbe4d1d..71c9e375ca1c 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -432,6 +432,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, @@ -58,7 +58,7 @@ index 33c3b16af556..900445d06623 100644 }; static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, -@@ -564,13 +571,18 @@ static int acpi_tad_remove(struct platform_device *pdev) +@@ -564,13 +571,18 @@ static void acpi_tad_remove(struct platform_device *pdev) pm_runtime_get_sync(dev); @@ -79,7 +79,7 @@ index 33c3b16af556..900445d06623 100644 if (dd->capabilities & ACPI_TAD_DC_WAKE) { acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); -@@ -613,12 +625,6 @@ static int acpi_tad_probe(struct platform_device *pdev) +@@ -612,12 +624,6 @@ static int acpi_tad_probe(struct platform_device *pdev) goto remove_handler; } @@ -92,7 +92,7 @@ index 33c3b16af556..900445d06623 100644 dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); if (!dd) { ret = -ENOMEM; -@@ -649,6 +655,12 @@ static int acpi_tad_probe(struct platform_device *pdev) +@@ -648,6 +654,12 @@ static int acpi_tad_probe(struct platform_device *pdev) if (ret) goto fail; @@ -106,5 +106,5 @@ index 33c3b16af556..900445d06623 100644 ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); if (ret) -- -2.44.0 +2.46.1 diff --git a/patches/6.11/0001-secureboot.patch b/patches/6.11/0001-secureboot.patch new file mode 100644 index 0000000000..056bf30e85 --- /dev/null +++ b/patches/6.11/0001-secureboot.patch @@ -0,0 +1,112 @@ +From 505a0ba56061dd331e950bcd43fe2266972ccc96 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index b5c79f43359b..a1bbedd989e4 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.47.1 + +From 0494ea66d3d0a1de801409b6e9a60b12be6a254b Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index be010fec7654..cc6e7ae5786e 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3020,6 +3020,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index 0a213f69a9e4..8e4f9dcc9f4c 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -37,6 +37,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -92,7 +93,7 @@ void hibernate_release(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1422,6 +1423,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1480,3 +1487,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.47.1 + diff --git a/patches/6.11/0002-surface3-oemb.patch b/patches/6.11/0002-surface3-oemb.patch new file mode 100644 index 0000000000..98d7c06f75 --- /dev/null +++ b/patches/6.11/0002-surface3-oemb.patch @@ -0,0 +1,101 @@ +From 81226380de9cd987ca5e358eb8dd7cd26928416f Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3-oemb +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index c15ed7a12784..1ec8edb5aafa 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 51187b1e0ed2..bfb83ce8d8f8 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3790,6 +3790,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0c28..0b930c91bccb 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.47.1 + diff --git a/patches/6.11/0003-mwifiex.patch b/patches/6.11/0003-mwifiex.patch new file mode 100644 index 0000000000..022627a1d9 --- /dev/null +++ b/patches/6.11/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 38ec660bc429bf686de0ba064daf93afb7b6b7ea Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 5f997becdbaa..9a9929424513 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd..f46b06f8d643 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5b..5d30ae39d65e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.47.1 + +From 31aea914a54c2324e8f564d1fe401bd4fd1ba9b4 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 9a9929424513..2273e3029776 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65e..c14eb56eb911 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.47.1 + +From 9f1e3122d73eb3c58496426357f6db81436a8ffb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 09afd75145ed..78953ee8816c 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -65,6 +65,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -468,6 +469,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -3867,6 +3869,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.47.1 + diff --git a/patches/6.11/0004-ath10k.patch b/patches/6.11/0004-ath10k.patch new file mode 100644 index 0000000000..3dd87ff333 --- /dev/null +++ b/patches/6.11/0004-ath10k.patch @@ -0,0 +1,120 @@ +From bfe0767ceac48580c30108ab6daac1c407060901 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index b3294287bce1..2936fdae823c 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -40,6 +40,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -52,6 +55,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -61,6 +67,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -931,6 +940,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -945,6 +990,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.47.1 + diff --git a/patches/6.11/0005-ipts.patch b/patches/6.11/0005-ipts.patch new file mode 100644 index 0000000000..f1a9e920ce --- /dev/null +++ b/patches/6.11/0005-ipts.patch @@ -0,0 +1,3241 @@ +From cf98baee650089d8d322ce941adb508f70e7b8ec Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index c3a6657dcd4a..82eef2f4eb0a 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 6589635f8ba3..a1df48a434e2 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.47.1 + +From ed5408f8d3447fe4a40515abbd6ccbf0aeaad63d Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index cdb8701a968f..d664dde86fcd 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -40,6 +40,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -217,12 +222,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -2168,6 +2175,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2468,6 +2478,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + ret = si_domain_init(hw_pass_through); +@@ -4713,6 +4726,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4748,6 +4773,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.47.1 + +From b96950fbdcba66483f5ce2608a5cc71430c4925b Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 08446c89eff6..ccddfba86004 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1367,4 +1367,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" + + source "drivers/hid/surface-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index e40f1ddebbb7..bdb17cffca2f 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -169,3 +169,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..7b9f54388a9f +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..639940794615 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..977724c728c3 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.47.1 + diff --git a/patches/6.11/0006-ithc.patch b/patches/6.11/0006-ithc.patch new file mode 100644 index 0000000000..9c07fdf110 --- /dev/null +++ b/patches/6.11/0006-ithc.patch @@ -0,0 +1,2771 @@ +From 3f81e12dfd2837a046c56c052edc3aabc8ee540e Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index e090ca07364b..e575193615bf 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -389,6 +389,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.47.1 + +From 0762a09dbf4911f892cab58d308c7328e2309ddf Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 431 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2568 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index ccddfba86004..8e2ea8175bfb 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1369,4 +1369,6 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index bdb17cffca2f..8987177f8b81 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -171,3 +171,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..4937ba131297 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..2d8c6afe9966 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..bf4eab33062b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..1749a5819b3e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..8883987fb352 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..ac56c253674b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,431 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_ANY_ID, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .class = PCI_CLASS_INPUT_PEN << 8, ++ .class_mask = ~0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ del_timer_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..e2d1690b8cf8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..c0f13506af20 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..4f541fe533fa +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..aec320d4e945 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.47.1 + diff --git a/patches/6.11/0007-surface-sam.patch b/patches/6.11/0007-surface-sam.patch new file mode 100644 index 0000000000..68b5270c89 --- /dev/null +++ b/patches/6.11/0007-surface-sam.patch @@ -0,0 +1,423 @@ +From 2942ac475f0ec3e0b18bc1bb3432053cd95071dd Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 30 Dec 2023 18:07:54 +0100 +Subject: [PATCH] hwmon: Add thermal sensor driver for Surface Aggregator + Module + +Some of the newer Microsoft Surface devices (such as the Surface Book +3 and Pro 9) have thermal sensors connected via the Surface Aggregator +Module (the embedded controller on those devices). Add a basic driver +to read out the temperature values of those sensors. + +Link: https://github.com/linux-surface/surface-aggregator-module/issues/59 +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/hwmon/Kconfig | 10 +++ + drivers/hwmon/Makefile | 1 + + drivers/hwmon/surface_temp.c | 165 +++++++++++++++++++++++++++++++++++ + 3 files changed, 176 insertions(+) + create mode 100644 drivers/hwmon/surface_temp.c + +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index 778e584c3a75..b8d65292db83 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -2084,6 +2084,16 @@ config SENSORS_SURFACE_FAN + + Select M or Y here, if you want to be able to read the fan's speed. + ++config SENSORS_SURFACE_TEMP ++ tristate "Microsoft Surface Thermal Sensor Driver" ++ depends on SURFACE_AGGREGATOR ++ help ++ Driver for monitoring thermal sensors connected via the Surface ++ Aggregator Module (embedded controller) on Microsoft Surface devices. ++ ++ This driver can also be built as a module. If so, the module ++ will be called surface_temp. ++ + config SENSORS_ADC128D818 + tristate "Texas Instruments ADC128D818" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index b1c7056c37db..3ce8d6a9202e 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -209,6 +209,7 @@ obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o + obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o + obj-$(CONFIG_SENSORS_STTS751) += stts751.o + obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o ++obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o + obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o + obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o + obj-$(CONFIG_SENSORS_TC74) += tc74.o +diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c +new file mode 100644 +index 000000000000..48c3e826713f +--- /dev/null ++++ b/drivers/hwmon/surface_temp.c +@@ -0,0 +1,165 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2022-2023 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++/* -- SAM interface. -------------------------------------------------------- */ ++ ++SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x04, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x01, ++}); ++ ++static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) ++{ ++ __le16 sensors_le; ++ int status; ++ ++ status = __ssam_tmp_get_available_sensors(sdev, &sensors_le); ++ if (status) ++ return status; ++ ++ *sensors = le16_to_cpu(sensors_le); ++ return 0; ++} ++ ++static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temperature) ++{ ++ __le16 temp_le; ++ int status; ++ ++ status = __ssam_tmp_get_temperature(sdev->ctrl, sdev->uid.target, iid, &temp_le); ++ if (status) ++ return status; ++ ++ /* Convert 1/10 °K to 1/1000 °C */ ++ *temperature = (le16_to_cpu(temp_le) - 2731) * 100L; ++ return 0; ++} ++ ++ ++/* -- Driver.---------------------------------------------------------------- */ ++ ++struct ssam_temp { ++ struct ssam_device *sdev; ++ s16 sensors; ++}; ++ ++static umode_t ssam_temp_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ const struct ssam_temp *ssam_temp = data; ++ ++ if (!(ssam_temp->sensors & BIT(channel))) ++ return 0; ++ ++ return 0444; ++} ++ ++static int ssam_temp_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, long *value) ++{ ++ const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); ++ ++ return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); ++} ++ ++static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ), ++ /* We have at most 16 thermal sensor channels. */ ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT, ++ HWMON_T_INPUT), ++ NULL ++}; ++ ++static const struct hwmon_ops ssam_temp_hwmon_ops = { ++ .is_visible = ssam_temp_hwmon_is_visible, ++ .read = ssam_temp_hwmon_read, ++}; ++ ++static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { ++ .ops = &ssam_temp_hwmon_ops, ++ .info = ssam_temp_hwmon_info, ++}; ++ ++static int ssam_temp_probe(struct ssam_device *sdev) ++{ ++ struct ssam_temp *ssam_temp; ++ struct device *hwmon_dev; ++ s16 sensors; ++ int status; ++ ++ status = ssam_tmp_get_available_sensors(sdev, &sensors); ++ if (status) ++ return status; ++ ++ ssam_temp = devm_kzalloc(&sdev->dev, sizeof(*ssam_temp), GFP_KERNEL); ++ if (!ssam_temp) ++ return -ENOMEM; ++ ++ ssam_temp->sdev = sdev; ++ ssam_temp->sensors = sensors; ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, ++ "surface_thermal", ssam_temp, &ssam_temp_hwmon_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++ return 0; ++} ++ ++static const struct ssam_device_id ssam_temp_match[] = { ++ { SSAM_SDEV(TMP, SAM, 0x00, 0x02) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_temp_match); ++ ++static struct ssam_device_driver ssam_temp = { ++ .probe = ssam_temp_probe, ++ .match_table = ssam_temp_match, ++ .driver = { ++ .name = "surface_temp", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(ssam_temp); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.47.1 + +From 74dc2e2aecbb5d1e9e0ba543ae87ca4708fa5e92 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 30 Dec 2023 18:12:23 +0100 +Subject: [PATCH] hwmon: surface_temp: Add support for sensor names + +The thermal subsystem of the Surface Aggregator Module allows us to +query the names of the respective thermal sensors. Forward those to +userspace. + +Signed-off-by: Ivor Wanders +Co-Developed-by: Maximilian Luz +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/hwmon/surface_temp.c | 113 +++++++++++++++++++++++++++++------ + 1 file changed, 96 insertions(+), 17 deletions(-) + +diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c +index 48c3e826713f..4c08926139db 100644 +--- a/drivers/hwmon/surface_temp.c ++++ b/drivers/hwmon/surface_temp.c +@@ -17,6 +17,27 @@ + + /* -- SAM interface. -------------------------------------------------------- */ + ++/* ++ * Available sensors are indicated by a 16-bit bitfield, where a 1 marks the ++ * presence of a sensor. So we have at most 16 possible sensors/channels. ++ */ ++#define SSAM_TMP_SENSOR_MAX_COUNT 16 ++ ++/* ++ * All names observed so far are 6 characters long, but there's only ++ * zeros after the name, so perhaps they can be longer. This number reflects ++ * the maximum zero-padded space observed in the returned buffer. ++ */ ++#define SSAM_TMP_SENSOR_NAME_LENGTH 18 ++ ++struct ssam_tmp_get_name_rsp { ++ __le16 unknown1; ++ char unknown2; ++ char name[SSAM_TMP_SENSOR_NAME_LENGTH]; ++} __packed; ++ ++static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21); ++ + SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x04, +@@ -27,6 +48,11 @@ SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { + .command_id = 0x01, + }); + ++SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name, struct ssam_tmp_get_name_rsp, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x0e, ++}); ++ + static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) + { + __le16 sensors_le; +@@ -54,12 +80,37 @@ static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temp + return 0; + } + ++static int ssam_tmp_get_name(struct ssam_device *sdev, u8 iid, char *buf, size_t buf_len) ++{ ++ struct ssam_tmp_get_name_rsp name_rsp; ++ int status; ++ ++ status = __ssam_tmp_get_name(sdev->ctrl, sdev->uid.target, iid, &name_rsp); ++ if (status) ++ return status; ++ ++ /* ++ * This should not fail unless the name in the returned struct is not ++ * null-terminated or someone changed something in the struct ++ * definitions above, since our buffer and struct have the same ++ * capacity by design. So if this fails blow this up with a warning. ++ * Since the more likely cause is that the returned string isn't ++ * null-terminated, we might have received garbage (as opposed to just ++ * an incomplete string), so also fail the function. ++ */ ++ status = strscpy(buf, name_rsp.name, buf_len); ++ WARN_ON(status < 0); ++ ++ return status < 0 ? status : 0; ++} ++ + + /* -- Driver.---------------------------------------------------------------- */ + + struct ssam_temp { + struct ssam_device *sdev; + s16 sensors; ++ char names[SSAM_TMP_SENSOR_MAX_COUNT][SSAM_TMP_SENSOR_NAME_LENGTH]; + }; + + static umode_t ssam_temp_hwmon_is_visible(const void *data, +@@ -83,33 +134,47 @@ static int ssam_temp_hwmon_read(struct device *dev, + return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); + } + ++static int ssam_temp_hwmon_read_string(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); ++ ++ *str = ssam_temp->names[channel]; ++ return 0; ++} ++ + static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), +- /* We have at most 16 thermal sensor channels. */ ++ /* ++ * We have at most SSAM_TMP_SENSOR_MAX_COUNT = 16 thermal sensor ++ * channels. ++ */ + HWMON_CHANNEL_INFO(temp, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT, +- HWMON_T_INPUT), ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL), + NULL + }; + + static const struct hwmon_ops ssam_temp_hwmon_ops = { + .is_visible = ssam_temp_hwmon_is_visible, + .read = ssam_temp_hwmon_read, ++ .read_string = ssam_temp_hwmon_read_string, + }; + + static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { +@@ -122,6 +187,7 @@ static int ssam_temp_probe(struct ssam_device *sdev) + struct ssam_temp *ssam_temp; + struct device *hwmon_dev; + s16 sensors; ++ int channel; + int status; + + status = ssam_tmp_get_available_sensors(sdev, &sensors); +@@ -135,6 +201,19 @@ static int ssam_temp_probe(struct ssam_device *sdev) + ssam_temp->sdev = sdev; + ssam_temp->sensors = sensors; + ++ /* Retrieve the name for each available sensor. */ ++ for (channel = 0; channel < SSAM_TMP_SENSOR_MAX_COUNT; channel++) ++ { ++ if (!(sensors & BIT(channel))) ++ continue; ++ ++ status = ssam_tmp_get_name(sdev, channel + 1, ++ ssam_temp->names[channel], ++ SSAM_TMP_SENSOR_NAME_LENGTH); ++ if (status) ++ return status; ++ } ++ + hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, + "surface_thermal", ssam_temp, &ssam_temp_hwmon_chip_info, + NULL); +-- +2.47.1 + diff --git a/patches/6.11/0008-surface-sam-over-hid.patch b/patches/6.11/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..f4fa3f455e --- /dev/null +++ b/patches/6.11/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From 3b7581a18eb5fe1329200505b20c5b2ff2295ee4 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index 14ae0cfc325e..6197c5252d2a 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -639,6 +639,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -740,6 +761,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.47.1 + +From 70118b3481fed555e71e15e3afc19d54648463cd Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b629e82af97c..68656e8f309e 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..68db237734a1 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.47.1 + diff --git a/patches/6.11/0009-surface-button.patch b/patches/6.11/0009-surface-button.patch new file mode 100644 index 0000000000..a90a9a41ed --- /dev/null +++ b/patches/6.11/0009-surface-button.patch @@ -0,0 +1,149 @@ +From ed528610f8b376e59834d8facc2f3bcde3aa4227 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index 5c5d407fe965..4e1bfe90e730 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.47.1 + +From b0202f08273bd8d28bc2652d6d28163a3c20c62d Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979c..4240c98ca226 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.47.1 + diff --git a/patches/6.11/0010-surface-typecover.patch b/patches/6.11/0010-surface-typecover.patch new file mode 100644 index 0000000000..b87b963797 --- /dev/null +++ b/patches/6.11/0010-surface-typecover.patch @@ -0,0 +1,573 @@ +From e7b4f40261a112d31be87dacfd756af00cbacf7e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 13171454f959..a83beefd25f3 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.47.1 + +From 0e5bd25f10026a249ebd03a85a0629957c3cbdd0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 24da73964763..42c393912645 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -34,7 +34,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -72,12 +76,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -168,6 +175,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -212,6 +221,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + + #define MT_DEFAULT_MAXCONTACT 10 + #define MT_MAX_MAXCONTACT 250 +@@ -396,6 +406,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_SEPARATE_APP_REPORT, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1745,6 +1765,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1768,6 +1851,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1806,15 +1892,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1864,6 +1954,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2290,6 +2381,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.47.1 + +From a316b2f907765659374f52acf21df1751c1eb371 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 42c393912645..c44a43db1444 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -77,6 +77,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -84,6 +85,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -408,6 +411,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1389,6 +1393,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1416,6 +1423,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1437,6 +1459,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1444,6 +1467,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1453,11 +1489,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1635,6 +1681,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1683,6 +1765,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1765,30 +1854,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1797,8 +1862,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1932,13 +1998,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1947,12 +2024,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); +-- +2.47.1 + diff --git a/patches/6.11/0011-surface-shutdown.patch b/patches/6.11/0011-surface-shutdown.patch new file mode 100644 index 0000000000..573f651d42 --- /dev/null +++ b/patches/6.11/0011-surface-shutdown.patch @@ -0,0 +1,97 @@ +From 44765b95447983e3e02aa26937c6b04266f09461 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index f412ef73a6e4..9892cd72dd2c 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index dccb60c1d9cc..7baec2d30547 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6298,3 +6298,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 37d97bef060f..056fce19477b 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -466,6 +466,7 @@ struct pci_dev { + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.47.1 + diff --git a/patches/6.11/0012-surface-gpe.patch b/patches/6.11/0012-surface-gpe.patch new file mode 100644 index 0000000000..01f1b4ca09 --- /dev/null +++ b/patches/6.11/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From b2ce5736b25f6c6f029c77d95f2d81ea35f8b879 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index 62fd4004db31..103fc4468262 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.47.1 + diff --git a/patches/6.11/0013-cameras.patch b/patches/6.11/0013-cameras.patch new file mode 100644 index 0000000000..e2410de9bf --- /dev/null +++ b/patches/6.11/0013-cameras.patch @@ -0,0 +1,781 @@ +From ea6650d04f5ab12a4985ebb97369ebf01ea40cd0 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 22ae7829a915..0fe281280316 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2185,6 +2185,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.47.1 + +From 00944c91ecce56b317b14bafecc91dabf437647e Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index d664dde86fcd..9b6f761edbfd 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -45,6 +45,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -223,12 +230,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -2176,6 +2185,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2478,6 +2490,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4726,6 +4741,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4773,6 +4800,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.47.1 + +From a5f60bb225cd2d5e75f0c6bfb658a49516d8f0d1 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 1e107fd49f82..e3e1696e7f0e 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.47.1 + +From 43afa58f1d17a9b89a13563814342413869d0968 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 2 Mar 2023 12:59:39 +0000 +Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E + +ACPI _HID INT347E represents the OmniVision 7251 camera sensor. The +driver for this sensor expects a single pin named "enable", but on +some Microsoft Surface platforms the sensor is assigned a single +GPIO who's type flag is INT3472_GPIO_TYPE_RESET. + +Remap the GPIO pin's function from "reset" to "enable". This is done +outside of the existing remap table since it is a more widespread +discrepancy than that method is designed for. Additionally swap the +polarity of the pin to match the driver's expectation. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/discrete.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index 07b302e09340..baad1e50ca81 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -83,12 +83,27 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + const char *func, u32 polarity) + { + int ret; ++ const struct acpi_device_id ov7251_ids[] = { ++ { "INT347E" }, ++ { } ++ }; + + if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { + dev_warn(int3472->dev, "Too many GPIOs mapped\n"); + return -EINVAL; + } + ++ /* ++ * In addition to the function remap table we need to bulk remap the ++ * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that ++ * expects its only GPIO pin to be called "enable" (and to have the ++ * opposite polarity). ++ */ ++ if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { ++ func = "enable"; ++ polarity ^= GPIO_ACTIVE_LOW; ++ } ++ + ret = skl_int3472_fill_gpiod_lookup(&int3472->gpios.table[int3472->n_sensor_gpios], + agpio, func, polarity); + if (ret) +-- +2.47.1 + +From 0427087234f484f6fc57ae685c3189da3fe8a0d8 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 30f61e04ecaf..9c1292ca8552 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1051,7 +1051,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1572,7 +1572,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.47.1 + +From 7babcb1b21ad4297eeddb2c401975b9243639fad Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a8221fb..4f6bafd900ee 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index f19c8adf2c61..923ed1b5ab8b 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1219,10 +1219,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.47.1 + +From ad83797e9e55858eb1534145ffaf477aaaf01682 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index e3e1696e7f0e..423dc555093f 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -200,7 +200,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.47.1 + +From 858bebab3db0d2f4481ac7c0d5d52b6cd5534d24 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db0..2d2abb25b944 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.47.1 + +From f3bbef325371ea42bf3de8683a583dee9d3314d4 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 8d9d8da376e4..d8597897aa83 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -933,6 +933,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 18afbb5a23ee..a1d16c0af82d 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -88,6 +88,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o + obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.47.1 + +From 8bccdfbc120914eb0364d20b5f2ea82d2a2b3643 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Sat, 3 Feb 2024 12:59:53 +0900 +Subject: [PATCH] media: staging: ipu3-imgu: Fix multiple calls of s_stream on + stream stop + +Adapt to 009905e "media: v4l2-subdev: Document and enforce .s_stream() requirements" + +Patchset: cameras +--- + drivers/staging/media/ipu3/ipu3-v4l2.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c +index 3df58eb3e882..81aff2d5d898 100644 +--- a/drivers/staging/media/ipu3/ipu3-v4l2.c ++++ b/drivers/staging/media/ipu3/ipu3-v4l2.c +@@ -538,18 +538,18 @@ static void imgu_vb2_stop_streaming(struct vb2_queue *vq) + + WARN_ON(!node->enabled); + +- pipe = node->pipe; +- dev_dbg(dev, "Try to stream off node [%u][%u]", pipe, node->id); +- imgu_pipe = &imgu->imgu_pipe[pipe]; +- r = v4l2_subdev_call(&imgu_pipe->imgu_sd.subdev, video, s_stream, 0); +- if (r) +- dev_err(&imgu->pci_dev->dev, +- "failed to stop subdev streaming\n"); +- + mutex_lock(&imgu->streaming_lock); + /* Was this the first node with streaming disabled? */ + if (imgu->streaming && imgu_all_nodes_streaming(imgu, node)) { + /* Yes, really stop streaming now */ ++ pipe = node->pipe; ++ dev_dbg(dev, "Try to stream off node [%u][%u]", pipe, node->id); ++ imgu_pipe = &imgu->imgu_pipe[pipe]; ++ r = v4l2_subdev_call(&imgu_pipe->imgu_sd.subdev, video, s_stream, 0); ++ if (r) ++ dev_err(&imgu->pci_dev->dev, ++ "failed to stop subdev streaming\n"); ++ + dev_dbg(dev, "IMGU streaming is ready to stop"); + r = imgu_s_stream(imgu, false); + if (!r) +-- +2.47.1 + +From eee949846b8d9fbd5855c45aa1413bb00649d202 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index c626ed845928..0094cfda57ea 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -82,6 +82,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); + +-- +2.47.1 + diff --git a/patches/6.11/0014-amd-gpio.patch b/patches/6.11/0014-amd-gpio.patch new file mode 100644 index 0000000000..23018e1162 --- /dev/null +++ b/patches/6.11/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From 32152c390f4118c40c31ab15936545309d96f077 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 4efecac49863..88377bb0d137 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1132,6 +1133,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1187,6 +1199,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.47.1 + +From cbc4937b9a38e01f7209870c115f4f2fea7e9b37 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 88377bb0d137..c58f26918b17 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1135,12 +1135,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.47.1 + diff --git a/patches/6.11/0015-rtc.patch b/patches/6.11/0015-rtc.patch new file mode 100644 index 0000000000..c571e0fd9b --- /dev/null +++ b/patches/6.11/0015-rtc.patch @@ -0,0 +1,110 @@ +From 574d90399ed8e97d063ac021e74b5e3cece2071a Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index b831cb8e53dc..78bd0f926505 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -565,13 +572,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + + pm_runtime_get_sync(dev); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -613,12 +625,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -649,6 +655,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.47.1 + diff --git a/patches/6.12/0001-secureboot.patch b/patches/6.12/0001-secureboot.patch new file mode 100644 index 0000000000..142e399708 --- /dev/null +++ b/patches/6.12/0001-secureboot.patch @@ -0,0 +1,112 @@ +From 6877febf6457275aa81ef3b912bc3393698ce3f3 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index b5c79f433..a1bbedd98 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.52.0 + +From b3b97c7e967ba187f7e19ba2dc601f6adf7bb7ce Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index e88505e94..15961654b 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3085,6 +3085,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index 85008ead2..48df5cfaf 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -37,6 +37,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -97,7 +98,7 @@ bool hibernation_in_progress(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1436,6 +1437,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1494,3 +1501,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.52.0 + diff --git a/patches/6.12/0002-surface3-oemb.patch b/patches/6.12/0002-surface3-oemb.patch new file mode 100644 index 0000000000..a92bba8099 --- /dev/null +++ b/patches/6.12/0002-surface3-oemb.patch @@ -0,0 +1,101 @@ +From 432544da5814c6ef03cea6fd19e304a732c638f9 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3-oemb +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index c15ed7a12..1ec8edb5a 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 51187b1e0..bfb83ce8d 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3790,6 +3790,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0..0b930c91b 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.52.0 + diff --git a/patches/6.12/0003-mwifiex.patch b/patches/6.12/0003-mwifiex.patch new file mode 100644 index 0000000000..ec284d30df --- /dev/null +++ b/patches/6.12/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 92c40c9dc2adc649202903a880a4376ba421a865 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 5f997becd..9a9929424 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1d..f46b06f8d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964ae..5d30ae39d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.52.0 + +From 426a0dace73b66616780d6cabe2fd4d3e097f0d2 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 9a9929424..2273e3029 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d..99b024ecb 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d..c14eb56eb 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.52.0 + +From 51e1c076bba9fcbff159ca77ba1a82fbf7033ecb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 603ff13d9..9209e8e29 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -66,6 +66,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) + #define BTUSB_BARROT BIT(28) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(29) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -469,6 +470,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -4009,6 +4011,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.52.0 + diff --git a/patches/6.12/0004-ath10k.patch b/patches/6.12/0004-ath10k.patch new file mode 100644 index 0000000000..dcfc8b554a --- /dev/null +++ b/patches/6.12/0004-ath10k.patch @@ -0,0 +1,120 @@ +From 8c3eb2772c31f18f4d3b9df993fc2984604f9035 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index f9b51d98d..e04bac901 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -41,6 +41,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -53,6 +56,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -62,6 +68,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -932,6 +941,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -946,6 +991,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.52.0 + diff --git a/patches/6.12/0005-ipts.patch b/patches/6.12/0005-ipts.patch new file mode 100644 index 0000000000..e1f9ec697a --- /dev/null +++ b/patches/6.12/0005-ipts.patch @@ -0,0 +1,3241 @@ +From 1890f0507b799548781b7a28886b8625399691c0 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index fa30899a5..a1864dcb7 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 1e4edf896..61d1a8816 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.52.0 + +From 7f8a88610707324d4109f07afe9279a79c651158 Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index c799cc67d..65adfc813 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -40,6 +40,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -208,12 +213,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -2066,6 +2073,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2364,6 +2374,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + /* +@@ -4691,6 +4704,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4729,6 +4754,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.52.0 + +From 4fc39217748aa357920ef5c82932541bb24f7a6a Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 95a4ede27..777d4619d 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1388,4 +1388,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" + + source "drivers/hid/surface-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 27ee02bf6..15a67dc83 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -170,3 +170,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000..297401bd3 +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000..883896f68 +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000..63a4934bb +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000..2b4079075 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000..ba33259f1 +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000..5360842d2 +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000..26629c514 +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000..307438c7c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000..7b9f54388 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000..eeeb6575e +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000..639940794 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000..064e37169 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000..e34a1a4f9 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000..1ebe77447 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000..fb5b5c13e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000..1e0395cea +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000..973bade6b +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000..977724c72 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000..3de7da62d +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000..cc14653b2 +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000..2068e1328 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000..e8dd98895 +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000..41845f9d9 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000..5a58d4a0a +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000..355e92bea +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000..1f966b8b3 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.52.0 + diff --git a/patches/6.12/0006-ithc.patch b/patches/6.12/0006-ithc.patch new file mode 100644 index 0000000000..a4bae0fb6d --- /dev/null +++ b/patches/6.12/0006-ithc.patch @@ -0,0 +1,2771 @@ +From bc6959f413d60e79c333fb8b24dd3d35558316e0 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 066e4cd6e..7b320d09d 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -381,6 +381,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.52.0 + +From 9e031dc46bd980b02718fd00b1a0a5b9af18f173 Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 431 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2568 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 777d4619d..d23316df4 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1390,4 +1390,6 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 15a67dc83..fcbc18ceb 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -172,3 +172,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000..4937ba131 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000..ede713023 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000..2d8c6afe9 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000..38c53d916 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000..bf4eab330 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000..1749a5819 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000..065646ab4 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000..599eb912c +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000..8883987fb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000..28d692462 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000..ac56c2536 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,431 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_ANY_ID, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .class = PCI_CLASS_INPUT_PEN << 8, ++ .class_mask = ~0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ del_timer_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000..e2d1690b8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000..74d882f6b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000..c0f13506a +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000..4f541fe53 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000..aec320d4e +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.52.0 + diff --git a/patches/6.12/0007-surface-sam-over-hid.patch b/patches/6.12/0007-surface-sam-over-hid.patch new file mode 100644 index 0000000000..e0a402df7a --- /dev/null +++ b/patches/6.12/0007-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From 2706f5bc42ccf89b99e29d9e6ccce296d577b20b Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index f43067f67..1761ca30e 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -662,6 +662,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -763,6 +784,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.52.0 + +From 014f329f675c21a32cf6f9639eed27230380422a Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b629e82af..68656e8f3 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 533443309..7efcd0cdb 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000..68db23773 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.52.0 + diff --git a/patches/6.12/0008-surface-button.patch b/patches/6.12/0008-surface-button.patch new file mode 100644 index 0000000000..129e728182 --- /dev/null +++ b/patches/6.12/0008-surface-button.patch @@ -0,0 +1,149 @@ +From a34c0a35f3e02e016a0f9025f0074b5073ed7acd Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index 5c5d407fe..4e1bfe90e 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.52.0 + +From f5ceefaf29e53be0086ecf4820e2a1719b77275a Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f9..4240c98ca 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.52.0 + diff --git a/patches/6.12/0009-surface-typecover.patch b/patches/6.12/0009-surface-typecover.patch new file mode 100644 index 0000000000..a3165a9546 --- /dev/null +++ b/patches/6.12/0009-surface-typecover.patch @@ -0,0 +1,575 @@ +From 42a180489bac8661318ad604eea360b786cebae3 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 323a949bb..abc2cdecd 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.52.0 + +From 107614f6918d76f712db23504912511c441f517d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 0e4cb0e66..26b551523 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -34,7 +34,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -72,12 +76,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -171,6 +178,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -215,6 +224,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -405,6 +415,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1763,6 +1783,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1786,6 +1869,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1824,8 +1910,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); +@@ -1834,8 +1922,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1885,6 +1975,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2314,6 +2405,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.52.0 + +From 41bb83b6c2ce88d10aa76f6e233e30beca605db0 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 26b551523..37a3e6489 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -77,6 +77,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -84,6 +85,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -417,6 +420,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1395,6 +1399,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1422,6 +1429,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1443,6 +1465,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1450,6 +1473,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1459,11 +1495,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1648,6 +1694,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1697,6 +1779,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1783,30 +1872,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1815,8 +1880,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1953,13 +2019,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1968,12 +2045,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); +-- +2.52.0 + diff --git a/patches/6.12/0010-surface-shutdown.patch b/patches/6.12/0010-surface-shutdown.patch new file mode 100644 index 0000000000..c9e75ced02 --- /dev/null +++ b/patches/6.12/0010-surface-shutdown.patch @@ -0,0 +1,97 @@ +From 6f15b3e9b2948b4e5c8fd3a22915d813432c1378 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown mehtod + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index a00a2ce01..9754d756c 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 18fa918b4..7a7613264 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6338,3 +6338,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 242ee3843..96753fb66 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -482,6 +482,7 @@ struct pci_dev { + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.52.0 + diff --git a/patches/6.12/0011-surface-gpe.patch b/patches/6.12/0011-surface-gpe.patch new file mode 100644 index 0000000000..8e7fad88ce --- /dev/null +++ b/patches/6.12/0011-surface-gpe.patch @@ -0,0 +1,51 @@ +From 790fb8b5cf76dc803866536a02da951aed484c52 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index 62fd4004d..103fc4468 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.52.0 + diff --git a/patches/6.12/0012-cameras.patch b/patches/6.12/0012-cameras.patch new file mode 100644 index 0000000000..828722b98d --- /dev/null +++ b/patches/6.12/0012-cameras.patch @@ -0,0 +1,734 @@ +From ee0d663e7b026f0d55bbaec01a5396dc854564b9 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index ba98763ce..6021907ec 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2209,6 +2209,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.52.0 + +From 2447fb5d79ac300dbb8a9d77c7345b4d480783fc Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 65adfc813..3dcabd6c5 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -45,6 +45,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -214,12 +221,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -2074,6 +2083,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2374,6 +2386,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4704,6 +4719,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4754,6 +4781,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.52.0 + +From df89ea0106e2dc9612841d751af1790dc7590f95 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 81ac4c691..f453c9043 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.52.0 + +From 5d26b73e9c4fafc85e10fa0f6cff5b521439daf9 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 2 Mar 2023 12:59:39 +0000 +Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E + +ACPI _HID INT347E represents the OmniVision 7251 camera sensor. The +driver for this sensor expects a single pin named "enable", but on +some Microsoft Surface platforms the sensor is assigned a single +GPIO who's type flag is INT3472_GPIO_TYPE_RESET. + +Remap the GPIO pin's function from "reset" to "enable". This is done +outside of the existing remap table since it is a more widespread +discrepancy than that method is designed for. Additionally swap the +polarity of the pin to match the driver's expectation. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/discrete.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index 9e69ac9cf..d6003198a 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -81,12 +81,27 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + const char *func, unsigned long gpio_flags) + { + int ret; ++ const struct acpi_device_id ov7251_ids[] = { ++ { "INT347E" }, ++ { } ++ }; + + if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { + dev_warn(int3472->dev, "Too many GPIOs mapped\n"); + return -EINVAL; + } + ++ /* ++ * In addition to the function remap table we need to bulk remap the ++ * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that ++ * expects its only GPIO pin to be called "enable" (and to have the ++ * opposite polarity). ++ */ ++ if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { ++ func = "enable"; ++ gpio_flags ^= GPIO_ACTIVE_LOW; ++ } ++ + ret = skl_int3472_fill_gpiod_lookup(&int3472->gpios.table[int3472->n_sensor_gpios], + agpio, func, gpio_flags); + if (ret) +-- +2.52.0 + +From 382fa1814a74f86e6fdb7cf1958819a47db75e55 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 3226888d7..3bfe45b76 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1053,7 +1053,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1574,7 +1574,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.52.0 + +From 3766a8f87d398ee3963d43faca14f695c1736171 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a822..4f6bafd90 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index f19c8adf2..923ed1b5a 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1219,10 +1219,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.52.0 + +From bbdda831930f5248735ac63df2696f2caf286e2a Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index f453c9043..b8ad6b413 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -203,7 +203,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.52.0 + +From 6e50522329564b39a2a263aae50eb61a279d299c Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329..2d2abb25b 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.52.0 + +From bd12a50a8fa84acc18af0cf1dfa0aa465947b855 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index b784bb74a..f8d8fcfac 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -941,6 +941,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 18afbb5a2..a1d16c0af 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -88,6 +88,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o + obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000..35aeb5db8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.52.0 + +From 483a9864ec356caec47b62251a5289962cbc189b Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index c626ed845..0094cfda5 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -82,6 +82,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); + +-- +2.52.0 + diff --git a/patches/6.12/0013-amd-gpio.patch b/patches/6.12/0013-amd-gpio.patch new file mode 100644 index 0000000000..40b66c0941 --- /dev/null +++ b/patches/6.12/0013-amd-gpio.patch @@ -0,0 +1,109 @@ +From 9993bf7a1efe55b66f8c8109e65e0ac355f1ba25 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 63adda8a1..00914222a 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + +@@ -1174,6 +1175,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1229,6 +1241,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.52.0 + +From 1494323ffcfaadbb7c19554d347dd0cf270134de Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 00914222a..2b35bb58b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1177,12 +1177,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.52.0 + diff --git a/patches/6.12/0014-rtc.patch b/patches/6.12/0014-rtc.patch new file mode 100644 index 0000000000..bdf7409226 --- /dev/null +++ b/patches/6.12/0014-rtc.patch @@ -0,0 +1,110 @@ +From e99383a91ae6cd8861bfdc564ed1d02d23263bec Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index b75ceaab7..3dfcfd32d 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -568,13 +575,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + if (dd->capabilities & ACPI_TAD_RT) + sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -616,12 +628,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -652,6 +658,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.52.0 + diff --git a/patches/6.13/0001-secureboot.patch b/patches/6.13/0001-secureboot.patch new file mode 100644 index 0000000000..32c1ff237d --- /dev/null +++ b/patches/6.13/0001-secureboot.patch @@ -0,0 +1,112 @@ +From 4b43dd2ed91c6e744808726ef85c4926a99bedf5 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index b5c79f43359b..a1bbedd989e4 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.49.0 + +From 558331080b6d47dfe55d3a4394fbdf2a3175940b Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index 3872bc6ec49d..e322b97879c2 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3074,6 +3074,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index 10a01af63a80..e53e4a86e4cc 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -37,6 +37,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -92,7 +93,7 @@ void hibernate_release(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1434,6 +1435,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1492,3 +1499,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.49.0 + diff --git a/patches/6.13/0002-surface3.patch b/patches/6.13/0002-surface3.patch new file mode 100644 index 0000000000..8e65cf4323 --- /dev/null +++ b/patches/6.13/0002-surface3.patch @@ -0,0 +1,234 @@ +From 1861d3780461b6a1640979f316576342b1a9c990 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3 +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index 6c8fb7a4dde4..22797a53f4d8 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 51187b1e0ed2..bfb83ce8d8f8 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3790,6 +3790,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0c28..0b930c91bccb 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.49.0 + +From d1425f39cb72c625b785ae50b563543f9cdf384b Mon Sep 17 00:00:00 2001 +From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> +Date: Fri, 6 Dec 2019 23:10:30 +0900 +Subject: [PATCH] surface3-spi: workaround: disable DMA mode to avoid crash by + default + +On Arch Linux kernel at least after 4.19, touch input is broken after suspend +(s2idle). + + kern :err : [ +0.203408] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + +On recent stable Arch Linux kernel (at least after 5.1), touch input will +crash after the first touch. + + kern :err : [ +0.203592] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + kern :err : [ +0.000173] spi_master spi1: failed to transfer one message from queue + +I found on an affected system (Arch Linux kernel, etc.), the touchscreen +driver uses DMA mode by default. Then, we found some kernels with different +kernel config (5.1 kernel config from Jakeday [1] or Chromium OS kernel +chromeos-4.19 [2]) will use PIO mode by default and no such issues there. + +So, this commit disables DMA mode on the touchscreen driver side as a quick +workaround to avoid touch input crash. +We may need to properly set up DMA mode to use the touchscreen driver with +DMA mode. + +You can still switch DMA/PIO mode if you want: + + switch to DMA mode (maybe broken) + echo 1 | sudo tee /sys/module/surface3_spi/parameters/use_dma + back to PIO mode + echo 0 | sudo tee /sys/module/surface3_spi/parameters/use_dma + +Link to issue: https://github.com/jakeday/linux-surface/issues/596 + +References: +[1] https://github.com/jakeday/linux-surface/blob/master/configs/5.1/config +[2] https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19 + +Tested on Arch Linux 5.4.1 with Surface 3, which will use DMA by default. +This commit made the driver use PIO by default and no touch input crash. +Also tested on chromeos-4.19 4.19.90 with Surface 3, which will use PIO by default +even without this commit. After this commit, it still uses PIO and confirmed +no functional changes regarding touch input. + +More details: + We can confirm which mode the touchscreen driver uses; first, enable + debug output: + + echo "file drivers/spi/spi-pxa2xx.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + echo "file drivers/input/touchscreen/surface3_spi.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + + Then, try to make a touch input and see dmesg log + + (On Arch Linux kernel, uses DMA) + kern :debug : [ +0.006383] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, DMA + kern :debug : [ +0.000495] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 18 00 e4 01 00 04 1a 04 1a e3 0c e3 0c b0 00 + c5 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + (On the kernels I referenced above, uses PIO) + kern :debug : [ +0.009260] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, PIO + kern :debug : [ +0.001105] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 24 00 e4 01 00 58 0b 58 0b 83 12 83 12 26 01 + 95 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +Note (2025-03-08): This patch was originally dropped due to the +comments in [3]. However, according to the commments in [4] it is still +required. + +[3]: https://github.com/linux-surface/kernel/commit/a3421c12bed0e46c28518bcb8c6b22f237c6dc7a +[4]: https://github.com/linux-surface/linux-surface/issues/1184 + +Patchset: surface3 +--- + drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c +index 6074b7730e86..6aa3e1d6f160 100644 +--- a/drivers/input/touchscreen/surface3_spi.c ++++ b/drivers/input/touchscreen/surface3_spi.c +@@ -25,6 +25,12 @@ + #define SURFACE3_REPORT_TOUCH 0xd2 + #define SURFACE3_REPORT_PEN 0x16 + ++bool use_dma = false; ++module_param(use_dma, bool, 0644); ++MODULE_PARM_DESC(use_dma, ++ "Disable DMA mode if you encounter touch input crash. " ++ "(default: false, disabled to avoid crash)"); ++ + struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; +@@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) + return 0; + } + ++static bool surface3_spi_can_dma(struct spi_controller *ctlr, ++ struct spi_device *spi, ++ struct spi_transfer *tfr) ++{ ++ return use_dma; ++} ++ + static int surface3_spi_probe(struct spi_device *spi) + { + struct surface3_ts_data *data; +@@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) + if (error) + return error; + ++ /* ++ * Set up DMA ++ * ++ * TODO: Currently, touch input with DMA seems to be broken. ++ * On 4.19 LTS, touch input will crash after suspend. ++ * On recent stable kernel (at least after 5.1), touch input will crash after ++ * the first touch. No problem with PIO on those kernels. ++ * Maybe we need to configure DMA here. ++ * ++ * Link to issue: https://github.com/jakeday/linux-surface/issues/596 ++ */ ++ spi->controller->can_dma = surface3_spi_can_dma; ++ + return 0; + } + +-- +2.49.0 + diff --git a/patches/6.13/0003-mwifiex.patch b/patches/6.13/0003-mwifiex.patch new file mode 100644 index 0000000000..1ba129b6a0 --- /dev/null +++ b/patches/6.13/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 9c9103776669669ae724ca844f9ea1c24a3858dd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 5f997becdbaa..9a9929424513 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd..f46b06f8d643 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5b..5d30ae39d65e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.49.0 + +From de88988bb0d5cbe32fabac2241c84c394d988c3d Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 9a9929424513..2273e3029776 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65e..c14eb56eb911 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.49.0 + +From 4abcf78a128e215d638f9718acf11858cd5f278a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 75dbe07e07e2..fd0f1aa5eaa9 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -65,6 +65,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -440,6 +441,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -3870,6 +3872,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.49.0 + diff --git a/patches/6.13/0004-ath10k.patch b/patches/6.13/0004-ath10k.patch new file mode 100644 index 0000000000..d659441c60 --- /dev/null +++ b/patches/6.13/0004-ath10k.patch @@ -0,0 +1,120 @@ +From c859f592a5dfe3f04fecbf2be6c6fd470759008e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index b3294287bce1..2936fdae823c 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -40,6 +40,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -52,6 +55,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -61,6 +67,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -931,6 +940,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -945,6 +990,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.49.0 + diff --git a/patches/6.13/0005-ipts.patch b/patches/6.13/0005-ipts.patch new file mode 100644 index 0000000000..31b55fbb8c --- /dev/null +++ b/patches/6.13/0005-ipts.patch @@ -0,0 +1,3241 @@ +From 9afe48759efd50b5a28f2e01118464a72f1cfb70 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index a5f88ec97df7..e379dc2373b5 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index d6ff9d82ae94..a1b714505f43 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.49.0 + +From 0e2a5e55b467f538bdf2ec6c91c4b7aa36fc083f Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 9ab5371c3538..1839a831a89f 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -40,6 +40,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -208,12 +213,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -1903,6 +1910,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2201,6 +2211,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + /* +@@ -4509,6 +4522,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4544,6 +4569,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.49.0 + +From bed448bd281fe2969f17049a5a2192d1e5374618 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 363c860835d3..799aceeeea89 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1387,4 +1387,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" + + source "drivers/hid/surface-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 1f50a6ecadbb..b82016429b01 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -170,3 +170,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..7b9f54388a9f +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..639940794615 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..977724c728c3 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.49.0 + diff --git a/patches/6.13/0006-ithc.patch b/patches/6.13/0006-ithc.patch new file mode 100644 index 0000000000..68553e49bc --- /dev/null +++ b/patches/6.13/0006-ithc.patch @@ -0,0 +1,2771 @@ +From bccd64f261b835edd1abfc24c2f6ec170fb54728 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 466c1412dd45..565686813588 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -389,6 +389,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.49.0 + +From 4ad821daf98ea471e649a4c7e5be4c4ea140000b Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 431 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2568 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 799aceeeea89..8b09289cbe8a 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1389,4 +1389,6 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index b82016429b01..9ac9e13ad64c 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -172,3 +172,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..4937ba131297 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..2d8c6afe9966 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..bf4eab33062b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..1749a5819b3e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..8883987fb352 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..ac56c253674b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,431 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_ANY_ID, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .class = PCI_CLASS_INPUT_PEN << 8, ++ .class_mask = ~0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ del_timer_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..e2d1690b8cf8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..c0f13506af20 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..4f541fe533fa +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..aec320d4e945 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.49.0 + diff --git a/patches/6.13/0007-surface-sam.patch b/patches/6.13/0007-surface-sam.patch new file mode 100644 index 0000000000..ccdd91ce36 --- /dev/null +++ b/patches/6.13/0007-surface-sam.patch @@ -0,0 +1,211 @@ +From 743c35534434dc42eecd818a5d6d02f33032f2f7 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 17 Jun 2022 02:14:00 +0200 +Subject: [PATCH] rtc: Add basic support for RTC via Surface System Aggregator + Module + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/rtc/Kconfig | 7 +++ + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-surface.c | 129 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 137 insertions(+) + create mode 100644 drivers/rtc/rtc-surface.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index a60bcc791a48..2de96e4c1ba6 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -1383,6 +1383,13 @@ config RTC_DRV_NTXEC + embedded controller found in certain e-book readers designed by the + original design manufacturer Netronix. + ++config RTC_DRV_SURFACE ++ tristate "Microsoft Surface Aggregator RTC" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ TODO ++ + comment "on-CPU RTC drivers" + + config RTC_DRV_ASM9260 +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index 489b4ab07068..061afa9db5b6 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -180,6 +180,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o + obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o + obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o + obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o ++obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o + obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o + obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o + obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o +diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c +new file mode 100644 +index 000000000000..f6c17c4e98d5 +--- /dev/null ++++ b/drivers/rtc/rtc-surface.c +@@ -0,0 +1,129 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * AC driver for 7th-generation Microsoft Surface devices via Surface System ++ * Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct surface_rtc { ++ struct ssam_device *sdev; ++ struct rtc_device *rtc; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x10, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x0f, ++}); ++ ++static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) ++{ ++ __le32 time_le; ++ int status; ++ ++ status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); ++ if (status) ++ return status; ++ ++ *time = le32_to_cpu(time_le); ++ return 0; ++} ++ ++static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) ++{ ++ __le32 time_le = cpu_to_le32(time); ++ ++ return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); ++} ++ ++static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ int status; ++ u32 time; ++ ++ status = ssam_rtc_get_unix_time(srtc, &time); ++ if (status) ++ return status; ++ ++ rtc_time64_to_tm(time, tm); ++ return 0; ++} ++ ++static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ time64_t time = rtc_tm_to_time64(tm); ++ ++ return ssam_rtc_set_unix_time(srtc, (u32)time); ++} ++ ++static const struct rtc_class_ops surface_rtc_ops = { ++ .read_time = surface_rtc_read_time, ++ .set_time = surface_rtc_set_time, ++}; ++ ++static int surface_rtc_probe(struct ssam_device *sdev) ++{ ++ struct surface_rtc *srtc; ++ ++ srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); ++ if (!srtc) ++ return -ENOMEM; ++ ++ srtc->sdev = sdev; ++ ++ srtc->rtc = devm_rtc_allocate_device(&sdev->dev); ++ if (IS_ERR(srtc->rtc)) ++ return PTR_ERR(srtc->rtc); ++ ++ srtc->rtc->ops = &surface_rtc_ops; ++ srtc->rtc->range_max = U32_MAX; ++ ++ ssam_device_set_drvdata(sdev, srtc); ++ ++ return devm_rtc_register_device(srtc->rtc); ++} ++ ++static void surface_rtc_remove(struct ssam_device *sdev) ++{ ++ /* Device-managed allocations take care of everything... */ ++} ++ ++static const struct ssam_device_id surface_rtc_match[] = { ++ { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_rtc_match); ++ ++static struct ssam_device_driver surface_rtc_driver = { ++ .probe = surface_rtc_probe, ++ .remove = surface_rtc_remove, ++ .match_table = surface_rtc_match, ++ .driver = { ++ .name = "surface_rtc", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_rtc_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.49.0 + +From 47fccc8a7d20139c64577c160bc56eaf4bc2b953 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 20 Apr 2025 01:05:14 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add Surface Laptop 7 + (ACPI) + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index a594d5fcfcfd..07b03aa4fa7f 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -460,6 +460,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + ++ /* Surface Laptop 7 */ ++ { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, ++ + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + +-- +2.49.0 + diff --git a/patches/6.13/0008-surface-sam-over-hid.patch b/patches/6.13/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..96d111c477 --- /dev/null +++ b/patches/6.13/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From b31805e73ebae15726da67431317007ff11e91df Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index d2499f302b50..77ce5ec3dd9e 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -661,6 +661,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -762,6 +783,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.49.0 + +From 1383dcb9227a733f83c4a5f5fb266ade3eab27c8 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b629e82af97c..68656e8f309e 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..68db237734a1 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.49.0 + diff --git a/patches/6.13/0009-surface-button.patch b/patches/6.13/0009-surface-button.patch new file mode 100644 index 0000000000..2925fa5b82 --- /dev/null +++ b/patches/6.13/0009-surface-button.patch @@ -0,0 +1,149 @@ +From 36744fee38cc8c3674e53a6a0521db8f84bf9ea1 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index b8cad415c62c..43b5d56383e3 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.49.0 + +From 4bd976ecb12b42aa6b88924078a81cb42d8476a9 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979c..4240c98ca226 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.49.0 + diff --git a/patches/6.13/0010-surface-typecover.patch b/patches/6.13/0010-surface-typecover.patch new file mode 100644 index 0000000000..b86ff0fe1f --- /dev/null +++ b/patches/6.13/0010-surface-typecover.patch @@ -0,0 +1,575 @@ +From 0409e9b3f615228d69485901cbcf251c830387a6 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 6926bd639ec6..b08e3d206c31 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.49.0 + +From e240ca7b5b78037f573916c0e8c5fd7cffb07613 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index e50887a6d22c..4ce18f21a141 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -35,7 +35,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -48,6 +51,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -73,12 +77,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -176,6 +183,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -220,6 +229,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -410,6 +420,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1759,6 +1779,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1782,6 +1865,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1820,8 +1906,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); +@@ -1830,8 +1918,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1881,6 +1971,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2304,6 +2395,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.49.0 + +From 09ee792ac8f01560084e2dad8eeaf7a4296dd850 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 4ce18f21a141..f5e4d52bd2eb 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -78,6 +78,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -85,6 +86,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -422,6 +425,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1403,6 +1407,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1430,6 +1437,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1451,6 +1473,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1458,6 +1481,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1467,11 +1503,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1646,6 +1692,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1694,6 +1776,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1779,30 +1868,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1811,8 +1876,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1949,13 +2015,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1964,12 +2041,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); +-- +2.49.0 + diff --git a/patches/6.13/0011-surface-shutdown.patch b/patches/6.13/0011-surface-shutdown.patch new file mode 100644 index 0000000000..5fec755a53 --- /dev/null +++ b/patches/6.13/0011-surface-shutdown.patch @@ -0,0 +1,97 @@ +From a8fd28a346634ae41963cabaa22c249c66289cf6 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index 35270172c833..529779994ef6 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 0a1f668999ce..c05c382c715d 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6314,3 +6314,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index f05903dd7695..d1e38fdaf8bb 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -476,6 +476,7 @@ struct pci_dev { + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.49.0 + diff --git a/patches/6.13/0012-surface-gpe.patch b/patches/6.13/0012-surface-gpe.patch new file mode 100644 index 0000000000..8328dc1e5d --- /dev/null +++ b/patches/6.13/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From b7ce7965f7676337a02f806cc57d3dec2ebfc946 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index b359413903b1..b4496db79f39 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.49.0 + diff --git a/patches/6.13/0013-cameras.patch b/patches/6.13/0013-cameras.patch new file mode 100644 index 0000000000..0e8a6bff42 --- /dev/null +++ b/patches/6.13/0013-cameras.patch @@ -0,0 +1,734 @@ +From 027ae0a6a7af6791b208eab02e2903d7584ebe2f Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 74dcccdc6482..9b104f979abe 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2205,6 +2205,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.49.0 + +From 0df5096c55f417ba51e40b7e094b0e1fcfb9162f Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 1839a831a89f..ebd7aa6b9038 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -45,6 +45,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -214,12 +221,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -1911,6 +1920,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2211,6 +2223,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4522,6 +4537,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4569,6 +4596,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.49.0 + +From 11180426d5fe34b886682a7f2bbe8c061502c312 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 81ac4c691963..f453c9043042 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.49.0 + +From ab154f95bf4acaf98a5732c1efceeb9fa602f6c8 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 2 Mar 2023 12:59:39 +0000 +Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E + +ACPI _HID INT347E represents the OmniVision 7251 camera sensor. The +driver for this sensor expects a single pin named "enable", but on +some Microsoft Surface platforms the sensor is assigned a single +GPIO who's type flag is INT3472_GPIO_TYPE_RESET. + +Remap the GPIO pin's function from "reset" to "enable". This is done +outside of the existing remap table since it is a more widespread +discrepancy than that method is designed for. Additionally swap the +polarity of the pin to match the driver's expectation. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/discrete.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index a80c981caa34..68d0f07dda0c 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -81,12 +81,27 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + const char *func, unsigned long gpio_flags) + { + int ret; ++ const struct acpi_device_id ov7251_ids[] = { ++ { "INT347E" }, ++ { } ++ }; + + if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { + dev_warn(int3472->dev, "Too many GPIOs mapped\n"); + return -EINVAL; + } + ++ /* ++ * In addition to the function remap table we need to bulk remap the ++ * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that ++ * expects its only GPIO pin to be called "enable" (and to have the ++ * opposite polarity). ++ */ ++ if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { ++ func = "enable"; ++ gpio_flags ^= GPIO_ACTIVE_LOW; ++ } ++ + ret = skl_int3472_fill_gpiod_lookup(&int3472->gpios.table[int3472->n_sensor_gpios], + agpio, func, gpio_flags); + if (ret) +-- +2.49.0 + +From fe2b7efab5d7a89b0335daf29d72bb08b853c4d7 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 30f61e04ecaf..9c1292ca8552 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1051,7 +1051,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1572,7 +1572,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.49.0 + +From 56d08f849ecd0a3f0a801a4ac7e90d757528bf0c Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a8221fb..4f6bafd900ee 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index f19c8adf2c61..923ed1b5ab8b 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1219,10 +1219,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.49.0 + +From ba92bf1c2ccef5492906f74cc2be3f0f583032fa Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index f453c9043042..b8ad6b413e8b 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -203,7 +203,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.49.0 + +From ac2829c3ae57463819f0cb28c48eae4147cfa12c Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db0..2d2abb25b944 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.49.0 + +From 9f5b3820f1dd06d363ed73ef3997a63e218516bd Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index b784bb74a837..f8d8fcfacac2 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -941,6 +941,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 18afbb5a23ee..a1d16c0af82d 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -88,6 +88,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o + obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.49.0 + +From 8766d0105b72c4c1f4fe9a36eb872cc2b3879c10 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index c626ed845928..0094cfda57ea 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -82,6 +82,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); + +-- +2.49.0 + diff --git a/patches/6.13/0014-amd-gpio.patch b/patches/6.13/0014-amd-gpio.patch new file mode 100644 index 0000000000..e6e2df08d3 --- /dev/null +++ b/patches/6.13/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From 944244c44f80d631fff8058b0d9692dfd761b4f4 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 18485170d51b..66d1b9e8aa9d 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1172,6 +1173,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1228,6 +1240,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.49.0 + +From 6c65b07967d9f713c87103cdfe80c1ded559f154 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 66d1b9e8aa9d..741e05190b41 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1175,12 +1175,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.49.0 + diff --git a/patches/6.13/0015-rtc.patch b/patches/6.13/0015-rtc.patch new file mode 100644 index 0000000000..ef1c5ebc27 --- /dev/null +++ b/patches/6.13/0015-rtc.patch @@ -0,0 +1,110 @@ +From 0600ea617b539cd7a04249b896ebdc28b43717b0 Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 825c2a8acea4..74612088ac5f 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -565,13 +572,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + + pm_runtime_get_sync(dev); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -613,12 +625,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -649,6 +655,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.49.0 + diff --git a/patches/6.14/0001-secureboot.patch b/patches/6.14/0001-secureboot.patch new file mode 100644 index 0000000000..de8ed744dc --- /dev/null +++ b/patches/6.14/0001-secureboot.patch @@ -0,0 +1,112 @@ +From 3ae65e76bec1728fd475ccdbef0dd0e162f78f71 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index b5c79f43359b..a1bbedd989e4 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.50.0 + +From 12328f0f896d94be374d0dc13aa233039b3d3c8e Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index a8e98f75b610..fc8abda3dfd6 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3173,6 +3173,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index b129ed1d25a8..547953e14503 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -37,6 +37,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -92,7 +93,7 @@ void hibernate_release(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1434,6 +1435,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1492,3 +1499,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.50.0 + diff --git a/patches/6.14/0002-surface3.patch b/patches/6.14/0002-surface3.patch new file mode 100644 index 0000000000..9b04db3d18 --- /dev/null +++ b/patches/6.14/0002-surface3.patch @@ -0,0 +1,234 @@ +From bbe0c33ca658e0081e2501255aae273b312e47bd Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3 +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index 6c8fb7a4dde4..22797a53f4d8 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 51187b1e0ed2..bfb83ce8d8f8 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3790,6 +3790,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0c28..0b930c91bccb 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.50.0 + +From a948106e29a2142f5a553a6431d174e2b5efcb52 Mon Sep 17 00:00:00 2001 +From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> +Date: Fri, 6 Dec 2019 23:10:30 +0900 +Subject: [PATCH] surface3-spi: workaround: disable DMA mode to avoid crash by + default + +On Arch Linux kernel at least after 4.19, touch input is broken after suspend +(s2idle). + + kern :err : [ +0.203408] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + +On recent stable Arch Linux kernel (at least after 5.1), touch input will +crash after the first touch. + + kern :err : [ +0.203592] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + kern :err : [ +0.000173] spi_master spi1: failed to transfer one message from queue + +I found on an affected system (Arch Linux kernel, etc.), the touchscreen +driver uses DMA mode by default. Then, we found some kernels with different +kernel config (5.1 kernel config from Jakeday [1] or Chromium OS kernel +chromeos-4.19 [2]) will use PIO mode by default and no such issues there. + +So, this commit disables DMA mode on the touchscreen driver side as a quick +workaround to avoid touch input crash. +We may need to properly set up DMA mode to use the touchscreen driver with +DMA mode. + +You can still switch DMA/PIO mode if you want: + + switch to DMA mode (maybe broken) + echo 1 | sudo tee /sys/module/surface3_spi/parameters/use_dma + back to PIO mode + echo 0 | sudo tee /sys/module/surface3_spi/parameters/use_dma + +Link to issue: https://github.com/jakeday/linux-surface/issues/596 + +References: +[1] https://github.com/jakeday/linux-surface/blob/master/configs/5.1/config +[2] https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19 + +Tested on Arch Linux 5.4.1 with Surface 3, which will use DMA by default. +This commit made the driver use PIO by default and no touch input crash. +Also tested on chromeos-4.19 4.19.90 with Surface 3, which will use PIO by default +even without this commit. After this commit, it still uses PIO and confirmed +no functional changes regarding touch input. + +More details: + We can confirm which mode the touchscreen driver uses; first, enable + debug output: + + echo "file drivers/spi/spi-pxa2xx.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + echo "file drivers/input/touchscreen/surface3_spi.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + + Then, try to make a touch input and see dmesg log + + (On Arch Linux kernel, uses DMA) + kern :debug : [ +0.006383] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, DMA + kern :debug : [ +0.000495] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 18 00 e4 01 00 04 1a 04 1a e3 0c e3 0c b0 00 + c5 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + (On the kernels I referenced above, uses PIO) + kern :debug : [ +0.009260] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, PIO + kern :debug : [ +0.001105] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 24 00 e4 01 00 58 0b 58 0b 83 12 83 12 26 01 + 95 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +Note (2025-03-08): This patch was originally dropped due to the +comments in [3]. However, according to the commments in [4] it is still +required. + +[3]: https://github.com/linux-surface/kernel/commit/a3421c12bed0e46c28518bcb8c6b22f237c6dc7a +[4]: https://github.com/linux-surface/linux-surface/issues/1184 + +Patchset: surface3 +--- + drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c +index 6074b7730e86..6aa3e1d6f160 100644 +--- a/drivers/input/touchscreen/surface3_spi.c ++++ b/drivers/input/touchscreen/surface3_spi.c +@@ -25,6 +25,12 @@ + #define SURFACE3_REPORT_TOUCH 0xd2 + #define SURFACE3_REPORT_PEN 0x16 + ++bool use_dma = false; ++module_param(use_dma, bool, 0644); ++MODULE_PARM_DESC(use_dma, ++ "Disable DMA mode if you encounter touch input crash. " ++ "(default: false, disabled to avoid crash)"); ++ + struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; +@@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) + return 0; + } + ++static bool surface3_spi_can_dma(struct spi_controller *ctlr, ++ struct spi_device *spi, ++ struct spi_transfer *tfr) ++{ ++ return use_dma; ++} ++ + static int surface3_spi_probe(struct spi_device *spi) + { + struct surface3_ts_data *data; +@@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) + if (error) + return error; + ++ /* ++ * Set up DMA ++ * ++ * TODO: Currently, touch input with DMA seems to be broken. ++ * On 4.19 LTS, touch input will crash after suspend. ++ * On recent stable kernel (at least after 5.1), touch input will crash after ++ * the first touch. No problem with PIO on those kernels. ++ * Maybe we need to configure DMA here. ++ * ++ * Link to issue: https://github.com/jakeday/linux-surface/issues/596 ++ */ ++ spi->controller->can_dma = surface3_spi_can_dma; ++ + return 0; + } + +-- +2.50.0 + diff --git a/patches/6.14/0003-mwifiex.patch b/patches/6.14/0003-mwifiex.patch new file mode 100644 index 0000000000..0249d9c2df --- /dev/null +++ b/patches/6.14/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 323568e951309bae33b7c468ddd927c921b4e41e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 5f997becdbaa..9a9929424513 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd..f46b06f8d643 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5b..5d30ae39d65e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.50.0 + +From bfe0140220b8e0c37fa3e969fef6a2f32c354dd7 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 9a9929424513..2273e3029776 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65e..c14eb56eb911 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.50.0 + +From 206186b3659beb6c47e32ee9b6dae62c96d9bcbd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index b15f3ed767c5..c314adde36a1 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -66,6 +66,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -469,6 +470,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -3990,6 +3992,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.50.0 + diff --git a/patches/6.14/0004-ath10k.patch b/patches/6.14/0004-ath10k.patch new file mode 100644 index 0000000000..dfd405561d --- /dev/null +++ b/patches/6.14/0004-ath10k.patch @@ -0,0 +1,120 @@ +From 1204ce622f4a487d274e369dccb3c6e16031f2bf Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index b3294287bce1..2936fdae823c 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -40,6 +40,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -52,6 +55,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -61,6 +67,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -931,6 +940,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -945,6 +990,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.50.0 + diff --git a/patches/6.14/0005-ipts.patch b/patches/6.14/0005-ipts.patch new file mode 100644 index 0000000000..608380fa99 --- /dev/null +++ b/patches/6.14/0005-ipts.patch @@ -0,0 +1,3243 @@ +From 490277cbb9ac90432c89b433e15f4cb4c4bb79ff Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index bc40b940ae21..45fbd856d416 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 3f9c60b579ae..853a67753333 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.50.0 + +From a57f5264ddb9f3407cf83d0dc83aa0d7b1a916d3 Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 580d24dd4edd..93c18f862e6c 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -39,6 +39,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -207,12 +212,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -1902,6 +1909,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2196,6 +2206,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + /* +@@ -4460,6 +4473,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4498,6 +4523,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.50.0 + +From c2ccf49147c5ba07a5602db0de6fa6c59903a79a Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 76be97c5fc2f..0d1446b45c36 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1402,6 +1402,8 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/intel-thc-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index c7ecfbb3e228..7b2939c8a477 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -173,3 +173,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..7b9f54388a9f +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..639940794615 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..977724c728c3 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.50.0 + diff --git a/patches/6.14/0006-ithc.patch b/patches/6.14/0006-ithc.patch new file mode 100644 index 0000000000..97e5902f0a --- /dev/null +++ b/patches/6.14/0006-ithc.patch @@ -0,0 +1,2773 @@ +From e4540875b9b5d3d9178aef6bf8bcecc5cddc1c4a Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 3bc2a03cceca..e4c95c0a87f9 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -380,6 +380,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.50.0 + +From 43a2c8fea689f6e377fb0790b4e611beec6c699c Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 431 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2568 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 0d1446b45c36..b8315f388240 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1404,6 +1404,8 @@ source "drivers/hid/intel-thc-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 7b2939c8a477..a3c9274c0bef 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -175,3 +175,4 @@ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..4937ba131297 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..2d8c6afe9966 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..bf4eab33062b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..1749a5819b3e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..8883987fb352 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..ac56c253674b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,431 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_ANY_ID, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .class = PCI_CLASS_INPUT_PEN << 8, ++ .class_mask = ~0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ del_timer_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..e2d1690b8cf8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..c0f13506af20 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..4f541fe533fa +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..aec320d4e945 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.50.0 + diff --git a/patches/6.14/0007-surface-sam.patch b/patches/6.14/0007-surface-sam.patch new file mode 100644 index 0000000000..b28b5cd0a3 --- /dev/null +++ b/patches/6.14/0007-surface-sam.patch @@ -0,0 +1,211 @@ +From a5eb89dfb9f2544e3271c60658a683801b7a5c23 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 17 Jun 2022 02:14:00 +0200 +Subject: [PATCH] rtc: Add basic support for RTC via Surface System Aggregator + Module + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/rtc/Kconfig | 7 +++ + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-surface.c | 129 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 137 insertions(+) + create mode 100644 drivers/rtc/rtc-surface.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 0bbbf778ecfa..bd1d5c0dd6df 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -1383,6 +1383,13 @@ config RTC_DRV_NTXEC + embedded controller found in certain e-book readers designed by the + original design manufacturer Netronix. + ++config RTC_DRV_SURFACE ++ tristate "Microsoft Surface Aggregator RTC" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ TODO ++ + comment "on-CPU RTC drivers" + + config RTC_DRV_ASM9260 +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index 489b4ab07068..061afa9db5b6 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -180,6 +180,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o + obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o + obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o + obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o ++obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o + obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o + obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o + obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o +diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c +new file mode 100644 +index 000000000000..f6c17c4e98d5 +--- /dev/null ++++ b/drivers/rtc/rtc-surface.c +@@ -0,0 +1,129 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * AC driver for 7th-generation Microsoft Surface devices via Surface System ++ * Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct surface_rtc { ++ struct ssam_device *sdev; ++ struct rtc_device *rtc; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x10, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x0f, ++}); ++ ++static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) ++{ ++ __le32 time_le; ++ int status; ++ ++ status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); ++ if (status) ++ return status; ++ ++ *time = le32_to_cpu(time_le); ++ return 0; ++} ++ ++static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) ++{ ++ __le32 time_le = cpu_to_le32(time); ++ ++ return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); ++} ++ ++static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ int status; ++ u32 time; ++ ++ status = ssam_rtc_get_unix_time(srtc, &time); ++ if (status) ++ return status; ++ ++ rtc_time64_to_tm(time, tm); ++ return 0; ++} ++ ++static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ time64_t time = rtc_tm_to_time64(tm); ++ ++ return ssam_rtc_set_unix_time(srtc, (u32)time); ++} ++ ++static const struct rtc_class_ops surface_rtc_ops = { ++ .read_time = surface_rtc_read_time, ++ .set_time = surface_rtc_set_time, ++}; ++ ++static int surface_rtc_probe(struct ssam_device *sdev) ++{ ++ struct surface_rtc *srtc; ++ ++ srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); ++ if (!srtc) ++ return -ENOMEM; ++ ++ srtc->sdev = sdev; ++ ++ srtc->rtc = devm_rtc_allocate_device(&sdev->dev); ++ if (IS_ERR(srtc->rtc)) ++ return PTR_ERR(srtc->rtc); ++ ++ srtc->rtc->ops = &surface_rtc_ops; ++ srtc->rtc->range_max = U32_MAX; ++ ++ ssam_device_set_drvdata(sdev, srtc); ++ ++ return devm_rtc_register_device(srtc->rtc); ++} ++ ++static void surface_rtc_remove(struct ssam_device *sdev) ++{ ++ /* Device-managed allocations take care of everything... */ ++} ++ ++static const struct ssam_device_id surface_rtc_match[] = { ++ { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_rtc_match); ++ ++static struct ssam_device_driver surface_rtc_driver = { ++ .probe = surface_rtc_probe, ++ .remove = surface_rtc_remove, ++ .match_table = surface_rtc_match, ++ .driver = { ++ .name = "surface_rtc", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_rtc_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.50.0 + +From b9ffcd1963e0995fde893e9ae8828ade30b95d64 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 20 Apr 2025 01:05:14 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add Surface Laptop 7 + (ACPI) + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index a594d5fcfcfd..07b03aa4fa7f 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -460,6 +460,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + ++ /* Surface Laptop 7 */ ++ { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, ++ + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + +-- +2.50.0 + diff --git a/patches/6.14/0008-surface-sam-over-hid.patch b/patches/6.14/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..bf44ccbc70 --- /dev/null +++ b/patches/6.14/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From 7a262cf2715615399d0c60816377df87009e326e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index d2499f302b50..77ce5ec3dd9e 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -661,6 +661,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -762,6 +783,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.50.0 + +From 1d78f095f8b30bfaa11e2d6d282c5605140c895c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b629e82af97c..68656e8f309e 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..68db237734a1 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.50.0 + diff --git a/patches/6.14/0009-surface-button.patch b/patches/6.14/0009-surface-button.patch new file mode 100644 index 0000000000..b454cb615c --- /dev/null +++ b/patches/6.14/0009-surface-button.patch @@ -0,0 +1,149 @@ +From 884c068a28164f2acedd3890fe7a07674ecbf9c0 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index b8cad415c62c..43b5d56383e3 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.50.0 + +From ffe10fa9dbb86acf7758091da53e8a420eeca7d0 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979c..4240c98ca226 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.50.0 + diff --git a/patches/6.14/0010-surface-typecover.patch b/patches/6.14/0010-surface-typecover.patch new file mode 100644 index 0000000000..47f7d989fd --- /dev/null +++ b/patches/6.14/0010-surface-typecover.patch @@ -0,0 +1,575 @@ +From 5cba6349d24ff4122dd272b4b3582a470bceef9d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 53d68d20fb62..cf85b30b1d60 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.50.0 + +From 8623befd2c9aabf3664f379adb9b359525b8772d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index e50887a6d22c..4ce18f21a141 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -35,7 +35,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -48,6 +51,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -73,12 +77,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -176,6 +183,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -220,6 +229,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -410,6 +420,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1759,6 +1779,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1782,6 +1865,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1820,8 +1906,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); +@@ -1830,8 +1918,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1881,6 +1971,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2304,6 +2395,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.50.0 + +From 67a0ee9310d87cb3880d0fbde669f84cfbfeab4c Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 4ce18f21a141..f5e4d52bd2eb 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -78,6 +78,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -85,6 +86,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -422,6 +425,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1403,6 +1407,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1430,6 +1437,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1451,6 +1473,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1458,6 +1481,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1467,11 +1503,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1646,6 +1692,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1694,6 +1776,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1779,30 +1868,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1811,8 +1876,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1949,13 +2015,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1964,12 +2041,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); +-- +2.50.0 + diff --git a/patches/6.14/0011-surface-shutdown.patch b/patches/6.14/0011-surface-shutdown.patch new file mode 100644 index 0000000000..149ba60de5 --- /dev/null +++ b/patches/6.14/0011-surface-shutdown.patch @@ -0,0 +1,97 @@ +From b19ebe86f0efac469f91581fe6ee53bce4586bc2 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index f57ea36d125d..cdb74ec6c082 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 82b21e34c545..ccf29cfa7082 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6316,3 +6316,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 8e028620642f..5dea44aa163b 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -478,6 +478,7 @@ struct pci_dev { + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.50.0 + diff --git a/patches/6.14/0012-surface-gpe.patch b/patches/6.14/0012-surface-gpe.patch new file mode 100644 index 0000000000..cada29180e --- /dev/null +++ b/patches/6.14/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From ca7157512287c654bd628c958f92b83764b2d3b1 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index b359413903b1..b4496db79f39 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.50.0 + diff --git a/patches/6.14/0013-cameras.patch b/patches/6.14/0013-cameras.patch new file mode 100644 index 0000000000..088647334f --- /dev/null +++ b/patches/6.14/0013-cameras.patch @@ -0,0 +1,678 @@ +From 9f359d3b71901df093dd03d2ccb3daca01a8a339 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 9f4efa8f75a6..7c7ef03cafa4 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2204,6 +2204,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.50.0 + +From 566b4d441c1d2ac2d038f4c3231187cc4b74c507 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 93c18f862e6c..1acf7906fbb9 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -44,6 +44,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -213,12 +220,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -1910,6 +1919,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2206,6 +2218,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4473,6 +4488,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4523,6 +4550,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.50.0 + +From 62427236fd018fba1a6c7127efb2a45289d6aabd Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 81ac4c691963..f453c9043042 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.50.0 + +From ecc15ede9f86d97ea6fdca7d6641949ec40edbe2 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 3226888d77e9..3bfe45b764f7 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1053,7 +1053,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1574,7 +1574,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.50.0 + +From db60cbc5de5c80f605a4db318cf34d58c69d6c95 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a8221fb..4f6bafd900ee 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index cb153ce42c45..f11b499e14bb 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1260,10 +1260,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.50.0 + +From 10ac2f54fdf7c498010dd4239e9fa2e8d0d3c6ca Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index f453c9043042..b8ad6b413e8b 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -203,7 +203,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.50.0 + +From 5ce308dae49694842cb19eb0c35a799ec5760969 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db0..2d2abb25b944 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.50.0 + +From 6eb3985c1d119ec3e3452dcad3a26707cea4416f Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 8859e8fe292a..04ee65c9eec6 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -986,6 +986,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 6ad52e219ec6..3519b4c32c9e 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -91,6 +91,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.50.0 + +From 19e0f19874902ac5791da82f7a54b1b40cbdf877 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index c626ed845928..0094cfda57ea 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -82,6 +82,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); + +-- +2.50.0 + diff --git a/patches/6.14/0014-amd-gpio.patch b/patches/6.14/0014-amd-gpio.patch new file mode 100644 index 0000000000..24f219aeee --- /dev/null +++ b/patches/6.14/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From cf85ecd3a2e278621062ebcf7406dc7aeb8a8faa Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 9fa321a95eb3..8914a922be2b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + +@@ -1171,6 +1172,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1227,6 +1239,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.50.0 + +From 24baea6b01abec92cb98122a380c3367f3e21d96 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 8914a922be2b..c43d0a553867 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1174,12 +1174,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.50.0 + diff --git a/patches/6.14/0015-rtc.patch b/patches/6.14/0015-rtc.patch new file mode 100644 index 0000000000..5868fb75b5 --- /dev/null +++ b/patches/6.14/0015-rtc.patch @@ -0,0 +1,110 @@ +From 8491ddedc4b6a27c0b4d81c5ea1670ef050235bc Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 825c2a8acea4..74612088ac5f 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -565,13 +572,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + + pm_runtime_get_sync(dev); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -613,12 +625,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -649,6 +655,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.50.0 + diff --git a/patches/6.15/0001-secureboot.patch b/patches/6.15/0001-secureboot.patch new file mode 100644 index 0000000000..06a16c27fe --- /dev/null +++ b/patches/6.15/0001-secureboot.patch @@ -0,0 +1,112 @@ +From bf0b72c5d06f09e4be378295ad67f3c26a9ae39a Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index b5c79f43359b..a1bbedd989e4 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.51.0 + +From 1b81b7a3de3b6b5538d11d4c9f78da7801590d1f Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index 76a34e0aef76..e7f3eb39eaf7 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3200,6 +3200,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index 5af9c7ee98cd..d52aa94e2fa2 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -38,6 +38,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -98,7 +99,7 @@ bool hibernation_in_progress(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1440,6 +1441,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1498,3 +1505,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.51.0 + diff --git a/patches/6.15/0002-surface3.patch b/patches/6.15/0002-surface3.patch new file mode 100644 index 0000000000..9340352408 --- /dev/null +++ b/patches/6.15/0002-surface3.patch @@ -0,0 +1,234 @@ +From 676d973e28a2b248f197b50081a5c7d3dc42823b Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3 +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index 6c8fb7a4dde4..22797a53f4d8 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index dba78efadc85..dbec816a2823 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3790,6 +3790,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0c28..0b930c91bccb 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.51.0 + +From 9ccee9531c3c29bc417a85de1cf6a17ca2c51f52 Mon Sep 17 00:00:00 2001 +From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> +Date: Fri, 6 Dec 2019 23:10:30 +0900 +Subject: [PATCH] surface3-spi: workaround: disable DMA mode to avoid crash by + default + +On Arch Linux kernel at least after 4.19, touch input is broken after suspend +(s2idle). + + kern :err : [ +0.203408] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + +On recent stable Arch Linux kernel (at least after 5.1), touch input will +crash after the first touch. + + kern :err : [ +0.203592] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + kern :err : [ +0.000173] spi_master spi1: failed to transfer one message from queue + +I found on an affected system (Arch Linux kernel, etc.), the touchscreen +driver uses DMA mode by default. Then, we found some kernels with different +kernel config (5.1 kernel config from Jakeday [1] or Chromium OS kernel +chromeos-4.19 [2]) will use PIO mode by default and no such issues there. + +So, this commit disables DMA mode on the touchscreen driver side as a quick +workaround to avoid touch input crash. +We may need to properly set up DMA mode to use the touchscreen driver with +DMA mode. + +You can still switch DMA/PIO mode if you want: + + switch to DMA mode (maybe broken) + echo 1 | sudo tee /sys/module/surface3_spi/parameters/use_dma + back to PIO mode + echo 0 | sudo tee /sys/module/surface3_spi/parameters/use_dma + +Link to issue: https://github.com/jakeday/linux-surface/issues/596 + +References: +[1] https://github.com/jakeday/linux-surface/blob/master/configs/5.1/config +[2] https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19 + +Tested on Arch Linux 5.4.1 with Surface 3, which will use DMA by default. +This commit made the driver use PIO by default and no touch input crash. +Also tested on chromeos-4.19 4.19.90 with Surface 3, which will use PIO by default +even without this commit. After this commit, it still uses PIO and confirmed +no functional changes regarding touch input. + +More details: + We can confirm which mode the touchscreen driver uses; first, enable + debug output: + + echo "file drivers/spi/spi-pxa2xx.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + echo "file drivers/input/touchscreen/surface3_spi.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + + Then, try to make a touch input and see dmesg log + + (On Arch Linux kernel, uses DMA) + kern :debug : [ +0.006383] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, DMA + kern :debug : [ +0.000495] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 18 00 e4 01 00 04 1a 04 1a e3 0c e3 0c b0 00 + c5 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + (On the kernels I referenced above, uses PIO) + kern :debug : [ +0.009260] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, PIO + kern :debug : [ +0.001105] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 24 00 e4 01 00 58 0b 58 0b 83 12 83 12 26 01 + 95 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +Note (2025-03-08): This patch was originally dropped due to the +comments in [3]. However, according to the commments in [4] it is still +required. + +[3]: https://github.com/linux-surface/kernel/commit/a3421c12bed0e46c28518bcb8c6b22f237c6dc7a +[4]: https://github.com/linux-surface/linux-surface/issues/1184 + +Patchset: surface3 +--- + drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c +index 6074b7730e86..6aa3e1d6f160 100644 +--- a/drivers/input/touchscreen/surface3_spi.c ++++ b/drivers/input/touchscreen/surface3_spi.c +@@ -25,6 +25,12 @@ + #define SURFACE3_REPORT_TOUCH 0xd2 + #define SURFACE3_REPORT_PEN 0x16 + ++bool use_dma = false; ++module_param(use_dma, bool, 0644); ++MODULE_PARM_DESC(use_dma, ++ "Disable DMA mode if you encounter touch input crash. " ++ "(default: false, disabled to avoid crash)"); ++ + struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; +@@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) + return 0; + } + ++static bool surface3_spi_can_dma(struct spi_controller *ctlr, ++ struct spi_device *spi, ++ struct spi_transfer *tfr) ++{ ++ return use_dma; ++} ++ + static int surface3_spi_probe(struct spi_device *spi) + { + struct surface3_ts_data *data; +@@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) + if (error) + return error; + ++ /* ++ * Set up DMA ++ * ++ * TODO: Currently, touch input with DMA seems to be broken. ++ * On 4.19 LTS, touch input will crash after suspend. ++ * On recent stable kernel (at least after 5.1), touch input will crash after ++ * the first touch. No problem with PIO on those kernels. ++ * Maybe we need to configure DMA here. ++ * ++ * Link to issue: https://github.com/jakeday/linux-surface/issues/596 ++ */ ++ spi->controller->can_dma = surface3_spi_can_dma; ++ + return 0; + } + +-- +2.51.0 + diff --git a/patches/6.15/0003-mwifiex.patch b/patches/6.15/0003-mwifiex.patch new file mode 100644 index 0000000000..adda69fd5c --- /dev/null +++ b/patches/6.15/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 2a55f56c999ac510ec304b8c2aae55bf6de6a788 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index dd2a42e732f2..226640e7d486 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd..f46b06f8d643 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5b..5d30ae39d65e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.51.0 + +From 689175f0a82a58268cc3ac29e00659f6989ef95e Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 226640e7d486..00abbb3997a8 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65e..c14eb56eb911 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.51.0 + +From aa56d0226f88cdfd12a285e831b336501fef162f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index c83de1772fdd..649063398e33 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -66,6 +66,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -469,6 +470,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -4012,6 +4014,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.51.0 + diff --git a/patches/6.15/0004-ath10k.patch b/patches/6.15/0004-ath10k.patch new file mode 100644 index 0000000000..f583e81d9e --- /dev/null +++ b/patches/6.15/0004-ath10k.patch @@ -0,0 +1,120 @@ +From da3aff48826a9ec1ca4383a32c3886d41d4ec0a7 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index 1adc35e37f40..3d68fe019b3c 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -40,6 +40,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -52,6 +55,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -61,6 +67,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -931,6 +940,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -945,6 +990,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.51.0 + diff --git a/patches/6.15/0005-ipts.patch b/patches/6.15/0005-ipts.patch new file mode 100644 index 0000000000..56405022f2 --- /dev/null +++ b/patches/6.15/0005-ipts.patch @@ -0,0 +1,3243 @@ +From 1533f2824f23d3a5c2c9c34ece4fb3bd2b4c35d6 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index bc40b940ae21..45fbd856d416 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 3f9c60b579ae..853a67753333 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.51.0 + +From 729712aa7ade4f9211b158ea561fa7e41628b83d Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 91fe53d671e6..4dc0c206d8a1 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -39,6 +39,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -207,12 +212,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -1949,6 +1956,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2243,6 +2253,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + /* +@@ -4466,6 +4479,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4504,6 +4529,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.51.0 + +From 4d065841244d18685f58d9ef74de9babf8267f49 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 43859fc75747..ded8868dcfea 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1429,6 +1429,8 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/intel-thc-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 10ae5dedbd84..7ba7d26391e9 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -175,3 +175,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..7b9f54388a9f +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..639940794615 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..977724c728c3 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.51.0 + diff --git a/patches/6.15/0006-ithc.patch b/patches/6.15/0006-ithc.patch new file mode 100644 index 0000000000..4cfaf6ad19 --- /dev/null +++ b/patches/6.15/0006-ithc.patch @@ -0,0 +1,2780 @@ +From 63de3e307ee680676d3bb9870200f5200149a952 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 3bc2a03cceca..e4c95c0a87f9 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -380,6 +380,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.51.0 + +From 1a6a07daa136e37917ca622211c734a0d173deca Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 438 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2575 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index ded8868dcfea..d6bc87be4854 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1431,6 +1431,8 @@ source "drivers/hid/intel-thc-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 7ba7d26391e9..d939da7ac2e8 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -177,3 +177,4 @@ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..4937ba131297 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..2d8c6afe9966 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..bf4eab33062b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..1749a5819b3e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..8883987fb352 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..094d878d671b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,438 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, ++ // MTL and up are handled by drivers/hid/intel-thc-hid ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ timer_delete_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..e2d1690b8cf8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..c0f13506af20 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..4f541fe533fa +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..aec320d4e945 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.51.0 + diff --git a/patches/6.15/0007-surface-sam.patch b/patches/6.15/0007-surface-sam.patch new file mode 100644 index 0000000000..402e285a3a --- /dev/null +++ b/patches/6.15/0007-surface-sam.patch @@ -0,0 +1,211 @@ +From 35f4b75b62840ca63be7ed1d2e141301a2288587 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 17 Jun 2022 02:14:00 +0200 +Subject: [PATCH] rtc: Add basic support for RTC via Surface System Aggregator + Module + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/rtc/Kconfig | 7 +++ + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-surface.c | 129 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 137 insertions(+) + create mode 100644 drivers/rtc/rtc-surface.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 838bdc138ffe..0b7f712f23ea 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -1376,6 +1376,13 @@ config RTC_DRV_NTXEC + embedded controller found in certain e-book readers designed by the + original design manufacturer Netronix. + ++config RTC_DRV_SURFACE ++ tristate "Microsoft Surface Aggregator RTC" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ TODO ++ + comment "on-CPU RTC drivers" + + config RTC_DRV_ASM9260 +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index 31473b3276d9..dba135e0a2a5 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -179,6 +179,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o + obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o + obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o + obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o ++obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o + obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o + obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o + obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o +diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c +new file mode 100644 +index 000000000000..f6c17c4e98d5 +--- /dev/null ++++ b/drivers/rtc/rtc-surface.c +@@ -0,0 +1,129 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * AC driver for 7th-generation Microsoft Surface devices via Surface System ++ * Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct surface_rtc { ++ struct ssam_device *sdev; ++ struct rtc_device *rtc; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x10, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x0f, ++}); ++ ++static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) ++{ ++ __le32 time_le; ++ int status; ++ ++ status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); ++ if (status) ++ return status; ++ ++ *time = le32_to_cpu(time_le); ++ return 0; ++} ++ ++static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) ++{ ++ __le32 time_le = cpu_to_le32(time); ++ ++ return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); ++} ++ ++static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ int status; ++ u32 time; ++ ++ status = ssam_rtc_get_unix_time(srtc, &time); ++ if (status) ++ return status; ++ ++ rtc_time64_to_tm(time, tm); ++ return 0; ++} ++ ++static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ time64_t time = rtc_tm_to_time64(tm); ++ ++ return ssam_rtc_set_unix_time(srtc, (u32)time); ++} ++ ++static const struct rtc_class_ops surface_rtc_ops = { ++ .read_time = surface_rtc_read_time, ++ .set_time = surface_rtc_set_time, ++}; ++ ++static int surface_rtc_probe(struct ssam_device *sdev) ++{ ++ struct surface_rtc *srtc; ++ ++ srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); ++ if (!srtc) ++ return -ENOMEM; ++ ++ srtc->sdev = sdev; ++ ++ srtc->rtc = devm_rtc_allocate_device(&sdev->dev); ++ if (IS_ERR(srtc->rtc)) ++ return PTR_ERR(srtc->rtc); ++ ++ srtc->rtc->ops = &surface_rtc_ops; ++ srtc->rtc->range_max = U32_MAX; ++ ++ ssam_device_set_drvdata(sdev, srtc); ++ ++ return devm_rtc_register_device(srtc->rtc); ++} ++ ++static void surface_rtc_remove(struct ssam_device *sdev) ++{ ++ /* Device-managed allocations take care of everything... */ ++} ++ ++static const struct ssam_device_id surface_rtc_match[] = { ++ { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_rtc_match); ++ ++static struct ssam_device_driver surface_rtc_driver = { ++ .probe = surface_rtc_probe, ++ .remove = surface_rtc_remove, ++ .match_table = surface_rtc_match, ++ .driver = { ++ .name = "surface_rtc", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_rtc_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.51.0 + +From efe6e1411a5b20c8f6d65715d29ce21d14a26c86 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 20 Apr 2025 01:05:14 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add Surface Laptop 7 + (ACPI) + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index a594d5fcfcfd..07b03aa4fa7f 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -460,6 +460,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + ++ /* Surface Laptop 7 */ ++ { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, ++ + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + +-- +2.51.0 + diff --git a/patches/6.15/0008-surface-sam-over-hid.patch b/patches/6.15/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..a7c5121e4b --- /dev/null +++ b/patches/6.15/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From 4a5332d608cc9203ec450546b2c18b5a1efe2c32 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index f43067f6797e..1761ca30e17a 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -662,6 +662,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -763,6 +784,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.51.0 + +From c548bea8e95195d03c39cb8ce2fd9f0d5e335da3 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b629e82af97c..68656e8f309e 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..68db237734a1 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.51.0 + diff --git a/patches/6.15/0009-surface-button.patch b/patches/6.15/0009-surface-button.patch new file mode 100644 index 0000000000..4984f1a054 --- /dev/null +++ b/patches/6.15/0009-surface-button.patch @@ -0,0 +1,149 @@ +From cd402996d53de2dcf039c19a187117b22725b417 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index b8cad415c62c..43b5d56383e3 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.51.0 + +From 025d75682c2142ff86fb96c5f25506fdf6a4f7fb Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979c..4240c98ca226 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.51.0 + diff --git a/patches/6.15/0010-surface-typecover.patch b/patches/6.15/0010-surface-typecover.patch new file mode 100644 index 0000000000..a2399f15f9 --- /dev/null +++ b/patches/6.15/0010-surface-typecover.patch @@ -0,0 +1,575 @@ +From 0509d466ee9e29adcc75f4adea741140caa53089 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 0cf94c7a2c9c..115d2628cdb6 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.51.0 + +From 1c67b683cbb32077ef4168702aa3c67742e74636 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 536a0a47518f..0a8e70136779 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -35,7 +35,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -48,6 +51,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -73,12 +77,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -176,6 +183,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -220,6 +229,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -410,6 +420,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1759,6 +1779,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1782,6 +1865,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1820,8 +1906,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); +@@ -1830,8 +1918,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1881,6 +1971,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2310,6 +2401,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.51.0 + +From b5719065b5faa78f2f8cbce4d4197fa3595b7691 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 0a8e70136779..2f46acc170b2 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -78,6 +78,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -85,6 +86,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -422,6 +425,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1403,6 +1407,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1430,6 +1437,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1451,6 +1473,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1458,6 +1481,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1467,11 +1503,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1646,6 +1692,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1694,6 +1776,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1779,30 +1868,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1811,8 +1876,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1949,13 +2015,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1964,12 +2041,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); +-- +2.51.0 + diff --git a/patches/6.15/0011-surface-shutdown.patch b/patches/6.15/0011-surface-shutdown.patch new file mode 100644 index 0000000000..2445440980 --- /dev/null +++ b/patches/6.15/0011-surface-shutdown.patch @@ -0,0 +1,97 @@ +From 46248cbac7ca6e688ebe5be29344239c78a884a7 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index 66e3bea7dc1a..6c30e1f649cd 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 6e29f2b39dce..2198c761c737 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6341,3 +6341,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 0b03176f07aa..c526db8b69d2 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -486,6 +486,7 @@ struct pci_dev { + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ + unsigned int non_mappable_bars:1; /* BARs can't be mapped to user-space */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.51.0 + diff --git a/patches/6.15/0012-surface-gpe.patch b/patches/6.15/0012-surface-gpe.patch new file mode 100644 index 0000000000..e17f7e53eb --- /dev/null +++ b/patches/6.15/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From 55f4befd22883ebdb8a2ace5ae266ccf0a4a138f Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index b359413903b1..b4496db79f39 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.51.0 + diff --git a/patches/6.15/0013-cameras.patch b/patches/6.15/0013-cameras.patch new file mode 100644 index 0000000000..1177da1c17 --- /dev/null +++ b/patches/6.15/0013-cameras.patch @@ -0,0 +1,678 @@ +From f4a6dd6dc6edd292095b0f01051d6e90e520ecca Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index fb1fe9f3b1a3..5be8893b3912 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2197,6 +2197,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.51.0 + +From 3eea25192fd66c8ced9694ec68757fb1d09c348a Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 4dc0c206d8a1..2132278e627e 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -44,6 +44,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -213,12 +220,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -1957,6 +1966,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2253,6 +2265,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4479,6 +4494,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4529,6 +4556,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.51.0 + +From 4b200899714ce804e4c7807218b25efdb069b655 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 81ac4c691963..f453c9043042 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.51.0 + +From b387e3ac7ddd78a29cc58f90935ada6a0bc93e35 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 3226888d77e9..3bfe45b764f7 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1053,7 +1053,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1574,7 +1574,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.51.0 + +From 160e73996b82ec3ec046359591e04b4b5a6166fc Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a8221fb..4f6bafd900ee 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index cb153ce42c45..f11b499e14bb 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1260,10 +1260,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.51.0 + +From 253c3518c9c6911bd24c6cd8af6bd17411fe9290 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index f453c9043042..b8ad6b413e8b 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -203,7 +203,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.51.0 + +From 6d968e793dfceab0ccd3364da326df65864477c4 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db0..2d2abb25b944 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.51.0 + +From 54fb950d16116f19179a7c95a6f69e9676e9f287 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index a104cbb0a001..535a10cbbff1 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -995,6 +995,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 2f170d69dcbf..17f0ecd1f52e 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -92,6 +92,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.51.0 + +From 7f206da7835dcaa506e4bac6c6b8d59f4e9a2f26 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index 032fbcb981f2..e03a1d8cdcb4 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -87,6 +87,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719, bool detect) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_SHUTDOWN, &ret); + fsleep(100); +-- +2.51.0 + diff --git a/patches/6.15/0014-amd-gpio.patch b/patches/6.15/0014-amd-gpio.patch new file mode 100644 index 0000000000..dc2ac60054 --- /dev/null +++ b/patches/6.15/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From 7f3827b691851b75ad0a6aef78f78ac2085fe586 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 9fa321a95eb3..8914a922be2b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + +@@ -1171,6 +1172,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1227,6 +1239,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.51.0 + +From 2de4d8dda47df161ae25722bad104bf28dae255a Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 8914a922be2b..c43d0a553867 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1174,12 +1174,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.51.0 + diff --git a/patches/6.15/0015-rtc.patch b/patches/6.15/0015-rtc.patch new file mode 100644 index 0000000000..0cfc2685c3 --- /dev/null +++ b/patches/6.15/0015-rtc.patch @@ -0,0 +1,110 @@ +From 122a63dc573c878b58e0b0728d13db666753f5cf Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 825c2a8acea4..74612088ac5f 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -565,13 +572,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + + pm_runtime_get_sync(dev); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -613,12 +625,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -649,6 +655,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.51.0 + diff --git a/patches/6.16/0001-secureboot.patch b/patches/6.16/0001-secureboot.patch new file mode 100644 index 0000000000..567ed4ad84 --- /dev/null +++ b/patches/6.16/0001-secureboot.patch @@ -0,0 +1,112 @@ +From ef381b568568d83624a9f527b394528bbf9f9cc4 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index e1f4fd5bc8ee..64f8dc548045 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLLCHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.51.0 + +From 2a3d0ca06b2a6f7c9f206b06886095f1d7658c94 Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index 089e1a395178..9111ec4feff4 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3243,6 +3243,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index c8022a477d3a..d6feab73fad5 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -38,6 +38,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -98,7 +99,7 @@ bool hibernation_in_progress(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1457,6 +1458,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1514,3 +1521,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.51.0 + diff --git a/patches/6.16/0002-surface3.patch b/patches/6.16/0002-surface3.patch new file mode 100644 index 0000000000..02c19042eb --- /dev/null +++ b/patches/6.16/0002-surface3.patch @@ -0,0 +1,234 @@ +From 0d1d229f54a68e14ee69bececce8fd06dbb1287e Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3 +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index 6c8fb7a4dde4..22797a53f4d8 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 29a403526cd9..986f32132c3d 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3792,6 +3792,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0c28..0b930c91bccb 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.51.0 + +From a5550b1de4700806a951fa58da2c7db0cb3a51c6 Mon Sep 17 00:00:00 2001 +From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> +Date: Fri, 6 Dec 2019 23:10:30 +0900 +Subject: [PATCH] surface3-spi: workaround: disable DMA mode to avoid crash by + default + +On Arch Linux kernel at least after 4.19, touch input is broken after suspend +(s2idle). + + kern :err : [ +0.203408] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + +On recent stable Arch Linux kernel (at least after 5.1), touch input will +crash after the first touch. + + kern :err : [ +0.203592] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + kern :err : [ +0.000173] spi_master spi1: failed to transfer one message from queue + +I found on an affected system (Arch Linux kernel, etc.), the touchscreen +driver uses DMA mode by default. Then, we found some kernels with different +kernel config (5.1 kernel config from Jakeday [1] or Chromium OS kernel +chromeos-4.19 [2]) will use PIO mode by default and no such issues there. + +So, this commit disables DMA mode on the touchscreen driver side as a quick +workaround to avoid touch input crash. +We may need to properly set up DMA mode to use the touchscreen driver with +DMA mode. + +You can still switch DMA/PIO mode if you want: + + switch to DMA mode (maybe broken) + echo 1 | sudo tee /sys/module/surface3_spi/parameters/use_dma + back to PIO mode + echo 0 | sudo tee /sys/module/surface3_spi/parameters/use_dma + +Link to issue: https://github.com/jakeday/linux-surface/issues/596 + +References: +[1] https://github.com/jakeday/linux-surface/blob/master/configs/5.1/config +[2] https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19 + +Tested on Arch Linux 5.4.1 with Surface 3, which will use DMA by default. +This commit made the driver use PIO by default and no touch input crash. +Also tested on chromeos-4.19 4.19.90 with Surface 3, which will use PIO by default +even without this commit. After this commit, it still uses PIO and confirmed +no functional changes regarding touch input. + +More details: + We can confirm which mode the touchscreen driver uses; first, enable + debug output: + + echo "file drivers/spi/spi-pxa2xx.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + echo "file drivers/input/touchscreen/surface3_spi.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + + Then, try to make a touch input and see dmesg log + + (On Arch Linux kernel, uses DMA) + kern :debug : [ +0.006383] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, DMA + kern :debug : [ +0.000495] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 18 00 e4 01 00 04 1a 04 1a e3 0c e3 0c b0 00 + c5 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + (On the kernels I referenced above, uses PIO) + kern :debug : [ +0.009260] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, PIO + kern :debug : [ +0.001105] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 24 00 e4 01 00 58 0b 58 0b 83 12 83 12 26 01 + 95 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +Note (2025-03-08): This patch was originally dropped due to the +comments in [3]. However, according to the commments in [4] it is still +required. + +[3]: https://github.com/linux-surface/kernel/commit/a3421c12bed0e46c28518bcb8c6b22f237c6dc7a +[4]: https://github.com/linux-surface/linux-surface/issues/1184 + +Patchset: surface3 +--- + drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c +index 6074b7730e86..6aa3e1d6f160 100644 +--- a/drivers/input/touchscreen/surface3_spi.c ++++ b/drivers/input/touchscreen/surface3_spi.c +@@ -25,6 +25,12 @@ + #define SURFACE3_REPORT_TOUCH 0xd2 + #define SURFACE3_REPORT_PEN 0x16 + ++bool use_dma = false; ++module_param(use_dma, bool, 0644); ++MODULE_PARM_DESC(use_dma, ++ "Disable DMA mode if you encounter touch input crash. " ++ "(default: false, disabled to avoid crash)"); ++ + struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; +@@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) + return 0; + } + ++static bool surface3_spi_can_dma(struct spi_controller *ctlr, ++ struct spi_device *spi, ++ struct spi_transfer *tfr) ++{ ++ return use_dma; ++} ++ + static int surface3_spi_probe(struct spi_device *spi) + { + struct surface3_ts_data *data; +@@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) + if (error) + return error; + ++ /* ++ * Set up DMA ++ * ++ * TODO: Currently, touch input with DMA seems to be broken. ++ * On 4.19 LTS, touch input will crash after suspend. ++ * On recent stable kernel (at least after 5.1), touch input will crash after ++ * the first touch. No problem with PIO on those kernels. ++ * Maybe we need to configure DMA here. ++ * ++ * Link to issue: https://github.com/jakeday/linux-surface/issues/596 ++ */ ++ spi->controller->can_dma = surface3_spi_can_dma; ++ + return 0; + } + +-- +2.51.0 + diff --git a/patches/6.16/0003-mwifiex.patch b/patches/6.16/0003-mwifiex.patch new file mode 100644 index 0000000000..e275bcade5 --- /dev/null +++ b/patches/6.16/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 5ebd733c7a2faf6442fdbb64620664c6c74a3c6d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index a760de191fce..db9b203226a8 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd..f46b06f8d643 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5b..5d30ae39d65e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.51.0 + +From 7fc537753e00ffba79cd3b93cff948569d0f85a0 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index db9b203226a8..ffd0c1fe9223 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65e..c14eb56eb911 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.51.0 + +From b13ce3956e2c84e8e0f9ede608419981c47238ce Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 9efdd111baf5..6c68639f9f5d 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -66,6 +66,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -469,6 +470,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -4079,6 +4081,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.51.0 + diff --git a/patches/6.16/0004-ath10k.patch b/patches/6.16/0004-ath10k.patch new file mode 100644 index 0000000000..92f146b30e --- /dev/null +++ b/patches/6.16/0004-ath10k.patch @@ -0,0 +1,120 @@ +From 509e9d1b4d4803b7b3061cae155b92b5129b8569 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index 8b659678856d..5ed699691b39 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -40,6 +40,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -52,6 +55,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -61,6 +67,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -931,6 +940,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -945,6 +990,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.51.0 + diff --git a/patches/6.16/0005-ipts.patch b/patches/6.16/0005-ipts.patch new file mode 100644 index 0000000000..0c456b5902 --- /dev/null +++ b/patches/6.16/0005-ipts.patch @@ -0,0 +1,3243 @@ +From 766b671b7b337d44a2d543e038af19705b65dc32 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index bc40b940ae21..45fbd856d416 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 3f9c60b579ae..853a67753333 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.51.0 + +From 52ab7cdaafa370cdaca812debc559a4c4f28afc5 Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 3dd4d73fcb5d..98def863f736 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -39,6 +39,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -209,12 +214,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -1897,6 +1904,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2187,6 +2197,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + /* +@@ -4516,6 +4529,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4554,6 +4579,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.51.0 + +From a94045db4fab0ee662697cdc70473e7162702b0a Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 43859fc75747..ded8868dcfea 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1429,6 +1429,8 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/intel-thc-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 10ae5dedbd84..7ba7d26391e9 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -175,3 +175,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..7b9f54388a9f +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..639940794615 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..977724c728c3 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.51.0 + diff --git a/patches/6.16/0006-ithc.patch b/patches/6.16/0006-ithc.patch new file mode 100644 index 0000000000..4104f00d06 --- /dev/null +++ b/patches/6.16/0006-ithc.patch @@ -0,0 +1,2780 @@ +From 1e8740a68d6ca59e6814a7470d04f139240c0c30 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index cf7b6882ec75..55ab16865774 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -380,6 +380,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.51.0 + +From 6253e4bc033038c1e682057b7992692b517c7231 Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 438 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2575 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index ded8868dcfea..d6bc87be4854 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1431,6 +1431,8 @@ source "drivers/hid/intel-thc-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 7ba7d26391e9..d939da7ac2e8 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -177,3 +177,4 @@ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..4937ba131297 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..2d8c6afe9966 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..bf4eab33062b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..1749a5819b3e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..8883987fb352 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..094d878d671b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,438 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, ++ // MTL and up are handled by drivers/hid/intel-thc-hid ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ timer_delete_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..e2d1690b8cf8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..c0f13506af20 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..4f541fe533fa +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..aec320d4e945 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.51.0 + diff --git a/patches/6.16/0007-surface-sam.patch b/patches/6.16/0007-surface-sam.patch new file mode 100644 index 0000000000..89ec00cf00 --- /dev/null +++ b/patches/6.16/0007-surface-sam.patch @@ -0,0 +1,211 @@ +From 1578f4db9e0da5dc08d1169cc6694474b9ab9ab7 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 17 Jun 2022 02:14:00 +0200 +Subject: [PATCH] rtc: Add basic support for RTC via Surface System Aggregator + Module + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/rtc/Kconfig | 7 +++ + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-surface.c | 129 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 137 insertions(+) + create mode 100644 drivers/rtc/rtc-surface.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 9aec922613ce..cd7071b17616 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -1376,6 +1376,13 @@ config RTC_DRV_NTXEC + embedded controller found in certain e-book readers designed by the + original design manufacturer Netronix. + ++config RTC_DRV_SURFACE ++ tristate "Microsoft Surface Aggregator RTC" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ TODO ++ + comment "on-CPU RTC drivers" + + config RTC_DRV_ASM9260 +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index 4619aa2ac469..58f1774c7368 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -181,6 +181,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o + obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o + obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o + obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o ++obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o + obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o + obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o + obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o +diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c +new file mode 100644 +index 000000000000..f6c17c4e98d5 +--- /dev/null ++++ b/drivers/rtc/rtc-surface.c +@@ -0,0 +1,129 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * AC driver for 7th-generation Microsoft Surface devices via Surface System ++ * Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct surface_rtc { ++ struct ssam_device *sdev; ++ struct rtc_device *rtc; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x10, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x0f, ++}); ++ ++static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) ++{ ++ __le32 time_le; ++ int status; ++ ++ status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); ++ if (status) ++ return status; ++ ++ *time = le32_to_cpu(time_le); ++ return 0; ++} ++ ++static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) ++{ ++ __le32 time_le = cpu_to_le32(time); ++ ++ return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); ++} ++ ++static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ int status; ++ u32 time; ++ ++ status = ssam_rtc_get_unix_time(srtc, &time); ++ if (status) ++ return status; ++ ++ rtc_time64_to_tm(time, tm); ++ return 0; ++} ++ ++static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ time64_t time = rtc_tm_to_time64(tm); ++ ++ return ssam_rtc_set_unix_time(srtc, (u32)time); ++} ++ ++static const struct rtc_class_ops surface_rtc_ops = { ++ .read_time = surface_rtc_read_time, ++ .set_time = surface_rtc_set_time, ++}; ++ ++static int surface_rtc_probe(struct ssam_device *sdev) ++{ ++ struct surface_rtc *srtc; ++ ++ srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); ++ if (!srtc) ++ return -ENOMEM; ++ ++ srtc->sdev = sdev; ++ ++ srtc->rtc = devm_rtc_allocate_device(&sdev->dev); ++ if (IS_ERR(srtc->rtc)) ++ return PTR_ERR(srtc->rtc); ++ ++ srtc->rtc->ops = &surface_rtc_ops; ++ srtc->rtc->range_max = U32_MAX; ++ ++ ssam_device_set_drvdata(sdev, srtc); ++ ++ return devm_rtc_register_device(srtc->rtc); ++} ++ ++static void surface_rtc_remove(struct ssam_device *sdev) ++{ ++ /* Device-managed allocations take care of everything... */ ++} ++ ++static const struct ssam_device_id surface_rtc_match[] = { ++ { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_rtc_match); ++ ++static struct ssam_device_driver surface_rtc_driver = { ++ .probe = surface_rtc_probe, ++ .remove = surface_rtc_remove, ++ .match_table = surface_rtc_match, ++ .driver = { ++ .name = "surface_rtc", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_rtc_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.51.0 + +From 6871f7ece4ce979923ea83e19c3cd2830188b4c5 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 20 Apr 2025 01:05:14 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add Surface Laptop 7 + (ACPI) + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index a594d5fcfcfd..07b03aa4fa7f 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -460,6 +460,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + ++ /* Surface Laptop 7 */ ++ { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, ++ + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + +-- +2.51.0 + diff --git a/patches/6.16/0008-surface-sam-over-hid.patch b/patches/6.16/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..1c7b32f574 --- /dev/null +++ b/patches/6.16/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From f5015fb9b2720652748f6bbc7ed8eb0adea07854 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index f43067f6797e..1761ca30e17a 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -662,6 +662,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -763,6 +784,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.51.0 + +From 4972b5ba149ead25f9b3300674225788043b40d2 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index f775c6ca1ec1..2075e3852053 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..68db237734a1 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.51.0 + diff --git a/patches/6.16/0009-surface-button.patch b/patches/6.16/0009-surface-button.patch new file mode 100644 index 0000000000..c34b1644f9 --- /dev/null +++ b/patches/6.16/0009-surface-button.patch @@ -0,0 +1,149 @@ +From 55168a263afd9a033c00ff9625b6324345d9405c Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index b8cad415c62c..43b5d56383e3 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.51.0 + +From bb174e7f9377b9f0afce68e2e52ceaf3584950a5 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979c..4240c98ca226 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.51.0 + diff --git a/patches/6.16/0010-surface-typecover.patch b/patches/6.16/0010-surface-typecover.patch new file mode 100644 index 0000000000..89d4958220 --- /dev/null +++ b/patches/6.16/0010-surface-typecover.patch @@ -0,0 +1,575 @@ +From 84deac886f50cf811673ad70953d5ab79b89617f Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index f5bc53875330..31fa895d73c4 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.51.0 + +From 8c85bf8003cf8dcbe8f04a100474513a10e3f207 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index edb8da49d916..650df45e9f13 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -35,7 +35,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -48,6 +51,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -74,12 +78,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -177,6 +184,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -221,6 +230,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -411,6 +421,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1802,6 +1822,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1825,6 +1908,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1863,8 +1949,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); +@@ -1873,8 +1961,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1924,6 +2014,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2363,6 +2454,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.51.0 + +From b4e3dfa79f33a0e59e7054ae764f2026f93a0113 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 650df45e9f13..bb63fe858d87 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -86,6 +87,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -423,6 +426,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1438,6 +1442,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1465,6 +1472,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1486,6 +1508,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1493,6 +1516,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1502,11 +1538,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1689,6 +1735,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1737,6 +1819,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1822,30 +1911,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1854,8 +1919,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1992,13 +2058,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -2007,12 +2084,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); +-- +2.51.0 + diff --git a/patches/6.16/0011-surface-shutdown.patch b/patches/6.16/0011-surface-shutdown.patch new file mode 100644 index 0000000000..0fef5cd5c4 --- /dev/null +++ b/patches/6.16/0011-surface-shutdown.patch @@ -0,0 +1,97 @@ +From 29accd39e3da7cc94b35a062853509f1de3d9e1d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index 01e6aea1b0c7..43d38767f773 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index db6e142b082d..c9bb44f2b37e 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6337,3 +6337,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index d56d0dd80afb..4bd88eda9c7f 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -484,6 +484,7 @@ struct pci_dev { + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ + unsigned int non_mappable_bars:1; /* BARs can't be mapped to user-space */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.51.0 + diff --git a/patches/6.16/0012-surface-gpe.patch b/patches/6.16/0012-surface-gpe.patch new file mode 100644 index 0000000000..178ba7dcac --- /dev/null +++ b/patches/6.16/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From f0ec37bb5b3ce1f5d651c6c422b3a1451b73d158 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index b359413903b1..b4496db79f39 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.51.0 + diff --git a/patches/6.16/0013-cameras.patch b/patches/6.16/0013-cameras.patch new file mode 100644 index 0000000000..6c997b9bdb --- /dev/null +++ b/patches/6.16/0013-cameras.patch @@ -0,0 +1,678 @@ +From cfe2852b5ec7fb20b7f0308a075680cbfeaf7705 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index fb1fe9f3b1a3..5be8893b3912 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2197,6 +2197,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.51.0 + +From cf577fc4e338d0c1d1fcae2fe865f2d918576da9 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 98def863f736..15af4773df7e 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -44,6 +44,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -215,12 +222,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -1905,6 +1914,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2197,6 +2209,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4529,6 +4544,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4579,6 +4606,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.51.0 + +From 0c5ca0dd30fa269db29ec608313418e87de3740a Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 0133405697dc..9e0763bdc758 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.51.0 + +From 2f2889ce527f770d01a15fbbe60af475898d673b Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 3226888d77e9..3bfe45b764f7 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1053,7 +1053,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1574,7 +1574,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.51.0 + +From 13b8a5bf77dba2eb32fba569c1c619d6a484dbbb Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a8221fb..4f6bafd900ee 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index cb153ce42c45..f11b499e14bb 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1260,10 +1260,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.51.0 + +From 6a9f73420a018f4ee807b06a7aedaa5a3abdeecd Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 9e0763bdc758..0976b267972b 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -203,7 +203,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.51.0 + +From 0b974c7f54cf8e827bda14dc9b4e8830ab7e7b21 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db0..2d2abb25b944 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.51.0 + +From 2b99d15b534e9c291557225dfc87b710cf97e026 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 6e3dce7e35a4..5b6ba103867c 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -1002,6 +1002,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 9a0333ec1a86..4e1ca8fc9b41 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -93,6 +93,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.51.0 + +From 7890a9f304c08fd53a218520e9f89a64c1c65c15 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index 032fbcb981f2..e03a1d8cdcb4 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -87,6 +87,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719, bool detect) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_SHUTDOWN, &ret); + fsleep(100); +-- +2.51.0 + diff --git a/patches/6.16/0014-amd-gpio.patch b/patches/6.16/0014-amd-gpio.patch new file mode 100644 index 0000000000..620d58151d --- /dev/null +++ b/patches/6.16/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From 48d639e6d267d1c87265b284bb2a511d46d8b776 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 9fa321a95eb3..8914a922be2b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + +@@ -1171,6 +1172,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1227,6 +1239,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.51.0 + +From 15fee80b2eed64de9f8a4bba533520476d7c9276 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 8914a922be2b..c43d0a553867 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1174,12 +1174,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.51.0 + diff --git a/patches/6.16/0015-rtc.patch b/patches/6.16/0015-rtc.patch new file mode 100644 index 0000000000..3fe174553c --- /dev/null +++ b/patches/6.16/0015-rtc.patch @@ -0,0 +1,110 @@ +From 160e5f8ff4b9b34fea0cf2575939470cd5d8a63b Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 825c2a8acea4..74612088ac5f 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -565,13 +572,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + + pm_runtime_get_sync(dev); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -613,12 +625,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -649,6 +655,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.51.0 + diff --git a/patches/6.16/0016-hid-surface-fn-key.patch b/patches/6.16/0016-hid-surface-fn-key.patch new file mode 100644 index 0000000000..fb9cbb61ac --- /dev/null +++ b/patches/6.16/0016-hid-surface-fn-key.patch @@ -0,0 +1,157 @@ +From 8ec2ee811c7d8c373f0a012f09f8f1582a6c3cdb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ciar=C3=A1n=20Coffey?= +Date: Fri, 10 Oct 2025 19:04:03 +0100 +Subject: [PATCH] HID: Add hid-surface driver to filter BTN_0 (FN key) + +The Surface Aggregator Module firmware incorrectly reports the FN key +as BTN_0 (button 256) through runtime HID events. This button can get +stuck in pressed state, flooding the input system and breaking focus +tracking in KWin Wayland compositor. + +Add a new hid-surface driver that filters BTN_0 events using the event +callback. The FN key should be handled as a hardware modifier and not +reported to the OS. + +Tested on Surface Laptop 4 AMD with KDE Plasma Wayland. After the fix: +- FN key combinations work correctly (volume, brightness) +- Focus tracking no longer breaks +- Mouse clicks work on all windows + +Fixes: https://github.com/linux-surface/linux-surface/issues/1851 +--- + drivers/hid/Kconfig | 8 ++++ + drivers/hid/Makefile | 1 + + drivers/hid/hid-surface.c | 90 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 99 insertions(+) + create mode 100644 drivers/hid/hid-surface.c + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index d6bc87be4854..ebb29cd695fe 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1131,6 +1131,14 @@ config HID_SUNPLUS + help + Support for Sunplus wireless desktop. + ++config HID_SURFACE ++ tristate "Microsoft Surface" ++ depends on SURFACE_AGGREGATOR ++ help ++ Say Y here to enable HID driver for Microsoft Surface integrated ++ keyboard and touchpad. This driver filters out erroneous BTN_0 ++ (FN key) events that can cause input focus issues. ++ + config HID_RMI + tristate "Synaptics RMI4 device support" + select RMI4_CORE +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index d939da7ac2e8..db4e80893ce5 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -173,6 +173,7 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++obj-$(CONFIG_HID_SURFACE) += hid-surface.o + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + +diff --git a/drivers/hid/hid-surface.c b/drivers/hid/hid-surface.c +new file mode 100644 +index 000000000000..a171ea65672f +--- /dev/null ++++ b/drivers/hid/hid-surface.c +@@ -0,0 +1,90 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * HID driver for Microsoft Surface devices ++ * ++ * Copyright (c) 2025 Linux Surface Project ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "hid-ids.h" ++ ++/* ++ * The Surface Aggregator Module firmware incorrectly reports the FN key ++ * as BTN_0 (button 256). This button can get stuck in pressed state, ++ * flooding the input system and breaking focus tracking in some ++ * compositors. Filter out BTN_0 as FN should be handled as a hardware ++ * modifier, not reported to the OS. ++ */ ++static int surface_input_mapping(struct hid_device *hdev, struct hid_input *hi, ++ struct hid_field *field, struct hid_usage *usage, ++ unsigned long **bit, int *max) ++{ ++ /* ++ * Filter BTN_0 during input mapping in case it appears in the ++ * HID descriptor (defense in depth). ++ */ ++ if (usage->type == EV_KEY && usage->code == BTN_0) ++ return -1; /* Don't map this usage */ ++ ++ return 0; /* Use default mapping */ ++} ++ ++static int surface_event(struct hid_device *hdev, struct hid_field *field, ++ struct hid_usage *usage, __s32 value) ++{ ++ /* ++ * The Surface Aggregator Module firmware reports the FN key as BTN_0 ++ * at runtime. This button can get stuck in pressed state, flooding ++ * the input system and breaking focus tracking. Filter out these ++ * events as FN should be a hardware modifier, not reported to the OS. ++ */ ++ if (usage->type == EV_KEY && usage->code == BTN_0) ++ return 1; /* Event handled, don't process further */ ++ ++ return 0; /* Process event normally */ ++} ++ ++static int surface_probe(struct hid_device *hdev, const struct hid_device_id *id) ++{ ++ int ret; ++ ++ ret = hid_parse(hdev); ++ if (ret) { ++ hid_err(hdev, "parse failed\n"); ++ return ret; ++ } ++ ++ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); ++ if (ret) { ++ hid_err(hdev, "hw start failed\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct hid_device_id surface_devices[] = { ++ { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, ++ USB_VENDOR_ID_MICROSOFT, 0x09AE) }, /* Surface Keyboard */ ++ { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, ++ USB_VENDOR_ID_MICROSOFT, 0x09AF) }, /* Surface Mouse/Touchpad */ ++ { } ++}; ++MODULE_DEVICE_TABLE(hid, surface_devices); ++ ++static struct hid_driver surface_driver = { ++ .name = "surface", ++ .id_table = surface_devices, ++ .probe = surface_probe, ++ .input_mapping = surface_input_mapping, ++ .event = surface_event, ++}; ++module_hid_driver(surface_driver); ++ ++MODULE_AUTHOR("Linux Surface Project"); ++MODULE_DESCRIPTION("Microsoft Surface HID driver"); ++MODULE_LICENSE("GPL"); +-- +2.51.0 + diff --git a/patches/6.17/0001-secureboot.patch b/patches/6.17/0001-secureboot.patch new file mode 100644 index 0000000000..e385f0afa1 --- /dev/null +++ b/patches/6.17/0001-secureboot.patch @@ -0,0 +1,112 @@ +From 9f986ecbf39f14cf5777227d6ef6db57c657b253 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index 9bea5a1e2c52..25848f886ad6 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLLCHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.52.0 + +From 7678fd13a6f86c4f58b733a79e8bf5396133b70b Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index e92c0056e4e0..02f765f275ca 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3298,6 +3298,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index 53166ef86ba4..5b33262b17b5 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -38,6 +38,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -109,7 +110,7 @@ bool hibernation_in_progress(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1492,6 +1493,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1549,3 +1556,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.52.0 + diff --git a/patches/6.17/0002-surface3.patch b/patches/6.17/0002-surface3.patch new file mode 100644 index 0000000000..e24592c57e --- /dev/null +++ b/patches/6.17/0002-surface3.patch @@ -0,0 +1,234 @@ +From d8699c97930721725f42472feefc91ece4f6f45d Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3 +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index 6c8fb7a4dde4..22797a53f4d8 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 29a403526cd9..986f32132c3d 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3792,6 +3792,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0c28..0b930c91bccb 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.52.0 + +From 9bfd71c1927f31059a4edef27a0eaf8e507a28d0 Mon Sep 17 00:00:00 2001 +From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> +Date: Fri, 6 Dec 2019 23:10:30 +0900 +Subject: [PATCH] surface3-spi: workaround: disable DMA mode to avoid crash by + default + +On Arch Linux kernel at least after 4.19, touch input is broken after suspend +(s2idle). + + kern :err : [ +0.203408] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + +On recent stable Arch Linux kernel (at least after 5.1), touch input will +crash after the first touch. + + kern :err : [ +0.203592] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + kern :err : [ +0.000173] spi_master spi1: failed to transfer one message from queue + +I found on an affected system (Arch Linux kernel, etc.), the touchscreen +driver uses DMA mode by default. Then, we found some kernels with different +kernel config (5.1 kernel config from Jakeday [1] or Chromium OS kernel +chromeos-4.19 [2]) will use PIO mode by default and no such issues there. + +So, this commit disables DMA mode on the touchscreen driver side as a quick +workaround to avoid touch input crash. +We may need to properly set up DMA mode to use the touchscreen driver with +DMA mode. + +You can still switch DMA/PIO mode if you want: + + switch to DMA mode (maybe broken) + echo 1 | sudo tee /sys/module/surface3_spi/parameters/use_dma + back to PIO mode + echo 0 | sudo tee /sys/module/surface3_spi/parameters/use_dma + +Link to issue: https://github.com/jakeday/linux-surface/issues/596 + +References: +[1] https://github.com/jakeday/linux-surface/blob/master/configs/5.1/config +[2] https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19 + +Tested on Arch Linux 5.4.1 with Surface 3, which will use DMA by default. +This commit made the driver use PIO by default and no touch input crash. +Also tested on chromeos-4.19 4.19.90 with Surface 3, which will use PIO by default +even without this commit. After this commit, it still uses PIO and confirmed +no functional changes regarding touch input. + +More details: + We can confirm which mode the touchscreen driver uses; first, enable + debug output: + + echo "file drivers/spi/spi-pxa2xx.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + echo "file drivers/input/touchscreen/surface3_spi.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + + Then, try to make a touch input and see dmesg log + + (On Arch Linux kernel, uses DMA) + kern :debug : [ +0.006383] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, DMA + kern :debug : [ +0.000495] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 18 00 e4 01 00 04 1a 04 1a e3 0c e3 0c b0 00 + c5 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + (On the kernels I referenced above, uses PIO) + kern :debug : [ +0.009260] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, PIO + kern :debug : [ +0.001105] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 24 00 e4 01 00 58 0b 58 0b 83 12 83 12 26 01 + 95 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +Note (2025-03-08): This patch was originally dropped due to the +comments in [3]. However, according to the commments in [4] it is still +required. + +[3]: https://github.com/linux-surface/kernel/commit/a3421c12bed0e46c28518bcb8c6b22f237c6dc7a +[4]: https://github.com/linux-surface/linux-surface/issues/1184 + +Patchset: surface3 +--- + drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c +index 6074b7730e86..6aa3e1d6f160 100644 +--- a/drivers/input/touchscreen/surface3_spi.c ++++ b/drivers/input/touchscreen/surface3_spi.c +@@ -25,6 +25,12 @@ + #define SURFACE3_REPORT_TOUCH 0xd2 + #define SURFACE3_REPORT_PEN 0x16 + ++bool use_dma = false; ++module_param(use_dma, bool, 0644); ++MODULE_PARM_DESC(use_dma, ++ "Disable DMA mode if you encounter touch input crash. " ++ "(default: false, disabled to avoid crash)"); ++ + struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; +@@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) + return 0; + } + ++static bool surface3_spi_can_dma(struct spi_controller *ctlr, ++ struct spi_device *spi, ++ struct spi_transfer *tfr) ++{ ++ return use_dma; ++} ++ + static int surface3_spi_probe(struct spi_device *spi) + { + struct surface3_ts_data *data; +@@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) + if (error) + return error; + ++ /* ++ * Set up DMA ++ * ++ * TODO: Currently, touch input with DMA seems to be broken. ++ * On 4.19 LTS, touch input will crash after suspend. ++ * On recent stable kernel (at least after 5.1), touch input will crash after ++ * the first touch. No problem with PIO on those kernels. ++ * Maybe we need to configure DMA here. ++ * ++ * Link to issue: https://github.com/jakeday/linux-surface/issues/596 ++ */ ++ spi->controller->can_dma = surface3_spi_can_dma; ++ + return 0; + } + +-- +2.52.0 + diff --git a/patches/6.17/0003-mwifiex.patch b/patches/6.17/0003-mwifiex.patch new file mode 100644 index 0000000000..bb00e40a2e --- /dev/null +++ b/patches/6.17/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From f9cbe41699012e3802f813f692e74c79ad1a1c0c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index a760de191fce..db9b203226a8 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd..f46b06f8d643 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5b..5d30ae39d65e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.52.0 + +From 49f5c1a53f5679011fc7f135a38593393fa8d3fb Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index db9b203226a8..ffd0c1fe9223 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65e..c14eb56eb911 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.52.0 + +From 23dc291355b16ee49055464ea4d8bd4095058988 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index fa683bb7f0b4..f80e194aa970 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -67,6 +67,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) + #define BTUSB_BARROT BIT(28) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(29) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -472,6 +473,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -4177,6 +4179,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.52.0 + diff --git a/patches/6.17/0004-ath10k.patch b/patches/6.17/0004-ath10k.patch new file mode 100644 index 0000000000..2895e8e9b8 --- /dev/null +++ b/patches/6.17/0004-ath10k.patch @@ -0,0 +1,120 @@ +From 9f52e06199405c2511325a784db7d4605a2191c1 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index 9ae3595fb698..b26a39c97998 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -41,6 +41,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -53,6 +56,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -62,6 +68,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -932,6 +941,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -946,6 +991,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.52.0 + diff --git a/patches/6.17/0005-ipts.patch b/patches/6.17/0005-ipts.patch new file mode 100644 index 0000000000..4a77be85cd --- /dev/null +++ b/patches/6.17/0005-ipts.patch @@ -0,0 +1,3243 @@ +From 702e4e0f0f82993157310d22b73e9ece119125b4 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index a4f75dc36929..e4a561b257d7 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index bc0fc584a8e4..51a91ef66cda 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.52.0 + +From dc61b222997c8a6d8b8753cbcb91c1373b31f227 Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index e236c7ec221f..9e31a5fe1f66 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -39,6 +39,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -209,12 +214,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -1879,6 +1886,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2169,6 +2179,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + /* +@@ -4515,6 +4528,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4553,6 +4578,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.52.0 + +From 841a09e0a458128d888a94118983406352f94256 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index b934523593d9..b2cc5db65f5b 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1428,6 +1428,8 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/intel-thc-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 10ae5dedbd84..7ba7d26391e9 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -175,3 +175,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..7b9f54388a9f +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..639940794615 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..977724c728c3 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.52.0 + diff --git a/patches/6.17/0006-ithc.patch b/patches/6.17/0006-ithc.patch new file mode 100644 index 0000000000..39c4a5a4cf --- /dev/null +++ b/patches/6.17/0006-ithc.patch @@ -0,0 +1,2780 @@ +From 4673894d99bcc8772535d42b2bee21fea544bb4d Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 4f9b01dc91e8..e4d73e45473c 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -381,6 +381,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.52.0 + +From 76846a0bd73bdd9331670be1f5006f2913500e7e Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 438 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2575 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index b2cc5db65f5b..707983ce6854 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1430,6 +1430,8 @@ source "drivers/hid/intel-thc-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 7ba7d26391e9..d939da7ac2e8 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -177,3 +177,4 @@ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..4937ba131297 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..2d8c6afe9966 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..bf4eab33062b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..1749a5819b3e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..8883987fb352 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..094d878d671b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,438 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, ++ // MTL and up are handled by drivers/hid/intel-thc-hid ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ timer_delete_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..e2d1690b8cf8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..c0f13506af20 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..4f541fe533fa +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..aec320d4e945 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.52.0 + diff --git a/patches/6.17/0007-surface-sam.patch b/patches/6.17/0007-surface-sam.patch new file mode 100644 index 0000000000..53964c52a1 --- /dev/null +++ b/patches/6.17/0007-surface-sam.patch @@ -0,0 +1,211 @@ +From d0a0f26ef063c7b5fec2c3a1b8ae1cd45ba4bba2 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 17 Jun 2022 02:14:00 +0200 +Subject: [PATCH] rtc: Add basic support for RTC via Surface System Aggregator + Module + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/rtc/Kconfig | 7 +++ + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-surface.c | 129 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 137 insertions(+) + create mode 100644 drivers/rtc/rtc-surface.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 64f6e9756aff..bf05361f7f13 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -1379,6 +1379,13 @@ config RTC_DRV_NTXEC + embedded controller found in certain e-book readers designed by the + original design manufacturer Netronix. + ++config RTC_DRV_SURFACE ++ tristate "Microsoft Surface Aggregator RTC" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ TODO ++ + comment "on-CPU RTC drivers" + + config RTC_DRV_ASM9260 +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index 789bddfea99d..a6df07332080 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -181,6 +181,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o + obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o + obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o + obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o ++obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o + obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o + obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o + obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o +diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c +new file mode 100644 +index 000000000000..f6c17c4e98d5 +--- /dev/null ++++ b/drivers/rtc/rtc-surface.c +@@ -0,0 +1,129 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * AC driver for 7th-generation Microsoft Surface devices via Surface System ++ * Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct surface_rtc { ++ struct ssam_device *sdev; ++ struct rtc_device *rtc; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x10, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x0f, ++}); ++ ++static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) ++{ ++ __le32 time_le; ++ int status; ++ ++ status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); ++ if (status) ++ return status; ++ ++ *time = le32_to_cpu(time_le); ++ return 0; ++} ++ ++static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) ++{ ++ __le32 time_le = cpu_to_le32(time); ++ ++ return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); ++} ++ ++static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ int status; ++ u32 time; ++ ++ status = ssam_rtc_get_unix_time(srtc, &time); ++ if (status) ++ return status; ++ ++ rtc_time64_to_tm(time, tm); ++ return 0; ++} ++ ++static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ time64_t time = rtc_tm_to_time64(tm); ++ ++ return ssam_rtc_set_unix_time(srtc, (u32)time); ++} ++ ++static const struct rtc_class_ops surface_rtc_ops = { ++ .read_time = surface_rtc_read_time, ++ .set_time = surface_rtc_set_time, ++}; ++ ++static int surface_rtc_probe(struct ssam_device *sdev) ++{ ++ struct surface_rtc *srtc; ++ ++ srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); ++ if (!srtc) ++ return -ENOMEM; ++ ++ srtc->sdev = sdev; ++ ++ srtc->rtc = devm_rtc_allocate_device(&sdev->dev); ++ if (IS_ERR(srtc->rtc)) ++ return PTR_ERR(srtc->rtc); ++ ++ srtc->rtc->ops = &surface_rtc_ops; ++ srtc->rtc->range_max = U32_MAX; ++ ++ ssam_device_set_drvdata(sdev, srtc); ++ ++ return devm_rtc_register_device(srtc->rtc); ++} ++ ++static void surface_rtc_remove(struct ssam_device *sdev) ++{ ++ /* Device-managed allocations take care of everything... */ ++} ++ ++static const struct ssam_device_id surface_rtc_match[] = { ++ { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_rtc_match); ++ ++static struct ssam_device_driver surface_rtc_driver = { ++ .probe = surface_rtc_probe, ++ .remove = surface_rtc_remove, ++ .match_table = surface_rtc_match, ++ .driver = { ++ .name = "surface_rtc", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_rtc_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.52.0 + +From 9cbed80f9cd071c2ffdd63590cf0d232612877dc Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 20 Apr 2025 01:05:14 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add Surface Laptop 7 + (ACPI) + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index a594d5fcfcfd..07b03aa4fa7f 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -460,6 +460,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + ++ /* Surface Laptop 7 */ ++ { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, ++ + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + +-- +2.52.0 + diff --git a/patches/6.17/0008-surface-sam-over-hid.patch b/patches/6.17/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..c02363c3f4 --- /dev/null +++ b/patches/6.17/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From 170d5fdab278878e6f94ade5988592fd988cf0fb Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index ed90858a27b7..070c36637811 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -662,6 +662,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -763,6 +784,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.52.0 + +From 1d7ce37d06481d6c9ce1c019788a64b86cf946b4 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index f775c6ca1ec1..2075e3852053 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..68db237734a1 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.52.0 + diff --git a/patches/6.17/0009-surface-button.patch b/patches/6.17/0009-surface-button.patch new file mode 100644 index 0000000000..b3f58f8d9d --- /dev/null +++ b/patches/6.17/0009-surface-button.patch @@ -0,0 +1,149 @@ +From 1a9bb05cc275049ad39b4e2db2c740e25482a249 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index b8cad415c62c..43b5d56383e3 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.52.0 + +From ccc1fc9362ec228d5821d9c5578eeaa90d9db42b Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979c..4240c98ca226 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.52.0 + diff --git a/patches/6.17/0010-surface-typecover.patch b/patches/6.17/0010-surface-typecover.patch new file mode 100644 index 0000000000..079d9b39db --- /dev/null +++ b/patches/6.17/0010-surface-typecover.patch @@ -0,0 +1,575 @@ +From 937c654057091ab9efc529ad44ac116251974fb9 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 47f589c4104a..6b00aec67ee2 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.52.0 + +From d5768e0e9b7b9fe1274170b6c4e2a62e508a2f00 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index a9ff84f0bd9b..1b2fc88ce1cf 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -35,7 +35,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -48,6 +51,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -74,12 +78,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -180,6 +187,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -225,6 +234,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 + #define MT_CLS_APPLE_TOUCHBAR 0x0114 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0115 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -421,6 +431,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1811,6 +1831,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1834,6 +1917,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1872,8 +1958,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->name == MT_CLS_APPLE_TOUCHBAR && + !hid_find_field(hdev, HID_INPUT_REPORT, +@@ -1887,8 +1975,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1938,6 +2028,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2382,6 +2473,11 @@ static const struct hid_device_id mt_devices[] = { + HID_USB_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.52.0 + +From 9ab3ab41c4292e3877f81a960147521b9c29a935 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 1b2fc88ce1cf..dd200064e309 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -86,6 +87,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -433,6 +436,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1445,6 +1449,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1472,6 +1479,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1493,6 +1515,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1500,6 +1523,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1509,11 +1545,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1696,6 +1742,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1745,6 +1827,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1831,30 +1920,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1863,8 +1928,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -2006,13 +2072,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -2021,12 +2098,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); +-- +2.52.0 + diff --git a/patches/6.17/0011-surface-shutdown.patch b/patches/6.17/0011-surface-shutdown.patch new file mode 100644 index 0000000000..b837c4fb48 --- /dev/null +++ b/patches/6.17/0011-surface-shutdown.patch @@ -0,0 +1,97 @@ +From 3233e846799f63d18bfafbc1d41bc65fbd337609 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index 302d61783f6c..4a8a08bd92dd 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 214ed060ca1b..104fba45ac76 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6339,3 +6339,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 7735acf6f349..27780b0f1115 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -484,6 +484,7 @@ struct pci_dev { + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ + unsigned int non_mappable_bars:1; /* BARs can't be mapped to user-space */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.52.0 + diff --git a/patches/6.17/0012-surface-gpe.patch b/patches/6.17/0012-surface-gpe.patch new file mode 100644 index 0000000000..f1c02814e3 --- /dev/null +++ b/patches/6.17/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From 8a8c257440871504b48739fcd4719049638178f6 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index b359413903b1..b4496db79f39 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.52.0 + diff --git a/patches/6.17/0013-cameras.patch b/patches/6.17/0013-cameras.patch new file mode 100644 index 0000000000..2ef4d7ed4a --- /dev/null +++ b/patches/6.17/0013-cameras.patch @@ -0,0 +1,1034 @@ +From 93a40ae9e889a11730e7e616bb313d6dc7d5a5dc Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index f2e032f38162..c45820be8fd0 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2201,6 +2201,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.52.0 + +From c552e13ba9c4e725c8ab68016affe3697a7102d5 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 9e31a5fe1f66..bdd32551c452 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -44,6 +44,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -215,12 +222,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -1887,6 +1896,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2179,6 +2191,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4528,6 +4543,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4578,6 +4605,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.52.0 + +From 9868645027474ea4be300614971df7bf4fbe0e4a Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 0133405697dc..9e0763bdc758 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.52.0 + +From add99db7db117172d47c6a3feb318cb83ab7b535 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 31a42d81e970..0d5a8fd86e67 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1053,7 +1053,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1579,7 +1579,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.52.0 + +From 4983fa30433b34386e8d43e72e348a00b3d0ef9d Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a8221fb..4f6bafd900ee 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index cb153ce42c45..f11b499e14bb 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1260,10 +1260,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.52.0 + +From 6a40dbaaa72f332b416a2722b93d22f4e7341ad7 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 9e0763bdc758..0976b267972b 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -203,7 +203,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.52.0 + +From 501f49d48266ee52109f35a50c7a63a7190d5bff Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db0..2d2abb25b944 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.52.0 + +From 93f9dbec991c78763d0abdf567539691b9ef3cab Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 6e3dce7e35a4..5b6ba103867c 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -1002,6 +1002,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 9a0333ec1a86..4e1ca8fc9b41 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -93,6 +93,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.52.0 + +From 82a0860d4f7b8226e21577a3956f620d3bbca6c2 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index 032fbcb981f2..e03a1d8cdcb4 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -87,6 +87,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719, bool detect) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_SHUTDOWN, &ret); + fsleep(100); +-- +2.52.0 + +From df7085a9fcdd1e8c2cf71fa555c2b63f13ae7d66 Mon Sep 17 00:00:00 2001 +From: Tooraj Taraz +Date: Wed, 31 Dec 2025 00:35:50 +0100 +Subject: [PATCH] Add camera support for Surface Pro 9 + +Experimental camera support for the Surface Pro 9. + +Link: https://github.com/linux-surface/linux-surface/pull/1867 +Patchset: cameras +--- + drivers/media/i2c/ov13858.c | 166 +++++++++++++++++- + drivers/media/i2c/ov5693.c | 1 + + drivers/media/pci/intel/ipu-bridge.c | 4 + + drivers/platform/x86/intel/int3472/discrete.c | 23 +++ + 4 files changed, 190 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c +index 7a3fc1d28514..970b224cb466 100644 +--- a/drivers/media/i2c/ov13858.c ++++ b/drivers/media/i2c/ov13858.c +@@ -2,6 +2,8 @@ + // Copyright (c) 2017 Intel Corporation. + + #include ++#include ++#include + #include + #include + #include +@@ -118,6 +120,14 @@ struct ov13858_mode { + struct ov13858_reg_list reg_list; + }; + ++// Or use standard names that might be more correct: ++static const char * const ov13858_supply_names[] = { ++ "avdd", // Analog voltage ++ "pwr1", // Digital core voltage ++}; ++ ++#define OV13858_NUM_SUPPLIES ARRAY_SIZE(ov13858_supply_names) ++ + /* 4224x3136 needs 1080Mbps/lane, 4 lanes */ + static const struct ov13858_reg mipi_data_rate_1080mbps[] = { + /* PLL1 registers */ +@@ -1041,13 +1051,131 @@ struct ov13858 { + + /* Current mode */ + const struct ov13858_mode *cur_mode; +- ++ struct regulator_bulk_data supplies[OV13858_NUM_SUPPLIES]; ++ struct gpio_desc *reset; ++ struct clk *xvclk; + /* Mutex for serialized access */ + struct mutex mutex; + }; + + #define to_ov13858(_sd) container_of(_sd, struct ov13858, sd) + ++ ++static int ov13858_get_regulators(struct ov13858 *ov13858) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); ++ unsigned int i; ++ ++ for (i = 0; i < OV13858_NUM_SUPPLIES; i++) ++ ov13858->supplies[i].supply = ov13858_supply_names[i]; ++ ++ return devm_regulator_bulk_get(&client->dev, OV13858_NUM_SUPPLIES, ++ ov13858->supplies); ++} ++ ++static int ov13858_sensor_powerup(struct ov13858 *ov13858) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); ++ int ret; ++ ++ dev_info(&client->dev, "Powering up sensor\n"); ++ ++ /* Assert reset */ ++ gpiod_set_value_cansleep(ov13858->reset, 1); ++ ++ /* Enable clock FIRST - sensor needs clock to communicate */ ++ ret = clk_prepare_enable(ov13858->xvclk); ++ if (ret) { ++ dev_err(&client->dev, "Failed to enable clock: %d\n", ret); ++ return ret; ++ } ++ dev_info(&client->dev, "Clock enabled\n"); ++ ++ /* Enable regulators */ ++ ret = regulator_bulk_enable(OV13858_NUM_SUPPLIES, ov13858->supplies); ++ if (ret) { ++ dev_err(&client->dev, "Failed to enable regulators: %d\n", ret); ++ clk_disable_unprepare(ov13858->xvclk); ++ return ret; ++ } ++ ++ dev_info(&client->dev, "Regulators enabled\n"); ++ ++ /* Wait for power to stabilize */ ++ usleep_range(5000, 10000); ++ ++ /* De-assert reset */ ++ gpiod_set_value_cansleep(ov13858->reset, 0); ++ ++ /* Wait for sensor to boot */ ++ msleep(20); ++ ++ dev_info(&client->dev, "Reset de-asserted, sensor should be ready\n"); ++ ++ return 0; ++} ++ ++static void ov13858_sensor_powerdown(struct ov13858 *ov13858) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); ++ ++ dev_info(&client->dev, "Powering down sensor\n"); ++ ++ /* Assert reset to put sensor in reset state */ ++ gpiod_set_value_cansleep(ov13858->reset, 1); ++ ++ /* Disable regulators */ ++ regulator_bulk_disable(OV13858_NUM_SUPPLIES, ov13858->supplies); ++ dev_info(&client->dev, "Regulators disabled\n"); ++ ++ /* Disable clock */ ++ clk_disable_unprepare(ov13858->xvclk); ++ dev_info(&client->dev, "Clock disabled\n"); ++} ++ ++static int __maybe_unused ov13858_suspend(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov13858 *ov13858 = to_ov13858(sd); ++ ++ ov13858_sensor_powerdown(ov13858); ++ ++ return 0; ++} ++ ++static int __maybe_unused ov13858_resume(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov13858 *ov13858 = to_ov13858(sd); ++ int ret; ++ ++ ret = ov13858_sensor_powerup(ov13858); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops ov13858_pm_ops = { ++ SET_RUNTIME_PM_OPS(ov13858_suspend, ov13858_resume, NULL) ++}; ++ ++static int ov13858_get_gpios(struct ov13858 *ov13858) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); ++ ++ ov13858->reset = devm_gpiod_get_optional(&client->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(ov13858->reset)) { ++ dev_err(&client->dev, "Error fetching reset GPIO\n"); ++ return PTR_ERR(ov13858->reset); ++ } ++ ++ return 0; ++} ++ + /* Read registers up to 4 at a time */ + static int ov13858_read_reg(struct ov13858 *ov13858, u16 reg, u32 len, + u32 *val) +@@ -1499,8 +1627,11 @@ static int ov13858_identify_module(struct ov13858 *ov13858) + int ret; + u32 val; + ++ dev_info(&client->dev, "Attempting to read chip ID from register 0x300a\n"); + ret = ov13858_read_reg(ov13858, OV13858_REG_CHIP_ID, + OV13858_REG_VALUE_24BIT, &val); ++ dev_info(&client->dev, "Chip ID read result: ret=%d, val=0x%06x (expected 0x%06x)\n", ++ ret, val, OV13858_CHIP_ID); + if (ret) + return ret; + +@@ -1509,7 +1640,7 @@ static int ov13858_identify_module(struct ov13858 *ov13858) + OV13858_CHIP_ID, val); + return -EIO; + } +- ++ dev_info(&client->dev, "Chip ID verified successfully!\n"); + return 0; + } + +@@ -1664,6 +1795,7 @@ static int ov13858_probe(struct i2c_client *client) + u32 val = 0; + + device_property_read_u32(&client->dev, "clock-frequency", &val); ++ dev_info(&client->dev, "Clock frequency read: %u (expected 19200000)\n", val); + if (val != 19200000) + return -EINVAL; + +@@ -1671,14 +1803,33 @@ static int ov13858_probe(struct i2c_client *client) + if (!ov13858) + return -ENOMEM; + ++ /* Get the external clock */ ++ ov13858->xvclk = devm_clk_get_optional(&client->dev, "xvclk"); ++ if (IS_ERR(ov13858->xvclk)) ++ return dev_err_probe(&client->dev, PTR_ERR(ov13858->xvclk), ++ "failed to get xvclk\n"); ++ + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov13858->sd, client, &ov13858_subdev_ops); + ++ ret = ov13858_get_regulators(ov13858); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "Error fetching regulators\n"); ++ ++ ret = ov13858_get_gpios(ov13858); ++ if (ret) ++ return ret; ++ ++ ret = ov13858_sensor_powerup(ov13858); ++ if (ret) ++ return ret; // No cleanup needed yet, devm handles GPIOs/regulators ++ + /* Check module identity */ + ret = ov13858_identify_module(ov13858); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); +- return ret; ++ goto error_power_off; // Now we need to power down + } + + /* Set default mode to max resolution */ +@@ -1686,7 +1837,7 @@ static int ov13858_probe(struct i2c_client *client) + + ret = ov13858_init_controls(ov13858); + if (ret) +- return ret; ++ goto error_power_off; + + /* Initialize subdev */ + ov13858->sd.internal_ops = &ov13858_internal_ops; +@@ -1722,6 +1873,9 @@ static int ov13858_probe(struct i2c_client *client) + + error_handler_free: + ov13858_free_controls(ov13858); ++ ++error_power_off: ++ ov13858_sensor_powerdown(ov13858); + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + + return ret; +@@ -1737,6 +1891,9 @@ static void ov13858_remove(struct i2c_client *client) + ov13858_free_controls(ov13858); + + pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ ov13858_sensor_powerdown(ov13858); ++ pm_runtime_set_suspended(&client->dev); + } + + static const struct i2c_device_id ov13858_id_table[] = { +@@ -1758,6 +1915,7 @@ MODULE_DEVICE_TABLE(acpi, ov13858_acpi_ids); + static struct i2c_driver ov13858_i2c_driver = { + .driver = { + .name = "ov13858", ++ .pm = &ov13858_pm_ops, + .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), + }, + .probe = ov13858_probe, +diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c +index 485efd15257e..476efd022f6b 100644 +--- a/drivers/media/i2c/ov5693.c ++++ b/drivers/media/i2c/ov5693.c +@@ -1408,6 +1408,7 @@ static const struct dev_pm_ops ov5693_pm_ops = { + + static const struct acpi_device_id ov5693_acpi_match[] = { + {"INT33BE"}, ++ {"OVTI5693"}, + {}, + }; + MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +index 4e579352ab2c..ac637207b644 100644 +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -60,6 +60,10 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), + /* Onsemi MT9M114 */ + IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), ++ /* Omnivision OV5693 - Surface Pro 9 */ ++ IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), ++ /* Omnivision OV13858 - Surface Pro 9 */ ++ IPU_SENSOR_CONFIG("OVTID858", 4, 540000000), + /* Omnivision OV2740 */ + IPU_SENSOR_CONFIG("INT3474", 1, 180000000), + /* Omnivision OV5670 */ +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index bdfb8a800c54..b559112ee763 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -205,6 +205,14 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 + *con_id = "dvdd"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; ++ case 0x08: /* Surface Pro 9 - additional power rail */ ++ *con_id = "pwr1"; ++ *gpio_flags = GPIO_ACTIVE_HIGH; ++ break; ++ case 0x10: /* Surface Pro 9 - secondary power rail */ ++ *con_id = "pwr2"; ++ *gpio_flags = GPIO_ACTIVE_HIGH; ++ break; + default: + *con_id = "unknown"; + *gpio_flags = GPIO_ACTIVE_HIGH; +@@ -307,6 +315,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + case INT3472_GPIO_TYPE_PRIVACY_LED: + case INT3472_GPIO_TYPE_POWER_ENABLE: + case INT3472_GPIO_TYPE_HANDSHAKE: ++ case 0x08: /* Surface Pro 9 power rails */ ++ case 0x10: + gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); + if (IS_ERR(gpio)) { + ret = PTR_ERR(gpio); +@@ -345,6 +355,19 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + err_msg = "Failed to map handshake to sensor\n"; + + break; ++ case 0x08: /* Surface Pro 9 - treat as power*/ ++ dev_info(int3472->dev, "GPIO type 0x%02x detected on pin 0x%02x\n", type, agpio->pin_table[0]); ++ dev_info(int3472->dev, " con_id=%s, flags=0x%x\n", con_id, gpio_flags); ++ ret = skl_int3472_register_regulator(int3472, gpio, ++ GPIO_REGULATOR_ENABLE_TIME, ++ con_id, NULL); ++ dev_info(int3472->dev, " register_regulator returned: %d\n", ret); ++ if (ret) { ++ dev_err(int3472->dev, "Failed to register type 0x02x: %d\n", type, ret); ++ } ++ break; ++ case 0x10: ++ break; + default: /* Never reached */ + ret = -EINVAL; + break; +-- +2.52.0 + diff --git a/patches/6.17/0014-amd-gpio.patch b/patches/6.17/0014-amd-gpio.patch new file mode 100644 index 0000000000..1b87fe1ece --- /dev/null +++ b/patches/6.17/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From faea5ffa744ffdce8341423e78d1514a30360026 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 9fa321a95eb3..8914a922be2b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + +@@ -1171,6 +1172,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1227,6 +1239,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.52.0 + +From 46545b3ec777de08bed3ef45022f09fe2029d56c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 8914a922be2b..c43d0a553867 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1174,12 +1174,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.52.0 + diff --git a/patches/6.17/0015-rtc.patch b/patches/6.17/0015-rtc.patch new file mode 100644 index 0000000000..1bb2ea06c9 --- /dev/null +++ b/patches/6.17/0015-rtc.patch @@ -0,0 +1,110 @@ +From a448236a43eec025f3af8967e2a54e91248e708d Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 33418dd6768a..f94bbdc1de06 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -568,13 +575,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + if (dd->capabilities & ACPI_TAD_RT) + sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -616,12 +628,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -652,6 +658,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.52.0 + diff --git a/patches/6.17/0016-hid-surface.patch b/patches/6.17/0016-hid-surface.patch new file mode 100644 index 0000000000..4dfae662b1 --- /dev/null +++ b/patches/6.17/0016-hid-surface.patch @@ -0,0 +1,246 @@ +From ee02a87cb3efa966f5168b0bfd0932c474f49ba5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ciar=C3=A1n=20Coffey?= +Date: Fri, 10 Oct 2025 19:04:03 +0100 +Subject: [PATCH] HID: Add hid-surface driver to filter BTN_0 (FN key) + +The Surface Aggregator Module firmware incorrectly reports the FN key +as BTN_0 (button 256) through runtime HID events. This button can get +stuck in pressed state, flooding the input system and breaking focus +tracking in KWin Wayland compositor. + +Add a new hid-surface driver that filters BTN_0 events using the event +callback. The FN key should be handled as a hardware modifier and not +reported to the OS. + +Tested on Surface Laptop 4 AMD with KDE Plasma Wayland. After the fix: +- FN key combinations work correctly (volume, brightness) +- Focus tracking no longer breaks +- Mouse clicks work on all windows + +Fixes: https://github.com/linux-surface/linux-surface/issues/1851 +Patchset: hid-surface +--- + drivers/hid/Kconfig | 8 ++++ + drivers/hid/Makefile | 1 + + drivers/hid/hid-surface.c | 90 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 99 insertions(+) + create mode 100644 drivers/hid/hid-surface.c + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 707983ce6854..9f8cf231d0e2 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1130,6 +1130,14 @@ config HID_SUNPLUS + help + Support for Sunplus wireless desktop. + ++config HID_SURFACE ++ tristate "Microsoft Surface" ++ depends on SURFACE_AGGREGATOR ++ help ++ Say Y here to enable HID driver for Microsoft Surface integrated ++ keyboard and touchpad. This driver filters out erroneous BTN_0 ++ (FN key) events that can cause input focus issues. ++ + config HID_RMI + tristate "Synaptics RMI4 device support" + select RMI4_CORE +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index d939da7ac2e8..db4e80893ce5 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -173,6 +173,7 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++obj-$(CONFIG_HID_SURFACE) += hid-surface.o + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + +diff --git a/drivers/hid/hid-surface.c b/drivers/hid/hid-surface.c +new file mode 100644 +index 000000000000..a171ea65672f +--- /dev/null ++++ b/drivers/hid/hid-surface.c +@@ -0,0 +1,90 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * HID driver for Microsoft Surface devices ++ * ++ * Copyright (c) 2025 Linux Surface Project ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "hid-ids.h" ++ ++/* ++ * The Surface Aggregator Module firmware incorrectly reports the FN key ++ * as BTN_0 (button 256). This button can get stuck in pressed state, ++ * flooding the input system and breaking focus tracking in some ++ * compositors. Filter out BTN_0 as FN should be handled as a hardware ++ * modifier, not reported to the OS. ++ */ ++static int surface_input_mapping(struct hid_device *hdev, struct hid_input *hi, ++ struct hid_field *field, struct hid_usage *usage, ++ unsigned long **bit, int *max) ++{ ++ /* ++ * Filter BTN_0 during input mapping in case it appears in the ++ * HID descriptor (defense in depth). ++ */ ++ if (usage->type == EV_KEY && usage->code == BTN_0) ++ return -1; /* Don't map this usage */ ++ ++ return 0; /* Use default mapping */ ++} ++ ++static int surface_event(struct hid_device *hdev, struct hid_field *field, ++ struct hid_usage *usage, __s32 value) ++{ ++ /* ++ * The Surface Aggregator Module firmware reports the FN key as BTN_0 ++ * at runtime. This button can get stuck in pressed state, flooding ++ * the input system and breaking focus tracking. Filter out these ++ * events as FN should be a hardware modifier, not reported to the OS. ++ */ ++ if (usage->type == EV_KEY && usage->code == BTN_0) ++ return 1; /* Event handled, don't process further */ ++ ++ return 0; /* Process event normally */ ++} ++ ++static int surface_probe(struct hid_device *hdev, const struct hid_device_id *id) ++{ ++ int ret; ++ ++ ret = hid_parse(hdev); ++ if (ret) { ++ hid_err(hdev, "parse failed\n"); ++ return ret; ++ } ++ ++ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); ++ if (ret) { ++ hid_err(hdev, "hw start failed\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct hid_device_id surface_devices[] = { ++ { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, ++ USB_VENDOR_ID_MICROSOFT, 0x09AE) }, /* Surface Keyboard */ ++ { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, ++ USB_VENDOR_ID_MICROSOFT, 0x09AF) }, /* Surface Mouse/Touchpad */ ++ { } ++}; ++MODULE_DEVICE_TABLE(hid, surface_devices); ++ ++static struct hid_driver surface_driver = { ++ .name = "surface", ++ .id_table = surface_devices, ++ .probe = surface_probe, ++ .input_mapping = surface_input_mapping, ++ .event = surface_event, ++}; ++module_hid_driver(surface_driver); ++ ++MODULE_AUTHOR("Linux Surface Project"); ++MODULE_DESCRIPTION("Microsoft Surface HID driver"); ++MODULE_LICENSE("GPL"); +-- +2.52.0 + +From 7391632cbb14114fbe7334e16f64cfebc03a4d74 Mon Sep 17 00:00:00 2001 +From: LegendaryFire +Date: Wed, 24 Dec 2025 00:54:44 -0800 +Subject: [PATCH] hid/multitouch: Prevent mode set on Surface Laptop Studio 2 + touch pad + +Patchset: hid-surface +--- + drivers/hid/hid-multitouch.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index dd200064e309..14d088fdabed 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -80,6 +80,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) + #define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) ++#define MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE BIT(26) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -238,6 +239,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_SMART_TECH 0x0113 + #define MT_CLS_APPLE_TOUCHBAR 0x0114 + #define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0115 ++#define MT_CLS_SURFACE_TOUCHPAD 0x0116 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -445,6 +447,10 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_WIN8_PTP_BUTTONS, + .export_all_inputs = true + }, ++ { .name = MT_CLS_SURFACE_TOUCHPAD, ++ .quirks = MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE ++ }, + { } + }; + +@@ -2133,11 +2139,30 @@ static void mt_remove(struct hid_device *hdev) + + static void mt_on_hid_hw_open(struct hid_device *hdev) + { ++ /* ++ * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck ++ * non-functional if we change touchpad reporting modes from the HID ++ * open/close hooks. Avoid mode switching on hw_open/hw_close for ++ * those devices. ++ */ ++ struct mt_device *td = hid_get_drvdata(hdev); ++ if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) ++ return; + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + } + + static void mt_on_hid_hw_close(struct hid_device *hdev) + { ++ /* ++ * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck ++ * non-functional if we change touchpad reporting modes from the HID ++ * open/close hooks. Avoid mode switching on hw_open/hw_close for ++ * those devices. ++ */ ++ struct mt_device *td = hid_get_drvdata(hdev); ++ if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) ++ return; ++ + mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE); + } + +@@ -2574,6 +2599,11 @@ static const struct hid_device_id mt_devices[] = { + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x09c0) }, + ++ /* Microsoft Surface touch pad */ ++ { .driver_data = MT_CLS_SURFACE_TOUCHPAD, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x0C46) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.52.0 + diff --git a/patches/6.18/0001-secureboot.patch b/patches/6.18/0001-secureboot.patch new file mode 100644 index 0000000000..8003e00f07 --- /dev/null +++ b/patches/6.18/0001-secureboot.patch @@ -0,0 +1,112 @@ +From bd4c46d5fcf52346c10baa2f93a8191b3666a7ca Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index 9bea5a1e2..25848f886 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLLCHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.52.0 + +From 19cebf117e481f0227d7122812b03591199828d1 Mon Sep 17 00:00:00 2001 +From: "J. Eduardo" +Date: Sun, 25 Aug 2024 14:17:45 +0200 +Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter + +This allows the user to tell the kernel that they know better (namely, +they secured their swap properly), and that it can enable hibernation. + +Signed-off-by: Kelvie Wong +Link: https://github.com/linux-surface/kernel/pull/158 +Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee +Patchset: secureboot +--- + Documentation/admin-guide/kernel-parameters.txt | 5 +++++ + kernel/power/hibernate.c | 10 +++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index 6c42061ca..f7cb5669b 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -3342,6 +3342,11 @@ + to extract confidential information from the kernel + are also disabled. + ++ lockdown_hibernate [HIBERNATION] ++ Enable hibernation even if lockdown is enabled. Enable this only if ++ your swap is encrypted and secured properly, as an attacker can ++ modify the kernel offline during hibernation. ++ + locktorture.acq_writer_lim= [KNL] + Set the time limit in jiffies for a lock + acquisition. Acquisitions exceeding this limit +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +index 26e45f86b..8ec46d456 100644 +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -38,6 +38,7 @@ + #include "power.h" + + ++static int lockdown_hibernate; + static int nocompress; + static int noresume; + static int nohibernate; +@@ -109,7 +110,7 @@ bool hibernation_in_progress(void) + bool hibernation_available(void) + { + return nohibernate == 0 && +- !security_locked_down(LOCKDOWN_HIBERNATION) && ++ (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && + !secretmem_active() && !cxl_mem_active(); + } + +@@ -1489,6 +1490,12 @@ static int __init nohibernate_setup(char *str) + return 1; + } + ++static int __init lockdown_hibernate_setup(char *str) ++{ ++ lockdown_hibernate = 1; ++ return 1; ++} ++ + static const char * const comp_alg_enabled[] = { + #if IS_ENABLED(CONFIG_CRYPTO_LZO) + COMPRESSION_ALGO_LZO, +@@ -1546,3 +1553,4 @@ __setup("hibernate=", hibernate_setup); + __setup("resumewait", resumewait_setup); + __setup("resumedelay=", resumedelay_setup); + __setup("nohibernate", nohibernate_setup); ++__setup("lockdown_hibernate", lockdown_hibernate_setup); +-- +2.52.0 + diff --git a/patches/6.18/0002-surface3.patch b/patches/6.18/0002-surface3.patch new file mode 100644 index 0000000000..ff49ca4c94 --- /dev/null +++ b/patches/6.18/0002-surface3.patch @@ -0,0 +1,234 @@ +From 69a85d6855669be7e986acef07ccbf8f8bf80448 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3 +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index 6c8fb7a4d..22797a53f 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 29a403526..986f32132 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3792,6 +3792,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index e4c3492a0..0b930c91b 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.52.0 + +From 3d30bbd21b86edc7aee7f04d63f48274d2b18c91 Mon Sep 17 00:00:00 2001 +From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> +Date: Fri, 6 Dec 2019 23:10:30 +0900 +Subject: [PATCH] surface3-spi: workaround: disable DMA mode to avoid crash by + default + +On Arch Linux kernel at least after 4.19, touch input is broken after suspend +(s2idle). + + kern :err : [ +0.203408] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + +On recent stable Arch Linux kernel (at least after 5.1), touch input will +crash after the first touch. + + kern :err : [ +0.203592] Surface3-spi spi-MSHW0037:00: SPI transfer timed out + kern :err : [ +0.000173] spi_master spi1: failed to transfer one message from queue + +I found on an affected system (Arch Linux kernel, etc.), the touchscreen +driver uses DMA mode by default. Then, we found some kernels with different +kernel config (5.1 kernel config from Jakeday [1] or Chromium OS kernel +chromeos-4.19 [2]) will use PIO mode by default and no such issues there. + +So, this commit disables DMA mode on the touchscreen driver side as a quick +workaround to avoid touch input crash. +We may need to properly set up DMA mode to use the touchscreen driver with +DMA mode. + +You can still switch DMA/PIO mode if you want: + + switch to DMA mode (maybe broken) + echo 1 | sudo tee /sys/module/surface3_spi/parameters/use_dma + back to PIO mode + echo 0 | sudo tee /sys/module/surface3_spi/parameters/use_dma + +Link to issue: https://github.com/jakeday/linux-surface/issues/596 + +References: +[1] https://github.com/jakeday/linux-surface/blob/master/configs/5.1/config +[2] https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19 + +Tested on Arch Linux 5.4.1 with Surface 3, which will use DMA by default. +This commit made the driver use PIO by default and no touch input crash. +Also tested on chromeos-4.19 4.19.90 with Surface 3, which will use PIO by default +even without this commit. After this commit, it still uses PIO and confirmed +no functional changes regarding touch input. + +More details: + We can confirm which mode the touchscreen driver uses; first, enable + debug output: + + echo "file drivers/spi/spi-pxa2xx.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + echo "file drivers/input/touchscreen/surface3_spi.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control + + Then, try to make a touch input and see dmesg log + + (On Arch Linux kernel, uses DMA) + kern :debug : [ +0.006383] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, DMA + kern :debug : [ +0.000495] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 18 00 e4 01 00 04 1a 04 1a e3 0c e3 0c b0 00 + c5 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + (On the kernels I referenced above, uses PIO) + kern :debug : [ +0.009260] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, PIO + kern :debug : [ +0.001105] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received + -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 24 00 e4 01 00 58 0b 58 0b 83 12 83 12 26 01 + 95 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +Note (2025-03-08): This patch was originally dropped due to the +comments in [3]. However, according to the commments in [4] it is still +required. + +[3]: https://github.com/linux-surface/kernel/commit/a3421c12bed0e46c28518bcb8c6b22f237c6dc7a +[4]: https://github.com/linux-surface/linux-surface/issues/1184 + +Patchset: surface3 +--- + drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c +index 6074b7730..6aa3e1d6f 100644 +--- a/drivers/input/touchscreen/surface3_spi.c ++++ b/drivers/input/touchscreen/surface3_spi.c +@@ -25,6 +25,12 @@ + #define SURFACE3_REPORT_TOUCH 0xd2 + #define SURFACE3_REPORT_PEN 0x16 + ++bool use_dma = false; ++module_param(use_dma, bool, 0644); ++MODULE_PARM_DESC(use_dma, ++ "Disable DMA mode if you encounter touch input crash. " ++ "(default: false, disabled to avoid crash)"); ++ + struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; +@@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) + return 0; + } + ++static bool surface3_spi_can_dma(struct spi_controller *ctlr, ++ struct spi_device *spi, ++ struct spi_transfer *tfr) ++{ ++ return use_dma; ++} ++ + static int surface3_spi_probe(struct spi_device *spi) + { + struct surface3_ts_data *data; +@@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) + if (error) + return error; + ++ /* ++ * Set up DMA ++ * ++ * TODO: Currently, touch input with DMA seems to be broken. ++ * On 4.19 LTS, touch input will crash after suspend. ++ * On recent stable kernel (at least after 5.1), touch input will crash after ++ * the first touch. No problem with PIO on those kernels. ++ * Maybe we need to configure DMA here. ++ * ++ * Link to issue: https://github.com/jakeday/linux-surface/issues/596 ++ */ ++ spi->controller->can_dma = surface3_spi_can_dma; ++ + return 0; + } + +-- +2.52.0 + diff --git a/patches/6.18/0003-mwifiex.patch b/patches/6.18/0003-mwifiex.patch new file mode 100644 index 0000000000..9ce3eba23d --- /dev/null +++ b/patches/6.18/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 9593c516ab9ffbaf6f3579fa9b4847334a06bedb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index a760de191..db9b20322 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1d..f46b06f8d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964ae..5d30ae39d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.52.0 + +From 9cfa9e79bfab86b0dd006db23b8f4578d3793f79 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index db9b20322..ffd0c1fe9 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d..99b024ecb 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d..c14eb56eb 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.52.0 + +From 8f1c63004d0669c9026ebf2c888a7aec4ee9956f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 3420f711f..50362656f 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -67,6 +67,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) + #define BTUSB_BARROT BIT(28) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(29) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -472,6 +473,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -4192,6 +4194,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.52.0 + diff --git a/patches/6.18/0004-ath10k.patch b/patches/6.18/0004-ath10k.patch new file mode 100644 index 0000000000..f46f25f17a --- /dev/null +++ b/patches/6.18/0004-ath10k.patch @@ -0,0 +1,120 @@ +From 6f78fa89621ae0d37150a56393c837610d4b64de Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 57 ++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index 9ae3595fb..b26a39c97 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -41,6 +41,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -53,6 +56,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -62,6 +68,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -932,6 +941,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -946,6 +991,18 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + if (ar->board_name) { + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, ar->board_name, file); +-- +2.52.0 + diff --git a/patches/6.18/0005-ipts.patch b/patches/6.18/0005-ipts.patch new file mode 100644 index 0000000000..acd5d5440a --- /dev/null +++ b/patches/6.18/0005-ipts.patch @@ -0,0 +1,3243 @@ +From 52589066af6dc055659cc7c36fcafb8dd221d00e Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index fa30899a5..a1864dcb7 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 2a6e56955..c19dfba54 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.52.0 + +From 4d6c4cb07f0486c014733f8f1641cc4c4cb79c6b Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index e236c7ec2..9e31a5fe1 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -39,6 +39,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -209,12 +214,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -1879,6 +1886,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2169,6 +2179,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + /* +@@ -4515,6 +4528,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4553,6 +4578,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.52.0 + +From 947c00f6f25ef885ff9cfeee85f449311d822d1a Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 104 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 145 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 251 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2853 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 04420a713..63b4eda94 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1439,6 +1439,8 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/intel-thc-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 361a7daed..b73a1bbd3 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -176,3 +176,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000..297401bd3 +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000..883896f68 +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000..63a4934bb +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000..2b4079075 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000..ba33259f1 +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000..5360842d2 +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000..26629c514 +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000..307438c7c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000..7b9f54388 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds1.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000..eeeb6575e +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000..639940794 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,145 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "eds2.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000..064e37169 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000..e34a1a4f9 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000..1ebe77447 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000..fb5b5c13e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000..1e0395cea +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000..973bade6b +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000..977724c72 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000..3de7da62d +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000..cc14653b2 +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000..2068e1328 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000..e8dd98895 +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000..41845f9d9 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000..5a58d4a0a +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000..355e92bea +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000..1f966b8b3 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.52.0 + diff --git a/patches/6.18/0006-ithc.patch b/patches/6.18/0006-ithc.patch new file mode 100644 index 0000000000..81e4233e82 --- /dev/null +++ b/patches/6.18/0006-ithc.patch @@ -0,0 +1,2780 @@ +From 35c9b0f076fc055736ab686e2e6b9f4ec73e1439 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 8bcbfe3d9..c78806d35 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -381,6 +381,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.52.0 + +From f1739d1bb079de11599ed4e58cba329c585c8bf5 Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@34539af4726d. + +Signed-off-by: Maximilian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 149 ++++++++ + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 47 +++ + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 438 ++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 ++ + drivers/hid/ithc/ithc-regs.c | 154 ++++++++ + drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ + drivers/hid/ithc/ithc.h | 89 +++++ + 18 files changed, 2575 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 63b4eda94..184a2b2f9 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1441,6 +1441,8 @@ source "drivers/hid/intel-thc-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID + + # USB support may be used with HID disabled +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index b73a1bbd3..3190ece25 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -178,3 +178,4 @@ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000..4937ba131 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000..ede713023 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000..2d8c6afe9 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,149 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 1) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init_device(struct ithc *ithc) ++{ ++ if (!dbg_dir) ++ return -ENOENT; ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000..38c53d916 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000..bf4eab330 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ ithc->max_tx_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) ++{ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000..1749a5819 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); ++ +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000..065646ab4 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000..599eb912c +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000..8883987fb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000..28d692462 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000..094d878d6 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,438 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, ++ // MTL and up are handled by drivers/hid/intel-thc-hid ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); ++ ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); ++ ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Interrupts/polling ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ithc_log_regs(ithc); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 50 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); ++ ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); ++ ithc_disable(ithc); ++ timer_delete_sync(&ithc->idle_timer); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); ++ ++ ithc_set_ltr_idle(ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, ++}; ++ ++static int __init ithc_init(void) ++{ ++ ithc_debug_init_module(); ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000..e2d1690b8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) { ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; ++ } ++ ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000..74d882f6b +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000..c0f13506a +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,154 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) ++{ ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000..4f541fe53 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,211 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; ++ } dma_tx; ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 init_unknown; ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000..aec320d4e +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-hid.h" ++#include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct timer_list idle_timer; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++ +-- +2.52.0 + diff --git a/patches/6.18/0007-surface-sam.patch b/patches/6.18/0007-surface-sam.patch new file mode 100644 index 0000000000..7c58972b3a --- /dev/null +++ b/patches/6.18/0007-surface-sam.patch @@ -0,0 +1,211 @@ +From b5dca285f0fa20a64793c71c541a517a01a53ecf Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 17 Jun 2022 02:14:00 +0200 +Subject: [PATCH] rtc: Add basic support for RTC via Surface System Aggregator + Module + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/rtc/Kconfig | 7 +++ + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-surface.c | 129 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 137 insertions(+) + create mode 100644 drivers/rtc/rtc-surface.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 2933c41c7..050eba74d 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -1399,6 +1399,13 @@ config RTC_DRV_NTXEC + embedded controller found in certain e-book readers designed by the + original design manufacturer Netronix. + ++config RTC_DRV_SURFACE ++ tristate "Microsoft Surface Aggregator RTC" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ TODO ++ + comment "on-CPU RTC drivers" + + config RTC_DRV_ASM9260 +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index 8221bda6e..8932df6e0 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -183,6 +183,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o + obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o + obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o + obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o ++obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o + obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o + obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o + obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o +diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c +new file mode 100644 +index 000000000..f6c17c4e9 +--- /dev/null ++++ b/drivers/rtc/rtc-surface.c +@@ -0,0 +1,129 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * AC driver for 7th-generation Microsoft Surface devices via Surface System ++ * Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct surface_rtc { ++ struct ssam_device *sdev; ++ struct rtc_device *rtc; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x10, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { ++ .target_category = SSAM_SSH_TC_SAM, ++ .target_id = SSAM_SSH_TID_SAM, ++ .instance_id = 0x00, ++ .command_id = 0x0f, ++}); ++ ++static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) ++{ ++ __le32 time_le; ++ int status; ++ ++ status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); ++ if (status) ++ return status; ++ ++ *time = le32_to_cpu(time_le); ++ return 0; ++} ++ ++static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) ++{ ++ __le32 time_le = cpu_to_le32(time); ++ ++ return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); ++} ++ ++static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ int status; ++ u32 time; ++ ++ status = ssam_rtc_get_unix_time(srtc, &time); ++ if (status) ++ return status; ++ ++ rtc_time64_to_tm(time, tm); ++ return 0; ++} ++ ++static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct surface_rtc *srtc = dev_get_drvdata(dev); ++ time64_t time = rtc_tm_to_time64(tm); ++ ++ return ssam_rtc_set_unix_time(srtc, (u32)time); ++} ++ ++static const struct rtc_class_ops surface_rtc_ops = { ++ .read_time = surface_rtc_read_time, ++ .set_time = surface_rtc_set_time, ++}; ++ ++static int surface_rtc_probe(struct ssam_device *sdev) ++{ ++ struct surface_rtc *srtc; ++ ++ srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); ++ if (!srtc) ++ return -ENOMEM; ++ ++ srtc->sdev = sdev; ++ ++ srtc->rtc = devm_rtc_allocate_device(&sdev->dev); ++ if (IS_ERR(srtc->rtc)) ++ return PTR_ERR(srtc->rtc); ++ ++ srtc->rtc->ops = &surface_rtc_ops; ++ srtc->rtc->range_max = U32_MAX; ++ ++ ssam_device_set_drvdata(sdev, srtc); ++ ++ return devm_rtc_register_device(srtc->rtc); ++} ++ ++static void surface_rtc_remove(struct ssam_device *sdev) ++{ ++ /* Device-managed allocations take care of everything... */ ++} ++ ++static const struct ssam_device_id surface_rtc_match[] = { ++ { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_rtc_match); ++ ++static struct ssam_device_driver surface_rtc_driver = { ++ .probe = surface_rtc_probe, ++ .remove = surface_rtc_remove, ++ .match_table = surface_rtc_match, ++ .driver = { ++ .name = "surface_rtc", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_rtc_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.52.0 + +From a06532897c1ea9225e069d750bcdc2959a160f76 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 20 Apr 2025 01:05:14 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add Surface Laptop 7 + (ACPI) + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index a594d5fcf..07b03aa4f 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -460,6 +460,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + ++ /* Surface Laptop 7 */ ++ { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, ++ + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + +-- +2.52.0 + diff --git a/patches/6.18/0008-surface-sam-over-hid.patch b/patches/6.18/0008-surface-sam-over-hid.patch new file mode 100644 index 0000000000..91e29f4ce6 --- /dev/null +++ b/patches/6.18/0008-surface-sam-over-hid.patch @@ -0,0 +1,308 @@ +From 6579116fd37c78498cdd8ec2761dc0253a7cc85c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index ed90858a2..070c36637 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -662,6 +662,27 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -763,6 +784,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.52.0 + +From d12c96869aab1623728e86b5d8f46e92ac2d0234 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 136 ++++++++++++++++++ + 3 files changed, 144 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index f775c6ca1..2075e3852 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 533443309..7efcd0cdb 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000..68db23773 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,136 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ ++static const guid_t dgpu_sw_guid = ++ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *obj; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EINVAL; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(struct device *dev) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ dev_err(dev, "failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ ACPI_FREE(buf.pointer); ++ ++ dev_info(dev, "turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool value; ++ int status; ++ ++ status = kstrtobool(buf, &value); ++ if (status < 0) ++ return status; ++ ++ if (!value) ++ return 0; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(dev); ++ else ++ status = sb1_dgpu_sw_hgof(dev); ++ ++ return status < 0 ? status : len; ++} ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(sb1_dgpu_sw); ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .dev_groups = sb1_dgpu_sw_groups, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.52.0 + diff --git a/patches/6.18/0009-surface-button.patch b/patches/6.18/0009-surface-button.patch new file mode 100644 index 0000000000..8bcd3f5ac2 --- /dev/null +++ b/patches/6.18/0009-surface-button.patch @@ -0,0 +1,149 @@ +From 0e6feff1f9a8b53c04ec9e8d3efb90688b9c047f Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index b8cad415c..43b5d5638 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.52.0 + +From 06dfa01586a862d15cbb633c909cf03c8e6db0b9 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f9..4240c98ca 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.52.0 + diff --git a/patches/6.18/0010-surface-typecover.patch b/patches/6.18/0010-surface-typecover.patch new file mode 100644 index 0000000000..d606c61115 --- /dev/null +++ b/patches/6.18/0010-surface-typecover.patch @@ -0,0 +1,575 @@ +From adbc1603b9afc63dcf78efc80a9ce74e54679f21 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index c4d85089d..3d0d35c72 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.52.0 + +From 2a2cd81bef4cf21419fc6bccb252b252ece03ac4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 179dc316b..7d644e1c4 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -35,7 +35,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -48,6 +51,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + #include "hid-haptic.h" + +@@ -76,12 +80,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -184,6 +191,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -229,6 +238,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 + #define MT_CLS_APPLE_TOUCHBAR 0x0114 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0115 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -425,6 +435,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1842,6 +1862,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1870,6 +1953,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1908,8 +1994,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->name == MT_CLS_APPLE_TOUCHBAR && + !hid_find_field(hdev, HID_INPUT_REPORT, +@@ -1923,8 +2011,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1985,6 +2075,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2429,6 +2520,11 @@ static const struct hid_device_id mt_devices[] = { + HID_USB_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.52.0 + +From 9431420d43fa3cc1e95e4f817b401ebb8da97793 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 7d644e1c4..0eb4f7d12 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -81,6 +81,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -88,6 +89,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -437,6 +440,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1463,6 +1467,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1490,6 +1497,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1516,6 +1538,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1523,6 +1546,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1532,11 +1568,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1719,6 +1765,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1776,6 +1858,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1862,30 +1951,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1894,8 +1959,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -2053,13 +2119,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -2068,12 +2145,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + timer_delete_sync(&td->release_timer); +-- +2.52.0 + diff --git a/patches/6.18/0011-surface-shutdown.patch b/patches/6.18/0011-surface-shutdown.patch new file mode 100644 index 0000000000..dfdd5d394c --- /dev/null +++ b/patches/6.18/0011-surface-shutdown.patch @@ -0,0 +1,140 @@ +From 61db0e44597c45b85a384a87294bec26f52c35d9 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index 327b21c48..51dd5e10f 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index b9c252aa6..ce4c0da99 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6341,3 +6341,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); + #endif ++ ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU +diff --git a/include/linux/pci.h b/include/linux/pci.h +index bf97d49c2..90eead93d 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -487,6 +487,7 @@ struct pci_dev { + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ + unsigned int non_mappable_bars:1; /* BARs can't be mapped to user-space */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.52.0 + +From 13dd4da628d723d312830f7174e5dc8fa6f6385b Mon Sep 17 00:00:00 2001 +From: LegendaryFire +Date: Wed, 7 Jan 2026 01:06:25 +0100 +Subject: [PATCH] PCI: Add Surface Laptop Studio 2 devices to shutdown ops + quirk + +Link: https://github.com/linux-surface/linux-surface/pull/1948 +Patchset: surface-shutdown +--- + drivers/pci/quirks.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index ce4c0da99..fdc410bf7 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6360,6 +6360,13 @@ static const struct dmi_system_id no_shutdown_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop Studio 2", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio 2"), ++ }, ++ }, + {} + }; + +@@ -6377,3 +6384,9 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thu + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU ++ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa76e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa71e, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73e, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa7a0, quirk_no_shutdown); // GPU +-- +2.52.0 + diff --git a/patches/6.18/0012-surface-gpe.patch b/patches/6.18/0012-surface-gpe.patch new file mode 100644 index 0000000000..a4b0e5a39b --- /dev/null +++ b/patches/6.18/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From 3f2c59f5654154a80b1fb978c8fa0b5a52c707b4 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index b35941390..b4496db79 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.52.0 + diff --git a/patches/6.18/0013-cameras.patch b/patches/6.18/0013-cameras.patch new file mode 100644 index 0000000000..d7c2db1c2e --- /dev/null +++ b/patches/6.18/0013-cameras.patch @@ -0,0 +1,1019 @@ +From 543b05525228ed555a763c4207e11f6c9355f32b Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index ef16d58b2..a6bad2679 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2205,6 +2205,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.52.0 + +From a99581089b3d1df6d9982973b2d2c2fc30530024 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 9e31a5fe1..bdd32551c 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -44,6 +44,13 @@ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -215,12 +222,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -1887,6 +1896,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -2179,6 +2191,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4528,6 +4543,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4578,6 +4605,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); +-- +2.52.0 + +From dca54560755ceecbed780d61e7935f4f9b73b01f Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 013340569..9e0763bdc 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.52.0 + +From df08cd915853c25601d57c3eba20de1fc66142fd Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 27afc3fc0..28b192c9a 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1053,7 +1053,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1579,7 +1579,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.52.0 + +From 388ea20ca3dc1d2341689908e685a892aa3ddc68 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index ee884a822..4f6bafd90 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index cb153ce42..f11b499e1 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1260,10 +1260,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.52.0 + +From 8b8cb7995f3524b791b99d73c0c07d7985db13e1 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 9e0763bdc..0976b2679 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -203,7 +203,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.52.0 + +From e8b69207500fc57cd884611791e7bd902be0d6a3 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329..2d2abb25b 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.52.0 + +From 78df6291c8680c90b325e3d3936614ed766b537a Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 06e6291be..8f94924ef 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -1002,6 +1002,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 9a0333ec1..4e1ca8fc9 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -93,6 +93,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o + obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o + obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o + obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o + obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o + obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000..35aeb5db8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.52.0 + +From 13f44051bfd17396abc876b53c732e15951160a2 Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index 032fbcb98..e03a1d8cd 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -87,6 +87,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719, bool detect) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_SHUTDOWN, &ret); + fsleep(100); +-- +2.52.0 + +From 228f2989668592d6fd7dbaf9afcb45a621f06489 Mon Sep 17 00:00:00 2001 +From: Tooraj Taraz +Date: Wed, 31 Dec 2025 00:35:50 +0100 +Subject: [PATCH] Add camera support for Surface Pro 9 + +Experimental camera support for the Surface Pro 9. + +Link: https://github.com/linux-surface/linux-surface/pull/1867 +Patchset: cameras +--- + drivers/media/i2c/ov13858.c | 158 +++++++++++++++++- + drivers/media/i2c/ov5693.c | 1 + + drivers/media/pci/intel/ipu-bridge.c | 4 + + drivers/platform/x86/intel/int3472/discrete.c | 23 +++ + 4 files changed, 184 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c +index 162b49046..85cd0c98a 100644 +--- a/drivers/media/i2c/ov13858.c ++++ b/drivers/media/i2c/ov13858.c +@@ -2,6 +2,7 @@ + // Copyright (c) 2017 Intel Corporation. + + #include ++#include + #include + #include + #include +@@ -119,6 +120,14 @@ struct ov13858_mode { + struct ov13858_reg_list reg_list; + }; + ++// Or use standard names that might be more correct: ++static const char * const ov13858_supply_names[] = { ++ "avdd", // Analog voltage ++ "pwr1", // Digital core voltage ++}; ++ ++#define OV13858_NUM_SUPPLIES ARRAY_SIZE(ov13858_supply_names) ++ + /* 4224x3136 needs 1080Mbps/lane, 4 lanes */ + static const struct ov13858_reg mipi_data_rate_1080mbps[] = { + /* PLL1 registers */ +@@ -1046,12 +1055,126 @@ struct ov13858 { + /* Current mode */ + const struct ov13858_mode *cur_mode; + ++ struct regulator_bulk_data supplies[OV13858_NUM_SUPPLIES]; ++ struct gpio_desc *reset; ++ struct clk *xvclk; ++ + /* Mutex for serialized access */ + struct mutex mutex; + }; + + #define to_ov13858(_sd) container_of(_sd, struct ov13858, sd) + ++ ++static int ov13858_get_regulators(struct ov13858 *ov13858) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < OV13858_NUM_SUPPLIES; i++) ++ ov13858->supplies[i].supply = ov13858_supply_names[i]; ++ ++ return devm_regulator_bulk_get(ov13858->dev, OV13858_NUM_SUPPLIES, ++ ov13858->supplies); ++} ++ ++static int ov13858_sensor_powerup(struct ov13858 *ov13858) ++{ ++ int ret; ++ ++ dev_info(ov13858->dev, "Powering up sensor\n"); ++ ++ /* Assert reset */ ++ gpiod_set_value_cansleep(ov13858->reset, 1); ++ ++ /* Enable clock FIRST - sensor needs clock to communicate */ ++ ret = clk_prepare_enable(ov13858->xvclk); ++ if (ret) { ++ dev_err(ov13858->dev, "Failed to enable clock: %d\n", ret); ++ return ret; ++ } ++ dev_info(ov13858->dev, "Clock enabled\n"); ++ ++ /* Enable regulators */ ++ ret = regulator_bulk_enable(OV13858_NUM_SUPPLIES, ov13858->supplies); ++ if (ret) { ++ dev_err(ov13858->dev, "Failed to enable regulators: %d\n", ret); ++ clk_disable_unprepare(ov13858->xvclk); ++ return ret; ++ } ++ ++ dev_info(ov13858->dev, "Regulators enabled\n"); ++ ++ /* Wait for power to stabilize */ ++ usleep_range(5000, 10000); ++ ++ /* De-assert reset */ ++ gpiod_set_value_cansleep(ov13858->reset, 0); ++ ++ /* Wait for sensor to boot */ ++ msleep(20); ++ ++ dev_info(ov13858->dev, "Reset de-asserted, sensor should be ready\n"); ++ ++ return 0; ++} ++ ++static void ov13858_sensor_powerdown(struct ov13858 *ov13858) ++{ ++ dev_info(ov13858->dev, "Powering down sensor\n"); ++ ++ /* Assert reset to put sensor in reset state */ ++ gpiod_set_value_cansleep(ov13858->reset, 1); ++ ++ /* Disable regulators */ ++ regulator_bulk_disable(OV13858_NUM_SUPPLIES, ov13858->supplies); ++ dev_info(ov13858->dev, "Regulators disabled\n"); ++ ++ /* Disable clock */ ++ clk_disable_unprepare(ov13858->xvclk); ++ dev_info(ov13858->dev, "Clock disabled\n"); ++} ++ ++static int __maybe_unused ov13858_suspend(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov13858 *ov13858 = to_ov13858(sd); ++ ++ ov13858_sensor_powerdown(ov13858); ++ ++ return 0; ++} ++ ++static int __maybe_unused ov13858_resume(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov13858 *ov13858 = to_ov13858(sd); ++ int ret; ++ ++ ret = ov13858_sensor_powerup(ov13858); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops ov13858_pm_ops = { ++ SET_RUNTIME_PM_OPS(ov13858_suspend, ov13858_resume, NULL) ++}; ++ ++static int ov13858_get_gpios(struct ov13858 *ov13858) ++{ ++ ov13858->reset = devm_gpiod_get_optional(ov13858->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(ov13858->reset)) { ++ dev_err(ov13858->dev, "Error fetching reset GPIO\n"); ++ return PTR_ERR(ov13858->reset); ++ } ++ ++ return 0; ++} ++ + /* Read registers up to 4 at a time */ + static int ov13858_read_reg(struct ov13858 *ov13858, u16 reg, u32 len, + u32 *val) +@@ -1498,8 +1621,11 @@ static int ov13858_identify_module(struct ov13858 *ov13858) + int ret; + u32 val; + ++ dev_info(ov13858->dev, "Attempting to read chip ID from register 0x300a\n"); + ret = ov13858_read_reg(ov13858, OV13858_REG_CHIP_ID, + OV13858_REG_VALUE_24BIT, &val); ++ dev_info(ov13858->dev, "Chip ID read result: ret=%d, val=0x%06x (expected 0x%06x)\n", ++ ret, val, OV13858_CHIP_ID); + if (ret) + return ret; + +@@ -1509,6 +1635,7 @@ static int ov13858_identify_module(struct ov13858 *ov13858) + return -EIO; + } + ++ dev_info(ov13858->dev, "Chip ID verified successfully!\n"); + return 0; + } + +@@ -1678,14 +1805,33 @@ static int ov13858_probe(struct i2c_client *client) + "external clock %lu is not supported\n", + freq); + ++ /* Get the external clock */ ++ ov13858->xvclk = devm_clk_get_optional(ov13858->dev, "xvclk"); ++ if (IS_ERR(ov13858->xvclk)) ++ return dev_err_probe(ov13858->dev, PTR_ERR(ov13858->xvclk), ++ "failed to get xvclk\n"); ++ + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov13858->sd, client, &ov13858_subdev_ops); + ++ ret = ov13858_get_regulators(ov13858); ++ if (ret) ++ return dev_err_probe(ov13858->dev, ret, ++ "Error fetching regulators\n"); ++ ++ ret = ov13858_get_gpios(ov13858); ++ if (ret) ++ return ret; ++ ++ ret = ov13858_sensor_powerup(ov13858); ++ if (ret) ++ return ret; // No cleanup needed yet, devm handles GPIOs/regulators ++ + /* Check module identity */ + ret = ov13858_identify_module(ov13858); + if (ret) { + dev_err(ov13858->dev, "failed to find sensor: %d\n", ret); +- return ret; ++ goto error_power_off; // Now we need to power down + } + + /* Set default mode to max resolution */ +@@ -1693,7 +1839,7 @@ static int ov13858_probe(struct i2c_client *client) + + ret = ov13858_init_controls(ov13858); + if (ret) +- return ret; ++ goto error_power_off; + + /* Initialize subdev */ + ov13858->sd.internal_ops = &ov13858_internal_ops; +@@ -1729,6 +1875,10 @@ static int ov13858_probe(struct i2c_client *client) + + error_handler_free: + ov13858_free_controls(ov13858); ++ ++error_power_off: ++ ov13858_sensor_powerdown(ov13858); ++ + dev_err(ov13858->dev, "%s failed:%d\n", __func__, ret); + + return ret; +@@ -1744,6 +1894,9 @@ static void ov13858_remove(struct i2c_client *client) + ov13858_free_controls(ov13858); + + pm_runtime_disable(ov13858->dev); ++ if (!pm_runtime_status_suspended(ov13858->dev)) ++ ov13858_sensor_powerdown(ov13858); ++ pm_runtime_set_suspended(ov13858->dev); + } + + static const struct i2c_device_id ov13858_id_table[] = { +@@ -1765,6 +1918,7 @@ MODULE_DEVICE_TABLE(acpi, ov13858_acpi_ids); + static struct i2c_driver ov13858_i2c_driver = { + .driver = { + .name = "ov13858", ++ .pm = &ov13858_pm_ops, + .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), + }, + .probe = ov13858_probe, +diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c +index d294477f9..46bc6c5b7 100644 +--- a/drivers/media/i2c/ov5693.c ++++ b/drivers/media/i2c/ov5693.c +@@ -1396,6 +1396,7 @@ static const struct dev_pm_ops ov5693_pm_ops = { + + static const struct acpi_device_id ov5693_acpi_match[] = { + {"INT33BE"}, ++ {"OVTI5693"}, + {}, + }; + MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +index 4e579352a..ac637207b 100644 +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -60,6 +60,10 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), + /* Onsemi MT9M114 */ + IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), ++ /* Omnivision OV5693 - Surface Pro 9 */ ++ IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), ++ /* Omnivision OV13858 - Surface Pro 9 */ ++ IPU_SENSOR_CONFIG("OVTID858", 4, 540000000), + /* Omnivision OV2740 */ + IPU_SENSOR_CONFIG("INT3474", 1, 180000000), + /* Omnivision OV5670 */ +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index 1505fc3ef..20962e8ac 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -229,6 +229,14 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 + /* Setups using a handshake pin need 25 ms enable delay */ + *enable_time_us = 25 * USEC_PER_MSEC; + break; ++ case 0x08: /* Surface Pro 9 - additional power rail */ ++ *con_id = "pwr1"; ++ *gpio_flags = GPIO_ACTIVE_HIGH; ++ break; ++ case 0x10: /* Surface Pro 9 - secondary power rail */ ++ *con_id = "pwr2"; ++ *gpio_flags = GPIO_ACTIVE_HIGH; ++ break; + default: + *con_id = "unknown"; + *gpio_flags = GPIO_ACTIVE_HIGH; +@@ -333,6 +341,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + case INT3472_GPIO_TYPE_PRIVACY_LED: + case INT3472_GPIO_TYPE_POWER_ENABLE: + case INT3472_GPIO_TYPE_HANDSHAKE: ++ case 0x08: /* Surface Pro 9 power rails */ ++ case 0x10: + gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); + if (IS_ERR(gpio)) { + ret = PTR_ERR(gpio); +@@ -363,6 +373,19 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + err_msg = "Failed to register regulator\n"; + + break; ++ case 0x08: /* Surface Pro 9 - treat as power*/ ++ dev_info(int3472->dev, "GPIO type 0x%02x detected on pin 0x%02x\n", type, agpio->pin_table[0]); ++ dev_info(int3472->dev, " con_id=%s, flags=0x%x\n", con_id, gpio_flags); ++ ret = skl_int3472_register_regulator(int3472, gpio, ++ GPIO_REGULATOR_ENABLE_TIME, ++ con_id, NULL); ++ dev_info(int3472->dev, " register_regulator returned: %d\n", ret); ++ if (ret) { ++ dev_err(int3472->dev, "Failed to register type 0x02x: %d\n", type, ret); ++ } ++ break; ++ case 0x10: ++ break; + default: /* Never reached */ + ret = -EINVAL; + break; +-- +2.52.0 + diff --git a/patches/6.18/0014-amd-gpio.patch b/patches/6.18/0014-amd-gpio.patch new file mode 100644 index 0000000000..bdaeb038c9 --- /dev/null +++ b/patches/6.18/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From 7de9c326ae3b13ac1c7d2fa69da742786e938acb Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 9fa321a95..8914a922b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + +@@ -1171,6 +1172,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1227,6 +1239,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.52.0 + +From c4785acab63b4f29bbbaf171d7bfd52aa21a2e40 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 8914a922b..c43d0a553 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1174,12 +1174,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.52.0 + diff --git a/patches/6.18/0015-rtc.patch b/patches/6.18/0015-rtc.patch new file mode 100644 index 0000000000..e68f87f345 --- /dev/null +++ b/patches/6.18/0015-rtc.patch @@ -0,0 +1,110 @@ +From 4f883a2192301f67ce808158aba2af39522c00ae Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 33418dd67..f94bbdc1d 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -433,6 +433,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -481,15 +489,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -568,13 +575,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + if (dd->capabilities & ACPI_TAD_RT) + sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -616,12 +628,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -652,6 +658,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.52.0 + diff --git a/patches/6.18/0016-hid-surface.patch b/patches/6.18/0016-hid-surface.patch new file mode 100644 index 0000000000..6d233d9b80 --- /dev/null +++ b/patches/6.18/0016-hid-surface.patch @@ -0,0 +1,246 @@ +From f201c243ea7f7a34d3c0743168b51b4cfa45d388 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ciar=C3=A1n=20Coffey?= +Date: Fri, 10 Oct 2025 19:04:03 +0100 +Subject: [PATCH] HID: Add hid-surface driver to filter BTN_0 (FN key) + +The Surface Aggregator Module firmware incorrectly reports the FN key +as BTN_0 (button 256) through runtime HID events. This button can get +stuck in pressed state, flooding the input system and breaking focus +tracking in KWin Wayland compositor. + +Add a new hid-surface driver that filters BTN_0 events using the event +callback. The FN key should be handled as a hardware modifier and not +reported to the OS. + +Tested on Surface Laptop 4 AMD with KDE Plasma Wayland. After the fix: +- FN key combinations work correctly (volume, brightness) +- Focus tracking no longer breaks +- Mouse clicks work on all windows + +Fixes: https://github.com/linux-surface/linux-surface/issues/1851 +Patchset: hid-surface +--- + drivers/hid/Kconfig | 8 ++++ + drivers/hid/Makefile | 1 + + drivers/hid/hid-surface.c | 90 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 99 insertions(+) + create mode 100644 drivers/hid/hid-surface.c + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 184a2b2f9..d2e308b25 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1141,6 +1141,14 @@ config HID_SUNPLUS + help + Support for Sunplus wireless desktop. + ++config HID_SURFACE ++ tristate "Microsoft Surface" ++ depends on SURFACE_AGGREGATOR ++ help ++ Say Y here to enable HID driver for Microsoft Surface integrated ++ keyboard and touchpad. This driver filters out erroneous BTN_0 ++ (FN key) events that can cause input focus issues. ++ + config HID_RMI + tristate "Synaptics RMI4 device support" + select RMI4_CORE +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 3190ece25..4d2891879 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -174,6 +174,7 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++obj-$(CONFIG_HID_SURFACE) += hid-surface.o + + obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ + +diff --git a/drivers/hid/hid-surface.c b/drivers/hid/hid-surface.c +new file mode 100644 +index 000000000..a171ea656 +--- /dev/null ++++ b/drivers/hid/hid-surface.c +@@ -0,0 +1,90 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * HID driver for Microsoft Surface devices ++ * ++ * Copyright (c) 2025 Linux Surface Project ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "hid-ids.h" ++ ++/* ++ * The Surface Aggregator Module firmware incorrectly reports the FN key ++ * as BTN_0 (button 256). This button can get stuck in pressed state, ++ * flooding the input system and breaking focus tracking in some ++ * compositors. Filter out BTN_0 as FN should be handled as a hardware ++ * modifier, not reported to the OS. ++ */ ++static int surface_input_mapping(struct hid_device *hdev, struct hid_input *hi, ++ struct hid_field *field, struct hid_usage *usage, ++ unsigned long **bit, int *max) ++{ ++ /* ++ * Filter BTN_0 during input mapping in case it appears in the ++ * HID descriptor (defense in depth). ++ */ ++ if (usage->type == EV_KEY && usage->code == BTN_0) ++ return -1; /* Don't map this usage */ ++ ++ return 0; /* Use default mapping */ ++} ++ ++static int surface_event(struct hid_device *hdev, struct hid_field *field, ++ struct hid_usage *usage, __s32 value) ++{ ++ /* ++ * The Surface Aggregator Module firmware reports the FN key as BTN_0 ++ * at runtime. This button can get stuck in pressed state, flooding ++ * the input system and breaking focus tracking. Filter out these ++ * events as FN should be a hardware modifier, not reported to the OS. ++ */ ++ if (usage->type == EV_KEY && usage->code == BTN_0) ++ return 1; /* Event handled, don't process further */ ++ ++ return 0; /* Process event normally */ ++} ++ ++static int surface_probe(struct hid_device *hdev, const struct hid_device_id *id) ++{ ++ int ret; ++ ++ ret = hid_parse(hdev); ++ if (ret) { ++ hid_err(hdev, "parse failed\n"); ++ return ret; ++ } ++ ++ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); ++ if (ret) { ++ hid_err(hdev, "hw start failed\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct hid_device_id surface_devices[] = { ++ { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, ++ USB_VENDOR_ID_MICROSOFT, 0x09AE) }, /* Surface Keyboard */ ++ { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, ++ USB_VENDOR_ID_MICROSOFT, 0x09AF) }, /* Surface Mouse/Touchpad */ ++ { } ++}; ++MODULE_DEVICE_TABLE(hid, surface_devices); ++ ++static struct hid_driver surface_driver = { ++ .name = "surface", ++ .id_table = surface_devices, ++ .probe = surface_probe, ++ .input_mapping = surface_input_mapping, ++ .event = surface_event, ++}; ++module_hid_driver(surface_driver); ++ ++MODULE_AUTHOR("Linux Surface Project"); ++MODULE_DESCRIPTION("Microsoft Surface HID driver"); ++MODULE_LICENSE("GPL"); +-- +2.52.0 + +From ed2a9266036762ab98a801ee5bd0cb04442d4618 Mon Sep 17 00:00:00 2001 +From: LegendaryFire +Date: Wed, 24 Dec 2025 00:54:44 -0800 +Subject: [PATCH] hid/multitouch: Prevent mode set on Surface Laptop Studio 2 + touch pad + +Patchset: hid-surface +--- + drivers/hid/hid-multitouch.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 0eb4f7d12..8f2ed29b4 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -82,6 +82,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) + #define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) ++#define MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE BIT(26) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -242,6 +243,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_SMART_TECH 0x0113 + #define MT_CLS_APPLE_TOUCHBAR 0x0114 + #define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0115 ++#define MT_CLS_SURFACE_TOUCHPAD 0x0116 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -449,6 +451,10 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_WIN8_PTP_BUTTONS, + .export_all_inputs = true + }, ++ { .name = MT_CLS_SURFACE_TOUCHPAD, ++ .quirks = MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE ++ }, + { } + }; + +@@ -2180,11 +2186,30 @@ static void mt_remove(struct hid_device *hdev) + + static void mt_on_hid_hw_open(struct hid_device *hdev) + { ++ /* ++ * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck ++ * non-functional if we change touchpad reporting modes from the HID ++ * open/close hooks. Avoid mode switching on hw_open/hw_close for ++ * those devices. ++ */ ++ struct mt_device *td = hid_get_drvdata(hdev); ++ if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) ++ return; + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + } + + static void mt_on_hid_hw_close(struct hid_device *hdev) + { ++ /* ++ * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck ++ * non-functional if we change touchpad reporting modes from the HID ++ * open/close hooks. Avoid mode switching on hw_open/hw_close for ++ * those devices. ++ */ ++ struct mt_device *td = hid_get_drvdata(hdev); ++ if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) ++ return; ++ + mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE); + } + +@@ -2621,6 +2646,11 @@ static const struct hid_device_id mt_devices[] = { + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x09c0) }, + ++ /* Microsoft Surface touch pad */ ++ { .driver_data = MT_CLS_SURFACE_TOUCHPAD, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x0C46) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.52.0 + diff --git a/patches/6.18/0017-powercap.patch b/patches/6.18/0017-powercap.patch new file mode 100644 index 0000000000..27c77fd511 --- /dev/null +++ b/patches/6.18/0017-powercap.patch @@ -0,0 +1,30 @@ +From 465b1c71a2c4bad54e9b84e02de93322df88d202 Mon Sep 17 00:00:00 2001 +From: Daniel Tang +Date: Wed, 14 Jan 2026 20:31:38 -0500 +Subject: [PATCH] powercap: intel_rapl: Add PL4 support for Ice Lake + +Microsoft Surface Pro 7 firmware throttles the processor upon +boot/resume. Userspace needs to be able to restore the correct value. + +Link: https://github.com/linux-surface/linux-surface/issues/706 +Signed-off-by: Daniel Tang +Patchset: powercap +--- + drivers/powercap/intel_rapl_msr.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c +index c6b9a7deb..e82724768 100644 +--- a/drivers/powercap/intel_rapl_msr.c ++++ b/drivers/powercap/intel_rapl_msr.c +@@ -140,6 +140,7 @@ static int rapl_msr_write_raw(int cpu, struct reg_action *ra) + + /* List of verified CPUs. */ + static const struct x86_cpu_id pl4_support_ids[] = { ++ X86_MATCH_VFM(INTEL_ICELAKE_L, NULL), + X86_MATCH_VFM(INTEL_TIGERLAKE_L, NULL), + X86_MATCH_VFM(INTEL_ALDERLAKE, NULL), + X86_MATCH_VFM(INTEL_ALDERLAKE_L, NULL), +-- +2.52.0 + diff --git a/patches/6.6/0010-surface-shutdown.patch b/patches/6.6/0010-surface-shutdown.patch index 7c0e9b2ba7..1d403c0550 100644 --- a/patches/6.6/0010-surface-shutdown.patch +++ b/patches/6.6/0010-surface-shutdown.patch @@ -1,7 +1,7 @@ From b57c1784927f0ef467efdee4cb2a026d7eea3c1c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 19 Feb 2023 22:12:24 +0100 -Subject: [PATCH] PCI: Add quirk to prevent calling shutdown mehtod +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method Work around buggy EFI firmware: On some Microsoft Surface devices (Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with diff --git a/patches/6.8/0005-ithc.patch b/patches/6.8/0005-ithc.patch deleted file mode 100644 index 38aaadbce2..0000000000 --- a/patches/6.8/0005-ithc.patch +++ /dev/null @@ -1,1820 +0,0 @@ -From 619b488e58367467f52d636b5182ff2134df68c0 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Sun, 11 Dec 2022 12:03:38 +0100 -Subject: [PATCH] iommu: intel: Disable source id verification for ITHC - -Signed-off-by: Dorian Stoll -Patchset: ithc ---- - drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ - 1 file changed, 16 insertions(+) - -diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c -index 566297bc87dd..a8cd8f12d593 100644 ---- a/drivers/iommu/intel/irq_remapping.c -+++ b/drivers/iommu/intel/irq_remapping.c -@@ -386,6 +386,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) - data.busmatch_count = 0; - pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); - -+ /* -+ * The Intel Touch Host Controller is at 00:10.6, but for some reason -+ * the MSI interrupts have request id 01:05.0. -+ * Disable id verification to work around this. -+ * FIXME Find proper fix or turn this into a quirk. -+ */ -+ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { -+ switch(dev->device) { -+ case 0x98d0: case 0x98d1: // LKF -+ case 0xa0d0: case 0xa0d1: // TGL LP -+ case 0x43d0: case 0x43d1: // TGL H -+ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); -+ return 0; -+ } -+ } -+ - /* - * DMA alias provides us with a PCI device and alias. The only case - * where the it will return an alias on a different bus than the --- -2.44.0 - -From ee5106b4069dd150a323dce8bce7879d2b27ed0b Mon Sep 17 00:00:00 2001 -From: quo -Date: Sun, 11 Dec 2022 12:10:54 +0100 -Subject: [PATCH] hid: Add support for Intel Touch Host Controller - -Based on quo/ithc-linux@0b8b45d - -Signed-off-by: Dorian Stoll -Patchset: ithc ---- - drivers/hid/Kconfig | 2 + - drivers/hid/Makefile | 1 + - drivers/hid/ithc/Kbuild | 6 + - drivers/hid/ithc/Kconfig | 12 + - drivers/hid/ithc/ithc-debug.c | 130 ++++++ - drivers/hid/ithc/ithc-dma.c | 373 +++++++++++++++++ - drivers/hid/ithc/ithc-dma.h | 69 ++++ - drivers/hid/ithc/ithc-main.c | 728 ++++++++++++++++++++++++++++++++++ - drivers/hid/ithc/ithc-regs.c | 96 +++++ - drivers/hid/ithc/ithc-regs.h | 189 +++++++++ - drivers/hid/ithc/ithc.h | 67 ++++ - 11 files changed, 1673 insertions(+) - create mode 100644 drivers/hid/ithc/Kbuild - create mode 100644 drivers/hid/ithc/Kconfig - create mode 100644 drivers/hid/ithc/ithc-debug.c - create mode 100644 drivers/hid/ithc/ithc-dma.c - create mode 100644 drivers/hid/ithc/ithc-dma.h - create mode 100644 drivers/hid/ithc/ithc-main.c - create mode 100644 drivers/hid/ithc/ithc-regs.c - create mode 100644 drivers/hid/ithc/ithc-regs.h - create mode 100644 drivers/hid/ithc/ithc.h - -diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig -index a263e49b2ae2..03f0f5af289a 100644 ---- a/drivers/hid/Kconfig -+++ b/drivers/hid/Kconfig -@@ -1353,4 +1353,6 @@ source "drivers/hid/surface-hid/Kconfig" - - source "drivers/hid/ipts/Kconfig" - -+source "drivers/hid/ithc/Kconfig" -+ - endif # HID_SUPPORT -diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile -index f4bad1b8d813..d32c194400ae 100644 ---- a/drivers/hid/Makefile -+++ b/drivers/hid/Makefile -@@ -172,3 +172,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ - obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ - - obj-$(CONFIG_HID_IPTS) += ipts/ -+obj-$(CONFIG_HID_ITHC) += ithc/ -diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild -new file mode 100644 -index 000000000000..aea83f2ac07b ---- /dev/null -+++ b/drivers/hid/ithc/Kbuild -@@ -0,0 +1,6 @@ -+obj-$(CONFIG_HID_ITHC) := ithc.o -+ -+ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-debug.o -+ -+ccflags-y := -std=gnu11 -Wno-declaration-after-statement -+ -diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig -new file mode 100644 -index 000000000000..ede713023609 ---- /dev/null -+++ b/drivers/hid/ithc/Kconfig -@@ -0,0 +1,12 @@ -+config HID_ITHC -+ tristate "Intel Touch Host Controller" -+ depends on PCI -+ depends on HID -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Touch Host Controller (ITHC / IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ithc. -diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c -new file mode 100644 -index 000000000000..1f1f1e33f2e5 ---- /dev/null -+++ b/drivers/hid/ithc/ithc-debug.c -@@ -0,0 +1,130 @@ -+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause -+ -+#include "ithc.h" -+ -+void ithc_log_regs(struct ithc *ithc) -+{ -+ if (!ithc->prev_regs) -+ return; -+ u32 __iomem *cur = (__iomem void *)ithc->regs; -+ u32 *prev = (void *)ithc->prev_regs; -+ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { -+ u32 x = readl(cur + i); -+ if (x != prev[i]) { -+ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); -+ prev[i] = x; -+ } -+ } -+} -+ -+static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, -+ loff_t *offset) -+{ -+ // Debug commands consist of a single letter followed by a list of numbers (decimal or -+ // hexadecimal, space-separated). -+ struct ithc *ithc = file_inode(f)->i_private; -+ char cmd[256]; -+ if (!ithc || !ithc->pci) -+ return -ENODEV; -+ if (!len) -+ return -EINVAL; -+ if (len >= sizeof(cmd)) -+ return -EINVAL; -+ if (copy_from_user(cmd, buf, len)) -+ return -EFAULT; -+ cmd[len] = 0; -+ if (cmd[len-1] == '\n') -+ cmd[len-1] = 0; -+ pci_info(ithc->pci, "debug command: %s\n", cmd); -+ -+ // Parse the list of arguments into a u32 array. -+ u32 n = 0; -+ const char *s = cmd + 1; -+ u32 a[32]; -+ while (*s && *s != '\n') { -+ if (n >= ARRAY_SIZE(a)) -+ return -EINVAL; -+ if (*s++ != ' ') -+ return -EINVAL; -+ char *e; -+ a[n++] = simple_strtoul(s, &e, 0); -+ if (e == s) -+ return -EINVAL; -+ s = e; -+ } -+ ithc_log_regs(ithc); -+ -+ // Execute the command. -+ switch (cmd[0]) { -+ case 'x': // reset -+ ithc_reset(ithc); -+ break; -+ case 'w': // write register: offset mask value -+ if (n != 3 || (a[0] & 3)) -+ return -EINVAL; -+ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", -+ a[0], a[2], a[1]); -+ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); -+ break; -+ case 'r': // read register: offset -+ if (n != 1 || (a[0] & 3)) -+ return -EINVAL; -+ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], -+ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); -+ break; -+ case 's': // spi command: cmd offset len data... -+ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -+ // set touch cfg: s 6 12 4 XX -+ if (n < 3 || a[2] > (n - 3) * 4) -+ return -EINVAL; -+ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); -+ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) -+ for (u32 i = 0; i < (a[2] + 3) / 4; i++) -+ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); -+ break; -+ case 'd': // dma command: cmd len data... -+ // get report descriptor: d 7 8 0 0 -+ // enable multitouch: d 3 2 0x0105 -+ if (n < 2 || a[1] > (n - 2) * 4) -+ return -EINVAL; -+ pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]); -+ if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) -+ pci_err(ithc->pci, "dma tx failed\n"); -+ break; -+ default: -+ return -EINVAL; -+ } -+ ithc_log_regs(ithc); -+ return len; -+} -+ -+static const struct file_operations ithc_debugfops_cmd = { -+ .owner = THIS_MODULE, -+ .write = ithc_debugfs_cmd_write, -+}; -+ -+static void ithc_debugfs_devres_release(struct device *dev, void *res) -+{ -+ struct dentry **dbgm = res; -+ if (*dbgm) -+ debugfs_remove_recursive(*dbgm); -+} -+ -+int ithc_debug_init(struct ithc *ithc) -+{ -+ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); -+ if (!dbgm) -+ return -ENOMEM; -+ devres_add(&ithc->pci->dev, dbgm); -+ struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); -+ if (IS_ERR(dbg)) -+ return PTR_ERR(dbg); -+ *dbgm = dbg; -+ -+ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); -+ if (IS_ERR(cmd)) -+ return PTR_ERR(cmd); -+ -+ return 0; -+} -+ -diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c -new file mode 100644 -index 000000000000..ffb8689b8a78 ---- /dev/null -+++ b/drivers/hid/ithc/ithc-dma.c -@@ -0,0 +1,373 @@ -+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause -+ -+#include "ithc.h" -+ -+// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. -+// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. -+// This allows each data buffer to consist of multiple non-contiguous blocks of memory. -+ -+static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, -+ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) -+{ -+ p->num_pages = num_pages; -+ p->dir = dir; -+ // We allocate enough space to have one PRD per data buffer page, however if the data -+ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so -+ // some will remain unused (which is fine). -+ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); -+ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); -+ if (!p->addr) -+ return -ENOMEM; -+ if (p->dma_addr & (PAGE_SIZE - 1)) -+ return -EFAULT; -+ return 0; -+} -+ -+// Devres managed sg_table wrapper. -+struct ithc_sg_table { -+ void *addr; -+ struct sg_table sgt; -+ enum dma_data_direction dir; -+}; -+static void ithc_dma_sgtable_free(struct sg_table *sgt) -+{ -+ struct scatterlist *sg; -+ int i; -+ for_each_sgtable_sg(sgt, sg, i) { -+ struct page *p = sg_page(sg); -+ if (p) -+ __free_page(p); -+ } -+ sg_free_table(sgt); -+} -+static void ithc_dma_data_devres_release(struct device *dev, void *res) -+{ -+ struct ithc_sg_table *sgt = res; -+ if (sgt->addr) -+ vunmap(sgt->addr); -+ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); -+ ithc_dma_sgtable_free(&sgt->sgt); -+} -+ -+static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, -+ struct ithc_dma_data_buffer *b) -+{ -+ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be -+ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). -+ // We could use dma_alloc_noncontiguous(), however this still always allocates a single -+ // DMA mapped segment, which is more restrictive than what we need. -+ // Instead we use an sg_table of individually allocated pages. -+ struct page *pages[16]; -+ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) -+ return -EINVAL; -+ b->active_idx = -1; -+ struct ithc_sg_table *sgt = devres_alloc( -+ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); -+ if (!sgt) -+ return -ENOMEM; -+ sgt->dir = prds->dir; -+ -+ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { -+ struct scatterlist *sg; -+ int i; -+ bool ok = true; -+ for_each_sgtable_sg(&sgt->sgt, sg, i) { -+ // NOTE: don't need __GFP_DMA for PCI DMA -+ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); -+ if (!p) { -+ ok = false; -+ break; -+ } -+ sg_set_page(sg, p, PAGE_SIZE, 0); -+ } -+ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { -+ devres_add(&ithc->pci->dev, sgt); -+ b->sgt = &sgt->sgt; -+ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); -+ if (!b->addr) -+ return -ENOMEM; -+ return 0; -+ } -+ ithc_dma_sgtable_free(&sgt->sgt); -+ } -+ devres_free(sgt); -+ return -ENOMEM; -+} -+ -+static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, -+ struct ithc_dma_data_buffer *b, unsigned int idx) -+{ -+ // Give a buffer to the THC. -+ struct ithc_phys_region_desc *prd = prds->addr; -+ prd += idx * prds->num_pages; -+ if (b->active_idx >= 0) { -+ pci_err(ithc->pci, "buffer already active\n"); -+ return -EINVAL; -+ } -+ b->active_idx = idx; -+ if (prds->dir == DMA_TO_DEVICE) { -+ // TX buffer: Caller should have already filled the data buffer, so just fill -+ // the PRD and flush. -+ // (TODO: Support multi-page TX buffers. So far no device seems to use or need -+ // these though.) -+ if (b->data_size > PAGE_SIZE) -+ return -EINVAL; -+ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; -+ prd->size = b->data_size | PRD_FLAG_END; -+ flush_kernel_vmap_range(b->addr, b->data_size); -+ } else if (prds->dir == DMA_FROM_DEVICE) { -+ // RX buffer: Reset PRDs. -+ struct scatterlist *sg; -+ int i; -+ for_each_sgtable_dma_sg(b->sgt, sg, i) { -+ prd->addr = sg_dma_address(sg) >> 10; -+ prd->size = sg_dma_len(sg); -+ prd++; -+ } -+ prd[-1].size |= PRD_FLAG_END; -+ } -+ dma_wmb(); // for the prds -+ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); -+ return 0; -+} -+ -+static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, -+ struct ithc_dma_data_buffer *b, unsigned int idx) -+{ -+ // Take a buffer from the THC. -+ struct ithc_phys_region_desc *prd = prds->addr; -+ prd += idx * prds->num_pages; -+ // This is purely a sanity check. We don't strictly need the idx parameter for this -+ // function, because it should always be the same as active_idx, unless we have a bug. -+ if (b->active_idx != idx) { -+ pci_err(ithc->pci, "wrong buffer index\n"); -+ return -EINVAL; -+ } -+ b->active_idx = -1; -+ if (prds->dir == DMA_FROM_DEVICE) { -+ // RX buffer: Calculate actual received data size from PRDs. -+ dma_rmb(); // for the prds -+ b->data_size = 0; -+ struct scatterlist *sg; -+ int i; -+ for_each_sgtable_dma_sg(b->sgt, sg, i) { -+ unsigned int size = prd->size; -+ b->data_size += size & PRD_SIZE_MASK; -+ if (size & PRD_FLAG_END) -+ break; -+ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { -+ pci_err(ithc->pci, "truncated prd\n"); -+ break; -+ } -+ prd++; -+ } -+ invalidate_kernel_vmap_range(b->addr, b->data_size); -+ } -+ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); -+ return 0; -+} -+ -+int ithc_dma_rx_init(struct ithc *ithc, u8 channel) -+{ -+ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; -+ mutex_init(&rx->mutex); -+ -+ // Allocate buffers. -+ u32 buf_size = DEVCFG_DMA_RX_SIZE(ithc->config.dma_buf_sizes); -+ unsigned int num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; -+ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", -+ NUM_RX_BUF, buf_size, num_pages); -+ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); -+ for (unsigned int i = 0; i < NUM_RX_BUF; i++) -+ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); -+ -+ // Init registers. -+ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); -+ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); -+ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); -+ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); -+ u8 head = readb(&ithc->regs->dma_rx[channel].head); -+ if (head) { -+ pci_err(ithc->pci, "head is nonzero (%u)\n", head); -+ return -EIO; -+ } -+ -+ // Init buffers. -+ for (unsigned int i = 0; i < NUM_RX_BUF; i++) -+ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); -+ -+ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); -+ return 0; -+} -+ -+void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) -+{ -+ bitsb_set(&ithc->regs->dma_rx[channel].control, -+ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); -+ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, -+ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); -+} -+ -+int ithc_dma_tx_init(struct ithc *ithc) -+{ -+ struct ithc_dma_tx *tx = &ithc->dma_tx; -+ mutex_init(&tx->mutex); -+ -+ // Allocate buffers. -+ tx->max_size = DEVCFG_DMA_TX_SIZE(ithc->config.dma_buf_sizes); -+ unsigned int num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; -+ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", -+ tx->max_size, num_pages); -+ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); -+ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); -+ -+ // Init registers. -+ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); -+ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); -+ -+ // Init buffers. -+ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); -+ return 0; -+} -+ -+static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, -+ u8 channel, u8 buf) -+{ -+ if (buf >= NUM_RX_BUF) { -+ pci_err(ithc->pci, "invalid dma ringbuffer index\n"); -+ return -EINVAL; -+ } -+ u32 len = data->data_size; -+ struct ithc_dma_rx_header *hdr = data->addr; -+ u8 *hiddata = (void *)(hdr + 1); -+ if (len >= sizeof(*hdr) && hdr->code == DMA_RX_CODE_RESET) { -+ // The THC sends a reset request when we need to reinitialize the device. -+ // This usually only happens if we send an invalid command or put the device -+ // in a bad state. -+ CHECK(ithc_reset, ithc); -+ } else if (len < sizeof(*hdr) || len != sizeof(*hdr) + hdr->data_size) { -+ if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { -+ // When the CPU enters a low power state during DMA, we can get truncated -+ // messages. For Surface devices, this will typically be a single touch -+ // report that is only 1 byte, or a multitouch report that is 257 bytes. -+ // See also ithc_set_active(). -+ } else { -+ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", -+ channel, buf, len, hdr->code, hdr->data_size); -+ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, -+ hdr, min(len, 0x400u), 0); -+ } -+ } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { -+ // Response to a 'get report descriptor' request. -+ // The actual descriptor is preceded by 8 nul bytes. -+ CHECK(hid_parse_report, ithc->hid, hiddata + 8, hdr->data_size - 8); -+ WRITE_ONCE(ithc->hid_parse_done, true); -+ wake_up(&ithc->wait_hid_parse); -+ } else if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { -+ // Standard HID input report containing touch data. -+ CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); -+ } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { -+ // Response to a 'get feature' request. -+ bool done = false; -+ mutex_lock(&ithc->hid_get_feature_mutex); -+ if (ithc->hid_get_feature_buf) { -+ if (hdr->data_size < ithc->hid_get_feature_size) -+ ithc->hid_get_feature_size = hdr->data_size; -+ memcpy(ithc->hid_get_feature_buf, hiddata, ithc->hid_get_feature_size); -+ ithc->hid_get_feature_buf = NULL; -+ done = true; -+ } -+ mutex_unlock(&ithc->hid_get_feature_mutex); -+ if (done) { -+ wake_up(&ithc->wait_hid_get_feature); -+ } else { -+ // Received data without a matching request, or the request already -+ // timed out. (XXX What's the correct thing to do here?) -+ CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, -+ hiddata, hdr->data_size, 1); -+ } -+ } else { -+ pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", -+ channel, buf, len, hdr->code); -+ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, -+ hdr, min(len, 0x400u), 0); -+ } -+ return 0; -+} -+ -+static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) -+{ -+ // Process all filled RX buffers from the ringbuffer. -+ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; -+ unsigned int n = rx->num_received; -+ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); -+ while (1) { -+ u8 tail = n % NUM_RX_BUF; -+ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); -+ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); -+ // ringbuffer is full if tail_wrap == head_wrap -+ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG -+ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) -+ return 0; -+ -+ // take the buffer that the device just filled -+ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; -+ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); -+ rx->num_received = ++n; -+ -+ // process data -+ CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); -+ -+ // give the buffer back to the device -+ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); -+ } -+} -+int ithc_dma_rx(struct ithc *ithc, u8 channel) -+{ -+ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; -+ mutex_lock(&rx->mutex); -+ int ret = ithc_dma_rx_unlocked(ithc, channel); -+ mutex_unlock(&rx->mutex); -+ return ret; -+} -+ -+static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) -+{ -+ ithc_set_active(ithc, 100 * USEC_PER_MSEC); -+ -+ // Send a single TX buffer to the THC. -+ pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); -+ struct ithc_dma_tx_header *hdr; -+ // Data must be padded to next 4-byte boundary. -+ u8 padding = datasize & 3 ? 4 - (datasize & 3) : 0; -+ unsigned int fullsize = sizeof(*hdr) + datasize + padding; -+ if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) -+ return -EINVAL; -+ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); -+ -+ // Fill the TX buffer with header and data. -+ ithc->dma_tx.buf.data_size = fullsize; -+ hdr = ithc->dma_tx.buf.addr; -+ hdr->code = cmdcode; -+ hdr->data_size = datasize; -+ u8 *dest = (void *)(hdr + 1); -+ memcpy(dest, data, datasize); -+ dest += datasize; -+ for (u8 p = 0; p < padding; p++) -+ *dest++ = 0; -+ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); -+ -+ // Let the THC process the buffer. -+ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); -+ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); -+ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); -+ return 0; -+} -+int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) -+{ -+ mutex_lock(&ithc->dma_tx.mutex); -+ int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); -+ mutex_unlock(&ithc->dma_tx.mutex); -+ return ret; -+} -+ -diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h -new file mode 100644 -index 000000000000..93652e4476bf ---- /dev/null -+++ b/drivers/hid/ithc/ithc-dma.h -@@ -0,0 +1,69 @@ -+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ -+ -+#define PRD_SIZE_MASK 0xffffff -+#define PRD_FLAG_END 0x1000000 -+#define PRD_FLAG_SUCCESS 0x2000000 -+#define PRD_FLAG_ERROR 0x4000000 -+ -+struct ithc_phys_region_desc { -+ u64 addr; // physical addr/1024 -+ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds -+ u32 unused; -+}; -+ -+#define DMA_RX_CODE_INPUT_REPORT 3 -+#define DMA_RX_CODE_FEATURE_REPORT 4 -+#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 -+#define DMA_RX_CODE_RESET 7 -+ -+struct ithc_dma_rx_header { -+ u32 code; -+ u32 data_size; -+ u32 _unknown[14]; -+}; -+ -+#define DMA_TX_CODE_SET_FEATURE 3 -+#define DMA_TX_CODE_GET_FEATURE 4 -+#define DMA_TX_CODE_OUTPUT_REPORT 5 -+#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 -+ -+struct ithc_dma_tx_header { -+ u32 code; -+ u32 data_size; -+}; -+ -+struct ithc_dma_prd_buffer { -+ void *addr; -+ dma_addr_t dma_addr; -+ u32 size; -+ u32 num_pages; // per data buffer -+ enum dma_data_direction dir; -+}; -+ -+struct ithc_dma_data_buffer { -+ void *addr; -+ struct sg_table *sgt; -+ int active_idx; -+ u32 data_size; -+}; -+ -+struct ithc_dma_tx { -+ struct mutex mutex; -+ u32 max_size; -+ struct ithc_dma_prd_buffer prds; -+ struct ithc_dma_data_buffer buf; -+}; -+ -+struct ithc_dma_rx { -+ struct mutex mutex; -+ u32 num_received; -+ struct ithc_dma_prd_buffer prds; -+ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; -+}; -+ -+int ithc_dma_rx_init(struct ithc *ithc, u8 channel); -+void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); -+int ithc_dma_tx_init(struct ithc *ithc); -+int ithc_dma_rx(struct ithc *ithc, u8 channel); -+int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); -+ -diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c -new file mode 100644 -index 000000000000..87ed4aa70fda ---- /dev/null -+++ b/drivers/hid/ithc/ithc-main.c -@@ -0,0 +1,728 @@ -+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause -+ -+#include "ithc.h" -+ -+MODULE_DESCRIPTION("Intel Touch Host Controller driver"); -+MODULE_LICENSE("Dual BSD/GPL"); -+ -+// Lakefield -+#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 -+#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 -+// Tiger Lake -+#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 -+#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 -+#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 -+#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 -+// Alder Lake -+#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 -+#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 -+#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 -+#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 -+#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 -+#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 -+// Raptor Lake -+#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 -+#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 -+// Meteor Lake -+#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 -+#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a -+ -+static const struct pci_device_id ithc_pci_tbl[] = { -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT1) }, -+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT2) }, -+ // XXX So far the THC seems to be the only Intel PCI device with PCI_CLASS_INPUT_PEN, -+ // so instead of the device list we could just do: -+ // { .vendor = PCI_VENDOR_ID_INTEL, .device = PCI_ANY_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = PCI_CLASS_INPUT_PEN, .class_mask = ~0, }, -+ {} -+}; -+MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); -+ -+// Module parameters -+ -+static bool ithc_use_polling = false; -+module_param_named(poll, ithc_use_polling, bool, 0); -+MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); -+ -+// Since all known devices seem to use only channel 1, by default we disable channel 0. -+static bool ithc_use_rx0 = false; -+module_param_named(rx0, ithc_use_rx0, bool, 0); -+MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); -+ -+static bool ithc_use_rx1 = true; -+module_param_named(rx1, ithc_use_rx1, bool, 0); -+MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); -+ -+// Values below 250 seem to work well on the SP7+. If this is set too high, you may observe cursor stuttering. -+static int ithc_dma_latency_us = 200; -+module_param_named(dma_latency_us, ithc_dma_latency_us, int, 0); -+MODULE_PARM_DESC(dma_latency_us, "Determines the CPU latency QoS value for DMA transfers (in microseconds), -1 to disable latency QoS"); -+ -+// Values above 1700 seem to work well on the SP7+. If this is set too low, you may observe cursor stuttering. -+static unsigned int ithc_dma_early_us = 2000; -+module_param_named(dma_early_us, ithc_dma_early_us, uint, 0); -+MODULE_PARM_DESC(dma_early_us, "Determines how early the CPU latency QoS value is applied before the next expected IRQ (in microseconds)"); -+ -+static bool ithc_log_regs_enabled = false; -+module_param_named(logregs, ithc_log_regs_enabled, bool, 0); -+MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); -+ -+// Sysfs attributes -+ -+static bool ithc_is_config_valid(struct ithc *ithc) -+{ -+ return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; -+} -+ -+static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ithc *ithc = dev_get_drvdata(dev); -+ if (!ithc || !ithc_is_config_valid(ithc)) -+ return -ENODEV; -+ return sprintf(buf, "0x%04x", ithc->config.vendor_id); -+} -+static DEVICE_ATTR_RO(vendor); -+static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ithc *ithc = dev_get_drvdata(dev); -+ if (!ithc || !ithc_is_config_valid(ithc)) -+ return -ENODEV; -+ return sprintf(buf, "0x%04x", ithc->config.product_id); -+} -+static DEVICE_ATTR_RO(product); -+static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ithc *ithc = dev_get_drvdata(dev); -+ if (!ithc || !ithc_is_config_valid(ithc)) -+ return -ENODEV; -+ return sprintf(buf, "%u", ithc->config.revision); -+} -+static DEVICE_ATTR_RO(revision); -+static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ithc *ithc = dev_get_drvdata(dev); -+ if (!ithc || !ithc_is_config_valid(ithc)) -+ return -ENODEV; -+ u32 v = ithc->config.fw_version; -+ return sprintf(buf, "%i.%i.%i.%i", v >> 24, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff); -+} -+static DEVICE_ATTR_RO(fw_version); -+ -+static const struct attribute_group *ithc_attribute_groups[] = { -+ &(const struct attribute_group){ -+ .name = DEVNAME, -+ .attrs = (struct attribute *[]){ -+ &dev_attr_vendor.attr, -+ &dev_attr_product.attr, -+ &dev_attr_revision.attr, -+ &dev_attr_fw_version.attr, -+ NULL -+ }, -+ }, -+ NULL -+}; -+ -+// HID setup -+ -+static int ithc_hid_start(struct hid_device *hdev) { return 0; } -+static void ithc_hid_stop(struct hid_device *hdev) { } -+static int ithc_hid_open(struct hid_device *hdev) { return 0; } -+static void ithc_hid_close(struct hid_device *hdev) { } -+ -+static int ithc_hid_parse(struct hid_device *hdev) -+{ -+ struct ithc *ithc = hdev->driver_data; -+ u64 val = 0; -+ WRITE_ONCE(ithc->hid_parse_done, false); -+ for (int retries = 0; ; retries++) { -+ CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof(val), &val); -+ if (wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), -+ msecs_to_jiffies(200))) -+ return 0; -+ if (retries > 5) { -+ pci_err(ithc->pci, "failed to read report descriptor\n"); -+ return -ETIMEDOUT; -+ } -+ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); -+ } -+} -+ -+static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, -+ size_t len, unsigned char rtype, int reqtype) -+{ -+ struct ithc *ithc = hdev->driver_data; -+ if (!buf || !len) -+ return -EINVAL; -+ u32 code; -+ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { -+ code = DMA_TX_CODE_OUTPUT_REPORT; -+ } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { -+ code = DMA_TX_CODE_SET_FEATURE; -+ } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { -+ code = DMA_TX_CODE_GET_FEATURE; -+ } else { -+ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", -+ rtype, reqtype, reportnum); -+ return -EINVAL; -+ } -+ buf[0] = reportnum; -+ -+ if (reqtype == HID_REQ_GET_REPORT) { -+ // Prepare for response. -+ mutex_lock(&ithc->hid_get_feature_mutex); -+ ithc->hid_get_feature_buf = buf; -+ ithc->hid_get_feature_size = len; -+ mutex_unlock(&ithc->hid_get_feature_mutex); -+ -+ // Transmit 'get feature' request. -+ int r = CHECK(ithc_dma_tx, ithc, code, 1, buf); -+ if (!r) { -+ r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, -+ !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); -+ if (!r) -+ r = -ETIMEDOUT; -+ else if (r < 0) -+ r = -EINTR; -+ else -+ r = 0; -+ } -+ -+ // If everything went ok, the buffer has been filled with the response data. -+ // Return the response size. -+ mutex_lock(&ithc->hid_get_feature_mutex); -+ ithc->hid_get_feature_buf = NULL; -+ if (!r) -+ r = ithc->hid_get_feature_size; -+ mutex_unlock(&ithc->hid_get_feature_mutex); -+ return r; -+ } -+ -+ // 'Set feature', or 'output report'. These don't have a response. -+ CHECK_RET(ithc_dma_tx, ithc, code, len, buf); -+ return 0; -+} -+ -+static struct hid_ll_driver ithc_ll_driver = { -+ .start = ithc_hid_start, -+ .stop = ithc_hid_stop, -+ .open = ithc_hid_open, -+ .close = ithc_hid_close, -+ .parse = ithc_hid_parse, -+ .raw_request = ithc_hid_raw_request, -+}; -+ -+static void ithc_hid_devres_release(struct device *dev, void *res) -+{ -+ struct hid_device **hidm = res; -+ if (*hidm) -+ hid_destroy_device(*hidm); -+} -+ -+static int ithc_hid_init(struct ithc *ithc) -+{ -+ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); -+ if (!hidm) -+ return -ENOMEM; -+ devres_add(&ithc->pci->dev, hidm); -+ struct hid_device *hid = hid_allocate_device(); -+ if (IS_ERR(hid)) -+ return PTR_ERR(hid); -+ *hidm = hid; -+ -+ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); -+ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); -+ hid->ll_driver = &ithc_ll_driver; -+ hid->bus = BUS_PCI; -+ hid->vendor = ithc->config.vendor_id; -+ hid->product = ithc->config.product_id; -+ hid->version = 0x100; -+ hid->dev.parent = &ithc->pci->dev; -+ hid->driver_data = ithc; -+ -+ ithc->hid = hid; -+ return 0; -+} -+ -+// Interrupts/polling -+ -+static enum hrtimer_restart ithc_activity_start_timer_callback(struct hrtimer *t) -+{ -+ struct ithc *ithc = container_of(t, struct ithc, activity_start_timer); -+ ithc_set_active(ithc, ithc_dma_early_us * 2 + USEC_PER_MSEC); -+ return HRTIMER_NORESTART; -+} -+ -+static enum hrtimer_restart ithc_activity_end_timer_callback(struct hrtimer *t) -+{ -+ struct ithc *ithc = container_of(t, struct ithc, activity_end_timer); -+ cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); -+ return HRTIMER_NORESTART; -+} -+ -+void ithc_set_active(struct ithc *ithc, unsigned int duration_us) -+{ -+ if (ithc_dma_latency_us < 0) -+ return; -+ // When CPU usage is very low, the CPU can enter various low power states (C2-C10). -+ // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_RX_TIMEOUT will be -+ // set when this happens. The amount of truncated messages can become very high, resulting -+ // in user-visible effects (laggy/stuttering cursor). To avoid this, we use a CPU latency -+ // QoS request to prevent the CPU from entering low power states during touch interactions. -+ cpu_latency_qos_update_request(&ithc->activity_qos, ithc_dma_latency_us); -+ hrtimer_start_range_ns(&ithc->activity_end_timer, -+ ns_to_ktime(duration_us * NSEC_PER_USEC), duration_us * NSEC_PER_USEC, HRTIMER_MODE_REL); -+} -+ -+static int ithc_set_device_enabled(struct ithc *ithc, bool enable) -+{ -+ u32 x = ithc->config.touch_cfg = -+ (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 | -+ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); -+ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, -+ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); -+} -+ -+static void ithc_disable_interrupts(struct ithc *ithc) -+{ -+ writel(0, &ithc->regs->error_control); -+ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); -+ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); -+ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); -+ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); -+} -+ -+static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) -+{ -+ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, -+ &ithc->regs->dma_rx[channel].status); -+} -+ -+static void ithc_clear_interrupts(struct ithc *ithc) -+{ -+ writel(0xffffffff, &ithc->regs->error_flags); -+ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); -+ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); -+ ithc_clear_dma_rx_interrupts(ithc, 0); -+ ithc_clear_dma_rx_interrupts(ithc, 1); -+ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, -+ &ithc->regs->dma_tx.status); -+} -+ -+static void ithc_process(struct ithc *ithc) -+{ -+ ithc_log_regs(ithc); -+ -+ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; -+ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; -+ -+ // Track time between DMA rx transfers, so we can try to predict when we need to enable CPU latency QoS for the next transfer -+ ktime_t t = ktime_get(); -+ ktime_t dt = ktime_sub(t, ithc->last_rx_time); -+ if (rx0 || rx1) { -+ ithc->last_rx_time = t; -+ if (dt > ms_to_ktime(100)) { -+ ithc->cur_rx_seq_count = 0; -+ ithc->cur_rx_seq_errors = 0; -+ } -+ ithc->cur_rx_seq_count++; -+ if (!ithc_use_polling && ithc_dma_latency_us >= 0) { -+ // Disable QoS, since the DMA transfer has completed (we re-enable it after a delay below) -+ cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); -+ hrtimer_try_to_cancel(&ithc->activity_end_timer); -+ } -+ } -+ -+ // Read and clear error bits -+ u32 err = readl(&ithc->regs->error_flags); -+ if (err) { -+ writel(err, &ithc->regs->error_flags); -+ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) -+ pci_err(ithc->pci, "error flags: 0x%08x\n", err); -+ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) { -+ // Only log an error if we see a significant number of these errors. -+ ithc->cur_rx_seq_errors++; -+ if (ithc->cur_rx_seq_errors && ithc->cur_rx_seq_errors % 50 == 0 && ithc->cur_rx_seq_errors > ithc->cur_rx_seq_count / 10) -+ pci_err(ithc->pci, "High number of DMA RX timeouts/errors (%u/%u, dt=%lldus). Try adjusting dma_early_us and/or dma_latency_us.\n", -+ ithc->cur_rx_seq_errors, ithc->cur_rx_seq_count, ktime_to_us(dt)); -+ } -+ } -+ -+ // Process DMA rx -+ if (ithc_use_rx0) { -+ ithc_clear_dma_rx_interrupts(ithc, 0); -+ if (rx0) -+ ithc_dma_rx(ithc, 0); -+ } -+ if (ithc_use_rx1) { -+ ithc_clear_dma_rx_interrupts(ithc, 1); -+ if (rx1) -+ ithc_dma_rx(ithc, 1); -+ } -+ -+ // Start timer to re-enable QoS for next rx, but only if we've seen an ERROR_FLAG_DMA_RX_TIMEOUT -+ if ((rx0 || rx1) && !ithc_use_polling && ithc_dma_latency_us >= 0 && ithc->cur_rx_seq_errors > 0) { -+ ktime_t expires = ktime_add(t, ktime_sub_us(dt, ithc_dma_early_us)); -+ hrtimer_start_range_ns(&ithc->activity_start_timer, expires, 10 * NSEC_PER_USEC, HRTIMER_MODE_ABS); -+ } -+ -+ ithc_log_regs(ithc); -+} -+ -+static irqreturn_t ithc_interrupt_thread(int irq, void *arg) -+{ -+ struct ithc *ithc = arg; -+ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", -+ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), -+ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), -+ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), -+ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), -+ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); -+ ithc_process(ithc); -+ return IRQ_HANDLED; -+} -+ -+static int ithc_poll_thread(void *arg) -+{ -+ struct ithc *ithc = arg; -+ unsigned int sleep = 100; -+ while (!kthread_should_stop()) { -+ u32 n = ithc->dma_rx[1].num_received; -+ ithc_process(ithc); -+ // Decrease polling interval to 20ms if we received data, otherwise slowly -+ // increase it up to 200ms. -+ if (n != ithc->dma_rx[1].num_received) { -+ ithc_set_active(ithc, 100 * USEC_PER_MSEC); -+ sleep = 20; -+ } else { -+ sleep = min(200u, sleep + (sleep >> 4) + 1); -+ } -+ msleep_interruptible(sleep); -+ } -+ return 0; -+} -+ -+// Device initialization and shutdown -+ -+static void ithc_disable(struct ithc *ithc) -+{ -+ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); -+ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); -+ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); -+ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); -+ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); -+ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); -+ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); -+ ithc_disable_interrupts(ithc); -+ ithc_clear_interrupts(ithc); -+} -+ -+static int ithc_init_device(struct ithc *ithc) -+{ -+ ithc_log_regs(ithc); -+ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; -+ ithc_disable(ithc); -+ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); -+ -+ // Since we don't yet know which SPI config the device wants, use default speed and mode -+ // initially for reading config data. -+ ithc_set_spi_config(ithc, 10, 0); -+ -+ // Setting the following bit seems to make reading the config more reliable. -+ bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); -+ -+ // If the device was previously enabled, wait a bit to make sure it's fully shut down. -+ if (was_enabled) -+ if (msleep_interruptible(100)) -+ return -EINTR; -+ -+ // Take the touch device out of reset. -+ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); -+ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); -+ for (int retries = 0; ; retries++) { -+ ithc_log_regs(ithc); -+ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); -+ if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) -+ break; -+ if (retries > 5) { -+ pci_err(ithc->pci, "failed to reset device, state = 0x%08x\n", readl(&ithc->regs->state)); -+ return -ETIMEDOUT; -+ } -+ pci_warn(ithc->pci, "invalid state, retrying reset\n"); -+ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); -+ if (msleep_interruptible(1000)) -+ return -EINTR; -+ } -+ ithc_log_regs(ithc); -+ -+ // Waiting for the following status bit makes reading config much more reliable, -+ // however the official driver does not seem to do this... -+ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); -+ -+ // Read configuration data. -+ for (int retries = 0; ; retries++) { -+ ithc_log_regs(ithc); -+ memset(&ithc->config, 0, sizeof(ithc->config)); -+ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(ithc->config), &ithc->config); -+ u32 *p = (void *)&ithc->config; -+ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", -+ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); -+ if (ithc_is_config_valid(ithc)) -+ break; -+ if (retries > 10) { -+ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", -+ ithc->config.device_id); -+ return -EIO; -+ } -+ pci_warn(ithc->pci, "failed to read config, retrying\n"); -+ if (msleep_interruptible(100)) -+ return -EINTR; -+ } -+ ithc_log_regs(ithc); -+ -+ // Apply SPI config and enable touch device. -+ CHECK_RET(ithc_set_spi_config, ithc, -+ DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), -+ DEVCFG_SPI_MODE(ithc->config.spi_config)); -+ CHECK_RET(ithc_set_device_enabled, ithc, true); -+ ithc_log_regs(ithc); -+ return 0; -+} -+ -+int ithc_reset(struct ithc *ithc) -+{ -+ // FIXME This should probably do devres_release_group()+ithc_start(). -+ // But because this is called during DMA processing, that would have to be done -+ // asynchronously (schedule_work()?). And with extra locking? -+ pci_err(ithc->pci, "reset\n"); -+ CHECK(ithc_init_device, ithc); -+ if (ithc_use_rx0) -+ ithc_dma_rx_enable(ithc, 0); -+ if (ithc_use_rx1) -+ ithc_dma_rx_enable(ithc, 1); -+ ithc_log_regs(ithc); -+ pci_dbg(ithc->pci, "reset completed\n"); -+ return 0; -+} -+ -+static void ithc_stop(void *res) -+{ -+ struct ithc *ithc = res; -+ pci_dbg(ithc->pci, "stopping\n"); -+ ithc_log_regs(ithc); -+ -+ if (ithc->poll_thread) -+ CHECK(kthread_stop, ithc->poll_thread); -+ if (ithc->irq >= 0) -+ disable_irq(ithc->irq); -+ CHECK(ithc_set_device_enabled, ithc, false); -+ ithc_disable(ithc); -+ hrtimer_cancel(&ithc->activity_start_timer); -+ hrtimer_cancel(&ithc->activity_end_timer); -+ cpu_latency_qos_remove_request(&ithc->activity_qos); -+ -+ // Clear DMA config. -+ for (unsigned int i = 0; i < 2; i++) { -+ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); -+ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); -+ writeb(0, &ithc->regs->dma_rx[i].num_bufs); -+ writeb(0, &ithc->regs->dma_rx[i].num_prds); -+ } -+ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); -+ writeb(0, &ithc->regs->dma_tx.num_prds); -+ -+ ithc_log_regs(ithc); -+ pci_dbg(ithc->pci, "stopped\n"); -+} -+ -+static void ithc_clear_drvdata(void *res) -+{ -+ struct pci_dev *pci = res; -+ pci_set_drvdata(pci, NULL); -+} -+ -+static int ithc_start(struct pci_dev *pci) -+{ -+ pci_dbg(pci, "starting\n"); -+ if (pci_get_drvdata(pci)) { -+ pci_err(pci, "device already initialized\n"); -+ return -EINVAL; -+ } -+ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) -+ return -ENOMEM; -+ -+ // Allocate/init main driver struct. -+ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); -+ if (!ithc) -+ return -ENOMEM; -+ ithc->irq = -1; -+ ithc->pci = pci; -+ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); -+ init_waitqueue_head(&ithc->wait_hid_parse); -+ init_waitqueue_head(&ithc->wait_hid_get_feature); -+ mutex_init(&ithc->hid_get_feature_mutex); -+ pci_set_drvdata(pci, ithc); -+ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); -+ if (ithc_log_regs_enabled) -+ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); -+ -+ // PCI initialization. -+ CHECK_RET(pcim_enable_device, pci); -+ pci_set_master(pci); -+ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); -+ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); -+ CHECK_RET(pci_set_power_state, pci, PCI_D0); -+ ithc->regs = pcim_iomap_table(pci)[0]; -+ -+ // Allocate IRQ. -+ if (!ithc_use_polling) { -+ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); -+ ithc->irq = CHECK(pci_irq_vector, pci, 0); -+ if (ithc->irq < 0) -+ return ithc->irq; -+ } -+ -+ // Initialize THC and touch device. -+ CHECK_RET(ithc_init_device, ithc); -+ CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); -+ if (ithc_use_rx0) -+ CHECK_RET(ithc_dma_rx_init, ithc, 0); -+ if (ithc_use_rx1) -+ CHECK_RET(ithc_dma_rx_init, ithc, 1); -+ CHECK_RET(ithc_dma_tx_init, ithc); -+ -+ cpu_latency_qos_add_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); -+ hrtimer_init(&ithc->activity_start_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); -+ ithc->activity_start_timer.function = ithc_activity_start_timer_callback; -+ hrtimer_init(&ithc->activity_end_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); -+ ithc->activity_end_timer.function = ithc_activity_end_timer_callback; -+ -+ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are -+ // disabled BEFORE the buffers are freed. -+ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); -+ -+ CHECK_RET(ithc_hid_init, ithc); -+ -+ // Start polling/IRQ. -+ if (ithc_use_polling) { -+ pci_info(pci, "using polling instead of irq\n"); -+ // Use a thread instead of simple timer because we want to be able to sleep. -+ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); -+ if (IS_ERR(ithc->poll_thread)) { -+ int err = PTR_ERR(ithc->poll_thread); -+ ithc->poll_thread = NULL; -+ return err; -+ } -+ } else { -+ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, -+ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); -+ } -+ -+ if (ithc_use_rx0) -+ ithc_dma_rx_enable(ithc, 0); -+ if (ithc_use_rx1) -+ ithc_dma_rx_enable(ithc, 1); -+ -+ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, -+ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. -+ CHECK_RET(hid_add_device, ithc->hid); -+ -+ CHECK(ithc_debug_init, ithc); -+ -+ pci_dbg(pci, "started\n"); -+ return 0; -+} -+ -+static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) -+{ -+ pci_dbg(pci, "device probe\n"); -+ return ithc_start(pci); -+} -+ -+static void ithc_remove(struct pci_dev *pci) -+{ -+ pci_dbg(pci, "device remove\n"); -+ // all cleanup is handled by devres -+} -+ -+// For suspend/resume, we just deinitialize and reinitialize everything. -+// TODO It might be cleaner to keep the HID device around, however we would then have to signal -+// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set -+// feature' requests. Hidraw does not seem to have a facility to do that. -+static int ithc_suspend(struct device *dev) -+{ -+ struct pci_dev *pci = to_pci_dev(dev); -+ pci_dbg(pci, "pm suspend\n"); -+ devres_release_group(dev, ithc_start); -+ return 0; -+} -+ -+static int ithc_resume(struct device *dev) -+{ -+ struct pci_dev *pci = to_pci_dev(dev); -+ pci_dbg(pci, "pm resume\n"); -+ return ithc_start(pci); -+} -+ -+static int ithc_freeze(struct device *dev) -+{ -+ struct pci_dev *pci = to_pci_dev(dev); -+ pci_dbg(pci, "pm freeze\n"); -+ devres_release_group(dev, ithc_start); -+ return 0; -+} -+ -+static int ithc_thaw(struct device *dev) -+{ -+ struct pci_dev *pci = to_pci_dev(dev); -+ pci_dbg(pci, "pm thaw\n"); -+ return ithc_start(pci); -+} -+ -+static int ithc_restore(struct device *dev) -+{ -+ struct pci_dev *pci = to_pci_dev(dev); -+ pci_dbg(pci, "pm restore\n"); -+ return ithc_start(pci); -+} -+ -+static struct pci_driver ithc_driver = { -+ .name = DEVNAME, -+ .id_table = ithc_pci_tbl, -+ .probe = ithc_probe, -+ .remove = ithc_remove, -+ .driver.pm = &(const struct dev_pm_ops) { -+ .suspend = ithc_suspend, -+ .resume = ithc_resume, -+ .freeze = ithc_freeze, -+ .thaw = ithc_thaw, -+ .restore = ithc_restore, -+ }, -+ //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway -+}; -+ -+static int __init ithc_init(void) -+{ -+ return pci_register_driver(&ithc_driver); -+} -+ -+static void __exit ithc_exit(void) -+{ -+ pci_unregister_driver(&ithc_driver); -+} -+ -+module_init(ithc_init); -+module_exit(ithc_exit); -+ -diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c -new file mode 100644 -index 000000000000..e058721886e3 ---- /dev/null -+++ b/drivers/hid/ithc/ithc-regs.c -@@ -0,0 +1,96 @@ -+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause -+ -+#include "ithc.h" -+ -+#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) -+ -+void bitsl(__iomem u32 *reg, u32 mask, u32 val) -+{ -+ if (val & ~mask) -+ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", -+ reg_num(reg), val, mask); -+ writel((readl(reg) & ~mask) | (val & mask), reg); -+} -+ -+void bitsb(__iomem u8 *reg, u8 mask, u8 val) -+{ -+ if (val & ~mask) -+ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", -+ reg_num(reg), val, mask); -+ writeb((readb(reg) & ~mask) | (val & mask), reg); -+} -+ -+int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) -+{ -+ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", -+ reg_num(reg), mask, val); -+ u32 x; -+ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { -+ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", -+ reg_num(reg), mask, val); -+ return -ETIMEDOUT; -+ } -+ pci_dbg(ithc->pci, "done waiting\n"); -+ return 0; -+} -+ -+int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) -+{ -+ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", -+ reg_num(reg), mask, val); -+ u8 x; -+ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { -+ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", -+ reg_num(reg), mask, val); -+ return -ETIMEDOUT; -+ } -+ pci_dbg(ithc->pci, "done waiting\n"); -+ return 0; -+} -+ -+int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) -+{ -+ pci_dbg(ithc->pci, "setting SPI speed to %i, mode %i\n", speed, mode); -+ if (mode == 3) -+ mode = 2; -+ bitsl(&ithc->regs->spi_config, -+ SPI_CONFIG_MODE(0xff) | SPI_CONFIG_SPEED(0xff) | SPI_CONFIG_UNKNOWN_18(0xff) | SPI_CONFIG_SPEED2(0xff), -+ SPI_CONFIG_MODE(mode) | SPI_CONFIG_SPEED(speed) | SPI_CONFIG_UNKNOWN_18(0) | SPI_CONFIG_SPEED2(speed)); -+ return 0; -+} -+ -+int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) -+{ -+ pci_dbg(ithc->pci, "SPI command %u, size %u, offset %u\n", command, size, offset); -+ if (size > sizeof(ithc->regs->spi_cmd.data)) -+ return -EINVAL; -+ -+ // Wait if the device is still busy. -+ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); -+ // Clear result flags. -+ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); -+ -+ // Init SPI command data. -+ writeb(command, &ithc->regs->spi_cmd.code); -+ writew(size, &ithc->regs->spi_cmd.size); -+ writel(offset, &ithc->regs->spi_cmd.offset); -+ u32 *p = data, n = (size + 3) / 4; -+ for (u32 i = 0; i < n; i++) -+ writel(p[i], &ithc->regs->spi_cmd.data[i]); -+ -+ // Start transmission. -+ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); -+ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); -+ -+ // Read response. -+ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) -+ return -EIO; -+ if (readw(&ithc->regs->spi_cmd.size) != size) -+ return -EMSGSIZE; -+ for (u32 i = 0; i < n; i++) -+ p[i] = readl(&ithc->regs->spi_cmd.data[i]); -+ -+ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); -+ return 0; -+} -+ -diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h -new file mode 100644 -index 000000000000..d4007d9e2bac ---- /dev/null -+++ b/drivers/hid/ithc/ithc-regs.h -@@ -0,0 +1,189 @@ -+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ -+ -+#define CONTROL_QUIESCE BIT(1) -+#define CONTROL_IS_QUIESCED BIT(2) -+#define CONTROL_NRESET BIT(3) -+#define CONTROL_READY BIT(29) -+ -+#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) -+#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) -+#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) -+#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? -+ -+#define ERROR_CONTROL_UNKNOWN_0 BIT(0) -+#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs -+#define ERROR_CONTROL_UNKNOWN_2 BIT(2) -+#define ERROR_CONTROL_UNKNOWN_3 BIT(3) -+#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) -+#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) -+#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) -+#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) -+#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? -+#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs -+ -+#define ERROR_STATUS_DMA BIT(28) -+#define ERROR_STATUS_SPI BIT(30) -+ -+#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) -+#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) -+#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message -+#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) -+#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) -+#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) -+#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) -+#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) -+#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) -+#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) -+ -+#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete -+#define SPI_CMD_CONTROL_IRQ BIT(1) -+ -+#define SPI_CMD_CODE_READ 4 -+#define SPI_CMD_CODE_WRITE 6 -+ -+#define SPI_CMD_STATUS_DONE BIT(0) -+#define SPI_CMD_STATUS_ERROR BIT(1) -+#define SPI_CMD_STATUS_BUSY BIT(3) -+ -+#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete -+#define DMA_TX_CONTROL_IRQ BIT(3) -+ -+#define DMA_TX_STATUS_DONE BIT(0) -+#define DMA_TX_STATUS_ERROR BIT(1) -+#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) -+#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? -+ -+#define DMA_RX_CONTROL_ENABLE BIT(0) -+#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? -+#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? -+#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? -+#define DMA_RX_CONTROL_IRQ_DATA BIT(5) -+ -+#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? -+#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices -+ -+#define DMA_RX_WRAP_FLAG BIT(7) -+ -+#define DMA_RX_STATUS_ERROR BIT(3) -+#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) -+#define DMA_RX_STATUS_HAVE_DATA BIT(5) -+#define DMA_RX_STATUS_ENABLED BIT(8) -+ -+// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. -+#define COUNTER_RESET BIT(31) -+ -+struct ithc_registers { -+ /* 0000 */ u32 _unknown_0000[1024]; -+ /* 1000 */ u32 _unknown_1000; -+ /* 1004 */ u32 _unknown_1004; -+ /* 1008 */ u32 control_bits; -+ /* 100c */ u32 _unknown_100c; -+ /* 1010 */ u32 spi_config; -+ /* 1014 */ u32 _unknown_1014[3]; -+ /* 1020 */ u32 error_control; -+ /* 1024 */ u32 error_status; // write to clear -+ /* 1028 */ u32 error_flags; // write to clear -+ /* 102c */ u32 _unknown_102c[5]; -+ struct { -+ /* 1040 */ u8 control; -+ /* 1041 */ u8 code; -+ /* 1042 */ u16 size; -+ /* 1044 */ u32 status; // write to clear -+ /* 1048 */ u32 offset; -+ /* 104c */ u32 data[16]; -+ /* 108c */ u32 _unknown_108c; -+ } spi_cmd; -+ struct { -+ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() -+ /* 1098 */ u8 control; -+ /* 1099 */ u8 _unknown_1099; -+ /* 109a */ u8 _unknown_109a; -+ /* 109b */ u8 num_prds; -+ /* 109c */ u32 status; // write to clear -+ } dma_tx; -+ /* 10a0 */ u32 _unknown_10a0[7]; -+ /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET -+ /* 10c0 */ u32 _unknown_10c0[8]; -+ /* 10e0 */ u32 _unknown_10e0_counters[3]; -+ /* 10ec */ u32 _unknown_10ec[5]; -+ struct { -+ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() -+ /* 1108/1208 */ u8 num_bufs; -+ /* 1109/1209 */ u8 num_prds; -+ /* 110a/120a */ u16 _unknown_110a; -+ /* 110c/120c */ u8 control; -+ /* 110d/120d */ u8 head; -+ /* 110e/120e */ u8 tail; -+ /* 110f/120f */ u8 control2; -+ /* 1110/1210 */ u32 status; // write to clear -+ /* 1114/1214 */ u32 _unknown_1114; -+ /* 1118/1218 */ u64 _unknown_1118_guc_addr; -+ /* 1120/1220 */ u32 _unknown_1120_guc; -+ /* 1124/1224 */ u32 _unknown_1124_guc; -+ /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related -+ /* 112c/122c */ u32 _unknown_112c; -+ /* 1130/1230 */ u64 _unknown_1130_guc_addr; -+ /* 1138/1238 */ u32 _unknown_1138_guc; -+ /* 113c/123c */ u32 _unknown_113c; -+ /* 1140/1240 */ u32 _unknown_1140_guc; -+ /* 1144/1244 */ u32 _unknown_1144[23]; -+ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; -+ /* 11b8/12b8 */ u32 _unknown_11b8[18]; -+ } dma_rx[2]; -+}; -+static_assert(sizeof(struct ithc_registers) == 0x1300); -+ -+#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) -+#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) -+ -+#define DEVCFG_TOUCH_MASK 0x3f -+#define DEVCFG_TOUCH_ENABLE BIT(0) -+#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) -+#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) -+#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) -+#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) -+#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) -+#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) -+ -+#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" -+ -+#define DEVCFG_SPI_MAX_FREQ(x) (((x) >> 1) & 0xf) // high bit = use high speed mode? -+#define DEVCFG_SPI_MODE(x) (((x) >> 6) & 3) -+#define DEVCFG_SPI_UNKNOWN_8(x) (((x) >> 8) & 0x3f) -+#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat -+#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) -+#define DEVCFG_SPI_UNKNOWN_25 BIT(25) -+#define DEVCFG_SPI_UNKNOWN_26 BIT(26) -+#define DEVCFG_SPI_UNKNOWN_27 BIT(27) -+#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this -+#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? -+ -+struct ithc_device_config { // (Example values are from an SP7+.) -+ u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) -+ u32 _unknown_04; // 04 = 0x00000000 -+ u32 dma_buf_sizes; // 08 = 0x000a00ff -+ u32 touch_cfg; // 0c = 0x0000001c -+ u32 _unknown_10; // 10 = 0x0000001c -+ u32 device_id; // 14 = 0x43495424 = "$TIC" -+ u32 spi_config; // 18 = 0xfda00a2e -+ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. -+ u16 product_id; // 1e = 0x0c1a -+ u32 revision; // 20 = 0x00000001 -+ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) -+ u32 _unknown_28; // 28 = 0x00000000 -+ u32 fw_mode; // 2c = 0x00000000 (for fw update?) -+ u32 _unknown_30; // 30 = 0x00000000 -+ u32 _unknown_34; // 34 = 0x0404035e (u8,u8,u8,u8 = version?) -+ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) -+ u32 _unknown_3c; // 3c = 0x00000002 -+}; -+ -+void bitsl(__iomem u32 *reg, u32 mask, u32 val); -+void bitsb(__iomem u8 *reg, u8 mask, u8 val); -+#define bitsl_set(reg, x) bitsl(reg, x, x) -+#define bitsb_set(reg, x) bitsb(reg, x, x) -+int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); -+int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); -+int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode); -+int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); -+ -diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h -new file mode 100644 -index 000000000000..028e55a4ec53 ---- /dev/null -+++ b/drivers/hid/ithc/ithc.h -@@ -0,0 +1,67 @@ -+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define DEVNAME "ithc" -+#define DEVFULLNAME "Intel Touch Host Controller" -+ -+#undef pr_fmt -+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -+ -+#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) -+#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) -+ -+#define NUM_RX_BUF 16 -+ -+struct ithc; -+ -+#include "ithc-regs.h" -+#include "ithc-dma.h" -+ -+struct ithc { -+ char phys[32]; -+ struct pci_dev *pci; -+ int irq; -+ struct task_struct *poll_thread; -+ -+ struct pm_qos_request activity_qos; -+ struct hrtimer activity_start_timer; -+ struct hrtimer activity_end_timer; -+ ktime_t last_rx_time; -+ unsigned int cur_rx_seq_count; -+ unsigned int cur_rx_seq_errors; -+ -+ struct hid_device *hid; -+ bool hid_parse_done; -+ wait_queue_head_t wait_hid_parse; -+ wait_queue_head_t wait_hid_get_feature; -+ struct mutex hid_get_feature_mutex; -+ void *hid_get_feature_buf; -+ size_t hid_get_feature_size; -+ -+ struct ithc_registers __iomem *regs; -+ struct ithc_registers *prev_regs; // for debugging -+ struct ithc_device_config config; -+ struct ithc_dma_rx dma_rx[2]; -+ struct ithc_dma_tx dma_tx; -+}; -+ -+int ithc_reset(struct ithc *ithc); -+void ithc_set_active(struct ithc *ithc, unsigned int duration_us); -+int ithc_debug_init(struct ithc *ithc); -+void ithc_log_regs(struct ithc *ithc); -+ --- -2.44.0 - diff --git a/patches/6.9/0001-secureboot.patch b/patches/6.9/0001-secureboot.patch new file mode 100644 index 0000000000..186d419ce1 --- /dev/null +++ b/patches/6.9/0001-secureboot.patch @@ -0,0 +1,37 @@ +From 0a65c8b311295b6fda9386f397a4adc1e3dc28b5 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 19:48:58 +0200 +Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag + unconditionally" + +This reverts commit 891f8890a4a3663da7056542757022870b499bc1. + +Revert because of compatibility issues of MS Surface devices and GRUB +with NX. In short, these devices get stuck on boot with NX advertised. +So to not advertise it, add the respective option back in. + +Signed-off-by: Maximilian Luz +Patchset: secureboot +--- + arch/x86/boot/header.S | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S +index b5c79f43359b..a1bbedd989e4 100644 +--- a/arch/x86/boot/header.S ++++ b/arch/x86/boot/header.S +@@ -111,7 +111,11 @@ extra_header_fields: + .long salign # SizeOfHeaders + .long 0 # CheckSum + .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) ++#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES + .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics ++#else ++ .word 0 # DllCharacteristics ++#endif + #ifdef CONFIG_X86_32 + .long 0 # SizeOfStackReserve + .long 0 # SizeOfStackCommit +-- +2.45.2 + diff --git a/patches/6.9/0002-surface3-oemb.patch b/patches/6.9/0002-surface3-oemb.patch new file mode 100644 index 0000000000..de56b9f6bf --- /dev/null +++ b/patches/6.9/0002-surface3-oemb.patch @@ -0,0 +1,101 @@ +From 3878b34a7bc634d6293876adb4f66f7ba332eb4a Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3-oemb +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index c15ed7a12784..1ec8edb5aafa 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index d0d24a53df74..43e06166a5d9 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3777,6 +3777,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index 5e2ec60e2954..207868c699f2 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.45.2 + diff --git a/patches/6.9/0003-mwifiex.patch b/patches/6.9/0003-mwifiex.patch new file mode 100644 index 0000000000..bdc6f4ea86 --- /dev/null +++ b/patches/6.9/0003-mwifiex.patch @@ -0,0 +1,400 @@ +From 20f0445b1c1b466002feed9461a2244c4426ea21 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 5f997becdbaa..9a9929424513 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static void mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap); + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd..f46b06f8d643 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5b..5d30ae39d65e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.45.2 + +From a6da6d0ae56bfcb0a6cbb29ffa5084f68d33ff80 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 9a9929424513..2273e3029776 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -377,6 +377,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -418,6 +419,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65e..c14eb56eb911 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.45.2 + +From 44f49d2976d1b066787084164f231a6752dc056c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index fb716849b60f..1e7b3798108f 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -65,6 +65,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -468,6 +469,7 @@ static const struct usb_device_id quirks_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -4417,6 +4419,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.45.2 + diff --git a/patches/6.8/0003-ath10k.patch b/patches/6.9/0004-ath10k.patch similarity index 94% rename from patches/6.8/0003-ath10k.patch rename to patches/6.9/0004-ath10k.patch index 3200456e59..9e000bdfaa 100644 --- a/patches/6.8/0003-ath10k.patch +++ b/patches/6.9/0004-ath10k.patch @@ -1,4 +1,4 @@ -From 7b414f11dfa0be3204b0a43b82a75744f8218d57 Mon Sep 17 00:00:00 2001 +From f5c625e43ac2d2dcd37ed08391e3a6e40437cc81 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 27 Feb 2021 00:45:52 +0100 Subject: [PATCH] ath10k: Add module parameters to override board files @@ -20,7 +20,7 @@ Patchset: ath10k 1 file changed, 58 insertions(+) diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 0032f8aa892f..17717b53316b 100644 +index fa5e2e651831..8921b0ebf36b 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -39,6 +39,9 @@ static bool fw_diag_log; @@ -53,7 +53,7 @@ index 0032f8aa892f..17717b53316b 100644 static const struct ath10k_hw_params ath10k_hw_params_list[] = { { .id = QCA988X_HW_2_0_VERSION, -@@ -928,6 +937,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) +@@ -931,6 +940,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) return 0; } @@ -96,7 +96,7 @@ index 0032f8aa892f..17717b53316b 100644 static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, const char *dir, const char *file) -@@ -942,6 +987,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, +@@ -945,6 +990,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, if (dir == NULL) dir = "."; @@ -117,5 +117,5 @@ index 0032f8aa892f..17717b53316b 100644 ret = firmware_request_nowarn(&fw, filename, ar->dev); ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", -- -2.44.0 +2.45.2 diff --git a/patches/6.9/0005-ipts.patch b/patches/6.9/0005-ipts.patch new file mode 100644 index 0000000000..e47882043f --- /dev/null +++ b/patches/6.9/0005-ipts.patch @@ -0,0 +1,3291 @@ +From 22e330a89ef3a3b8765bffcc67ad1426c715ff46 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] mei: me: Add Icelake device ID for iTouch + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index c3a6657dcd4a..82eef2f4eb0a 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 6589635f8ba3..a1df48a434e2 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.45.2 + +From 11229997041587d1c0613fc274e87b442c6596d9 Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index e4a03588a8a0..61bc54299a59 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -39,6 +39,11 @@ + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + ++#define IS_IPTS(pdev) ( \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ ++ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ++ ) ++ + #define IOAPIC_RANGE_START (0xfee00000) + #define IOAPIC_RANGE_END (0xfeefffff) + #define IOVA_START_ADDR (0x1000) +@@ -221,12 +226,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); + int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + static int disable_igfx_iommu; + + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + static const struct iommu_dirty_ops intel_dirty_ops; +@@ -2401,6 +2408,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2701,6 +2711,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + ret = si_domain_init(hw_pass_through); +@@ -4871,6 +4884,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Disabling IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4906,6 +4931,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.45.2 + +From 2bd70f61f21bd369fcb90399530b9d2e63ad91b6 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 103 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 144 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 250 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2850 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 4c682c650704..a263e49b2ae2 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1351,4 +1351,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" + + source "drivers/hid/surface-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 082a728eac60..f4bad1b8d813 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -170,3 +170,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ ++ ++obj-$(CONFIG_HID_IPTS) += ipts/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); ++ return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); ++} +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..ecbb3a8bdaf6 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..198dc65d7887 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,144 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum hid_report_type report_type, enum hid_class_request request_type); +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..ef66c3c9db80 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,250 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.45.2 + +From 9548ddae8daf8630884b8dc207df42ea524dc500 Mon Sep 17 00:00:00 2001 +From: Jasmin Huber +Date: Mon, 15 Apr 2024 10:22:55 +0200 +Subject: [PATCH] Inlude headers to avoid compiler warnings 6.8 kernels compile + with -Wmissing-prototypes. + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/ipts/eds1.c | 1 + + drivers/hid/ipts/eds2.c | 1 + + drivers/hid/ipts/receiver.c | 1 + + 3 files changed, 3 insertions(+) + +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +index ecbb3a8bdaf6..7b9f54388a9f 100644 +--- a/drivers/hid/ipts/eds1.c ++++ b/drivers/hid/ipts/eds1.c +@@ -14,6 +14,7 @@ + #include "context.h" + #include "control.h" + #include "desc.h" ++#include "eds1.h" + #include "spec-device.h" + + int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +index 198dc65d7887..639940794615 100644 +--- a/drivers/hid/ipts/eds2.c ++++ b/drivers/hid/ipts/eds2.c +@@ -15,6 +15,7 @@ + #include "context.h" + #include "control.h" + #include "desc.h" ++#include "eds2.h" + #include "spec-data.h" + + int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +index ef66c3c9db80..977724c728c3 100644 +--- a/drivers/hid/ipts/receiver.c ++++ b/drivers/hid/ipts/receiver.c +@@ -16,6 +16,7 @@ + #include "context.h" + #include "control.h" + #include "hid.h" ++#include "receiver.h" + #include "resources.h" + #include "spec-device.h" + #include "thread.h" +-- +2.45.2 + diff --git a/patches/6.9/0006-ithc.patch b/patches/6.9/0006-ithc.patch new file mode 100644 index 0000000000..cf2738caaa --- /dev/null +++ b/patches/6.9/0006-ithc.patch @@ -0,0 +1,4679 @@ +From 888f8f300f9a547535e251f192759902545208f0 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 566297bc87dd..a8cd8f12d593 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -386,6 +386,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.45.2 + +From e5bbe336297f8d6fbaac16f8b091522bb394e30a Mon Sep 17 00:00:00 2001 +From: quo +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@0b8b45d + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 130 ++++++ + drivers/hid/ithc/ithc-dma.c | 373 +++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 69 ++++ + drivers/hid/ithc/ithc-main.c | 728 ++++++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-regs.c | 96 +++++ + drivers/hid/ithc/ithc-regs.h | 189 +++++++++ + drivers/hid/ithc/ithc.h | 67 ++++ + 11 files changed, 1673 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index a263e49b2ae2..03f0f5af289a 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1353,4 +1353,6 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index f4bad1b8d813..d32c194400ae 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -172,3 +172,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 000000000000..aea83f2ac07b +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..1f1f1e33f2e5 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,130 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). ++ struct ithc *ithc = file_inode(f)->i_private; ++ char cmd[256]; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. ++ u32 n = 0; ++ const char *s = cmd + 1; ++ u32 a[32]; ++ while (*s && *s != '\n') { ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) ++ return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ ++ // Execute the command. ++ switch (cmd[0]) { ++ case 'x': // reset ++ ithc_reset(ithc); ++ break; ++ case 'w': // write register: offset mask value ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); ++ bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); ++ break; ++ case 'r': // read register: offset ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ break; ++ case 's': // spi command: cmd offset len data... ++ // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ++ // set touch cfg: s 6 12 4 XX ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); ++ if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ break; ++ case 'd': // dma command: cmd len data... ++ // get report descriptor: d 7 8 0 0 ++ // enable multitouch: d 3 2 0x0105 ++ if (n < 2 || a[1] > (n - 2) * 4) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]); ++ if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) ++ pci_err(ithc->pci, "dma tx failed\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ ++ struct dentry **dbgm = res; ++ if (*dbgm) ++ debugfs_remove_recursive(*dbgm); ++} ++ ++int ithc_debug_init(struct ithc *ithc) ++{ ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); ++ *dbgm = dbg; ++ ++ struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..ffb8689b8a78 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,373 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ ++ p->num_pages = num_pages; ++ p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). ++ p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); ++ p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; ++ return 0; ++} ++ ++// Devres managed sg_table wrapper. ++struct ithc_sg_table { ++ void *addr; ++ struct sg_table sgt; ++ enum dma_data_direction dir; ++}; ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) ++ __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ ++ struct ithc_sg_table *sgt = res; ++ if (sgt->addr) ++ vunmap(sgt->addr); ++ dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); ++ ithc_dma_sgtable_free(&sgt->sgt); ++} ++ ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. ++ struct page *pages[16]; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; ++ b->active_idx = -1; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ sgt->dir = prds->dir; ++ ++ if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { ++ struct scatterlist *sg; ++ int i; ++ bool ok = true; ++ for_each_sgtable_sg(&sgt->sgt, sg, i) { ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } ++ sg_set_page(sg, p, PAGE_SIZE, 0); ++ } ++ if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { ++ devres_add(&ithc->pci->dev, sgt); ++ b->sgt = &sgt->sgt; ++ b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); ++ if (!b->addr) ++ return -ENOMEM; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ return -ENOMEM; ++} ++ ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; ++ prd->addr = sg_dma_address(b->sgt->sgl) >> 10; ++ prd->size = b->data_size | PRD_FLAG_END; ++ flush_kernel_vmap_range(b->addr, b->data_size); ++ } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. ++ struct ithc_phys_region_desc *prd = prds->addr; ++ prd += idx * prds->num_pages; ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. ++ dma_rmb(); // for the prds ++ b->data_size = 0; ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ unsigned int size = prd->size; ++ b->data_size += size & PRD_SIZE_MASK; ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ ++ // Allocate buffers. ++ u32 buf_size = DEVCFG_DMA_RX_SIZE(ithc->config.dma_buf_sizes); ++ unsigned int num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, buf_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. ++ writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); ++ lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); ++ writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); ++ writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); ++ u8 head = readb(&ithc->regs->dma_rx[channel].head); ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ ++ writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); ++ return 0; ++} ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) ++{ ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ ++ // Allocate buffers. ++ tx->max_size = DEVCFG_DMA_TX_SIZE(ithc->config.dma_buf_sizes); ++ unsigned int num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ tx->max_size, num_pages); ++ CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); ++ CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ return 0; ++} ++ ++static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, ++ u8 channel, u8 buf) ++{ ++ if (buf >= NUM_RX_BUF) { ++ pci_err(ithc->pci, "invalid dma ringbuffer index\n"); ++ return -EINVAL; ++ } ++ u32 len = data->data_size; ++ struct ithc_dma_rx_header *hdr = data->addr; ++ u8 *hiddata = (void *)(hdr + 1); ++ if (len >= sizeof(*hdr) && hdr->code == DMA_RX_CODE_RESET) { ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ CHECK(ithc_reset, ithc); ++ } else if (len < sizeof(*hdr) || len != sizeof(*hdr) + hdr->data_size) { ++ if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { ++ // When the CPU enters a low power state during DMA, we can get truncated ++ // messages. For Surface devices, this will typically be a single touch ++ // report that is only 1 byte, or a multitouch report that is 257 bytes. ++ // See also ithc_set_active(). ++ } else { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", ++ channel, buf, len, hdr->code, hdr->data_size); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ hdr, min(len, 0x400u), 0); ++ } ++ } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { ++ // Response to a 'get report descriptor' request. ++ // The actual descriptor is preceded by 8 nul bytes. ++ CHECK(hid_parse_report, ithc->hid, hiddata + 8, hdr->data_size - 8); ++ WRITE_ONCE(ithc->hid_parse_done, true); ++ wake_up(&ithc->wait_hid_parse); ++ } else if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { ++ // Standard HID input report containing touch data. ++ CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); ++ } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { ++ // Response to a 'get feature' request. ++ bool done = false; ++ mutex_lock(&ithc->hid_get_feature_mutex); ++ if (ithc->hid_get_feature_buf) { ++ if (hdr->data_size < ithc->hid_get_feature_size) ++ ithc->hid_get_feature_size = hdr->data_size; ++ memcpy(ithc->hid_get_feature_buf, hiddata, ithc->hid_get_feature_size); ++ ithc->hid_get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid_get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->wait_hid_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, ++ hiddata, hdr->data_size, 1); ++ } ++ } else { ++ pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", ++ channel, buf, len, hdr->code); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ hdr, min(len, 0x400u), 0); ++ } ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned int n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); ++ // ringbuffer is full if tail_wrap == head_wrap ++ // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_lock(&rx->mutex); ++ int ret = ithc_dma_rx_unlocked(ithc, channel); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) ++{ ++ ithc_set_active(ithc, 100 * USEC_PER_MSEC); ++ ++ // Send a single TX buffer to the THC. ++ pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); ++ struct ithc_dma_tx_header *hdr; ++ // Data must be padded to next 4-byte boundary. ++ u8 padding = datasize & 3 ? 4 - (datasize & 3) : 0; ++ unsigned int fullsize = sizeof(*hdr) + datasize + padding; ++ if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) ++ return -EINVAL; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Fill the TX buffer with header and data. ++ ithc->dma_tx.buf.data_size = fullsize; ++ hdr = ithc->dma_tx.buf.addr; ++ hdr->code = cmdcode; ++ hdr->data_size = datasize; ++ u8 *dest = (void *)(hdr + 1); ++ memcpy(dest, data, datasize); ++ dest += datasize; ++ for (u8 p = 0; p < padding; p++) ++ *dest++ = 0; ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ // Let the THC process the buffer. ++ bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); ++ CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); ++ return 0; ++} ++int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) ++{ ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..93652e4476bf +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,69 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++#define DMA_RX_CODE_INPUT_REPORT 3 ++#define DMA_RX_CODE_FEATURE_REPORT 4 ++#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 ++#define DMA_RX_CODE_RESET 7 ++ ++struct ithc_dma_rx_header { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++}; ++ ++#define DMA_TX_CODE_SET_FEATURE 3 ++#define DMA_TX_CODE_GET_FEATURE 4 ++#define DMA_TX_CODE_OUTPUT_REPORT 5 ++#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++struct ithc_dma_tx_header { ++ u32 code; ++ u32 data_size; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ u32 max_size; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); ++int ithc_dma_tx_init(struct ithc *ithc); ++int ithc_dma_rx(struct ithc *ithc, u8 channel); ++int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..87ed4aa70fda +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,728 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT2) }, ++ // XXX So far the THC seems to be the only Intel PCI device with PCI_CLASS_INPUT_PEN, ++ // so instead of the device list we could just do: ++ // { .vendor = PCI_VENDOR_ID_INTEL, .device = PCI_ANY_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = PCI_CLASS_INPUT_PEN, .class_mask = ~0, }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++// Since all known devices seem to use only channel 1, by default we disable channel 0. ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++// Values below 250 seem to work well on the SP7+. If this is set too high, you may observe cursor stuttering. ++static int ithc_dma_latency_us = 200; ++module_param_named(dma_latency_us, ithc_dma_latency_us, int, 0); ++MODULE_PARM_DESC(dma_latency_us, "Determines the CPU latency QoS value for DMA transfers (in microseconds), -1 to disable latency QoS"); ++ ++// Values above 1700 seem to work well on the SP7+. If this is set too low, you may observe cursor stuttering. ++static unsigned int ithc_dma_early_us = 2000; ++module_param_named(dma_early_us, ithc_dma_early_us, uint, 0); ++MODULE_PARM_DESC(dma_early_us, "Determines how early the CPU latency QoS value is applied before the next expected IRQ (in microseconds)"); ++ ++static bool ithc_log_regs_enabled = false; ++module_param_named(logregs, ithc_log_regs_enabled, bool, 0); ++MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); ++ ++// Sysfs attributes ++ ++static bool ithc_is_config_valid(struct ithc *ithc) ++{ ++ return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; ++} ++ ++static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ithc *ithc = dev_get_drvdata(dev); ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; ++ return sprintf(buf, "0x%04x", ithc->config.vendor_id); ++} ++static DEVICE_ATTR_RO(vendor); ++static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ithc *ithc = dev_get_drvdata(dev); ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; ++ return sprintf(buf, "0x%04x", ithc->config.product_id); ++} ++static DEVICE_ATTR_RO(product); ++static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ithc *ithc = dev_get_drvdata(dev); ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; ++ return sprintf(buf, "%u", ithc->config.revision); ++} ++static DEVICE_ATTR_RO(revision); ++static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ithc *ithc = dev_get_drvdata(dev); ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; ++ u32 v = ithc->config.fw_version; ++ return sprintf(buf, "%i.%i.%i.%i", v >> 24, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff); ++} ++static DEVICE_ATTR_RO(fw_version); ++ ++static const struct attribute_group *ithc_attribute_groups[] = { ++ &(const struct attribute_group){ ++ .name = DEVNAME, ++ .attrs = (struct attribute *[]){ ++ &dev_attr_vendor.attr, ++ &dev_attr_product.attr, ++ &dev_attr_revision.attr, ++ &dev_attr_fw_version.attr, ++ NULL ++ }, ++ }, ++ NULL ++}; ++ ++// HID setup ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ u64 val = 0; ++ WRITE_ONCE(ithc->hid_parse_done, false); ++ for (int retries = 0; ; retries++) { ++ CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof(val), &val); ++ if (wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), ++ msecs_to_jiffies(200))) ++ return 0; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ u32 code; ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ code = DMA_TX_CODE_OUTPUT_REPORT; ++ } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ code = DMA_TX_CODE_SET_FEATURE; ++ } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ code = DMA_TX_CODE_GET_FEATURE; ++ } else { ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++ } ++ buf[0] = reportnum; ++ ++ if (reqtype == HID_REQ_GET_REPORT) { ++ // Prepare for response. ++ mutex_lock(&ithc->hid_get_feature_mutex); ++ ithc->hid_get_feature_buf = buf; ++ ithc->hid_get_feature_size = len; ++ mutex_unlock(&ithc->hid_get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, code, 1, buf); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, ++ !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid_get_feature_mutex); ++ ithc->hid_get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid_get_feature_size; ++ mutex_unlock(&ithc->hid_get_feature_mutex); ++ return r; ++ } ++ ++ // 'Set feature', or 'output report'. These don't have a response. ++ CHECK_RET(ithc_dma_tx, ithc, code, len, buf); ++ return 0; ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++static int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->config.vendor_id; ++ hid->product = ithc->config.product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid = hid; ++ return 0; ++} ++ ++// Interrupts/polling ++ ++static enum hrtimer_restart ithc_activity_start_timer_callback(struct hrtimer *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, activity_start_timer); ++ ithc_set_active(ithc, ithc_dma_early_us * 2 + USEC_PER_MSEC); ++ return HRTIMER_NORESTART; ++} ++ ++static enum hrtimer_restart ithc_activity_end_timer_callback(struct hrtimer *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, activity_end_timer); ++ cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); ++ return HRTIMER_NORESTART; ++} ++ ++void ithc_set_active(struct ithc *ithc, unsigned int duration_us) ++{ ++ if (ithc_dma_latency_us < 0) ++ return; ++ // When CPU usage is very low, the CPU can enter various low power states (C2-C10). ++ // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_RX_TIMEOUT will be ++ // set when this happens. The amount of truncated messages can become very high, resulting ++ // in user-visible effects (laggy/stuttering cursor). To avoid this, we use a CPU latency ++ // QoS request to prevent the CPU from entering low power states during touch interactions. ++ cpu_latency_qos_update_request(&ithc->activity_qos, ithc_dma_latency_us); ++ hrtimer_start_range_ns(&ithc->activity_end_timer, ++ ns_to_ktime(duration_us * NSEC_PER_USEC), duration_us * NSEC_PER_USEC, HRTIMER_MODE_REL); ++} ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->config.touch_cfg = ++ (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ ++ writel(0, &ithc->regs->error_control); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); ++} ++ ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); ++} ++ ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ ++ writel(0xffffffff, &ithc->regs->error_flags); ++ writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); ++} ++ ++static void ithc_process(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Track time between DMA rx transfers, so we can try to predict when we need to enable CPU latency QoS for the next transfer ++ ktime_t t = ktime_get(); ++ ktime_t dt = ktime_sub(t, ithc->last_rx_time); ++ if (rx0 || rx1) { ++ ithc->last_rx_time = t; ++ if (dt > ms_to_ktime(100)) { ++ ithc->cur_rx_seq_count = 0; ++ ithc->cur_rx_seq_errors = 0; ++ } ++ ithc->cur_rx_seq_count++; ++ if (!ithc_use_polling && ithc_dma_latency_us >= 0) { ++ // Disable QoS, since the DMA transfer has completed (we re-enable it after a delay below) ++ cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); ++ hrtimer_try_to_cancel(&ithc->activity_end_timer); ++ } ++ } ++ ++ // Read and clear error bits ++ u32 err = readl(&ithc->regs->error_flags); ++ if (err) { ++ writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) { ++ // Only log an error if we see a significant number of these errors. ++ ithc->cur_rx_seq_errors++; ++ if (ithc->cur_rx_seq_errors && ithc->cur_rx_seq_errors % 50 == 0 && ithc->cur_rx_seq_errors > ithc->cur_rx_seq_count / 10) ++ pci_err(ithc->pci, "High number of DMA RX timeouts/errors (%u/%u, dt=%lldus). Try adjusting dma_early_us and/or dma_latency_us.\n", ++ ithc->cur_rx_seq_errors, ithc->cur_rx_seq_count, ktime_to_us(dt)); ++ } ++ } ++ ++ // Process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ // Start timer to re-enable QoS for next rx, but only if we've seen an ERROR_FLAG_DMA_RX_TIMEOUT ++ if ((rx0 || rx1) && !ithc_use_polling && ithc_dma_latency_us >= 0 && ithc->cur_rx_seq_errors > 0) { ++ ktime_t expires = ktime_add(t, ktime_sub_us(dt, ithc_dma_early_us)); ++ hrtimer_start_range_ns(&ithc->activity_start_timer, expires, 10 * NSEC_PER_USEC, HRTIMER_MODE_ABS); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ ++ struct ithc *ithc = arg; ++ pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", ++ readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) ++{ ++ struct ithc *ithc = arg; ++ unsigned int sleep = 100; ++ while (!kthread_should_stop()) { ++ u32 n = ithc->dma_rx[1].num_received; ++ ithc_process(ithc); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ if (n != ithc->dma_rx[1].num_received) { ++ ithc_set_active(ithc, 100 * USEC_PER_MSEC); ++ sleep = 20; ++ } else { ++ sleep = min(200u, sleep + (sleep >> 4) + 1); ++ } ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++static void ithc_disable(struct ithc *ithc) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(ithc); ++} ++ ++static int ithc_init_device(struct ithc *ithc) ++{ ++ ithc_log_regs(ithc); ++ bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ++ ithc_disable(ithc); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ ithc_set_spi_config(ithc, 10, 0); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, state = 0x%08x\n", readl(&ithc->regs->state)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid state, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Waiting for the following status bit makes reading config much more reliable, ++ // however the official driver does not seem to do this... ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); ++ ++ // Read configuration data. ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ memset(&ithc->config, 0, sizeof(ithc->config)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(ithc->config), &ithc->config); ++ u32 *p = (void *)&ithc->config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (ithc_is_config_valid(ithc)) ++ break; ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ ithc->config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), ++ DEVCFG_SPI_MODE(ithc->config.spi_config)); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? ++ pci_err(ithc->pci, "reset\n"); ++ CHECK(ithc_init_device, ithc); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++static void ithc_stop(void *res) ++{ ++ struct ithc *ithc = res; ++ pci_dbg(ithc->pci, "stopping\n"); ++ ithc_log_regs(ithc); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); ++ CHECK(ithc_set_device_enabled, ithc, false); ++ ithc_disable(ithc); ++ hrtimer_cancel(&ithc->activity_start_timer); ++ hrtimer_cancel(&ithc->activity_end_timer); ++ cpu_latency_qos_remove_request(&ithc->activity_qos); ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); ++ lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); ++ writeb(0, &ithc->regs->dma_rx[i].num_bufs); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ lo_hi_writeq(0, &ithc->regs->dma_tx.addr); ++ writeb(0, &ithc->regs->dma_tx.num_prds); ++ ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "stopped\n"); ++} ++ ++static void ithc_clear_drvdata(void *res) ++{ ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++static int ithc_start(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "starting\n"); ++ if (pci_get_drvdata(pci)) { ++ pci_err(pci, "device already initialized\n"); ++ return -EINVAL; ++ } ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; ++ ithc->irq = -1; ++ ithc->pci = pci; ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); ++ init_waitqueue_head(&ithc->wait_hid_parse); ++ init_waitqueue_head(&ithc->wait_hid_get_feature); ++ mutex_init(&ithc->hid_get_feature_mutex); ++ pci_set_drvdata(pci, ithc); ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); ++ ++ // PCI initialization. ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ // Allocate IRQ. ++ if (!ithc_use_polling) { ++ CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); ++ ithc->irq = CHECK(pci_irq_vector, pci, 0); ++ if (ithc->irq < 0) ++ return ithc->irq; ++ } ++ ++ // Initialize THC and touch device. ++ CHECK_RET(ithc_init_device, ithc); ++ CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); ++ CHECK_RET(ithc_dma_tx_init, ithc); ++ ++ cpu_latency_qos_add_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); ++ hrtimer_init(&ithc->activity_start_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); ++ ithc->activity_start_timer.function = ithc_activity_start_timer_callback; ++ hrtimer_init(&ithc->activity_end_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ ithc->activity_end_timer.function = ithc_activity_end_timer_callback; ++ ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. ++ CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); ++ ++ CHECK_RET(ithc_hid_init, ithc); ++ ++ // Start polling/IRQ. ++ if (ithc_use_polling) { ++ pci_info(pci, "using polling instead of irq\n"); ++ // Use a thread instead of simple timer because we want to be able to sleep. ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ } ++ ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); ++ ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. ++ CHECK_RET(hid_add_device, ithc->hid); ++ ++ CHECK(ithc_debug_init, ithc); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ ++ pci_dbg(pci, "device probe\n"); ++ return ithc_start(pci); ++} ++ ++static void ithc_remove(struct pci_dev *pci) ++{ ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm suspend\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_resume(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm resume\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_freeze(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm freeze\n"); ++ devres_release_group(dev, ithc_start); ++ return 0; ++} ++ ++static int ithc_thaw(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm thaw\n"); ++ return ithc_start(pci); ++} ++ ++static int ithc_restore(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway ++}; ++ ++static int __init ithc_init(void) ++{ ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) ++{ ++ pci_unregister_driver(&ithc_driver); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..e058721886e3 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,96 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writel((readl(reg) & ~mask) | (val & mask), reg); ++} ++ ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); ++ writeb((readb(reg) & ~mask) | (val & mask), reg); ++} ++ ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ u32 x; ++ if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ u8 x; ++ if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); ++ return -ETIMEDOUT; ++ } ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) ++{ ++ pci_dbg(ithc->pci, "setting SPI speed to %i, mode %i\n", speed, mode); ++ if (mode == 3) ++ mode = 2; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_MODE(0xff) | SPI_CONFIG_SPEED(0xff) | SPI_CONFIG_UNKNOWN_18(0xff) | SPI_CONFIG_SPEED2(0xff), ++ SPI_CONFIG_MODE(mode) | SPI_CONFIG_SPEED(speed) | SPI_CONFIG_UNKNOWN_18(0) | SPI_CONFIG_SPEED2(speed)); ++ return 0; ++} ++ ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset %u\n", command, size, offset); ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. ++ writeb(command, &ithc->regs->spi_cmd.code); ++ writew(size, &ithc->regs->spi_cmd.size); ++ writel(offset, &ithc->regs->spi_cmd.offset); ++ u32 *p = data, n = (size + 3) / 4; ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. ++ bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 000000000000..d4007d9e2bac +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,189 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) ++#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) ++#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[1024]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ /* 1014 */ u32 _unknown_1014[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ } dma_tx; ++ /* 10a0 */ u32 _unknown_10a0[7]; ++ /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 _unknown_10ec[5]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[23]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) ++#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) ++#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) ++#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) ++#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_MAX_FREQ(x) (((x) >> 1) & 0xf) // high bit = use high speed mode? ++#define DEVCFG_SPI_MODE(x) (((x) >> 6) & 3) ++#define DEVCFG_SPI_UNKNOWN_8(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 _unknown_04; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 _unknown_10; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 _unknown_28; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u32 _unknown_34; // 34 = 0x0404035e (u8,u8,u8,u8 = version?) ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode); ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); ++ +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..028e55a4ec53 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,67 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) ++ ++#define NUM_RX_BUF 16 ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-dma.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ ++ struct pm_qos_request activity_qos; ++ struct hrtimer activity_start_timer; ++ struct hrtimer activity_end_timer; ++ ktime_t last_rx_time; ++ unsigned int cur_rx_seq_count; ++ unsigned int cur_rx_seq_errors; ++ ++ struct hid_device *hid; ++ bool hid_parse_done; ++ wait_queue_head_t wait_hid_parse; ++ wait_queue_head_t wait_hid_get_feature; ++ struct mutex hid_get_feature_mutex; ++ void *hid_get_feature_buf; ++ size_t hid_get_feature_size; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_device_config config; ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++void ithc_set_active(struct ithc *ithc, unsigned int duration_us); ++int ithc_debug_init(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +-- +2.45.2 + +From 299f645a4bc247c2f5adae925f56978870b133f8 Mon Sep 17 00:00:00 2001 +From: quo +Date: Fri, 19 Apr 2024 22:11:09 +0200 +Subject: [PATCH] hid: ithc: Update from quo/ithc-linux + + - Added QuickSPI support for Surface Laptop Studio 2 + - Use Latency Tolerance Reporting instead of manual CPU latency adjustments + +Based on: https://github.com/quo/ithc-linux/commit/18afc6ffacd70b49fdee2eb1ab0a8acd159edb31 + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/hid/ithc/Kbuild | 2 +- + drivers/hid/ithc/ithc-debug.c | 33 +- + drivers/hid/ithc/ithc-debug.h | 7 + + drivers/hid/ithc/ithc-dma.c | 125 ++----- + drivers/hid/ithc/ithc-dma.h | 24 +- + drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ + drivers/hid/ithc/ithc-hid.h | 32 ++ + drivers/hid/ithc/ithc-legacy.c | 252 ++++++++++++++ + drivers/hid/ithc/ithc-legacy.h | 8 + + drivers/hid/ithc/ithc-main.c | 386 ++++----------------- + drivers/hid/ithc/ithc-quickspi.c | 578 +++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-quickspi.h | 39 +++ + drivers/hid/ithc/ithc-regs.c | 72 +++- + drivers/hid/ithc/ithc-regs.h | 143 ++++---- + drivers/hid/ithc/ithc.h | 71 ++-- + 15 files changed, 1441 insertions(+), 538 deletions(-) + create mode 100644 drivers/hid/ithc/ithc-debug.h + create mode 100644 drivers/hid/ithc/ithc-hid.c + create mode 100644 drivers/hid/ithc/ithc-hid.h + create mode 100644 drivers/hid/ithc/ithc-legacy.c + create mode 100644 drivers/hid/ithc/ithc-legacy.h + create mode 100644 drivers/hid/ithc/ithc-quickspi.c + create mode 100644 drivers/hid/ithc/ithc-quickspi.h + +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +index aea83f2ac07b..4937ba131297 100644 +--- a/drivers/hid/ithc/Kbuild ++++ b/drivers/hid/ithc/Kbuild +@@ -1,6 +1,6 @@ + obj-$(CONFIG_HID_ITHC) := ithc.o + +-ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-debug.o ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o + + ccflags-y := -std=gnu11 -Wno-declaration-after-statement + +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +index 1f1f1e33f2e5..2d8c6afe9966 100644 +--- a/drivers/hid/ithc/ithc-debug.c ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -85,10 +85,11 @@ static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, si + case 'd': // dma command: cmd len data... + // get report descriptor: d 7 8 0 0 + // enable multitouch: d 3 2 0x0105 +- if (n < 2 || a[1] > (n - 2) * 4) ++ if (n < 1) + return -EINVAL; +- pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]); +- if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) ++ pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); ++ struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; ++ if (ithc_dma_tx(ithc, &data)) + pci_err(ithc->pci, "dma tx failed\n"); + break; + default: +@@ -98,6 +99,23 @@ static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, si + return len; + } + ++static struct dentry *dbg_dir; ++ ++void __init ithc_debug_init_module(void) ++{ ++ struct dentry *d = debugfs_create_dir(DEVNAME, NULL); ++ if (IS_ERR(d)) ++ pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); ++ else ++ dbg_dir = d; ++} ++ ++void __exit ithc_debug_exit_module(void) ++{ ++ debugfs_remove_recursive(dbg_dir); ++ dbg_dir = NULL; ++} ++ + static const struct file_operations ithc_debugfops_cmd = { + .owner = THIS_MODULE, + .write = ithc_debugfs_cmd_write, +@@ -106,17 +124,18 @@ static const struct file_operations ithc_debugfops_cmd = { + static void ithc_debugfs_devres_release(struct device *dev, void *res) + { + struct dentry **dbgm = res; +- if (*dbgm) +- debugfs_remove_recursive(*dbgm); ++ debugfs_remove_recursive(*dbgm); + } + +-int ithc_debug_init(struct ithc *ithc) ++int ithc_debug_init_device(struct ithc *ithc) + { ++ if (!dbg_dir) ++ return -ENOENT; + struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); + if (!dbgm) + return -ENOMEM; + devres_add(&ithc->pci->dev, dbgm); +- struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); ++ struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); + if (IS_ERR(dbg)) + return PTR_ERR(dbg); + *dbgm = dbg; +diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h +new file mode 100644 +index 000000000000..38c53d916bdb +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++void ithc_debug_init_module(void); ++void ithc_debug_exit_module(void); ++int ithc_debug_init_device(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +index ffb8689b8a78..bf4eab33062b 100644 +--- a/drivers/hid/ithc/ithc-dma.c ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -173,10 +173,9 @@ int ithc_dma_rx_init(struct ithc *ithc, u8 channel) + mutex_init(&rx->mutex); + + // Allocate buffers. +- u32 buf_size = DEVCFG_DMA_RX_SIZE(ithc->config.dma_buf_sizes); +- unsigned int num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", +- NUM_RX_BUF, buf_size, num_pages); ++ NUM_RX_BUF, ithc->max_rx_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); + for (unsigned int i = 0; i < NUM_RX_BUF; i++) + CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); +@@ -214,10 +213,9 @@ int ithc_dma_tx_init(struct ithc *ithc) + mutex_init(&tx->mutex); + + // Allocate buffers. +- tx->max_size = DEVCFG_DMA_TX_SIZE(ithc->config.dma_buf_sizes); +- unsigned int num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", +- tx->max_size, num_pages); ++ ithc->max_tx_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); + CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); + +@@ -230,71 +228,6 @@ int ithc_dma_tx_init(struct ithc *ithc) + return 0; + } + +-static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, +- u8 channel, u8 buf) +-{ +- if (buf >= NUM_RX_BUF) { +- pci_err(ithc->pci, "invalid dma ringbuffer index\n"); +- return -EINVAL; +- } +- u32 len = data->data_size; +- struct ithc_dma_rx_header *hdr = data->addr; +- u8 *hiddata = (void *)(hdr + 1); +- if (len >= sizeof(*hdr) && hdr->code == DMA_RX_CODE_RESET) { +- // The THC sends a reset request when we need to reinitialize the device. +- // This usually only happens if we send an invalid command or put the device +- // in a bad state. +- CHECK(ithc_reset, ithc); +- } else if (len < sizeof(*hdr) || len != sizeof(*hdr) + hdr->data_size) { +- if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { +- // When the CPU enters a low power state during DMA, we can get truncated +- // messages. For Surface devices, this will typically be a single touch +- // report that is only 1 byte, or a multitouch report that is 257 bytes. +- // See also ithc_set_active(). +- } else { +- pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", +- channel, buf, len, hdr->code, hdr->data_size); +- print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, +- hdr, min(len, 0x400u), 0); +- } +- } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { +- // Response to a 'get report descriptor' request. +- // The actual descriptor is preceded by 8 nul bytes. +- CHECK(hid_parse_report, ithc->hid, hiddata + 8, hdr->data_size - 8); +- WRITE_ONCE(ithc->hid_parse_done, true); +- wake_up(&ithc->wait_hid_parse); +- } else if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { +- // Standard HID input report containing touch data. +- CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); +- } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { +- // Response to a 'get feature' request. +- bool done = false; +- mutex_lock(&ithc->hid_get_feature_mutex); +- if (ithc->hid_get_feature_buf) { +- if (hdr->data_size < ithc->hid_get_feature_size) +- ithc->hid_get_feature_size = hdr->data_size; +- memcpy(ithc->hid_get_feature_buf, hiddata, ithc->hid_get_feature_size); +- ithc->hid_get_feature_buf = NULL; +- done = true; +- } +- mutex_unlock(&ithc->hid_get_feature_mutex); +- if (done) { +- wake_up(&ithc->wait_hid_get_feature); +- } else { +- // Received data without a matching request, or the request already +- // timed out. (XXX What's the correct thing to do here?) +- CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, +- hiddata, hdr->data_size, 1); +- } +- } else { +- pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", +- channel, buf, len, hdr->code); +- print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, +- hdr, min(len, 0x400u), 0); +- } +- return 0; +-} +- + static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) + { + // Process all filled RX buffers from the ringbuffer. +@@ -316,7 +249,16 @@ static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) + rx->num_received = ++n; + + // process data +- CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); ++ struct ithc_data d; ++ if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) ++ (ithc, b->addr, b->data_size, &d) < 0) { ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", ++ channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ b->addr, min(b->data_size, 0x400u), 0); ++ } else { ++ ithc_hid_process_data(ithc, &d); ++ } + + // give the buffer back to the device + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); +@@ -331,31 +273,28 @@ int ithc_dma_rx(struct ithc *ithc, u8 channel) + return ret; + } + +-static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) ++static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) + { +- ithc_set_active(ithc, 100 * USEC_PER_MSEC); +- + // Send a single TX buffer to the THC. +- pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); +- struct ithc_dma_tx_header *hdr; +- // Data must be padded to next 4-byte boundary. +- u8 padding = datasize & 3 ? 4 - (datasize & 3) : 0; +- unsigned int fullsize = sizeof(*hdr) + datasize + padding; +- if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) +- return -EINVAL; ++ pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); + CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + + // Fill the TX buffer with header and data. +- ithc->dma_tx.buf.data_size = fullsize; +- hdr = ithc->dma_tx.buf.addr; +- hdr->code = cmdcode; +- hdr->data_size = datasize; +- u8 *dest = (void *)(hdr + 1); +- memcpy(dest, data, datasize); +- dest += datasize; +- for (u8 p = 0; p < padding; p++) +- *dest++ = 0; ++ ssize_t sz; ++ if (data->type == ITHC_DATA_RAW) { ++ sz = min(data->size, ithc->max_tx_size); ++ memcpy(ithc->dma_tx.buf.addr, data->data, sz); ++ } else { ++ sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) ++ (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); ++ } ++ ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ if (sz < 0) { ++ pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", ++ data->type, data->size, (int)sz); ++ return -EINVAL; ++ } + + // Let the THC process the buffer. + bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); +@@ -363,10 +302,10 @@ static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, vo + writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); + return 0; + } +-int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) + { + mutex_lock(&ithc->dma_tx.mutex); +- int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); ++ int ret = ithc_dma_tx_unlocked(ithc, data); + mutex_unlock(&ithc->dma_tx.mutex); + return ret; + } +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +index 93652e4476bf..1749a5819b3e 100644 +--- a/drivers/hid/ithc/ithc-dma.h ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -11,27 +11,6 @@ struct ithc_phys_region_desc { + u32 unused; + }; + +-#define DMA_RX_CODE_INPUT_REPORT 3 +-#define DMA_RX_CODE_FEATURE_REPORT 4 +-#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 +-#define DMA_RX_CODE_RESET 7 +- +-struct ithc_dma_rx_header { +- u32 code; +- u32 data_size; +- u32 _unknown[14]; +-}; +- +-#define DMA_TX_CODE_SET_FEATURE 3 +-#define DMA_TX_CODE_GET_FEATURE 4 +-#define DMA_TX_CODE_OUTPUT_REPORT 5 +-#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 +- +-struct ithc_dma_tx_header { +- u32 code; +- u32 data_size; +-}; +- + struct ithc_dma_prd_buffer { + void *addr; + dma_addr_t dma_addr; +@@ -49,7 +28,6 @@ struct ithc_dma_data_buffer { + + struct ithc_dma_tx { + struct mutex mutex; +- u32 max_size; + struct ithc_dma_prd_buffer prds; + struct ithc_dma_data_buffer buf; + }; +@@ -65,5 +43,5 @@ int ithc_dma_rx_init(struct ithc *ithc, u8 channel); + void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); + int ithc_dma_tx_init(struct ithc *ithc); + int ithc_dma_rx(struct ithc *ithc, u8 channel); +-int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); ++int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); + +diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c +new file mode 100644 +index 000000000000..065646ab499e +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++static void ithc_hid_stop(struct hid_device *hdev) { } ++static int ithc_hid_open(struct hid_device *hdev) { return 0; } ++static void ithc_hid_close(struct hid_device *hdev) { } ++ ++static int ithc_hid_parse(struct hid_device *hdev) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; ++ WRITE_ONCE(ithc->hid.parse_done, false); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); ++ if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), ++ msecs_to_jiffies(200))) { ++ ithc_log_regs(ithc); ++ return 0; ++ } ++ if (retries > 5) { ++ ithc_log_regs(ithc); ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } ++} ++ ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct ithc *ithc = hdev->driver_data; ++ if (!buf || !len) ++ return -EINVAL; ++ ++ struct ithc_data d = { .size = len, .data = buf }; ++ buf[0] = reportnum; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_OUTPUT_REPORT; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ d.type = ITHC_DATA_SET_FEATURE; ++ CHECK_RET(ithc_dma_tx, ithc, &d); ++ return 0; ++ } ++ ++ if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ d.type = ITHC_DATA_GET_FEATURE; ++ d.data = &reportnum; ++ d.size = 1; ++ ++ // Prepare for response. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = buf; ++ ithc->hid.get_feature_size = len; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ ++ // Transmit 'get feature' request. ++ int r = CHECK(ithc_dma_tx, ithc, &d); ++ if (!r) { ++ r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, ++ !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; ++ } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ ithc->hid.get_feature_buf = NULL; ++ if (!r) ++ r = ithc->hid.get_feature_size; ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ return r; ++ } ++ ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); ++ return -EINVAL; ++} ++ ++// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to ++// cast away the const to avoid a compiler warning... ++#define NOCONST(x) ((void *)x) ++ ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) ++{ ++ WARN_ON(!ithc->hid.dev); ++ if (!ithc->hid.dev) ++ return; ++ ++ switch (d->type) { ++ ++ case ITHC_DATA_IGNORE: ++ return; ++ ++ case ITHC_DATA_ERROR: ++ CHECK(ithc_reset, ithc); ++ return; ++ ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ // Response to the report descriptor request sent by ithc_hid_parse(). ++ CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); ++ WRITE_ONCE(ithc->hid.parse_done, true); ++ wake_up(&ithc->hid.wait_parse); ++ return; ++ ++ case ITHC_DATA_INPUT_REPORT: ++ { ++ // Standard HID input report. ++ int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); ++ if (r < 0) { ++ pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", ++ r, d->size, d->size ? *(u8 *)d->data : 0); ++ print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, ++ d->data, min(d->size, 0x400u), 0); ++ } ++ return; ++ } ++ ++ case ITHC_DATA_GET_FEATURE: ++ { ++ // Response to a 'get feature' request sent by ithc_hid_raw_request(). ++ bool done = false; ++ mutex_lock(&ithc->hid.get_feature_mutex); ++ if (ithc->hid.get_feature_buf) { ++ if (d->size < ithc->hid.get_feature_size) ++ ithc->hid.get_feature_size = d->size; ++ memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); ++ ithc->hid.get_feature_buf = NULL; ++ done = true; ++ } ++ mutex_unlock(&ithc->hid.get_feature_mutex); ++ if (done) { ++ wake_up(&ithc->hid.wait_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, ++ NOCONST(d->data), d->size, 1); ++ } ++ return; ++ } ++ ++ default: ++ pci_err(ithc->pci, "unhandled data type %i\n", d->type); ++ return; ++ } ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ ++ struct hid_device **hidm = res; ++ if (*hidm) ++ hid_destroy_device(*hidm); ++} ++ ++int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->vendor_id; ++ hid->product = ithc->product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid.dev = hid; ++ ++ init_waitqueue_head(&ithc->hid.wait_parse); ++ init_waitqueue_head(&ithc->hid.wait_get_feature); ++ mutex_init(&ithc->hid.get_feature_mutex); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h +new file mode 100644 +index 000000000000..599eb912c8c8 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-hid.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++enum ithc_data_type { ++ ITHC_DATA_IGNORE, ++ ITHC_DATA_RAW, ++ ITHC_DATA_ERROR, ++ ITHC_DATA_REPORT_DESCRIPTOR, ++ ITHC_DATA_INPUT_REPORT, ++ ITHC_DATA_OUTPUT_REPORT, ++ ITHC_DATA_GET_FEATURE, ++ ITHC_DATA_SET_FEATURE, ++}; ++ ++struct ithc_data { ++ enum ithc_data_type type; ++ u32 size; ++ const void *data; ++}; ++ ++struct ithc_hid { ++ struct hid_device *dev; ++ bool parse_done; ++ wait_queue_head_t wait_parse; ++ wait_queue_head_t wait_get_feature; ++ struct mutex get_feature_mutex; ++ void *get_feature_buf; ++ size_t get_feature_size; ++}; ++ ++int ithc_hid_init(struct ithc *ithc); ++void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); ++ +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +new file mode 100644 +index 000000000000..5c1da11e3f1d +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -0,0 +1,252 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++#include "ithc.h" ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) ++#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) ++#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) ++#define DEVCFG_SPI_CLKDIV_8 BIT(4) ++#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) ++#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) ++#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) ++#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) ++#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) ++#define DEVCFG_SPI_UNKNOWN_25 BIT(25) ++#define DEVCFG_SPI_UNKNOWN_26 BIT(26) ++#define DEVCFG_SPI_UNKNOWN_27 BIT(27) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? ++ ++struct ithc_device_config { // (Example values are from an SP7+.) ++ u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 error; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 touch_state; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ u16 vendor_id; // 1c = 0x045e = Microsoft Corp. ++ u16 product_id; // 1e = 0x0c1a ++ u32 revision; // 20 = 0x00000001 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) ++ u32 command; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) ++ u32 _unknown_30; // 30 = 0x00000000 ++ u8 eds_minor_ver; // 34 = 0x5e ++ u8 eds_major_ver; // 35 = 0x03 ++ u8 interface_rev; // 36 = 0x04 ++ u8 eu_kernel_ver; // 37 = 0x04 ++ u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++static_assert(sizeof(struct ithc_device_config) == 64); ++ ++#define RX_CODE_INPUT_REPORT 3 ++#define RX_CODE_FEATURE_REPORT 4 ++#define RX_CODE_REPORT_DESCRIPTOR 5 ++#define RX_CODE_RESET 7 ++ ++#define TX_CODE_SET_FEATURE 3 ++#define TX_CODE_GET_FEATURE 4 ++#define TX_CODE_OUTPUT_REPORT 5 ++#define TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->legacy_touch_cfg = ++ (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | ++ DEVCFG_TOUCH_HID_REPORT_ENABLE | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++int ithc_legacy_init(struct ithc *ithc) ++{ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. ++ CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); ++ ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ // Setting this bit may be necessary on some ADL devices. ++ switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); ++ break; ++ } ++ ++ // Take the touch device out of reset. ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); ++ if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) ++ break; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", ++ readl(&ithc->regs->irq_cause)); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ ++ // Read configuration data. ++ u32 spi_cfg; ++ for (int retries = 0; ; retries++) { ++ ithc_log_regs(ithc); ++ struct ithc_device_config config = { 0 }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); ++ u32 *p = (void *)&config; ++ pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", ++ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); ++ if (config.device_id == DEVCFG_DEVICE_ID_TIC) { ++ spi_cfg = config.spi_config; ++ ithc->vendor_id = config.vendor_id; ++ ithc->product_id = config.product_id; ++ ithc->product_rev = config.revision; ++ ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); ++ ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); ++ ithc->legacy_touch_cfg = config.touch_cfg; ++ ithc->have_config = true; ++ break; ++ } ++ if (retries > 10) { ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ config.device_id); ++ return -EIO; ++ } ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, ++ spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : ++ spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : ++ SPI_MODE_SINGLE, ++ SPI_MODE_SINGLE); ++ CHECK_RET(ithc_set_device_enabled, ithc, true); ++ ithc_log_regs(ithc); ++ return 0; ++} ++ ++void ithc_legacy_exit(struct ithc *ithc) ++{ ++ CHECK(ithc_set_device_enabled, ithc, false); ++} ++ ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++ } *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // Note: RX data is not padded, even though TX data must be padded. ++ if (len != sizeof(*hdr) + hdr->data_size) ++ return -EMSGSIZE; ++ ++ dest->data = hdr + 1; ++ dest->size = hdr->data_size; ++ ++ switch (hdr->code) { ++ case RX_CODE_RESET: ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case RX_CODE_REPORT_DESCRIPTOR: ++ // The descriptor is preceded by 8 nul bytes. ++ if (hdr->data_size < 8) ++ return -ENODATA; ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = (char *)(hdr + 1) + 8; ++ dest->size = hdr->data_size - 8; ++ return 0; ++ case RX_CODE_INPUT_REPORT: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ return 0; ++ case RX_CODE_FEATURE_REPORT: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct { ++ u32 code; ++ u32 data_size; ++ } *hdr = dest; ++ ++ size_t src_size = src->size; ++ const void *src_data = src->data; ++ const u64 get_report_desc_data = 0; ++ u32 code; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ code = TX_CODE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ code = TX_CODE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ code = TX_CODE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ code = TX_CODE_GET_REPORT_DESCRIPTOR; ++ src_size = sizeof(get_report_desc_data); ++ src_data = &get_report_desc_data; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->code = code; ++ hdr->data_size = src_size; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h +new file mode 100644 +index 000000000000..28d692462072 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-legacy.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++int ithc_legacy_init(struct ithc *ithc); ++void ithc_legacy_exit(struct ithc *ithc); ++int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +index 87ed4aa70fda..2acf02e41d40 100644 +--- a/drivers/hid/ithc/ithc-main.c ++++ b/drivers/hid/ithc/ithc-main.c +@@ -5,28 +5,6 @@ + MODULE_DESCRIPTION("Intel Touch Host Controller driver"); + MODULE_LICENSE("Dual BSD/GPL"); + +-// Lakefield +-#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 +-#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 +-// Tiger Lake +-#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 +-#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 +-#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 +-#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 +-// Alder Lake +-#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 +-#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 +-#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 +-#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 +-#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 +-#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 +-// Raptor Lake +-#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 +-#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 +-// Meteor Lake +-#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 +-#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a +- + static const struct pci_device_id ithc_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, +@@ -66,15 +44,13 @@ static bool ithc_use_rx1 = true; + module_param_named(rx1, ithc_use_rx1, bool, 0); + MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); + +-// Values below 250 seem to work well on the SP7+. If this is set too high, you may observe cursor stuttering. +-static int ithc_dma_latency_us = 200; +-module_param_named(dma_latency_us, ithc_dma_latency_us, int, 0); +-MODULE_PARM_DESC(dma_latency_us, "Determines the CPU latency QoS value for DMA transfers (in microseconds), -1 to disable latency QoS"); ++static int ithc_active_ltr_us = -1; ++module_param_named(activeltr, ithc_active_ltr_us, int, 0); ++MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); + +-// Values above 1700 seem to work well on the SP7+. If this is set too low, you may observe cursor stuttering. +-static unsigned int ithc_dma_early_us = 2000; +-module_param_named(dma_early_us, ithc_dma_early_us, uint, 0); +-MODULE_PARM_DESC(dma_early_us, "Determines how early the CPU latency QoS value is applied before the next expected IRQ (in microseconds)"); ++static int ithc_idle_ltr_us = -1; ++module_param_named(idleltr, ithc_idle_ltr_us, int, 0); ++MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); + + static bool ithc_log_regs_enabled = false; + module_param_named(logregs, ithc_log_regs_enabled, bool, 0); +@@ -82,44 +58,30 @@ MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); + + // Sysfs attributes + +-static bool ithc_is_config_valid(struct ithc *ithc) +-{ +- return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; +-} +- + static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) + { + struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) ++ if (!ithc || !ithc->have_config) + return -ENODEV; +- return sprintf(buf, "0x%04x", ithc->config.vendor_id); ++ return sprintf(buf, "0x%04x", ithc->vendor_id); + } + static DEVICE_ATTR_RO(vendor); + static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) + { + struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) ++ if (!ithc || !ithc->have_config) + return -ENODEV; +- return sprintf(buf, "0x%04x", ithc->config.product_id); ++ return sprintf(buf, "0x%04x", ithc->product_id); + } + static DEVICE_ATTR_RO(product); + static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) + { + struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) ++ if (!ithc || !ithc->have_config) + return -ENODEV; +- return sprintf(buf, "%u", ithc->config.revision); ++ return sprintf(buf, "%u", ithc->product_rev); + } + static DEVICE_ATTR_RO(revision); +-static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) +-{ +- struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) +- return -ENODEV; +- u32 v = ithc->config.fw_version; +- return sprintf(buf, "%i.%i.%i.%i", v >> 24, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff); +-} +-static DEVICE_ATTR_RO(fw_version); + + static const struct attribute_group *ithc_attribute_groups[] = { + &(const struct attribute_group){ +@@ -128,185 +90,26 @@ static const struct attribute_group *ithc_attribute_groups[] = { + &dev_attr_vendor.attr, + &dev_attr_product.attr, + &dev_attr_revision.attr, +- &dev_attr_fw_version.attr, + NULL + }, + }, + NULL + }; + +-// HID setup +- +-static int ithc_hid_start(struct hid_device *hdev) { return 0; } +-static void ithc_hid_stop(struct hid_device *hdev) { } +-static int ithc_hid_open(struct hid_device *hdev) { return 0; } +-static void ithc_hid_close(struct hid_device *hdev) { } +- +-static int ithc_hid_parse(struct hid_device *hdev) +-{ +- struct ithc *ithc = hdev->driver_data; +- u64 val = 0; +- WRITE_ONCE(ithc->hid_parse_done, false); +- for (int retries = 0; ; retries++) { +- CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof(val), &val); +- if (wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), +- msecs_to_jiffies(200))) +- return 0; +- if (retries > 5) { +- pci_err(ithc->pci, "failed to read report descriptor\n"); +- return -ETIMEDOUT; +- } +- pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); +- } +-} +- +-static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, +- size_t len, unsigned char rtype, int reqtype) +-{ +- struct ithc *ithc = hdev->driver_data; +- if (!buf || !len) +- return -EINVAL; +- u32 code; +- if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { +- code = DMA_TX_CODE_OUTPUT_REPORT; +- } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { +- code = DMA_TX_CODE_SET_FEATURE; +- } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { +- code = DMA_TX_CODE_GET_FEATURE; +- } else { +- pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", +- rtype, reqtype, reportnum); +- return -EINVAL; +- } +- buf[0] = reportnum; +- +- if (reqtype == HID_REQ_GET_REPORT) { +- // Prepare for response. +- mutex_lock(&ithc->hid_get_feature_mutex); +- ithc->hid_get_feature_buf = buf; +- ithc->hid_get_feature_size = len; +- mutex_unlock(&ithc->hid_get_feature_mutex); +- +- // Transmit 'get feature' request. +- int r = CHECK(ithc_dma_tx, ithc, code, 1, buf); +- if (!r) { +- r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, +- !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); +- if (!r) +- r = -ETIMEDOUT; +- else if (r < 0) +- r = -EINTR; +- else +- r = 0; +- } +- +- // If everything went ok, the buffer has been filled with the response data. +- // Return the response size. +- mutex_lock(&ithc->hid_get_feature_mutex); +- ithc->hid_get_feature_buf = NULL; +- if (!r) +- r = ithc->hid_get_feature_size; +- mutex_unlock(&ithc->hid_get_feature_mutex); +- return r; +- } +- +- // 'Set feature', or 'output report'. These don't have a response. +- CHECK_RET(ithc_dma_tx, ithc, code, len, buf); +- return 0; +-} +- +-static struct hid_ll_driver ithc_ll_driver = { +- .start = ithc_hid_start, +- .stop = ithc_hid_stop, +- .open = ithc_hid_open, +- .close = ithc_hid_close, +- .parse = ithc_hid_parse, +- .raw_request = ithc_hid_raw_request, +-}; +- +-static void ithc_hid_devres_release(struct device *dev, void *res) +-{ +- struct hid_device **hidm = res; +- if (*hidm) +- hid_destroy_device(*hidm); +-} +- +-static int ithc_hid_init(struct ithc *ithc) +-{ +- struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); +- if (!hidm) +- return -ENOMEM; +- devres_add(&ithc->pci->dev, hidm); +- struct hid_device *hid = hid_allocate_device(); +- if (IS_ERR(hid)) +- return PTR_ERR(hid); +- *hidm = hid; +- +- strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); +- strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); +- hid->ll_driver = &ithc_ll_driver; +- hid->bus = BUS_PCI; +- hid->vendor = ithc->config.vendor_id; +- hid->product = ithc->config.product_id; +- hid->version = 0x100; +- hid->dev.parent = &ithc->pci->dev; +- hid->driver_data = ithc; +- +- ithc->hid = hid; +- return 0; +-} +- + // Interrupts/polling + +-static enum hrtimer_restart ithc_activity_start_timer_callback(struct hrtimer *t) +-{ +- struct ithc *ithc = container_of(t, struct ithc, activity_start_timer); +- ithc_set_active(ithc, ithc_dma_early_us * 2 + USEC_PER_MSEC); +- return HRTIMER_NORESTART; +-} +- +-static enum hrtimer_restart ithc_activity_end_timer_callback(struct hrtimer *t) +-{ +- struct ithc *ithc = container_of(t, struct ithc, activity_end_timer); +- cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); +- return HRTIMER_NORESTART; +-} +- +-void ithc_set_active(struct ithc *ithc, unsigned int duration_us) +-{ +- if (ithc_dma_latency_us < 0) +- return; +- // When CPU usage is very low, the CPU can enter various low power states (C2-C10). +- // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_RX_TIMEOUT will be +- // set when this happens. The amount of truncated messages can become very high, resulting +- // in user-visible effects (laggy/stuttering cursor). To avoid this, we use a CPU latency +- // QoS request to prevent the CPU from entering low power states during touch interactions. +- cpu_latency_qos_update_request(&ithc->activity_qos, ithc_dma_latency_us); +- hrtimer_start_range_ns(&ithc->activity_end_timer, +- ns_to_ktime(duration_us * NSEC_PER_USEC), duration_us * NSEC_PER_USEC, HRTIMER_MODE_REL); +-} +- +-static int ithc_set_device_enabled(struct ithc *ithc, bool enable) +-{ +- u32 x = ithc->config.touch_cfg = +- (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 | +- (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); +- return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, +- offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); +-} +- + static void ithc_disable_interrupts(struct ithc *ithc) + { + writel(0, &ithc->regs->error_control); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); +- bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); +- bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); + } + + static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) + { +- writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, + &ithc->regs->dma_rx[channel].status); + } + +@@ -325,39 +128,22 @@ static void ithc_process(struct ithc *ithc) + { + ithc_log_regs(ithc); + ++ // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. ++ // It does not appear to automatically go back to idle, so we switch it back here, since ++ // the DMA transfer should be complete. ++ ithc_set_ltr_idle(ithc); ++ + bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; + bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; + +- // Track time between DMA rx transfers, so we can try to predict when we need to enable CPU latency QoS for the next transfer +- ktime_t t = ktime_get(); +- ktime_t dt = ktime_sub(t, ithc->last_rx_time); +- if (rx0 || rx1) { +- ithc->last_rx_time = t; +- if (dt > ms_to_ktime(100)) { +- ithc->cur_rx_seq_count = 0; +- ithc->cur_rx_seq_errors = 0; +- } +- ithc->cur_rx_seq_count++; +- if (!ithc_use_polling && ithc_dma_latency_us >= 0) { +- // Disable QoS, since the DMA transfer has completed (we re-enable it after a delay below) +- cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); +- hrtimer_try_to_cancel(&ithc->activity_end_timer); +- } +- } +- + // Read and clear error bits + u32 err = readl(&ithc->regs->error_flags); + if (err) { + writel(err, &ithc->regs->error_flags); + if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) + pci_err(ithc->pci, "error flags: 0x%08x\n", err); +- if (err & ERROR_FLAG_DMA_RX_TIMEOUT) { +- // Only log an error if we see a significant number of these errors. +- ithc->cur_rx_seq_errors++; +- if (ithc->cur_rx_seq_errors && ithc->cur_rx_seq_errors % 50 == 0 && ithc->cur_rx_seq_errors > ithc->cur_rx_seq_count / 10) +- pci_err(ithc->pci, "High number of DMA RX timeouts/errors (%u/%u, dt=%lldus). Try adjusting dma_early_us and/or dma_latency_us.\n", +- ithc->cur_rx_seq_errors, ithc->cur_rx_seq_count, ktime_to_us(dt)); +- } ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); + } + + // Process DMA rx +@@ -372,12 +158,6 @@ static void ithc_process(struct ithc *ithc) + ithc_dma_rx(ithc, 1); + } + +- // Start timer to re-enable QoS for next rx, but only if we've seen an ERROR_FLAG_DMA_RX_TIMEOUT +- if ((rx0 || rx1) && !ithc_use_polling && ithc_dma_latency_us >= 0 && ithc->cur_rx_seq_errors > 0) { +- ktime_t expires = ktime_add(t, ktime_sub_us(dt, ithc_dma_early_us)); +- hrtimer_start_range_ns(&ithc->activity_start_timer, expires, 10 * NSEC_PER_USEC, HRTIMER_MODE_ABS); +- } +- + ithc_log_regs(ithc); + } + +@@ -403,12 +183,8 @@ static int ithc_poll_thread(void *arg) + ithc_process(ithc); + // Decrease polling interval to 20ms if we received data, otherwise slowly + // increase it up to 200ms. +- if (n != ithc->dma_rx[1].num_received) { +- ithc_set_active(ithc, 100 * USEC_PER_MSEC); +- sleep = 20; +- } else { +- sleep = min(200u, sleep + (sleep >> 4) + 1); +- } ++ sleep = n != ithc->dma_rx[1].num_received ? 20 ++ : min(200u, sleep + (sleep >> 4) + 1); + msleep_interruptible(sleep); + } + return 0; +@@ -431,73 +207,44 @@ static void ithc_disable(struct ithc *ithc) + + static int ithc_init_device(struct ithc *ithc) + { ++ // Read ACPI config for QuickSPI mode ++ struct ithc_acpi_config cfg = { 0 }; ++ CHECK_RET(ithc_read_acpi_config, ithc, &cfg); ++ if (!cfg.has_config) ++ pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); ++ else ++ ithc_print_acpi_config(ithc, &cfg); ++ ithc->use_quickspi = cfg.has_config; ++ ++ // Shut down device + ithc_log_regs(ithc); + bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; + ithc_disable(ithc); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); +- +- // Since we don't yet know which SPI config the device wants, use default speed and mode +- // initially for reading config data. +- ithc_set_spi_config(ithc, 10, 0); +- +- // Setting the following bit seems to make reading the config more reliable. +- bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); ++ ithc_log_regs(ithc); + + // If the device was previously enabled, wait a bit to make sure it's fully shut down. + if (was_enabled) + if (msleep_interruptible(100)) + return -EINTR; + +- // Take the touch device out of reset. +- bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); +- CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); +- for (int retries = 0; ; retries++) { +- ithc_log_regs(ithc); +- bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); +- if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) +- break; +- if (retries > 5) { +- pci_err(ithc->pci, "failed to reset device, state = 0x%08x\n", readl(&ithc->regs->state)); +- return -ETIMEDOUT; +- } +- pci_warn(ithc->pci, "invalid state, retrying reset\n"); +- bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); +- if (msleep_interruptible(1000)) +- return -EINTR; +- } +- ithc_log_regs(ithc); ++ // Set Latency Tolerance Reporting config. The device will automatically ++ // apply these values depending on whether it is active or idle. ++ // If active value is too high, DMA buffer data can become truncated. ++ // By default, we set the active LTR value to 100us, and idle to 100ms. ++ u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 ++ : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 ++ : 100 * 1000; ++ u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 ++ : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 ++ : 100 * 1000 * 1000; ++ ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); ++ ++ if (ithc->use_quickspi) ++ CHECK_RET(ithc_quickspi_init, ithc, &cfg); ++ else ++ CHECK_RET(ithc_legacy_init, ithc); + +- // Waiting for the following status bit makes reading config much more reliable, +- // however the official driver does not seem to do this... +- CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); +- +- // Read configuration data. +- for (int retries = 0; ; retries++) { +- ithc_log_regs(ithc); +- memset(&ithc->config, 0, sizeof(ithc->config)); +- CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(ithc->config), &ithc->config); +- u32 *p = (void *)&ithc->config; +- pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", +- p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); +- if (ithc_is_config_valid(ithc)) +- break; +- if (retries > 10) { +- pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", +- ithc->config.device_id); +- return -EIO; +- } +- pci_warn(ithc->pci, "failed to read config, retrying\n"); +- if (msleep_interruptible(100)) +- return -EINTR; +- } +- ithc_log_regs(ithc); +- +- // Apply SPI config and enable touch device. +- CHECK_RET(ithc_set_spi_config, ithc, +- DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), +- DEVCFG_SPI_MODE(ithc->config.spi_config)); +- CHECK_RET(ithc_set_device_enabled, ithc, true); +- ithc_log_regs(ithc); + return 0; + } + +@@ -527,11 +274,11 @@ static void ithc_stop(void *res) + CHECK(kthread_stop, ithc->poll_thread); + if (ithc->irq >= 0) + disable_irq(ithc->irq); +- CHECK(ithc_set_device_enabled, ithc, false); ++ if (ithc->use_quickspi) ++ ithc_quickspi_exit(ithc); ++ else ++ ithc_legacy_exit(ithc); + ithc_disable(ithc); +- hrtimer_cancel(&ithc->activity_start_timer); +- hrtimer_cancel(&ithc->activity_end_timer); +- cpu_latency_qos_remove_request(&ithc->activity_qos); + + // Clear DMA config. + for (unsigned int i = 0; i < 2; i++) { +@@ -570,9 +317,6 @@ static int ithc_start(struct pci_dev *pci) + ithc->irq = -1; + ithc->pci = pci; + snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); +- init_waitqueue_head(&ithc->wait_hid_parse); +- init_waitqueue_head(&ithc->wait_hid_get_feature); +- mutex_init(&ithc->hid_get_feature_mutex); + pci_set_drvdata(pci, ithc); + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); + if (ithc_log_regs_enabled) +@@ -596,6 +340,9 @@ static int ithc_start(struct pci_dev *pci) + + // Initialize THC and touch device. + CHECK_RET(ithc_init_device, ithc); ++ ++ // Initialize HID and DMA. ++ CHECK_RET(ithc_hid_init, ithc); + CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); + if (ithc_use_rx0) + CHECK_RET(ithc_dma_rx_init, ithc, 0); +@@ -603,18 +350,10 @@ static int ithc_start(struct pci_dev *pci) + CHECK_RET(ithc_dma_rx_init, ithc, 1); + CHECK_RET(ithc_dma_tx_init, ithc); + +- cpu_latency_qos_add_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); +- hrtimer_init(&ithc->activity_start_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); +- ithc->activity_start_timer.function = ithc_activity_start_timer_callback; +- hrtimer_init(&ithc->activity_end_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); +- ithc->activity_end_timer.function = ithc_activity_end_timer_callback; +- + // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are + // disabled BEFORE the buffers are freed. + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); + +- CHECK_RET(ithc_hid_init, ithc); +- + // Start polling/IRQ. + if (ithc_use_polling) { + pci_info(pci, "using polling instead of irq\n"); +@@ -637,9 +376,11 @@ static int ithc_start(struct pci_dev *pci) + + // hid_add_device() can only be called after irq/polling is started and DMA is enabled, + // because it calls ithc_hid_parse() which reads the report descriptor via DMA. +- CHECK_RET(hid_add_device, ithc->hid); ++ CHECK_RET(hid_add_device, ithc->hid.dev); ++ ++ CHECK(ithc_debug_init_device, ithc); + +- CHECK(ithc_debug_init, ithc); ++ ithc_set_ltr_idle(ithc); + + pci_dbg(pci, "started\n"); + return 0; +@@ -710,17 +451,20 @@ static struct pci_driver ithc_driver = { + .thaw = ithc_thaw, + .restore = ithc_restore, + }, ++ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, + //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway + }; + + static int __init ithc_init(void) + { ++ ithc_debug_init_module(); + return pci_register_driver(&ithc_driver); + } + + static void __exit ithc_exit(void) + { + pci_unregister_driver(&ithc_driver); ++ ithc_debug_exit_module(); + } + + module_init(ithc_init); +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +new file mode 100644 +index 000000000000..760e55ead078 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -0,0 +1,578 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ ++// Some public THC/QuickSPI documentation can be found in: ++// - Intel Firmware Support Package repo: https://github.com/intel/FSP ++// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 ++ ++#include "ithc.h" ++ ++static const guid_t guid_hidspi = ++ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); ++static const guid_t guid_thc_quickspi = ++ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); ++static const guid_t guid_thc_ltr = ++ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); ++ ++// TODO The HIDSPI spec says revision should be 3. Should we try both? ++#define DSM_REV 2 ++ ++struct hidspi_header { ++ u8 type; ++ u16 len; ++ u8 id; ++} __packed; ++static_assert(sizeof(struct hidspi_header) == 4); ++ ++#define HIDSPI_INPUT_TYPE_DATA 1 ++#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 ++#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 ++#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 ++#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 ++#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 ++#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 ++#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 ++#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 ++ ++#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 ++#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 ++#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 ++#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 ++#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 ++#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 ++#define HIDSPI_OUTPUT_TYPE_COMMAND 7 ++ ++struct hidspi_device_descriptor { ++ u16 wDeviceDescLength; ++ u16 bcdVersion; ++ u16 wReportDescLength; ++ u16 wMaxInputLength; ++ u16 wMaxOutputLength; ++ u16 wMaxFragmentLength; ++ u16 wVendorID; ++ u16 wProductID; ++ u16 wVersionID; ++ u16 wFlags; ++ u32 dwReserved; ++}; ++static_assert(sizeof(struct hidspi_device_descriptor) == 24); ++ ++static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_INTEGER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); ++ *dest = (u32)o->integer.value; ++ ACPI_FREE(o); ++ return 1; ++} ++ ++static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) ++{ ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); ++ if (!o) ++ return 0; ++ if (o->type != ACPI_TYPE_BUFFER) { ++ pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", ++ guid, func, o->type); ++ ACPI_FREE(o); ++ return -1; ++ } ++ if (o->buffer.length != len) { ++ pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", ++ guid, func, o->buffer.length, len); ++ ACPI_FREE(o); ++ return -1; ++ } ++ memcpy(dest, o->buffer.pointer, len); ++ pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); ++ ACPI_FREE(o); ++ return 1; ++} ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) ++{ ++ int r; ++ acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); ++ ++ cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); ++ if (!cfg->has_config) ++ return 0; ++ ++ // HIDSPI settings ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_header_address = r > 0; ++ if (r > 0 && cfg->input_report_header_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report header address 0x%x\n", ++ cfg->input_report_header_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_input_report_body_address = r > 0; ++ if (r > 0 && cfg->input_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid input report body address 0x%x\n", ++ cfg->input_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); ++ if (r < 0) ++ return r; ++ cfg->has_output_report_body_address = r > 0; ++ if (r > 0 && cfg->output_report_body_address > 0xffffff) { ++ pci_err(ithc->pci, "Invalid output report body address 0x%x\n", ++ cfg->output_report_body_address); ++ return -1; ++ } ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_read_opcode = r > 0; ++ ++ r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); ++ if (r < 0) ++ return r; ++ cfg->has_write_opcode = r > 0; ++ ++ u32 flags; ++ r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); ++ if (r < 0) ++ return r; ++ cfg->has_read_mode = cfg->has_write_mode = r > 0; ++ if (r > 0) { ++ cfg->read_mode = (flags >> 14) & 3; ++ cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; ++ } ++ ++ // Quick SPI settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); ++ if (r < 0) ++ return r; ++ cfg->has_spi_frequency = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); ++ if (r < 0) ++ return r; ++ cfg->has_limit_packet_size = r > 0; ++ ++ r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); ++ if (r < 0) ++ return r; ++ cfg->has_tx_delay = r > 0; ++ if (r > 0) ++ cfg->tx_delay &= 0xffff; ++ ++ // LTR settings ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_active_ltr = r > 0; ++ if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { ++ if (cfg->active_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", ++ cfg->active_ltr); ++ cfg->active_ltr = 500; ++ } ++ ++ r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); ++ if (r < 0) ++ return r; ++ cfg->has_idle_ltr = r > 0; ++ if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { ++ if (cfg->idle_ltr != 0xffffffff) ++ pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", ++ cfg->idle_ltr); ++ cfg->idle_ltr = 500; ++ if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) ++ cfg->idle_ltr = cfg->active_ltr; ++ } ++ ++ return 0; ++} ++ ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ if (!cfg->has_config) { ++ pci_info(ithc->pci, "No ACPI config"); ++ return; ++ } ++ ++ char input_report_header_address[16] = "-"; ++ if (cfg->has_input_report_header_address) ++ sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); ++ char input_report_body_address[16] = "-"; ++ if (cfg->has_input_report_body_address) ++ sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); ++ char output_report_body_address[16] = "-"; ++ if (cfg->has_output_report_body_address) ++ sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); ++ char read_opcode[16] = "-"; ++ if (cfg->has_read_opcode) ++ sprintf(read_opcode, "0x%02x", cfg->read_opcode); ++ char write_opcode[16] = "-"; ++ if (cfg->has_write_opcode) ++ sprintf(write_opcode, "0x%02x", cfg->write_opcode); ++ char read_mode[16] = "-"; ++ if (cfg->has_read_mode) ++ sprintf(read_mode, "%i", cfg->read_mode); ++ char write_mode[16] = "-"; ++ if (cfg->has_write_mode) ++ sprintf(write_mode, "%i", cfg->write_mode); ++ char spi_frequency[16] = "-"; ++ if (cfg->has_spi_frequency) ++ sprintf(spi_frequency, "%u", cfg->spi_frequency); ++ char limit_packet_size[16] = "-"; ++ if (cfg->has_limit_packet_size) ++ sprintf(limit_packet_size, "%u", cfg->limit_packet_size); ++ char tx_delay[16] = "-"; ++ if (cfg->has_tx_delay) ++ sprintf(tx_delay, "%u", cfg->tx_delay); ++ char active_ltr[16] = "-"; ++ if (cfg->has_active_ltr) ++ sprintf(active_ltr, "%u", cfg->active_ltr); ++ char idle_ltr[16] = "-"; ++ if (cfg->has_idle_ltr) ++ sprintf(idle_ltr, "%u", cfg->idle_ltr); ++ ++ pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", ++ input_report_header_address, input_report_body_address, output_report_body_address, ++ read_opcode, write_opcode, read_mode, write_mode, ++ spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); ++} ++ ++static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); ++ ++ // SPI frequency and mode ++ if (!cfg->has_spi_frequency || !cfg->spi_frequency) { ++ pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); ++ return -EINVAL; ++ } ++ unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); ++ bool clkdiv8 = clkdiv > 7; ++ if (clkdiv8) ++ clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); ++ if (!clkdiv) ++ clkdiv = 1; ++ CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, ++ cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, ++ cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); ++ ++ // SPI addresses and opcodes ++ if (cfg->has_input_report_header_address) ++ writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); ++ if (cfg->has_input_report_body_address) ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ if (cfg->has_output_report_body_address) ++ writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); ++ ++ if (cfg->has_read_opcode) { ++ writeb(cfg->read_opcode, &ithc->regs->read_opcode); ++ writeb(cfg->read_opcode, &ithc->regs->read_opcode_single); ++ writeb(cfg->read_opcode, &ithc->regs->read_opcode_dual); ++ writeb(cfg->read_opcode, &ithc->regs->read_opcode_quad); ++ } ++ if (cfg->has_write_opcode) { ++ writeb(cfg->write_opcode, &ithc->regs->write_opcode); ++ writeb(cfg->write_opcode, &ithc->regs->write_opcode_single); ++ writeb(cfg->write_opcode, &ithc->regs->write_opcode_dual); ++ writeb(cfg->write_opcode, &ithc->regs->write_opcode_quad); ++ } ++ ithc_log_regs(ithc); ++ ++ // The rest... ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), ++ QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | ++ QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); ++ ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(0xff), ++ QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | ++ QUICKSPI_CONFIG2_UNKNOWN_12(2)); ++ ++ u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; ++ bitsl(&ithc->regs->spi_config, ++ SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), ++ SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); ++ ++ bitsl_set(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); ++ bitsl(&ithc->regs->quickspi_config2, ++ QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | ++ QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); ++ ++ return 0; ++} ++ ++static int wait_for_report(struct ithc *ithc) ++{ ++ CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, ++ DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); ++ writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); ++ ++ u32 h = readl(&ithc->regs->input_header); ++ ithc_log_regs(ithc); ++ if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE ++ || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { ++ pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); ++ return -ENODATA; ++ } ++ return INPUT_HEADER_REPORT_LENGTH(h) * 4; ++} ++ ++static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ pci_dbg(ithc->pci, "initializing HIDSPI\n"); ++ ++ // HIDSPI initialization sequence: ++ // "1. The host shall invoke the ACPI reset method to clear the device state." ++ acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); ++ if (ACPI_FAILURE(s)) { ++ pci_err(ithc->pci, "ACPI reset failed\n"); ++ return -EIO; ++ } ++ ++ bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); ++ ++ // "2. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing a device reset response." ++ int size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(struct hidspi_header)) { ++ pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "3. The host shall read the reset response from the device at the Input Report addresses ++ // specified in ACPI." ++ u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; ++ struct { ++ struct hidspi_header header; ++ union { ++ struct hidspi_device_descriptor device_desc; ++ u32 data[16]; ++ }; ++ } resp = { 0 }; ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { ++ pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); ++ return -ENOMSG; ++ } ++ ++ // "4. The host shall then write an Output Report to the device at the Output Report Address ++ // specified in ACPI, requesting the Device Descriptor from the device." ++ u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; ++ struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); ++ ++ // "5. Within 1 second, the device shall signal an interrupt and make available to the host ++ // an input report containing the Device Descriptor." ++ size = wait_for_report(ithc); ++ if (size < 0) ++ return size; ++ if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { ++ pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ ++ // "6. The host shall read the Device Descriptor from the Input Report addresses specified ++ // in ACPI." ++ if (size > sizeof(resp)) { ++ pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); ++ return -EMSGSIZE; ++ } ++ memset(&resp, 0, sizeof(resp)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); ++ if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { ++ pci_err(ithc->pci, "received type %i instead of device descriptor\n", ++ resp.header.type); ++ return -ENOMSG; ++ } ++ struct hidspi_device_descriptor *d = &resp.device_desc; ++ if (resp.header.len < sizeof(*d)) { ++ pci_err(ithc->pci, "response too small for device descriptor (%u)\n", ++ resp.header.len); ++ return -EMSGSIZE; ++ } ++ if (d->wDeviceDescLength != sizeof(*d)) { ++ pci_err(ithc->pci, "invalid device descriptor length (%u)\n", ++ d->wDeviceDescLength); ++ return -EMSGSIZE; ++ } ++ ++ pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", ++ d->bcdVersion, d->wReportDescLength, ++ d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, ++ d->wVendorID, d->wProductID, d->wVersionID, ++ d->wFlags, d->dwReserved); ++ ++ ithc->vendor_id = d->wVendorID; ++ ithc->product_id = d->wProductID; ++ ithc->product_rev = d->wVersionID; ++ ithc->max_rx_size = max_t(u32, d->wMaxInputLength, ++ d->wReportDescLength + sizeof(struct hidspi_header)); ++ ithc->max_tx_size = d->wMaxOutputLength; ++ ithc->have_config = true; ++ ++ // "7. The device and host shall then enter their "Ready" states - where the device may ++ // begin sending Input Reports, and the device shall be prepared for Output Reports from ++ // the host." ++ ++ return 0; ++} ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) ++{ ++ bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); ++ CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); ++ ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); ++ ithc_log_regs(ithc); ++ CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); ++ ithc_log_regs(ithc); ++ ++ // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, ++ // otherwise DMA will not work. Maybe selects between DMA and PIO mode? ++ bitsl(&ithc->regs->quickspi_config1, ++ QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); ++ ++ // TODO Do we need to set any of the following bits here? ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); ++ //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); ++ //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ ++ ithc_log_regs(ithc); ++ ++ return 0; ++} ++ ++void ithc_quickspi_exit(struct ithc *ithc) ++{ ++ // TODO Should we send HIDSPI 'power off' command? ++ //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; ++ //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; ++ //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() ++} ++ ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) ++{ ++ const struct hidspi_header *hdr = src; ++ ++ if (len < sizeof(*hdr)) ++ return -ENODATA; ++ // TODO Do we need to handle HIDSPI packet fragmentation? ++ if (len < sizeof(*hdr) + hdr->len) ++ return -EMSGSIZE; ++ if (len > round_up(sizeof(*hdr) + hdr->len, 4)) ++ return -EMSGSIZE; ++ ++ switch (hdr->type) { ++ case HIDSPI_INPUT_TYPE_RESET_RESPONSE: ++ // TODO "When the device detects an error condition, it may interrupt and make ++ // available to the host an Input Report containing an unsolicited Reset Response. ++ // After receiving an unsolicited Reset Response, the host shall initiate the ++ // request procedure from step (4) in the [HIDSPI initialization] process." ++ dest->type = ITHC_DATA_ERROR; ++ return 0; ++ case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: ++ dest->type = ITHC_DATA_REPORT_DESCRIPTOR; ++ dest->data = hdr + 1; ++ dest->size = hdr->len; ++ return 0; ++ case HIDSPI_INPUT_TYPE_DATA: ++ case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_INPUT_REPORT; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: ++ dest->type = ITHC_DATA_GET_FEATURE; ++ dest->data = &hdr->id; ++ dest->size = hdr->len + 1; ++ return 0; ++ case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: ++ case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: ++ dest->type = ITHC_DATA_IGNORE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen) ++{ ++ struct hidspi_header *hdr = dest; ++ ++ size_t src_size = src->size; ++ const u8 *src_data = src->data; ++ u8 type; ++ ++ switch (src->type) { ++ case ITHC_DATA_SET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; ++ break; ++ case ITHC_DATA_GET_FEATURE: ++ type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; ++ break; ++ case ITHC_DATA_OUTPUT_REPORT: ++ type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; ++ break; ++ case ITHC_DATA_REPORT_DESCRIPTOR: ++ type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; ++ src_size = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ u8 id = 0; ++ if (src_size) { ++ id = *src_data++; ++ src_size--; ++ } ++ ++ // Data must be padded to next 4-byte boundary. ++ size_t padded = round_up(src_size, 4); ++ if (sizeof(*hdr) + padded > maxlen) ++ return -EOVERFLOW; ++ ++ // Fill the TX buffer with header and data. ++ hdr->type = type; ++ hdr->len = (u16)src_size; ++ hdr->id = id; ++ memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); ++ ++ return sizeof(*hdr) + padded; ++} ++ +diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h +new file mode 100644 +index 000000000000..74d882f6b2f0 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-quickspi.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ ++struct ithc_acpi_config { ++ bool has_config: 1; ++ bool has_input_report_header_address: 1; ++ bool has_input_report_body_address: 1; ++ bool has_output_report_body_address: 1; ++ bool has_read_opcode: 1; ++ bool has_write_opcode: 1; ++ bool has_read_mode: 1; ++ bool has_write_mode: 1; ++ bool has_spi_frequency: 1; ++ bool has_limit_packet_size: 1; ++ bool has_tx_delay: 1; ++ bool has_active_ltr: 1; ++ bool has_idle_ltr: 1; ++ u32 input_report_header_address; ++ u32 input_report_body_address; ++ u32 output_report_body_address; ++ u8 read_opcode; ++ u8 write_opcode; ++ u8 read_mode; ++ u8 write_mode; ++ u32 spi_frequency; ++ u32 limit_packet_size; ++ u32 tx_delay; // us/10 // TODO use? ++ u32 active_ltr; // ns/1024 ++ u32 idle_ltr; // ns/1024 ++}; ++ ++int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); ++void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++ ++int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); ++void ithc_quickspi_exit(struct ithc *ithc); ++int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); ++ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, ++ size_t maxlen); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +index e058721886e3..c0f13506af20 100644 +--- a/drivers/hid/ithc/ithc-regs.c ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -22,46 +22,104 @@ void bitsb(__iomem u8 *reg, u8 mask, u8 val) + + int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) + { ++ ithc_log_regs(ithc); + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", + reg_num(reg), mask, val); + u32 x; + if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", + reg_num(reg), mask, val); + return -ETIMEDOUT; + } ++ ithc_log_regs(ithc); + pci_dbg(ithc->pci, "done waiting\n"); + return 0; + } + + int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) + { ++ ithc_log_regs(ithc); + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", + reg_num(reg), mask, val); + u8 x; + if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { ++ ithc_log_regs(ithc); + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", + reg_num(reg), mask, val); + return -ETIMEDOUT; + } ++ ithc_log_regs(ithc); + pci_dbg(ithc->pci, "done waiting\n"); + return 0; + } + +-int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) ++static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) + { +- pci_dbg(ithc->pci, "setting SPI speed to %i, mode %i\n", speed, mode); +- if (mode == 3) +- mode = 2; ++ unsigned int s = 0; ++ u64 v = *ns; ++ while (v > 0x3ff) { ++ s++; ++ v >>= 5; ++ } ++ if (s > 5) { ++ s = 5; ++ v = 0x3ff; ++ } ++ *val = v; ++ *scale = s; ++ *ns = v << (5 * s); ++} ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) ++{ ++ unsigned int active_val, active_scale, idle_val, idle_scale; ++ calc_ltr(&active_ltr_ns, &active_val, &active_scale); ++ calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); ++ pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", ++ active_ltr_ns, idle_ltr_ns); ++ writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | ++ LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | ++ LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), ++ &ithc->regs->ltr_config); ++} ++ ++void ithc_set_ltr_idle(struct ithc *ithc) ++{ ++ u32 ltr = readl(&ithc->regs->ltr_config); ++ switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { ++ case LTR_CONFIG_STATUS_IDLE: ++ break; ++ case LTR_CONFIG_STATUS_ACTIVE: ++ writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); ++ break; ++ default: ++ pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); ++ break; ++ } ++} ++ ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) ++{ ++ if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) ++ return -EINVAL; ++ static const char * const modes[] = { "single", "dual", "quad" }; ++ pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", ++ SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), ++ modes[read_mode], modes[write_mode]); + bitsl(&ithc->regs->spi_config, +- SPI_CONFIG_MODE(0xff) | SPI_CONFIG_SPEED(0xff) | SPI_CONFIG_UNKNOWN_18(0xff) | SPI_CONFIG_SPEED2(0xff), +- SPI_CONFIG_MODE(mode) | SPI_CONFIG_SPEED(speed) | SPI_CONFIG_UNKNOWN_18(0) | SPI_CONFIG_SPEED2(speed)); ++ SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | ++ SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | ++ SPI_CONFIG_CLKDIV_8, ++ SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | ++ SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | ++ (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); + return 0; + } + + int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) + { +- pci_dbg(ithc->pci, "SPI command %u, size %u, offset %u\n", command, size, offset); ++ pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); + if (size > sizeof(ithc->regs->spi_cmd.data)) + return -EINVAL; + +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +index d4007d9e2bac..a9d236454644 100644 +--- a/drivers/hid/ithc/ithc-regs.h ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -1,14 +1,34 @@ + /* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + ++#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) ++#define LTR_CONFIG_TOGGLE BIT(1) ++#define LTR_CONFIG_ENABLE_IDLE BIT(2) ++#define LTR_CONFIG_APPLY BIT(3) ++#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) ++#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) ++#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) ++#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) ++#define LTR_CONFIG_STATUS_ACTIVE BIT(30) ++#define LTR_CONFIG_STATUS_IDLE BIT(31) ++ + #define CONTROL_QUIESCE BIT(1) + #define CONTROL_IS_QUIESCED BIT(2) + #define CONTROL_NRESET BIT(3) ++#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) + #define CONTROL_READY BIT(29) + +-#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) +-#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) +-#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) +-#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? ++#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) ++#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) ++#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) ++#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) ++#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write ++#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) ++ ++#define SPI_CLK_FREQ_BASE 125000000 ++#define SPI_MODE_SINGLE 0 ++#define SPI_MODE_DUAL 1 ++#define SPI_MODE_QUAD 2 + + #define ERROR_CONTROL_UNKNOWN_0 BIT(0) + #define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs +@@ -53,33 +73,71 @@ + #define DMA_TX_STATUS_UNKNOWN_2 BIT(2) + #define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? + ++#define INPUT_HEADER_VERSION(x) ((x) & 0xf) ++#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) ++#define INPUT_HEADER_SYNC(x) ((x) >> 24) ++#define INPUT_HEADER_VERSION_VALUE 3 ++#define INPUT_HEADER_SYNC_VALUE 0x5a ++ ++#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) ++#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) ++ ++#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) ++#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) ++#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) ++#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) ++#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) ++#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) ++#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) ++#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) ++#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) ++ + #define DMA_RX_CONTROL_ENABLE BIT(0) + #define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? + #define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? +-#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? ++#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only + #define DMA_RX_CONTROL_IRQ_DATA BIT(5) + ++#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? + #define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? + #define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices + + #define DMA_RX_WRAP_FLAG BIT(7) + + #define DMA_RX_STATUS_ERROR BIT(3) +-#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) + #define DMA_RX_STATUS_HAVE_DATA BIT(5) + #define DMA_RX_STATUS_ENABLED BIT(8) + ++#define INIT_UNKNOWN_GUC_2 BIT(2) ++#define INIT_UNKNOWN_3 BIT(3) ++#define INIT_UNKNOWN_GUC_4 BIT(4) ++#define INIT_UNKNOWN_5 BIT(5) ++#define INIT_UNKNOWN_31 BIT(31) ++ + // COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. + #define COUNTER_RESET BIT(31) + + struct ithc_registers { +- /* 0000 */ u32 _unknown_0000[1024]; ++ /* 0000 */ u32 _unknown_0000[5]; ++ /* 0014 */ u32 ltr_config; ++ /* 0018 */ u32 _unknown_0018[1018]; + /* 1000 */ u32 _unknown_1000; + /* 1004 */ u32 _unknown_1004; + /* 1008 */ u32 control_bits; + /* 100c */ u32 _unknown_100c; + /* 1010 */ u32 spi_config; +- /* 1014 */ u32 _unknown_1014[3]; ++ /* 1014 */ u8 read_opcode; // maybe for header? ++ /* 1015 */ u8 read_opcode_quad; ++ /* 1016 */ u8 read_opcode_dual; ++ /* 1017 */ u8 read_opcode_single; ++ /* 1018 */ u8 write_opcode; // not used? ++ /* 1019 */ u8 write_opcode_quad; ++ /* 101a */ u8 write_opcode_dual; ++ /* 101b */ u8 write_opcode_single; ++ /* 101c */ u32 _unknown_101c; + /* 1020 */ u32 error_control; + /* 1024 */ u32 error_status; // write to clear + /* 1028 */ u32 error_flags; // write to clear +@@ -100,12 +158,19 @@ struct ithc_registers { + /* 109a */ u8 _unknown_109a; + /* 109b */ u8 num_prds; + /* 109c */ u32 status; // write to clear ++ /* 10a0 */ u32 _unknown_10a0[5]; ++ /* 10b4 */ u32 spi_addr; + } dma_tx; +- /* 10a0 */ u32 _unknown_10a0[7]; +- /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET ++ /* 10b8 */ u32 spi_header_addr; ++ union { ++ /* 10bc */ u32 irq_cause; // in legacy THC mode ++ /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) ++ }; + /* 10c0 */ u32 _unknown_10c0[8]; + /* 10e0 */ u32 _unknown_10e0_counters[3]; +- /* 10ec */ u32 _unknown_10ec[5]; ++ /* 10ec */ u32 quickspi_config1; ++ /* 10f0 */ u32 quickspi_config2; ++ /* 10f4 */ u32 _unknown_10f4[3]; + struct { + /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() + /* 1108/1208 */ u8 num_bufs; +@@ -120,70 +185,30 @@ struct ithc_registers { + /* 1118/1218 */ u64 _unknown_1118_guc_addr; + /* 1120/1220 */ u32 _unknown_1120_guc; + /* 1124/1224 */ u32 _unknown_1124_guc; +- /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related ++ /* 1128/1228 */ u32 init_unknown; + /* 112c/122c */ u32 _unknown_112c; + /* 1130/1230 */ u64 _unknown_1130_guc_addr; + /* 1138/1238 */ u32 _unknown_1138_guc; + /* 113c/123c */ u32 _unknown_113c; + /* 1140/1240 */ u32 _unknown_1140_guc; +- /* 1144/1244 */ u32 _unknown_1144[23]; ++ /* 1144/1244 */ u32 _unknown_1144[11]; ++ /* 1170/1270 */ u32 spi_addr; ++ /* 1174/1274 */ u32 _unknown_1174[11]; + /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; + /* 11b8/12b8 */ u32 _unknown_11b8[18]; + } dma_rx[2]; + }; + static_assert(sizeof(struct ithc_registers) == 0x1300); + +-#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) +-#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) +- +-#define DEVCFG_TOUCH_MASK 0x3f +-#define DEVCFG_TOUCH_ENABLE BIT(0) +-#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) +-#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) +-#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) +-#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) +-#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) +-#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) +- +-#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" +- +-#define DEVCFG_SPI_MAX_FREQ(x) (((x) >> 1) & 0xf) // high bit = use high speed mode? +-#define DEVCFG_SPI_MODE(x) (((x) >> 6) & 3) +-#define DEVCFG_SPI_UNKNOWN_8(x) (((x) >> 8) & 0x3f) +-#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat +-#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) +-#define DEVCFG_SPI_UNKNOWN_25 BIT(25) +-#define DEVCFG_SPI_UNKNOWN_26 BIT(26) +-#define DEVCFG_SPI_UNKNOWN_27 BIT(27) +-#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this +-#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? +- +-struct ithc_device_config { // (Example values are from an SP7+.) +- u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) +- u32 _unknown_04; // 04 = 0x00000000 +- u32 dma_buf_sizes; // 08 = 0x000a00ff +- u32 touch_cfg; // 0c = 0x0000001c +- u32 _unknown_10; // 10 = 0x0000001c +- u32 device_id; // 14 = 0x43495424 = "$TIC" +- u32 spi_config; // 18 = 0xfda00a2e +- u16 vendor_id; // 1c = 0x045e = Microsoft Corp. +- u16 product_id; // 1e = 0x0c1a +- u32 revision; // 20 = 0x00000001 +- u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) +- u32 _unknown_28; // 28 = 0x00000000 +- u32 fw_mode; // 2c = 0x00000000 (for fw update?) +- u32 _unknown_30; // 30 = 0x00000000 +- u32 _unknown_34; // 34 = 0x0404035e (u8,u8,u8,u8 = version?) +- u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) +- u32 _unknown_3c; // 3c = 0x00000002 +-}; +- + void bitsl(__iomem u32 *reg, u32 mask, u32 val); + void bitsb(__iomem u8 *reg, u8 mask, u8 val); + #define bitsl_set(reg, x) bitsl(reg, x, x) + #define bitsb_set(reg, x) bitsb(reg, x, x) + int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); + int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); +-int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode); ++ ++void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); ++void ithc_set_ltr_idle(struct ithc *ithc); ++int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); + int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); + +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +index 028e55a4ec53..e90c38044432 100644 +--- a/drivers/hid/ithc/ithc.h ++++ b/drivers/hid/ithc/ithc.h +@@ -1,20 +1,19 @@ + /* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +-#include +-#include +-#include ++#include ++#include ++#include + #include ++#include + #include +-#include ++#include + #include + #include +-#include + #include + #include +-#include ++#include ++#include + #include +-#include +-#include + + #define DEVNAME "ithc" + #define DEVFULLNAME "Intel Touch Host Controller" +@@ -27,10 +26,37 @@ + + #define NUM_RX_BUF 16 + ++// PCI device IDs: ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a ++ + struct ithc; + + #include "ithc-regs.h" ++#include "ithc-hid.h" + #include "ithc-dma.h" ++#include "ithc-legacy.h" ++#include "ithc-quickspi.h" ++#include "ithc-debug.h" + + struct ithc { + char phys[32]; +@@ -38,30 +64,21 @@ struct ithc { + int irq; + struct task_struct *poll_thread; + +- struct pm_qos_request activity_qos; +- struct hrtimer activity_start_timer; +- struct hrtimer activity_end_timer; +- ktime_t last_rx_time; +- unsigned int cur_rx_seq_count; +- unsigned int cur_rx_seq_errors; +- +- struct hid_device *hid; +- bool hid_parse_done; +- wait_queue_head_t wait_hid_parse; +- wait_queue_head_t wait_hid_get_feature; +- struct mutex hid_get_feature_mutex; +- void *hid_get_feature_buf; +- size_t hid_get_feature_size; +- + struct ithc_registers __iomem *regs; + struct ithc_registers *prev_regs; // for debugging +- struct ithc_device_config config; + struct ithc_dma_rx dma_rx[2]; + struct ithc_dma_tx dma_tx; ++ struct ithc_hid hid; ++ ++ bool use_quickspi; ++ bool have_config; ++ u16 vendor_id; ++ u16 product_id; ++ u32 product_rev; ++ u32 max_rx_size; ++ u32 max_tx_size; ++ u32 legacy_touch_cfg; + }; + + int ithc_reset(struct ithc *ithc); +-void ithc_set_active(struct ithc *ithc, unsigned int duration_us); +-int ithc_debug_init(struct ithc *ithc); +-void ithc_log_regs(struct ithc *ithc); + +-- +2.45.2 + +From 79abe7fc9d3cd1eda0d9904695a98e98eab037aa Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 4 Aug 2024 16:04:53 +0200 +Subject: [PATCH] hid: ithc: Update from quo/ithc-linux + + - Enable support for SL6/SP10 + - Fixes for SP8 + +Based on: https://github.com/quo/ithc-linux/commit/34539af4726d970f9765363bb78b5fd920611a0b + +Signed-off-by: Maximilian Luz +Patchset: ithc +--- + drivers/hid/ithc/ithc-legacy.c | 4 +- + drivers/hid/ithc/ithc-main.c | 91 +++++++++----------------------- + drivers/hid/ithc/ithc-quickspi.c | 53 ++++++++++++++----- + drivers/hid/ithc/ithc-regs.h | 15 +++--- + drivers/hid/ithc/ithc.h | 9 +++- + 5 files changed, 82 insertions(+), 90 deletions(-) + +diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c +index 5c1da11e3f1d..8883987fb352 100644 +--- a/drivers/hid/ithc/ithc-legacy.c ++++ b/drivers/hid/ithc/ithc-legacy.c +@@ -82,8 +82,10 @@ int ithc_legacy_init(struct ithc *ithc) + // Setting the following bit seems to make reading the config more reliable. + bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); + +- // Setting this bit may be necessary on some ADL devices. ++ // Setting this bit may be necessary on ADL devices. + switch (ithc->pci->device) { ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: + case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +index 2acf02e41d40..ac56c253674b 100644 +--- a/drivers/hid/ithc/ithc-main.c ++++ b/drivers/hid/ithc/ithc-main.c +@@ -6,25 +6,14 @@ MODULE_DESCRIPTION("Intel Touch Host Controller driver"); + MODULE_LICENSE("Dual BSD/GPL"); + + static const struct pci_device_id ithc_pci_tbl[] = { +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT1) }, +- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT2) }, +- // XXX So far the THC seems to be the only Intel PCI device with PCI_CLASS_INPUT_PEN, +- // so instead of the device list we could just do: +- // { .vendor = PCI_VENDOR_ID_INTEL, .device = PCI_ANY_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = PCI_CLASS_INPUT_PEN, .class_mask = ~0, }, ++ { ++ .vendor = PCI_VENDOR_ID_INTEL, ++ .device = PCI_ANY_ID, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ .class = PCI_CLASS_INPUT_PEN << 8, ++ .class_mask = ~0, ++ }, + {} + }; + MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); +@@ -52,50 +41,14 @@ static int ithc_idle_ltr_us = -1; + module_param_named(idleltr, ithc_idle_ltr_us, int, 0); + MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); + ++static unsigned int ithc_idle_delay_ms = 1000; ++module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); ++MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); ++ + static bool ithc_log_regs_enabled = false; + module_param_named(logregs, ithc_log_regs_enabled, bool, 0); + MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); + +-// Sysfs attributes +- +-static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) +-{ +- struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc->have_config) +- return -ENODEV; +- return sprintf(buf, "0x%04x", ithc->vendor_id); +-} +-static DEVICE_ATTR_RO(vendor); +-static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) +-{ +- struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc->have_config) +- return -ENODEV; +- return sprintf(buf, "0x%04x", ithc->product_id); +-} +-static DEVICE_ATTR_RO(product); +-static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) +-{ +- struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc->have_config) +- return -ENODEV; +- return sprintf(buf, "%u", ithc->product_rev); +-} +-static DEVICE_ATTR_RO(revision); +- +-static const struct attribute_group *ithc_attribute_groups[] = { +- &(const struct attribute_group){ +- .name = DEVNAME, +- .attrs = (struct attribute *[]){ +- &dev_attr_vendor.attr, +- &dev_attr_product.attr, +- &dev_attr_revision.attr, +- NULL +- }, +- }, +- NULL +-}; +- + // Interrupts/polling + + static void ithc_disable_interrupts(struct ithc *ithc) +@@ -124,14 +77,19 @@ static void ithc_clear_interrupts(struct ithc *ithc) + &ithc->regs->dma_tx.status); + } + ++static void ithc_idle_timer_callback(struct timer_list *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, idle_timer); ++ ithc_set_ltr_idle(ithc); ++} ++ + static void ithc_process(struct ithc *ithc) + { + ithc_log_regs(ithc); + + // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. +- // It does not appear to automatically go back to idle, so we switch it back here, since +- // the DMA transfer should be complete. +- ithc_set_ltr_idle(ithc); ++ // It does not appear to automatically go back to idle, so we switch it back after a delay. ++ mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); + + bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; + bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; +@@ -231,10 +189,10 @@ static int ithc_init_device(struct ithc *ithc) + // Set Latency Tolerance Reporting config. The device will automatically + // apply these values depending on whether it is active or idle. + // If active value is too high, DMA buffer data can become truncated. +- // By default, we set the active LTR value to 100us, and idle to 100ms. ++ // By default, we set the active LTR value to 50us, and idle to 100ms. + u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 + : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 +- : 100 * 1000; ++ : 50 * 1000; + u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 + : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 + : 100 * 1000 * 1000; +@@ -279,6 +237,7 @@ static void ithc_stop(void *res) + else + ithc_legacy_exit(ithc); + ithc_disable(ithc); ++ del_timer_sync(&ithc->idle_timer); + + // Clear DMA config. + for (unsigned int i = 0; i < 2; i++) { +@@ -343,13 +302,14 @@ static int ithc_start(struct pci_dev *pci) + + // Initialize HID and DMA. + CHECK_RET(ithc_hid_init, ithc); +- CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); + if (ithc_use_rx0) + CHECK_RET(ithc_dma_rx_init, ithc, 0); + if (ithc_use_rx1) + CHECK_RET(ithc_dma_rx_init, ithc, 1); + CHECK_RET(ithc_dma_tx_init, ithc); + ++ timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); ++ + // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are + // disabled BEFORE the buffers are freed. + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); +@@ -452,7 +412,6 @@ static struct pci_driver ithc_driver = { + .restore = ithc_restore, + }, + .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, +- //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway + }; + + static int __init ithc_init(void) +diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c +index 760e55ead078..e2d1690b8cf8 100644 +--- a/drivers/hid/ithc/ithc-quickspi.c ++++ b/drivers/hid/ithc/ithc-quickspi.c +@@ -257,6 +257,14 @@ void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cf + spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); + } + ++static void set_opcode(struct ithc *ithc, size_t i, u8 opcode) ++{ ++ writeb(opcode, &ithc->regs->opcode[i].header); ++ writeb(opcode, &ithc->regs->opcode[i].single); ++ writeb(opcode, &ithc->regs->opcode[i].dual); ++ writeb(opcode, &ithc->regs->opcode[i].quad); ++} ++ + static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) + { + pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); +@@ -279,26 +287,47 @@ static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_con + // SPI addresses and opcodes + if (cfg->has_input_report_header_address) + writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); +- if (cfg->has_input_report_body_address) ++ if (cfg->has_input_report_body_address) { + writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); ++ writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); ++ } + if (cfg->has_output_report_body_address) + writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); + +- if (cfg->has_read_opcode) { +- writeb(cfg->read_opcode, &ithc->regs->read_opcode); +- writeb(cfg->read_opcode, &ithc->regs->read_opcode_single); +- writeb(cfg->read_opcode, &ithc->regs->read_opcode_dual); +- writeb(cfg->read_opcode, &ithc->regs->read_opcode_quad); +- } +- if (cfg->has_write_opcode) { +- writeb(cfg->write_opcode, &ithc->regs->write_opcode); +- writeb(cfg->write_opcode, &ithc->regs->write_opcode_single); +- writeb(cfg->write_opcode, &ithc->regs->write_opcode_dual); +- writeb(cfg->write_opcode, &ithc->regs->write_opcode_quad); ++ switch (ithc->pci->device) { ++ // LKF/TGL don't support QuickSPI. ++ // For ADL, opcode layout is RX/TX/unused. ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: ++ case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 1, cfg->write_opcode); ++ } ++ break; ++ // For MTL, opcode layout was changed to RX/RX/TX. ++ // (RPL layout is unknown.) ++ default: ++ if (cfg->has_read_opcode) { ++ set_opcode(ithc, 0, cfg->read_opcode); ++ set_opcode(ithc, 1, cfg->read_opcode); ++ } ++ if (cfg->has_write_opcode) { ++ set_opcode(ithc, 2, cfg->write_opcode); ++ } ++ break; + } ++ + ithc_log_regs(ithc); + + // The rest... ++ bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); ++ + bitsl(&ithc->regs->quickspi_config1, + QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | + QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +index a9d236454644..4f541fe533fa 100644 +--- a/drivers/hid/ithc/ithc-regs.h ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -129,15 +129,12 @@ struct ithc_registers { + /* 1008 */ u32 control_bits; + /* 100c */ u32 _unknown_100c; + /* 1010 */ u32 spi_config; +- /* 1014 */ u8 read_opcode; // maybe for header? +- /* 1015 */ u8 read_opcode_quad; +- /* 1016 */ u8 read_opcode_dual; +- /* 1017 */ u8 read_opcode_single; +- /* 1018 */ u8 write_opcode; // not used? +- /* 1019 */ u8 write_opcode_quad; +- /* 101a */ u8 write_opcode_dual; +- /* 101b */ u8 write_opcode_single; +- /* 101c */ u32 _unknown_101c; ++ struct { ++ /* 1014/1018/101c */ u8 header; ++ /* 1015/1019/101d */ u8 quad; ++ /* 1016/101a/101e */ u8 dual; ++ /* 1017/101b/101f */ u8 single; ++ } opcode[3]; + /* 1020 */ u32 error_control; + /* 1024 */ u32 error_status; // write to clear + /* 1028 */ u32 error_flags; // write to clear +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +index e90c38044432..aec320d4e945 100644 +--- a/drivers/hid/ithc/ithc.h ++++ b/drivers/hid/ithc/ithc.h +@@ -14,6 +14,8 @@ + #include + #include + #include ++#include ++#include + + #define DEVNAME "ithc" + #define DEVFULLNAME "Intel Touch Host Controller" +@@ -46,8 +48,10 @@ + #define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 + #define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 + // Meteor Lake +-#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 +-#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT1 0x7f59 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b + + struct ithc; + +@@ -63,6 +67,7 @@ struct ithc { + struct pci_dev *pci; + int irq; + struct task_struct *poll_thread; ++ struct timer_list idle_timer; + + struct ithc_registers __iomem *regs; + struct ithc_registers *prev_regs; // for debugging +-- +2.45.2 + diff --git a/patches/6.8/0006-surface-sam.patch b/patches/6.9/0007-surface-sam.patch similarity index 62% rename from patches/6.8/0006-surface-sam.patch rename to patches/6.9/0007-surface-sam.patch index 60808905ef..d1cf25f042 100644 --- a/patches/6.8/0006-surface-sam.patch +++ b/patches/6.9/0007-surface-sam.patch @@ -1,4 +1,276 @@ -From 973bf943acca4abdd932b5fdff032de0af07f96e Mon Sep 17 00:00:00 2001 +From cebca0467ba24265a1e51432ac35beac33891a6e Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Thu, 9 May 2024 16:15:49 +0200 +Subject: [PATCH] serial: Clear UPF_DEAD before calling + tty_port_register_device_attr_serdev() + +If a serdev_device_driver is already loaded for a serdev_tty_port when it +gets registered by tty_port_register_device_attr_serdev() then that +driver's probe() method will be called immediately. + +The serdev_device_driver's probe() method should then be able to call +serdev_device_open() successfully, but because UPF_DEAD is still dead +serdev_device_open() will fail with -ENXIO in this scenario: + + serdev_device_open() + ctrl->ops->open() /* this callback being ttyport_open() */ + tty->ops->open() /* this callback being uart_open() */ + tty_port_open() + port->ops->activate() /* this callback being uart_port_activate() */ + Find bit UPF_DEAD is set in uport->flags and fail with errno -ENXIO. + +Fix this be clearing UPF_DEAD before tty_port_register_device_attr_serdev() +note this only moves up the UPD_DEAD clearing a small bit, before: + + tty_port_register_device_attr_serdev(); + mutex_unlock(&tty_port.mutex); + uart_port.flags &= ~UPF_DEAD; + mutex_unlock(&port_mutex); + +after: + + uart_port.flags &= ~UPF_DEAD; + tty_port_register_device_attr_serdev(); + mutex_unlock(&tty_port.mutex); + mutex_unlock(&port_mutex); + +Reported-by: Weifeng Liu +Closes: https://lore.kernel.org/platform-driver-x86/20240505130800.2546640-1-weifeng.liu.z@gmail.com/ +Tested-by: Weifeng Liu +Signed-off-by: Hans de Goede +Link: https://lore.kernel.org/r/20240509141549.63704-1-hdegoede@redhat.com +Signed-off-by: Greg Kroah-Hartman +Patchset: surface-sam +--- + drivers/tty/serial/serial_core.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c +index c476d884356d..b47a277978a0 100644 +--- a/drivers/tty/serial/serial_core.c ++++ b/drivers/tty/serial/serial_core.c +@@ -3211,6 +3211,9 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u + if (uport->attr_group) + uport->tty_groups[1] = uport->attr_group; + ++ /* Ensure serdev drivers can call serdev_device_open() right away */ ++ uport->flags &= ~UPF_DEAD; ++ + /* + * Register the port whether it's detected or not. This allows + * setserial to be used to alter this port's parameters. +@@ -3221,6 +3224,7 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u + if (!IS_ERR(tty_dev)) { + device_set_wakeup_capable(tty_dev, 1); + } else { ++ uport->flags |= UPF_DEAD; + dev_err(uport->dev, "Cannot register tty device on line %d\n", + uport->line); + } +@@ -3426,8 +3430,6 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) + if (ret) + goto err_unregister_port_dev; + +- port->flags &= ~UPF_DEAD; +- + mutex_unlock(&port_mutex); + + return 0; +-- +2.45.2 + +From e453ab926ce00cd87c30f55342031d4f3bf78e53 Mon Sep 17 00:00:00 2001 +From: Weifeng Liu +Date: Sun, 5 May 2024 21:07:50 +0800 +Subject: [PATCH] platform/surface: aggregator: Log critical errors during SAM + probing + +Emits messages upon errors during probing of SAM. Hopefully this could +provide useful context to user for the purpose of diagnosis when +something miserable happen. + +Reviewed-by: Maximilian Luz +Reviewed-by: Andy Shevchenko +Signed-off-by: Weifeng Liu +Link: https://lore.kernel.org/r/20240505130800.2546640-3-weifeng.liu.z@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/core.c | 42 ++++++++++++++-------- + 1 file changed, 28 insertions(+), 14 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c +index ba550eaa06fc..797d0645bd77 100644 +--- a/drivers/platform/surface/aggregator/core.c ++++ b/drivers/platform/surface/aggregator/core.c +@@ -618,15 +618,17 @@ static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { + + static int ssam_serial_hub_probe(struct serdev_device *serdev) + { +- struct acpi_device *ssh = ACPI_COMPANION(&serdev->dev); ++ struct device *dev = &serdev->dev; ++ struct acpi_device *ssh = ACPI_COMPANION(dev); + struct ssam_controller *ctrl; + acpi_status astatus; + int status; + +- if (gpiod_count(&serdev->dev, NULL) < 0) +- return -ENODEV; ++ status = gpiod_count(dev, NULL); ++ if (status < 0) ++ return dev_err_probe(dev, status, "no GPIO found\n"); + +- status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); ++ status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios); + if (status) + return status; + +@@ -637,8 +639,10 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + + /* Initialize controller. */ + status = ssam_controller_init(ctrl, serdev); +- if (status) ++ if (status) { ++ dev_err_probe(dev, status, "failed to initialize ssam controller\n"); + goto err_ctrl_init; ++ } + + ssam_controller_lock(ctrl); + +@@ -646,12 +650,14 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + serdev_device_set_drvdata(serdev, ctrl); + serdev_device_set_client_ops(serdev, &ssam_serdev_ops); + status = serdev_device_open(serdev); +- if (status) ++ if (status) { ++ dev_err_probe(dev, status, "failed to open serdev device\n"); + goto err_devopen; ++ } + + astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); + if (ACPI_FAILURE(astatus)) { +- status = -ENXIO; ++ status = dev_err_probe(dev, -ENXIO, "failed to setup serdev\n"); + goto err_devinit; + } + +@@ -667,25 +673,33 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + * states. + */ + status = ssam_log_firmware_version(ctrl); +- if (status) ++ if (status) { ++ dev_err_probe(dev, status, "failed to get firmware version\n"); + goto err_initrq; ++ } + + status = ssam_ctrl_notif_d0_entry(ctrl); +- if (status) ++ if (status) { ++ dev_err_probe(dev, status, "D0-entry notification failed\n"); + goto err_initrq; ++ } + + status = ssam_ctrl_notif_display_on(ctrl); +- if (status) ++ if (status) { ++ dev_err_probe(dev, status, "display-on notification failed\n"); + goto err_initrq; ++ } + +- status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); ++ status = sysfs_create_group(&dev->kobj, &ssam_sam_group); + if (status) + goto err_initrq; + + /* Set up IRQ. */ + status = ssam_irq_setup(ctrl); +- if (status) ++ if (status) { ++ dev_err_probe(dev, status, "failed to setup IRQ\n"); + goto err_irq; ++ } + + /* Finally, set main controller reference. */ + status = ssam_try_set_controller(ctrl); +@@ -702,7 +716,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + * resumed. In short, this causes some spurious unwanted wake-ups. + * For now let's thus default power/wakeup to false. + */ +- device_set_wakeup_capable(&serdev->dev, true); ++ device_set_wakeup_capable(dev, true); + acpi_dev_clear_dependencies(ssh); + + return 0; +@@ -710,7 +724,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + err_mainref: + ssam_irq_free(ctrl); + err_irq: +- sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); ++ sysfs_remove_group(&dev->kobj, &ssam_sam_group); + err_initrq: + ssam_controller_lock(ctrl); + ssam_controller_shutdown(ctrl); +-- +2.45.2 + +From d3ddbc07009ade7bd60e9575b4d280e44c8c2f63 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 19 Apr 2024 20:41:47 +0200 +Subject: [PATCH] platform/surface: aggregator: Fix warning when controller is + destroyed in probe + +There is a small window in ssam_serial_hub_probe() where the controller +is initialized but has not been started yet. Specifically, between +ssam_controller_init() and ssam_controller_start(). Any failure in this +window, for example caused by a failure of serdev_device_open(), +currently results in an incorrect warning being emitted. + +In particular, any failure in this window results in the controller +being destroyed via ssam_controller_destroy(). This function checks the +state of the controller and, in an attempt to validate that the +controller has been cleanly shut down before we try and deallocate any +resources, emits a warning if that state is not SSAM_CONTROLLER_STOPPED. + +However, since we have only just initialized the controller and have not +yet started it, its state is SSAM_CONTROLLER_INITIALIZED. Note that this +is the only point at which the controller has this state, as it will +change after we start the controller with ssam_controller_start() and +never revert back. Further, at this point no communication has taken +place and the sender and receiver threads have not been started yet (and +we may not even have an open serdev device either). + +Therefore, it is perfectly safe to call ssam_controller_destroy() with a +state of SSAM_CONTROLLER_INITIALIZED. This, however, means that the +warning currently being emitted is incorrect. Fix it by extending the +check. + +Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/controller.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index 7fc602e01487..7e89f547999b 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -1354,7 +1354,8 @@ void ssam_controller_destroy(struct ssam_controller *ctrl) + if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) + return; + +- WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); ++ WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED && ++ ctrl->state != SSAM_CONTROLLER_INITIALIZED); + + /* + * Note: New events could still have been received after the previous +-- +2.45.2 + +From 46aeacf7b116bd6e1711b1678f8c8c9d631e95c9 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 22 Oct 2023 14:57:11 +0200 Subject: [PATCH] platform/surface: aggregator_registry: Add support for @@ -15,10 +287,10 @@ Patchset: surface-sam 1 file changed, 3 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index aeb3feae40ff..2bc4977037fc 100644 +index 035d6b4105cd..74688a2ed4b2 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -367,6 +367,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { +@@ -374,6 +374,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop Go 2 */ { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, @@ -29,9 +301,9 @@ index aeb3feae40ff..2bc4977037fc 100644 { "MSHW0123", (unsigned long)ssam_node_group_sls }, -- -2.44.0 +2.45.2 -From 7dedc84364f9c1fb73d271c859dce19b4a9644d6 Mon Sep 17 00:00:00 2001 +From e42a81ec22305c91f44562c0386f651dfdc48db1 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 20 Nov 2023 19:47:00 +0100 Subject: [PATCH] platform/surface: aggregator_registry: Add support for @@ -49,10 +321,10 @@ Patchset: surface-sam 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 2bc4977037fc..26cb6229ad16 100644 +index 74688a2ed4b2..f02a933160ff 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -247,8 +247,8 @@ static const struct software_node *ssam_node_group_sl5[] = { +@@ -253,8 +253,8 @@ static const struct software_node *ssam_node_group_sl5[] = { NULL, }; @@ -63,7 +335,7 @@ index 2bc4977037fc..26cb6229ad16 100644 &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, -@@ -263,6 +263,20 @@ static const struct software_node *ssam_node_group_sls[] = { +@@ -269,6 +269,20 @@ static const struct software_node *ssam_node_group_sls[] = { NULL, }; @@ -84,7 +356,7 @@ index 2bc4977037fc..26cb6229ad16 100644 /* Devices for Surface Laptop Go. */ static const struct software_node *ssam_node_group_slg1[] = { &ssam_node_root, -@@ -370,8 +384,11 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { +@@ -377,8 +391,11 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop Go 3 */ { "MSHW0440", (unsigned long)ssam_node_group_slg1 }, @@ -99,277 +371,66 @@ index 2bc4977037fc..26cb6229ad16 100644 { }, }; -- -2.44.0 +2.45.2 -From 96a7b3dd527f3e1b97e2d089a727c16cd5045aa5 Mon Sep 17 00:00:00 2001 -From: Ivor Wanders -Date: Mon, 18 Dec 2023 19:21:32 -0500 -Subject: [PATCH] platform/surface: aggregator_registry: add entry for fan - speed +From 779ad78ae568b1bf03927655133b23bdee5fc77c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 9 Jun 2024 20:05:57 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for + Surface Laptop 6 -Add an entry for the fan speed function. -Add this new entry to the Surface Pro 9 group. +Add SAM client device nodes for the Surface Laptop Studio 6 (SL6). The +SL6 is similar to the SL5, with the typical battery/AC, platform +profile, and HID nodes. It also has support for the newly supported fan +interface. -Signed-off-by: Ivor Wanders -Link: https://github.com/linux-surface/kernel/pull/144 -Reviewed-by: Maximilian Luz +Signed-off-by: Maximilian Luz Patchset: surface-sam --- - drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++ - 1 file changed, 7 insertions(+) + .../surface/surface_aggregator_registry.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 26cb6229ad16..f02a933160ff 100644 +index f02a933160ff..34df1bdad83b 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -74,6 +74,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, +@@ -253,6 +253,22 @@ static const struct software_node *ssam_node_group_sl5[] = { + NULL, }; -+/* Fan speed function. */ -+static const struct software_node ssam_node_fan_speed = { -+ .name = "ssam:01:05:01:01:01", -+ .parent = &ssam_node_root, -+}; -+ - /* Tablet-mode switch via KIP subsystem. */ - static const struct software_node ssam_node_kip_tablet_switch = { - .name = "ssam:01:0e:01:00:01", -@@ -319,6 +325,7 @@ static const struct software_node *ssam_node_group_sp9[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, ++/* Devices for Surface Laptop 6. */ ++static const struct software_node *ssam_node_group_sl6[] = { ++ &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_tmp_perf_profile_with_fan, ++ &ssam_node_tmp_sensors, + &ssam_node_fan_speed, - &ssam_node_pos_tablet_switch, - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, --- -2.44.0 - -From b4eb65349df9859f65aa9990c24c5036d35382da Mon Sep 17 00:00:00 2001 -From: Ivor Wanders -Date: Thu, 30 Nov 2023 20:20:24 -0500 -Subject: [PATCH] hwmon: add fan speed monitoring driver for Surface devices - -Adds a driver that provides read only access to the fan speed for Microsoft -Surface Pro devices. The fan speed is always regulated by the EC and cannot -be influenced directly. - -Signed-off-by: Ivor Wanders -Link: https://github.com/linux-surface/kernel/pull/144 -Patchset: surface-sam ---- - Documentation/hwmon/index.rst | 1 + - Documentation/hwmon/surface_fan.rst | 25 ++++++++ - MAINTAINERS | 8 +++ - drivers/hwmon/Kconfig | 13 ++++ - drivers/hwmon/Makefile | 1 + - drivers/hwmon/surface_fan.c | 93 +++++++++++++++++++++++++++++ - 6 files changed, 141 insertions(+) - create mode 100644 Documentation/hwmon/surface_fan.rst - create mode 100644 drivers/hwmon/surface_fan.c - -diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst -index c7ed1f73ac06..58be92e94a8d 100644 ---- a/Documentation/hwmon/index.rst -+++ b/Documentation/hwmon/index.rst -@@ -208,6 +208,7 @@ Hardware Monitoring Kernel Drivers - smsc47m1 - sparx5-temp - stpddc60 -+ surface_fan - sy7636a-hwmon - tc654 - tc74 -diff --git a/Documentation/hwmon/surface_fan.rst b/Documentation/hwmon/surface_fan.rst -new file mode 100644 -index 000000000000..07942574c4f0 ---- /dev/null -+++ b/Documentation/hwmon/surface_fan.rst -@@ -0,0 +1,25 @@ -+.. SPDX-License-Identifier: GPL-2.0-or-later -+ -+Kernel driver surface_fan -+========================= -+ -+Supported Devices: -+ -+ * Microsoft Surface Pro 9 -+ -+Author: Ivor Wanders -+ -+Description -+----------- -+ -+This provides monitoring of the fan found in some Microsoft Surface Pro devices, -+like the Surface Pro 9. The fan is always controlled by the onboard controller. -+ -+Sysfs interface -+--------------- -+ -+======================= ======= ========================================= -+Name Perm Description -+======================= ======= ========================================= -+``fan1_input`` RO Current fan speed in RPM. -+======================= ======= ========================================= -diff --git a/MAINTAINERS b/MAINTAINERS -index 1aabf1c15bb3..b6416cf3f022 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -14576,6 +14576,14 @@ 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 SENSOR FAN DRIVER -+M: Maximilian Luz -+M: Ivor Wanders -+L: linux-hwmon@vger.kernel.org -+S: Maintained -+F: Documentation/hwmon/surface_fan.rst -+F: drivers/hwmon/surface_fan.c -+ - MICROSOFT SURFACE GPE LID SUPPORT DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig -index a608264da87d..e762f6138970 100644 ---- a/drivers/hwmon/Kconfig -+++ b/drivers/hwmon/Kconfig -@@ -1994,6 +1994,19 @@ config SENSORS_SFCTEMP - This driver can also be built as a module. If so, the module - will be called sfctemp. - -+config SENSORS_SURFACE_FAN -+ tristate "Surface Fan Driver" -+ depends on SURFACE_AGGREGATOR -+ help -+ Driver that provides monitoring of the fan on Surface Pro devices that -+ have a fan, like the Surface Pro 9. -+ -+ This makes the fan's current speed accessible through the hwmon -+ system. It does not provide control over the fan, the firmware is -+ responsible for that, this driver merely provides monitoring. -+ -+ Select M or Y here, if you want to be able to read the fan's speed. -+ - config SENSORS_ADC128D818 - tristate "Texas Instruments ADC128D818" - depends on I2C -diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile -index 47be39af5c03..30cc90f40844 100644 ---- a/drivers/hwmon/Makefile -+++ b/drivers/hwmon/Makefile -@@ -201,6 +201,7 @@ obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o - obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o - obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o - obj-$(CONFIG_SENSORS_STTS751) += stts751.o -+obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o - obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o - obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o - obj-$(CONFIG_SENSORS_TC74) += tc74.o -diff --git a/drivers/hwmon/surface_fan.c b/drivers/hwmon/surface_fan.c -new file mode 100644 -index 000000000000..7c2e3ae3eb40 ---- /dev/null -+++ b/drivers/hwmon/surface_fan.c -@@ -0,0 +1,93 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Fan driver for Surface System Aggregator Module. It provides access -+ * to the fan's rpm through the hwmon system. -+ * -+ * Copyright (C) 2023 Ivor Wanders -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+// SSAM -+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_fan_rpm_get, __le16, { -+ .target_category = SSAM_SSH_TC_FAN, -+ .command_id = 0x01, -+}); -+ -+// hwmon -+umode_t surface_fan_hwmon_is_visible(const void *drvdata, -+ enum hwmon_sensor_types type, u32 attr, -+ int channel) -+{ -+ return 0444; -+} -+ -+static int surface_fan_hwmon_read(struct device *dev, -+ enum hwmon_sensor_types type, u32 attr, -+ int channel, long *val) -+{ -+ struct ssam_device *sdev = dev_get_drvdata(dev); -+ int ret; -+ __le16 value; -+ -+ ret = __ssam_fan_rpm_get(sdev, &value); -+ if (ret) -+ return ret; -+ -+ *val = le16_to_cpu(value); -+ -+ return ret; -+} -+ -+static const struct hwmon_channel_info *const surface_fan_info[] = { -+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), -+ NULL -+}; -+ -+static const struct hwmon_ops surface_fan_hwmon_ops = { -+ .is_visible = surface_fan_hwmon_is_visible, -+ .read = surface_fan_hwmon_read, -+}; -+ -+static const struct hwmon_chip_info surface_fan_chip_info = { -+ .ops = &surface_fan_hwmon_ops, -+ .info = surface_fan_info, -+}; -+ -+static int surface_fan_probe(struct ssam_device *sdev) -+{ -+ struct device *hdev; -+ -+ hdev = devm_hwmon_device_register_with_info(&sdev->dev, -+ "surface_fan", sdev, -+ &surface_fan_chip_info, -+ NULL); -+ if (IS_ERR(hdev)) -+ return PTR_ERR(hdev); -+ -+ return 0; -+} -+ -+static const struct ssam_device_id ssam_fan_match[] = { -+ { SSAM_SDEV(FAN, SAM, 0x01, 0x01) }, -+ {}, ++ &ssam_node_hid_main_keyboard, ++ &ssam_node_hid_main_touchpad, ++ &ssam_node_hid_main_iid5, ++ &ssam_node_hid_sam_sensors, ++ &ssam_node_hid_sam_ucm_ucsi, ++ NULL, +}; -+MODULE_DEVICE_TABLE(ssam, ssam_fan_match); + -+static struct ssam_device_driver surface_fan = { -+ .probe = surface_fan_probe, -+ .match_table = ssam_fan_match, -+ .driver = { -+ .name = "surface_fan", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_fan); + /* Devices for Surface Laptop Studio 1. */ + static const struct software_node *ssam_node_group_sls1[] = { + &ssam_node_root, +@@ -382,6 +398,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { + /* Surface Laptop 5 */ + { "MSHW0350", (unsigned long)ssam_node_group_sl5 }, + ++ /* Surface Laptop 6 */ ++ { "MSHW0530", (unsigned long)ssam_node_group_sl5 }, + -+MODULE_AUTHOR("Ivor Wanders "); -+MODULE_DESCRIPTION("Fan Driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + -- -2.44.0 +2.45.2 -From 5c18bed9c7ad073b61e3c3686dc4bc1858f958dc Mon Sep 17 00:00:00 2001 +From 4a7f3f74a7d92f3e0567f7e876e77bf7c8bbcf6d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 30 Dec 2023 18:07:54 +0100 Subject: [PATCH] hwmon: Add thermal sensor driver for Surface Aggregator @@ -391,10 +452,10 @@ Patchset: surface-sam create mode 100644 drivers/hwmon/surface_temp.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig -index e762f6138970..41261b49f8be 100644 +index 83945397b6eb..338ef73c96a3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig -@@ -2007,6 +2007,16 @@ config SENSORS_SURFACE_FAN +@@ -2070,6 +2070,16 @@ config SENSORS_SURFACE_FAN Select M or Y here, if you want to be able to read the fan's speed. @@ -412,10 +473,10 @@ index e762f6138970..41261b49f8be 100644 tristate "Texas Instruments ADC128D818" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile -index 30cc90f40844..6644fd4598a4 100644 +index 5c31808f6378..de8bc99719e6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile -@@ -202,6 +202,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o +@@ -208,6 +208,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o @@ -595,9 +656,9 @@ index 000000000000..48c3e826713f +MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -- -2.44.0 +2.45.2 -From 8b1e37ad9423a6fdf8a8b3bfb1e15aadefe136de Mon Sep 17 00:00:00 2001 +From 98e9eaae15a93f4dbe3898d3c188e710b791528a Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 30 Dec 2023 18:12:23 +0100 Subject: [PATCH] hwmon: surface_temp: Add support for sensor names @@ -790,9 +851,9 @@ index 48c3e826713f..4c08926139db 100644 "surface_thermal", ssam_temp, &ssam_temp_hwmon_chip_info, NULL); -- -2.44.0 +2.45.2 -From 9b490a17f59518e59cf3c77c103bf5ddc52041a3 Mon Sep 17 00:00:00 2001 +From fdf1850fe9d4ee229a5602361be23c126ce65c8a Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 30 Dec 2023 18:21:12 +0100 Subject: [PATCH] platform/surface: aggregator_registry: Add support for @@ -808,7 +869,7 @@ Patchset: surface-sam 1 file changed, 7 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index f02a933160ff..67686042e009 100644 +index 34df1bdad83b..c0bf0cadcd25 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -74,6 +74,12 @@ static const struct software_node ssam_node_tmp_pprof = { @@ -824,7 +885,7 @@ index f02a933160ff..67686042e009 100644 /* Fan speed function. */ static const struct software_node ssam_node_fan_speed = { .name = "ssam:01:05:01:01:01", -@@ -325,6 +331,7 @@ static const struct software_node *ssam_node_group_sp9[] = { +@@ -341,6 +347,7 @@ static const struct software_node *ssam_node_group_sp9[] = { &ssam_node_bat_ac, &ssam_node_bat_main, &ssam_node_tmp_pprof, @@ -833,9 +894,9 @@ index f02a933160ff..67686042e009 100644 &ssam_node_pos_tablet_switch, &ssam_node_hid_kip_keyboard, -- -2.44.0 +2.45.2 -From ec0b680c0ce36ef6bffd682e10589cf5d8d467ae Mon Sep 17 00:00:00 2001 +From c98dd80f6541a1bd7108324f65d77a04a6978e39 Mon Sep 17 00:00:00 2001 From: Ivor Wanders Date: Sat, 16 Dec 2023 15:56:39 -0500 Subject: [PATCH] platform/surface: platform_profile: add fan profile switching @@ -852,7 +913,7 @@ Patchset: surface-sam 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 67686042e009..058b6654a91a 100644 +index c0bf0cadcd25..07a4c4e1120d 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -68,8 +68,8 @@ static const struct software_node ssam_node_bat_sb3base = { @@ -923,7 +984,7 @@ index 67686042e009..058b6654a91a 100644 &ssam_node_hid_main_keyboard, &ssam_node_hid_main_touchpad, &ssam_node_hid_main_iid5, -@@ -264,7 +278,7 @@ static const struct software_node *ssam_node_group_sls1[] = { +@@ -280,7 +294,7 @@ static const struct software_node *ssam_node_group_sls1[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, @@ -932,7 +993,7 @@ index 67686042e009..058b6654a91a 100644 &ssam_node_pos_tablet_switch, &ssam_node_hid_sam_keyboard, &ssam_node_hid_sam_penstash, -@@ -280,7 +294,7 @@ static const struct software_node *ssam_node_group_sls2[] = { +@@ -296,7 +310,7 @@ static const struct software_node *ssam_node_group_sls2[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, @@ -941,7 +1002,7 @@ index 67686042e009..058b6654a91a 100644 &ssam_node_pos_tablet_switch, &ssam_node_hid_sam_keyboard, &ssam_node_hid_sam_penstash, -@@ -294,7 +308,7 @@ static const struct software_node *ssam_node_group_slg1[] = { +@@ -310,7 +324,7 @@ static const struct software_node *ssam_node_group_slg1[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, @@ -950,7 +1011,7 @@ index 67686042e009..058b6654a91a 100644 NULL, }; -@@ -303,7 +317,7 @@ static const struct software_node *ssam_node_group_sp7[] = { +@@ -319,7 +333,7 @@ static const struct software_node *ssam_node_group_sp7[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, @@ -959,7 +1020,7 @@ index 67686042e009..058b6654a91a 100644 NULL, }; -@@ -313,7 +327,7 @@ static const struct software_node *ssam_node_group_sp8[] = { +@@ -329,7 +343,7 @@ static const struct software_node *ssam_node_group_sp8[] = { &ssam_node_hub_kip, &ssam_node_bat_ac, &ssam_node_bat_main, @@ -968,7 +1029,7 @@ index 67686042e009..058b6654a91a 100644 &ssam_node_kip_tablet_switch, &ssam_node_hid_kip_keyboard, &ssam_node_hid_kip_penstash, -@@ -330,7 +344,7 @@ static const struct software_node *ssam_node_group_sp9[] = { +@@ -346,7 +360,7 @@ static const struct software_node *ssam_node_group_sp9[] = { &ssam_node_hub_kip, &ssam_node_bat_ac, &ssam_node_bat_main, @@ -1160,5 +1221,117 @@ index a5a3941b3f43..e54d0a8f7daa 100644 set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices); set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices); -- -2.44.0 +2.45.2 + +From 4adc9fe7e3e43927845ff1711d8f0a7c0a3c7e74 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Mon, 10 Jun 2024 21:47:47 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add fan and thermal + sensor support for Surface Laptop 5 + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 07a4c4e1120d..4dc79f791d39 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -265,7 +265,9 @@ static const struct software_node *ssam_node_group_sl5[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, +- &ssam_node_tmp_perf_profile, ++ &ssam_node_tmp_perf_profile_with_fan, ++ &ssam_node_tmp_sensors, ++ &ssam_node_fan_speed, + &ssam_node_hid_main_keyboard, + &ssam_node_hid_main_touchpad, + &ssam_node_hid_main_iid5, +-- +2.45.2 + +From df74cd4f195731eec0d7423b9b5ac5ad54e66206 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Mon, 10 Jun 2024 21:48:02 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add fan and thermal + sensor support for Surface Laptop Studio 2 + +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 4dc79f791d39..77f903a04d12 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -312,7 +312,9 @@ static const struct software_node *ssam_node_group_sls2[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, +- &ssam_node_tmp_perf_profile, ++ &ssam_node_tmp_perf_profile_with_fan, ++ &ssam_node_tmp_sensors, ++ &ssam_node_fan_speed, + &ssam_node_pos_tablet_switch, + &ssam_node_hid_sam_keyboard, + &ssam_node_hid_sam_penstash, +-- +2.45.2 + +From fbda016e6525668c554714feb8aa09e1baa3d174 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 28 Jun 2024 22:31:37 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add Support for + Surface Pro 10 + +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 22 +++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 77f903a04d12..7d01d989d2a4 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -377,6 +377,25 @@ static const struct software_node *ssam_node_group_sp9[] = { + NULL, + }; + ++/* Devices for Surface Pro 10 */ ++static const struct software_node *ssam_node_group_sp10[] = { ++ &ssam_node_root, ++ &ssam_node_hub_kip, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_tmp_perf_profile_with_fan, ++ &ssam_node_tmp_sensors, ++ &ssam_node_fan_speed, ++ &ssam_node_pos_tablet_switch, ++ &ssam_node_hid_kip_keyboard, ++ &ssam_node_hid_kip_penstash, ++ &ssam_node_hid_kip_touchpad, ++ &ssam_node_hid_kip_fwupd, ++ &ssam_node_hid_sam_sensors, ++ &ssam_node_hid_sam_ucm_ucsi, ++ NULL, ++}; ++ + + /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ + +@@ -399,6 +418,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { + /* Surface Pro 9 */ + { "MSHW0343", (unsigned long)ssam_node_group_sp9 }, + ++ /* Surface Pro 10 */ ++ { "MSHW0510", (unsigned long)ssam_node_group_sp10 }, ++ + /* Surface Book 2 */ + { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, + +-- +2.45.2 diff --git a/patches/6.8/0007-surface-sam-over-hid.patch b/patches/6.9/0008-surface-sam-over-hid.patch similarity index 97% rename from patches/6.8/0007-surface-sam-over-hid.patch rename to patches/6.9/0008-surface-sam-over-hid.patch index 635d87df0c..31c36da0e2 100644 --- a/patches/6.8/0007-surface-sam-over-hid.patch +++ b/patches/6.9/0008-surface-sam-over-hid.patch @@ -1,4 +1,4 @@ -From 95b66fb97652988a7b4be5bb1deaa625e1bb3c3f Mon Sep 17 00:00:00 2001 +From 7585229ad1aea953568798708fc2ead8cb90f63d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 25 Jul 2020 17:19:53 +0200 Subject: [PATCH] i2c: acpi: Implement RawBytes read access @@ -55,10 +55,10 @@ Patchset: surface-sam-over-hid 1 file changed, 35 insertions(+) diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index d6037a328669..a290ebc77aea 100644 +index 14ae0cfc325e..a3a9f81fb47f 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c -@@ -628,6 +628,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, +@@ -639,6 +639,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, return (ret == 1) ? 0 : -EIO; } @@ -87,7 +87,7 @@ index d6037a328669..a290ebc77aea 100644 static acpi_status i2c_acpi_space_handler(u32 function, acpi_physical_address command, u32 bits, u64 *value64, -@@ -729,6 +751,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, +@@ -740,6 +762,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, } break; @@ -108,9 +108,9 @@ index d6037a328669..a290ebc77aea 100644 dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", accessor_type, client->addr); -- -2.44.0 +2.45.2 -From 2b86ac312b956799265cdd1411d305cd2dcaf6db Mon Sep 17 00:00:00 2001 +From 801a75c7cc5bef355d1e631a44ae5633859b7846 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 13 Feb 2021 16:41:18 +0100 Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch @@ -331,5 +331,5 @@ index 000000000000..8b816ed8f35c +MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); +MODULE_LICENSE("GPL"); -- -2.44.0 +2.45.2 diff --git a/patches/6.9/0009-surface-button.patch b/patches/6.9/0009-surface-button.patch new file mode 100644 index 0000000000..86091b1d2f --- /dev/null +++ b/patches/6.9/0009-surface-button.patch @@ -0,0 +1,149 @@ +From a012f0cf803890100e9f94b0f7b6407a53878f3c Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index f6d060377d18..b8603f74eb28 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.45.2 + +From 5cd243c3828123961c317d7fa72f87757b4309ef Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979c..4240c98ca226 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.45.2 + diff --git a/patches/6.9/0010-surface-typecover.patch b/patches/6.9/0010-surface-typecover.patch new file mode 100644 index 0000000000..c882bd64c6 --- /dev/null +++ b/patches/6.9/0010-surface-typecover.patch @@ -0,0 +1,573 @@ +From 8337e937c2495cd8b9de9b44f9f64c2817904788 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 13171454f959..a83beefd25f3 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.45.2 + +From 9bf4f93024adfd53daffce089a685fcc5d6d5d9b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 56fc78841f24..a266449065a0 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -34,7 +34,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -72,12 +76,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -168,6 +175,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -212,6 +221,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + + #define MT_DEFAULT_MAXCONTACT 10 + #define MT_MAX_MAXCONTACT 250 +@@ -396,6 +406,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_SEPARATE_APP_REPORT, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1720,6 +1740,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1743,6 +1826,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1781,15 +1867,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1839,6 +1929,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2235,6 +2326,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.45.2 + +From afee306eb14e58d48ddac5e2807d1e3ddb532d7f Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index a266449065a0..060c706e936a 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -77,6 +77,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -84,6 +85,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -408,6 +411,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1389,6 +1393,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1416,6 +1423,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1437,6 +1459,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1444,6 +1467,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1453,11 +1489,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1610,6 +1656,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1658,6 +1740,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1740,30 +1829,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1772,8 +1837,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1907,13 +1973,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1922,12 +1999,31 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); +-- +2.45.2 + diff --git a/patches/6.8/0010-surface-shutdown.patch b/patches/6.9/0011-surface-shutdown.patch similarity index 89% rename from patches/6.8/0010-surface-shutdown.patch rename to patches/6.9/0011-surface-shutdown.patch index 14f012042c..5a4d4eade5 100644 --- a/patches/6.8/0010-surface-shutdown.patch +++ b/patches/6.9/0011-surface-shutdown.patch @@ -1,7 +1,7 @@ -From aa49dc59b192cc038ca789ac70215d7492f043cb Mon Sep 17 00:00:00 2001 +From 6ab472fbfe2677b038998f6482da35a7daf3c14d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 19 Feb 2023 22:12:24 +0100 -Subject: [PATCH] PCI: Add quirk to prevent calling shutdown mehtod +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown method Work around buggy EFI firmware: On some Microsoft Surface devices (Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with @@ -23,10 +23,10 @@ Patchset: surface-shutdown 3 files changed, 40 insertions(+) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c -index 51ec9e7e784f..40554890d721 100644 +index af2996d0d17f..3ce0fb61257d 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c -@@ -507,6 +507,9 @@ static void pci_device_shutdown(struct device *dev) +@@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_driver *drv = pci_dev->driver; @@ -37,10 +37,10 @@ index 51ec9e7e784f..40554890d721 100644 if (drv && drv->shutdown) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c -index d797df6e5f3e..c674ee496a0b 100644 +index eff7f5df08e2..d1cb4ff3ebc5 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c -@@ -6250,3 +6250,39 @@ static void pci_fixup_d3cold_delay_1sec(struct pci_dev *pdev) +@@ -6253,3 +6253,39 @@ static void pci_fixup_d3cold_delay_1sec(struct pci_dev *pdev) pdev->d3cold_delay = 1000; } DECLARE_PCI_FIXUP_FINAL(0x5555, 0x0004, pci_fixup_d3cold_delay_1sec); @@ -81,7 +81,7 @@ index d797df6e5f3e..c674ee496a0b 100644 +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU diff --git a/include/linux/pci.h b/include/linux/pci.h -index 7ab0d13672da..8d8d9225e0db 100644 +index 6f9c5ed5eb3b..01ec9872f2bc 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -465,6 +465,7 @@ struct pci_dev { @@ -93,5 +93,5 @@ index 7ab0d13672da..8d8d9225e0db 100644 atomic_t enable_cnt; /* pci_enable_device has been called */ -- -2.44.0 +2.45.2 diff --git a/patches/6.9/0012-surface-gpe.patch b/patches/6.9/0012-surface-gpe.patch new file mode 100644 index 0000000000..0fe3fd90c5 --- /dev/null +++ b/patches/6.9/0012-surface-gpe.patch @@ -0,0 +1,51 @@ +From 453898d99903c0e2a3c9b9b7f297abcb1af80d25 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index 62fd4004db31..103fc4468262 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.45.2 + diff --git a/patches/6.8/0012-cameras.patch b/patches/6.9/0013-cameras.patch similarity index 58% rename from patches/6.8/0012-cameras.patch rename to patches/6.9/0013-cameras.patch index f18dc443de..2a6d0cc43c 100644 --- a/patches/6.8/0012-cameras.patch +++ b/patches/6.9/0013-cameras.patch @@ -1,4 +1,4 @@ -From 526bea529e4befa282fcfd01bbadbed7325faf01 Mon Sep 17 00:00:00 2001 +From 2c667679a6694ea9720c30be32ba73977638ac3a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 10 Oct 2021 20:56:57 +0200 Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an @@ -58,10 +58,10 @@ Patchset: cameras 1 file changed, 3 insertions(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index e6ed1ba91e5c..b367890b7438 100644 +index d1464324de95..5d865a34dd9d 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c -@@ -2138,6 +2138,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, +@@ -2181,6 +2181,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, static void acpi_default_enumeration(struct acpi_device *device) { @@ -72,9 +72,9 @@ index e6ed1ba91e5c..b367890b7438 100644 * Do not enumerate devices with enumeration_by_parent flag set as * they will be enumerated by their respective parents. -- -2.44.0 +2.45.2 -From d41ef92974135b1c22c2f46cbaba926701e0d4af Mon Sep 17 00:00:00 2001 +From 0ac07f51d4c870ab988a572a9bdf9e3dde99ae46 Mon Sep 17 00:00:00 2001 From: zouxiaoh Date: Fri, 25 Jun 2021 08:52:59 +0800 Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs @@ -100,10 +100,10 @@ Patchset: cameras 1 file changed, 30 insertions(+) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index 6c01b1aebf27..ceed043464b1 100644 +index 61bc54299a59..a61af0f4e9fc 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c -@@ -45,6 +45,13 @@ +@@ -44,6 +44,13 @@ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ) @@ -117,23 +117,23 @@ index 6c01b1aebf27..ceed043464b1 100644 #define IOAPIC_RANGE_START (0xfee00000) #define IOAPIC_RANGE_END (0xfeefffff) #define IOVA_START_ADDR (0x1000) -@@ -154,12 +161,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); +@@ -227,12 +234,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); - static int dmar_map_gfx = 1; static int dmar_map_ipts = 1; +static int dmar_map_ipu = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; + static int disable_igfx_iommu; - #define IDENTMAP_GFX 2 #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPU 8 #define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; -@@ -2420,6 +2429,9 @@ static int device_def_domain_type(struct device *dev) - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) +@@ -2409,6 +2418,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) @@ -142,9 +142,9 @@ index 6c01b1aebf27..ceed043464b1 100644 if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) return IOMMU_DOMAIN_IDENTITY; } -@@ -2729,6 +2741,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; +@@ -2711,6 +2723,9 @@ static int __init init_dmars(void) + iommu_set_root_entry(iommu); + } + if (!dmar_map_ipu) + iommu_identity_mapping |= IDENTMAP_IPU; @@ -152,8 +152,8 @@ index 6c01b1aebf27..ceed043464b1 100644 if (!dmar_map_ipts) iommu_identity_mapping |= IDENTMAP_IPTS; -@@ -4909,6 +4924,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; +@@ -4884,6 +4899,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + disable_igfx_iommu = 1; } +static void quirk_iommu_ipu(struct pci_dev *dev) @@ -171,7 +171,7 @@ index 6c01b1aebf27..ceed043464b1 100644 static void quirk_iommu_ipts(struct pci_dev *dev) { if (!IS_IPTS(dev)) -@@ -4956,6 +4983,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); +@@ -4931,6 +4958,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); @@ -182,9 +182,9 @@ index 6c01b1aebf27..ceed043464b1 100644 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); -- -2.44.0 +2.45.2 -From 214600c4c0c3039ba0d0a5e522a2eb162da3857b Mon Sep 17 00:00:00 2001 +From 1c90833561276f2ca96a844a980a4660255fd241 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sun, 10 Oct 2021 20:57:02 +0200 Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain @@ -219,9 +219,9 @@ index 1e107fd49f82..e3e1696e7f0e 100644 return 0; -- -2.44.0 +2.45.2 -From f5c4f5e1de99e04416ddffca65246a7769a202e3 Mon Sep 17 00:00:00 2001 +From 9a9c937d189290f66f1744119731887a3acc6d10 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Mar 2023 12:59:39 +0000 Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E @@ -274,9 +274,9 @@ index 07b302e09340..1d3097bc7e48 100644 agpio, func, polarity); if (ret) -- -2.44.0 +2.45.2 -From 216df183e0ad29051b42fcb856d0818a6094f16d Mon Sep 17 00:00:00 2001 +From b329ea16ddeae06460a32495f79cd82d1c58ee65 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 21 Mar 2023 13:45:26 +0000 Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 @@ -313,9 +313,9 @@ index 30f61e04ecaf..9c1292ca8552 100644 V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov7251_test_pattern_menu) - 1, -- -2.44.0 +2.45.2 -From 0573bb8c22ed0f0476a2ca6c5df2a7f09c6a1b66 Mon Sep 17 00:00:00 2001 +From 4b407381f7a1272abf62bd19a32240b4abbb5128 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 22 Mar 2023 11:01:42 +0000 Subject: [PATCH] media: v4l2-core: Acquire privacy led in @@ -334,10 +334,10 @@ Patchset: cameras 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c -index 3ec323bd528b..b55570a0142c 100644 +index 4bb073587817..cbcbf91db640 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c -@@ -796,6 +796,10 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) +@@ -792,6 +792,10 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) INIT_LIST_HEAD(&sd->asc_list); @@ -364,9 +364,9 @@ index 89c7192148df..44eca113e772 100644 if (ret < 0) goto out_cleanup; -- -2.44.0 +2.45.2 -From 84d70102a7892f720a11a0b3d313f3932c859798 Mon Sep 17 00:00:00 2001 +From 1058d4acabef10a3e2002f4c6017cc9f83300ec9 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:16 +0800 Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED @@ -405,9 +405,9 @@ index e3e1696e7f0e..423dc555093f 100644 for (i = 0; i < board_data->n_gpiod_lookups; i++) gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); -- -2.44.0 +2.45.2 -From 183f7e4da9cacc2a0f9cb3549adad9a3c95f1b94 Mon Sep 17 00:00:00 2001 +From 0c84792426ef55475cf21abe08692e849baea648 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:17 +0800 Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB @@ -446,9 +446,9 @@ index 7807fa329db0..2d2abb25b944 100644 + #endif /* __LINUX_MFD_TPS68470_H */ -- -2.44.0 +2.45.2 -From 0f3811853f436eed853d7d226eb811f65137d03a Mon Sep 17 00:00:00 2001 +From bcdf2d2c87ec937d77fad091d249f321d9adc727 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:18 +0800 Subject: [PATCH] leds: tps68470: Add LED control for tps68470 @@ -471,10 +471,10 @@ Patchset: cameras create mode 100644 drivers/leds/leds-tps68470.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig -index d721b254e1e4..1717f94d1491 100644 +index 05e6af88b88c..c120eb0b5aa8 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig -@@ -899,6 +899,18 @@ config LEDS_TPS6105X +@@ -909,6 +909,18 @@ config LEDS_TPS6105X It is a single boost converter primarily for white LEDs and audio amplifiers. @@ -494,7 +494,7 @@ index d721b254e1e4..1717f94d1491 100644 tristate "LED support for SGI Octane machines" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile -index ce07dc295ff0..0ebf6a9f9f7f 100644 +index effdfc6f1e95..6ce609b2cdac 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o @@ -697,509 +697,9 @@ index 000000000000..35aeb5db89c8 +MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); +MODULE_LICENSE("GPL v2"); -- -2.44.0 - -From b7e66758c1bd493c7140c17d0f00f727f8fefb32 Mon Sep 17 00:00:00 2001 -From: Hidenori Kobayashi -Date: Tue, 9 Jan 2024 17:09:09 +0900 -Subject: [PATCH] media: staging: ipu3-imgu: Set fields before - media_entity_pads_init() - -The imgu driver fails to probe with the following message because it -does not set the pad's flags before calling media_entity_pads_init(). +2.45.2 -[ 14.596315] ipu3-imgu 0000:00:05.0: failed initialize subdev media entity (-22) -[ 14.596322] ipu3-imgu 0000:00:05.0: failed to register subdev0 ret (-22) -[ 14.596327] ipu3-imgu 0000:00:05.0: failed to register pipes (-22) -[ 14.596331] ipu3-imgu 0000:00:05.0: failed to create V4L2 devices (-22) - -Fix the initialization order so that the driver probe succeeds. The ops -initialization is also moved together for readability. - -Fixes: a0ca1627b450 ("media: staging/intel-ipu3: Add v4l2 driver based on media framework") -Cc: # 6.7 -Cc: Dan Carpenter -Signed-off-by: Hidenori Kobayashi -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil -Patchset: cameras ---- - drivers/staging/media/ipu3/ipu3-v4l2.c | 16 ++++++++-------- - 1 file changed, 8 insertions(+), 8 deletions(-) - -diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c -index a66f034380c0..3df58eb3e882 100644 ---- a/drivers/staging/media/ipu3/ipu3-v4l2.c -+++ b/drivers/staging/media/ipu3/ipu3-v4l2.c -@@ -1069,6 +1069,11 @@ static int imgu_v4l2_subdev_register(struct imgu_device *imgu, - struct imgu_media_pipe *imgu_pipe = &imgu->imgu_pipe[pipe]; - - /* Initialize subdev media entity */ -+ imgu_sd->subdev.entity.ops = &imgu_media_ops; -+ for (i = 0; i < IMGU_NODE_NUM; i++) { -+ imgu_sd->subdev_pads[i].flags = imgu_pipe->nodes[i].output ? -+ MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; -+ } - r = media_entity_pads_init(&imgu_sd->subdev.entity, IMGU_NODE_NUM, - imgu_sd->subdev_pads); - if (r) { -@@ -1076,11 +1081,6 @@ static int imgu_v4l2_subdev_register(struct imgu_device *imgu, - "failed initialize subdev media entity (%d)\n", r); - return r; - } -- imgu_sd->subdev.entity.ops = &imgu_media_ops; -- for (i = 0; i < IMGU_NODE_NUM; i++) { -- imgu_sd->subdev_pads[i].flags = imgu_pipe->nodes[i].output ? -- MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; -- } - - /* Initialize subdev */ - v4l2_subdev_init(&imgu_sd->subdev, &imgu_subdev_ops); -@@ -1177,15 +1177,15 @@ static int imgu_v4l2_node_setup(struct imgu_device *imgu, unsigned int pipe, - } - - /* Initialize media entities */ -+ node->vdev_pad.flags = node->output ? -+ MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK; -+ vdev->entity.ops = NULL; - r = media_entity_pads_init(&vdev->entity, 1, &node->vdev_pad); - if (r) { - dev_err(dev, "failed initialize media entity (%d)\n", r); - mutex_destroy(&node->lock); - return r; - } -- node->vdev_pad.flags = node->output ? -- MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK; -- vdev->entity.ops = NULL; - - /* Initialize vbq */ - vbq->type = node->vdev_fmt.type; --- -2.44.0 - -From 87ebc160cb35a068acfaf59847c84656cb52b1b7 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus -Date: Thu, 25 May 2023 14:12:04 +0300 -Subject: [PATCH] media: ipu3-cio2: Further clean up async subdev link creation - -Use v4l2_create_fwnode_links_to_pad() to create links from async -sub-devices to the CSI-2 receiver subdevs. - -Signed-off-by: Sakari Ailus -Reviewed-by: Laurent Pinchart -Signed-off-by: Hans Verkuil -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2.c | 22 +++++----------------- - 1 file changed, 5 insertions(+), 17 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -index ed08bf4178f0..83e29c56fe33 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -@@ -28,6 +28,7 @@ - #include - #include - #include -+#include - #include - #include - -@@ -1407,7 +1408,6 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, - static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) - { - struct cio2_device *cio2 = to_cio2_device(notifier); -- struct device *dev = &cio2->pci_dev->dev; - struct sensor_async_subdev *s_asd; - struct v4l2_async_connection *asd; - struct cio2_queue *q; -@@ -1417,23 +1417,10 @@ static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) - s_asd = to_sensor_asd(asd); - q = &cio2->queue[s_asd->csi2.port]; - -- ret = media_entity_get_fwnode_pad(&q->sensor->entity, -- s_asd->asd.match.fwnode, -- MEDIA_PAD_FL_SOURCE); -- if (ret < 0) { -- dev_err(dev, "no pad for endpoint %pfw (%d)\n", -- s_asd->asd.match.fwnode, ret); -- return ret; -- } -- -- ret = media_create_pad_link(&q->sensor->entity, ret, -- &q->subdev.entity, CIO2_PAD_SINK, -- 0); -- if (ret) { -- dev_err(dev, "failed to create link for %s (endpoint %pfw, error %d)\n", -- q->sensor->name, s_asd->asd.match.fwnode, ret); -+ ret = v4l2_create_fwnode_links_to_pad(asd->sd, -+ &q->subdev_pads[CIO2_PAD_SINK], 0); -+ if (ret) - return ret; -- } - } - - return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); -@@ -1572,6 +1559,7 @@ static int cio2_queue_init(struct cio2_device *cio2, struct cio2_queue *q) - v4l2_subdev_init(subdev, &cio2_subdev_ops); - subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; - subdev->owner = THIS_MODULE; -+ subdev->dev = dev; - snprintf(subdev->name, sizeof(subdev->name), - CIO2_ENTITY_NAME " %td", q - cio2->queue); - subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; --- -2.44.0 - -From a9681c29588d67321733877e11f9588ed9e54861 Mon Sep 17 00:00:00 2001 -From: mojyack -Date: Sat, 3 Feb 2024 12:53:33 +0900 -Subject: [PATCH] media: i2c: Revert DW9719 driver - -Patchset: cameras ---- - drivers/media/i2c/dw9719.c | 199 +++++++++++++++++++++++++------------ - 1 file changed, 137 insertions(+), 62 deletions(-) - -diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c -index c626ed845928..d5f585dabb60 100644 ---- a/drivers/media/i2c/dw9719.c -+++ b/drivers/media/i2c/dw9719.c -@@ -6,13 +6,14 @@ - * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 - */ - -+#include -+ - #include - #include - #include - #include - #include - --#include - #include - #include - #include -@@ -20,31 +21,29 @@ - #define DW9719_MAX_FOCUS_POS 1023 - #define DW9719_CTRL_STEPS 16 - #define DW9719_CTRL_DELAY_US 1000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) - --#define DW9719_INFO CCI_REG8(0) -+#define DW9719_INFO 0 - #define DW9719_ID 0xF1 -+#define DW9719_CONTROL 2 -+#define DW9719_VCM_CURRENT 3 - --#define DW9719_CONTROL CCI_REG8(2) --#define DW9719_ENABLE_RINGING 0x02 -- --#define DW9719_VCM_CURRENT CCI_REG16(3) -- --#define DW9719_MODE CCI_REG8(6) --#define DW9719_MODE_SAC_SHIFT 4 --#define DW9719_MODE_SAC3 4 -+#define DW9719_MODE 6 -+#define DW9719_VCM_FREQ 7 - --#define DW9719_VCM_FREQ CCI_REG8(7) -+#define DW9719_MODE_SAC3 0x40 - #define DW9719_DEFAULT_VCM_FREQ 0x60 -+#define DW9719_ENABLE_RINGING 0x02 -+ -+#define NUM_REGULATORS 2 - - #define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) - - struct dw9719_device { -- struct v4l2_subdev sd; - struct device *dev; -- struct regmap *regmap; -- struct regulator *regulator; -- u32 sac_mode; -- u32 vcm_freq; -+ struct i2c_client *client; -+ struct regulator_bulk_data regulators[NUM_REGULATORS]; -+ struct v4l2_subdev sd; - - struct dw9719_v4l2_ctrls { - struct v4l2_ctrl_handler handler; -@@ -52,18 +51,79 @@ struct dw9719_device { - } ctrls; - }; - -+static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) -+{ -+ struct i2c_msg msg[2]; -+ u8 buf[2] = { reg }; -+ int ret; -+ -+ msg[0].addr = client->addr; -+ msg[0].flags = 0; -+ msg[0].len = 1; -+ msg[0].buf = buf; -+ -+ msg[1].addr = client->addr; -+ msg[1].flags = I2C_M_RD; -+ msg[1].len = 1; -+ msg[1].buf = &buf[1]; -+ *val = 0; -+ -+ ret = i2c_transfer(client->adapter, msg, 2); -+ if (ret < 0) -+ return ret; -+ -+ *val = buf[1]; -+ -+ return 0; -+} -+ -+static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) -+{ -+ struct i2c_msg msg; -+ int ret; -+ -+ u8 buf[2] = { reg, val }; -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) -+{ -+ struct i2c_msg msg; -+ u8 buf[3] = { reg }; -+ int ret; -+ -+ put_unaligned_be16(val, buf + 1); -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ - static int dw9719_detect(struct dw9719_device *dw9719) - { - int ret; -- u64 val; -+ u8 val; - -- ret = cci_read(dw9719->regmap, DW9719_INFO, &val, NULL); -+ ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); - if (ret < 0) - return ret; - - if (val != DW9719_ID) { - dev_err(dw9719->dev, "Failed to detect correct id\n"); -- return -ENXIO; -+ ret = -ENXIO; - } - - return 0; -@@ -71,37 +131,54 @@ static int dw9719_detect(struct dw9719_device *dw9719) - - static int dw9719_power_down(struct dw9719_device *dw9719) - { -- return regulator_disable(dw9719->regulator); -+ return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); - } - - static int dw9719_power_up(struct dw9719_device *dw9719) - { - int ret; - -- ret = regulator_enable(dw9719->regulator); -+ ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); - if (ret) - return ret; - - /* Jiggle SCL pin to wake up device */ -- cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); - -- /* Need 100us to transit from SHUTDOWN to STANDBY */ -- fsleep(100); -+ /* Need 100us to transit from SHUTDOWN to STANDBY*/ -+ usleep_range(100, 1000); - -- cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_ENABLE_RINGING, &ret); -- cci_write(dw9719->regmap, DW9719_MODE, -- dw9719->sac_mode << DW9719_MODE_SAC_SHIFT, &ret); -- cci_write(dw9719->regmap, DW9719_VCM_FREQ, dw9719->vcm_freq, &ret); -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, -+ DW9719_ENABLE_RINGING); -+ if (ret < 0) -+ goto fail_powerdown; - -- if (ret) -- dw9719_power_down(dw9719); -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, -+ DW9719_DEFAULT_VCM_FREQ); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ return 0; - -+fail_powerdown: -+ dw9719_power_down(dw9719); - return ret; - } - - static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) - { -- return cci_write(dw9719->regmap, DW9719_VCM_CURRENT, value, NULL); -+ int ret; -+ -+ value = clamp(value, 0, DW9719_MAX_FOCUS_POS); -+ ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); -+ if (ret < 0) -+ return ret; -+ -+ return 0; - } - - static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) -@@ -132,7 +209,7 @@ static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { - .s_ctrl = dw9719_set_ctrl, - }; - --static int dw9719_suspend(struct device *dev) -+static int __maybe_unused dw9719_suspend(struct device *dev) - { - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct dw9719_device *dw9719 = to_dw9719_device(sd); -@@ -151,7 +228,7 @@ static int dw9719_suspend(struct device *dev) - return dw9719_power_down(dw9719); - } - --static int dw9719_resume(struct device *dev) -+static int __maybe_unused dw9719_resume(struct device *dev) - { - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct dw9719_device *dw9719 = to_dw9719_device(sd); -@@ -201,7 +278,9 @@ static int dw9719_init_controls(struct dw9719_device *dw9719) - const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; - int ret; - -- v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); -+ ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); -+ if (ret) -+ return ret; - - dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, - V4L2_CID_FOCUS_ABSOLUTE, 0, -@@ -214,7 +293,8 @@ static int dw9719_init_controls(struct dw9719_device *dw9719) - } - - dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; -- return 0; -+ -+ return ret; - - err_free_handler: - v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -@@ -232,26 +312,24 @@ static int dw9719_probe(struct i2c_client *client) - if (!dw9719) - return -ENOMEM; - -- dw9719->regmap = devm_cci_regmap_init_i2c(client, 8); -- if (IS_ERR(dw9719->regmap)) -- return PTR_ERR(dw9719->regmap); -- -+ dw9719->client = client; - dw9719->dev = &client->dev; -- dw9719->sac_mode = DW9719_MODE_SAC3; -- dw9719->vcm_freq = DW9719_DEFAULT_VCM_FREQ; - -- /* Optional indication of SAC mode select */ -- device_property_read_u32(&client->dev, "dongwoon,sac-mode", -- &dw9719->sac_mode); -- -- /* Optional indication of VCM frequency */ -- device_property_read_u32(&client->dev, "dongwoon,vcm-freq", -- &dw9719->vcm_freq); -+ dw9719->regulators[0].supply = "vdd"; -+ /* -+ * The DW9719 has only the 1 VDD voltage input, but some PMICs such as -+ * the TPS68470 PMIC have I2C passthrough capability, to disconnect the -+ * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) -+ * is off, because some sensors then short these pins to ground; -+ * and the DW9719 might sit behind this passthrough, this it needs to -+ * enable VSIO as that will also enable the I2C passthrough. -+ */ -+ dw9719->regulators[1].supply = "vsio"; - -- dw9719->regulator = devm_regulator_get(&client->dev, "vdd"); -- if (IS_ERR(dw9719->regulator)) -- return dev_err_probe(&client->dev, PTR_ERR(dw9719->regulator), -- "getting regulator\n"); -+ ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, -+ dw9719->regulators); -+ if (ret) -+ return dev_err_probe(&client->dev, ret, "getting regulators\n"); - - v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); - dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -@@ -312,17 +390,13 @@ static int dw9719_probe(struct i2c_client *client) - static void dw9719_remove(struct i2c_client *client) - { - struct v4l2_subdev *sd = i2c_get_clientdata(client); -- struct dw9719_device *dw9719 = -- container_of(sd, struct dw9719_device, sd); -+ struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, -+ sd); - -+ pm_runtime_disable(&client->dev); - v4l2_async_unregister_subdev(sd); - v4l2_ctrl_handler_free(&dw9719->ctrls.handler); - media_entity_cleanup(&dw9719->sd.entity); -- -- pm_runtime_disable(&client->dev); -- if (!pm_runtime_status_suspended(&client->dev)) -- dw9719_power_down(dw9719); -- pm_runtime_set_suspended(&client->dev); - } - - static const struct i2c_device_id dw9719_id_table[] = { -@@ -331,13 +405,14 @@ static const struct i2c_device_id dw9719_id_table[] = { - }; - MODULE_DEVICE_TABLE(i2c, dw9719_id_table); - --static DEFINE_RUNTIME_DEV_PM_OPS(dw9719_pm_ops, dw9719_suspend, dw9719_resume, -- NULL); -+static const struct dev_pm_ops dw9719_pm_ops = { -+ SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) -+}; - - static struct i2c_driver dw9719_i2c_driver = { - .driver = { - .name = "dw9719", -- .pm = pm_sleep_ptr(&dw9719_pm_ops), -+ .pm = &dw9719_pm_ops, - }, - .probe = dw9719_probe, - .remove = dw9719_remove, --- -2.44.0 - -From c5d6c95fd5cefbd4ba9779fc965bce0a36bdbe5e Mon Sep 17 00:00:00 2001 +From 95b7370aee5aed2b2cad5baf8a6835b47fd2d8a5 Mon Sep 17 00:00:00 2001 From: mojyack Date: Sat, 3 Feb 2024 12:59:53 +0900 Subject: [PATCH] media: staging: ipu3-imgu: Fix multiple calls of s_stream on @@ -1244,5 +744,37 @@ index 3df58eb3e882..81aff2d5d898 100644 r = imgu_s_stream(imgu, false); if (!r) -- -2.44.0 +2.45.2 + +From 7078234b232f3ebc7b79b14e385c70a3bfd31b4d Mon Sep 17 00:00:00 2001 +From: mojyack +Date: Tue, 26 Mar 2024 05:55:44 +0900 +Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 + +On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". +The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. +So just add some delay. +There is no exact reason for this 10000us, but 100us failed. + +Patchset: cameras +--- + drivers/media/i2c/dw9719.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +index c626ed845928..0094cfda57ea 100644 +--- a/drivers/media/i2c/dw9719.c ++++ b/drivers/media/i2c/dw9719.c +@@ -82,6 +82,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719) + if (ret) + return ret; + ++ /* Wait for device to be acknowledged */ ++ fsleep(10000); ++ + /* Jiggle SCL pin to wake up device */ + cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); + +-- +2.45.2 diff --git a/patches/6.9/0014-amd-gpio.patch b/patches/6.9/0014-amd-gpio.patch new file mode 100644 index 0000000000..09a39a3f43 --- /dev/null +++ b/patches/6.9/0014-amd-gpio.patch @@ -0,0 +1,109 @@ +From e77ba620d8f52dacab1b3515fe05617fd7b28141 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 4bf82dbd2a6b..7a8cb090c656 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1216,6 +1217,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1271,6 +1283,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.45.2 + +From 07d6fb46ee1d967a8882c85e0abc0ce12dffbf1d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 7a8cb090c656..0faafc323e67 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1219,12 +1219,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.45.2 + diff --git a/patches/6.9/0015-rtc.patch b/patches/6.9/0015-rtc.patch new file mode 100644 index 0000000000..90bdf64689 --- /dev/null +++ b/patches/6.9/0015-rtc.patch @@ -0,0 +1,110 @@ +From d42ba535b58d5fb2325b26fba1460435afa86dac Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 1d670dbe4d1d..71c9e375ca1c 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -432,6 +432,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, ++ NULL, ++}; ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, ++}; ++ + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) + { +@@ -480,15 +488,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RW(ac_status); + +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, + }; + + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -564,13 +571,18 @@ static void acpi_tad_remove(struct platform_device *pdev) + + pm_runtime_get_sync(dev); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -612,12 +624,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -648,6 +654,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.45.2 + diff --git a/pkg/arch/kernel/0001-secureboot.patch b/pkg/arch/kernel/0001-secureboot.patch new file mode 120000 index 0000000000..5b0195eb8d --- /dev/null +++ b/pkg/arch/kernel/0001-secureboot.patch @@ -0,0 +1 @@ +../../../patches/6.18/0001-secureboot.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0001-surface3-oemb.patch b/pkg/arch/kernel/0001-surface3-oemb.patch deleted file mode 120000 index 70abb1cbb5..0000000000 --- a/pkg/arch/kernel/0001-surface3-oemb.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0001-surface3-oemb.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0002-mwifiex.patch b/pkg/arch/kernel/0002-mwifiex.patch deleted file mode 120000 index bd8ac5ab1c..0000000000 --- a/pkg/arch/kernel/0002-mwifiex.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0002-mwifiex.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0002-surface3.patch b/pkg/arch/kernel/0002-surface3.patch new file mode 120000 index 0000000000..35e0ba2241 --- /dev/null +++ b/pkg/arch/kernel/0002-surface3.patch @@ -0,0 +1 @@ +../../../patches/6.18/0002-surface3.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0003-ath10k.patch b/pkg/arch/kernel/0003-ath10k.patch deleted file mode 120000 index 0be71beb76..0000000000 --- a/pkg/arch/kernel/0003-ath10k.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0003-ath10k.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0003-mwifiex.patch b/pkg/arch/kernel/0003-mwifiex.patch new file mode 120000 index 0000000000..64548e7d9d --- /dev/null +++ b/pkg/arch/kernel/0003-mwifiex.patch @@ -0,0 +1 @@ +../../../patches/6.18/0003-mwifiex.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0004-ath10k.patch b/pkg/arch/kernel/0004-ath10k.patch new file mode 120000 index 0000000000..52693d8bb3 --- /dev/null +++ b/pkg/arch/kernel/0004-ath10k.patch @@ -0,0 +1 @@ +../../../patches/6.18/0004-ath10k.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0004-ipts.patch b/pkg/arch/kernel/0004-ipts.patch deleted file mode 120000 index 7bd11594ed..0000000000 --- a/pkg/arch/kernel/0004-ipts.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0004-ipts.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0005-ipts.patch b/pkg/arch/kernel/0005-ipts.patch new file mode 120000 index 0000000000..255fbd10cf --- /dev/null +++ b/pkg/arch/kernel/0005-ipts.patch @@ -0,0 +1 @@ +../../../patches/6.18/0005-ipts.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0005-ithc.patch b/pkg/arch/kernel/0005-ithc.patch deleted file mode 120000 index 51503ed31a..0000000000 --- a/pkg/arch/kernel/0005-ithc.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0005-ithc.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0006-ithc.patch b/pkg/arch/kernel/0006-ithc.patch new file mode 120000 index 0000000000..fc3080a983 --- /dev/null +++ b/pkg/arch/kernel/0006-ithc.patch @@ -0,0 +1 @@ +../../../patches/6.18/0006-ithc.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0006-surface-sam.patch b/pkg/arch/kernel/0006-surface-sam.patch deleted file mode 120000 index c8bcc9ae03..0000000000 --- a/pkg/arch/kernel/0006-surface-sam.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0006-surface-sam.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0007-surface-sam-over-hid.patch b/pkg/arch/kernel/0007-surface-sam-over-hid.patch deleted file mode 120000 index a99ff8176f..0000000000 --- a/pkg/arch/kernel/0007-surface-sam-over-hid.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0007-surface-sam-over-hid.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0007-surface-sam.patch b/pkg/arch/kernel/0007-surface-sam.patch new file mode 120000 index 0000000000..6c09d90243 --- /dev/null +++ b/pkg/arch/kernel/0007-surface-sam.patch @@ -0,0 +1 @@ +../../../patches/6.18/0007-surface-sam.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0008-surface-button.patch b/pkg/arch/kernel/0008-surface-button.patch deleted file mode 120000 index 3cddd1a917..0000000000 --- a/pkg/arch/kernel/0008-surface-button.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0008-surface-button.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0008-surface-sam-over-hid.patch b/pkg/arch/kernel/0008-surface-sam-over-hid.patch new file mode 120000 index 0000000000..08f79dcde2 --- /dev/null +++ b/pkg/arch/kernel/0008-surface-sam-over-hid.patch @@ -0,0 +1 @@ +../../../patches/6.18/0008-surface-sam-over-hid.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0009-surface-button.patch b/pkg/arch/kernel/0009-surface-button.patch new file mode 120000 index 0000000000..7ff9dad876 --- /dev/null +++ b/pkg/arch/kernel/0009-surface-button.patch @@ -0,0 +1 @@ +../../../patches/6.18/0009-surface-button.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0009-surface-typecover.patch b/pkg/arch/kernel/0009-surface-typecover.patch deleted file mode 120000 index 5a55975721..0000000000 --- a/pkg/arch/kernel/0009-surface-typecover.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0009-surface-typecover.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0010-surface-shutdown.patch b/pkg/arch/kernel/0010-surface-shutdown.patch deleted file mode 120000 index 222bcfaabb..0000000000 --- a/pkg/arch/kernel/0010-surface-shutdown.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0010-surface-shutdown.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0010-surface-typecover.patch b/pkg/arch/kernel/0010-surface-typecover.patch new file mode 120000 index 0000000000..9671bcc9b5 --- /dev/null +++ b/pkg/arch/kernel/0010-surface-typecover.patch @@ -0,0 +1 @@ +../../../patches/6.18/0010-surface-typecover.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0011-surface-gpe.patch b/pkg/arch/kernel/0011-surface-gpe.patch deleted file mode 120000 index 705191ef40..0000000000 --- a/pkg/arch/kernel/0011-surface-gpe.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0011-surface-gpe.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0011-surface-shutdown.patch b/pkg/arch/kernel/0011-surface-shutdown.patch new file mode 120000 index 0000000000..301ed6896e --- /dev/null +++ b/pkg/arch/kernel/0011-surface-shutdown.patch @@ -0,0 +1 @@ +../../../patches/6.18/0011-surface-shutdown.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0012-cameras.patch b/pkg/arch/kernel/0012-cameras.patch deleted file mode 120000 index 478a05bfc0..0000000000 --- a/pkg/arch/kernel/0012-cameras.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0012-cameras.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0012-surface-gpe.patch b/pkg/arch/kernel/0012-surface-gpe.patch new file mode 120000 index 0000000000..36c0968268 --- /dev/null +++ b/pkg/arch/kernel/0012-surface-gpe.patch @@ -0,0 +1 @@ +../../../patches/6.18/0012-surface-gpe.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0013-amd-gpio.patch b/pkg/arch/kernel/0013-amd-gpio.patch deleted file mode 120000 index 8128acb79b..0000000000 --- a/pkg/arch/kernel/0013-amd-gpio.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0013-amd-gpio.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0013-cameras.patch b/pkg/arch/kernel/0013-cameras.patch new file mode 120000 index 0000000000..a558b038bf --- /dev/null +++ b/pkg/arch/kernel/0013-cameras.patch @@ -0,0 +1 @@ +../../../patches/6.18/0013-cameras.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0014-amd-gpio.patch b/pkg/arch/kernel/0014-amd-gpio.patch new file mode 120000 index 0000000000..26387d23f1 --- /dev/null +++ b/pkg/arch/kernel/0014-amd-gpio.patch @@ -0,0 +1 @@ +../../../patches/6.18/0014-amd-gpio.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0014-rtc.patch b/pkg/arch/kernel/0014-rtc.patch deleted file mode 120000 index e66855191c..0000000000 --- a/pkg/arch/kernel/0014-rtc.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/6.8/0014-rtc.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0015-rtc.patch b/pkg/arch/kernel/0015-rtc.patch new file mode 120000 index 0000000000..ab76e5fea1 --- /dev/null +++ b/pkg/arch/kernel/0015-rtc.patch @@ -0,0 +1 @@ +../../../patches/6.18/0015-rtc.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0016-hid-surface.patch b/pkg/arch/kernel/0016-hid-surface.patch new file mode 120000 index 0000000000..721d18b34c --- /dev/null +++ b/pkg/arch/kernel/0016-hid-surface.patch @@ -0,0 +1 @@ +../../../patches/6.18/0016-hid-surface.patch \ No newline at end of file diff --git a/pkg/arch/kernel/0017-powercap.patch b/pkg/arch/kernel/0017-powercap.patch new file mode 120000 index 0000000000..4e00a36f8d --- /dev/null +++ b/pkg/arch/kernel/0017-powercap.patch @@ -0,0 +1 @@ +../../../patches/6.18/0017-powercap.patch \ No newline at end of file diff --git a/pkg/arch/kernel/PKGBUILD b/pkg/arch/kernel/PKGBUILD index 4202895cfd..c192df9ec8 100644 --- a/pkg/arch/kernel/PKGBUILD +++ b/pkg/arch/kernel/PKGBUILD @@ -3,7 +3,7 @@ # Maintainer: Jan Alexander Steffens (heftig) pkgbase=linux-surface -pkgver=6.8.1.arch1 +pkgver=6.18.8.arch2 pkgrel=1 pkgdesc='Linux' _shortver=${pkgver%.*} @@ -19,6 +19,9 @@ makedepends=( git libelf perl + rust + rust-bindgen + rust-src tar xz python @@ -28,7 +31,10 @@ makedepends=( optdepends=('iptsd: Touchscreen support' 'linux-firmware: Firmware files for Linux' 'linux-firmware-marvell: Firmware files for Marvell WiFi / Bluetooth') -options=('!strip') +options=( + !debug + !strip +) _srcname=archlinux-linux source=( "$_srcname::git+https://github.com/archlinux/linux#tag=$_srctag" @@ -36,44 +42,50 @@ source=( surface.config # surface specific options arch.config # config changes for linux-surface kernel - 0001-surface3-oemb.patch - 0002-mwifiex.patch - 0003-ath10k.patch - 0004-ipts.patch - 0005-ithc.patch - 0006-surface-sam.patch - 0007-surface-sam-over-hid.patch - 0008-surface-button.patch - 0009-surface-typecover.patch - 0010-surface-shutdown.patch - 0011-surface-gpe.patch - 0012-cameras.patch - 0013-amd-gpio.patch - 0014-rtc.patch + 0001-secureboot.patch + 0002-surface3.patch + 0003-mwifiex.patch + 0004-ath10k.patch + 0005-ipts.patch + 0006-ithc.patch + 0007-surface-sam.patch + 0008-surface-sam-over-hid.patch + 0009-surface-button.patch + 0010-surface-typecover.patch + 0011-surface-shutdown.patch + 0012-surface-gpe.patch + 0013-cameras.patch + 0014-amd-gpio.patch + 0015-rtc.patch + 0016-hid-surface.patch + 0017-powercap.patch ) validpgpkeys=( 'ABAF11C65A2970B130ABE3C479BE3E4300411886' # Linus Torvalds '647F28654894E3BD457199BE38DBBDC86092693E' # Greg Kroah-Hartman 'A2FF3A36AAA56654109064AB19802F8B0D70FC30' # Jan Alexander Steffens (heftig) ) -sha256sums=('SKIP' - 'c2b00c84c4b543db431e06604d939a62f93107d18369f4d9860dc8062b01ab45' - '985cd5e0b11538f31887e451c398d226c06ca4b34c5466e93a21ebd65b7b3919' - '3cabe391cc39dbee88a79f76e9e7a68f7fbcd5518941aa2ab73a77692c87dead' - 'ee9857e96ee5d871aa557f6d6d142474c26187faa2c21b351bafde065b288fe0' - '12f914866916181f232b4f159a270bce1fc8bd8e93c4f86ae7b18f8d6a777573' - '1984f355569702ff87cfab75682ceb89bfe40ad22c5c4dd8653abe623e000dfb' - 'be01d5c3e851830be8ef55c794e8480e5cf19ff6b66266db32d352c2c60eebe4' - '0b27a1ea32c6cdbb3ab89866724b0b193998d7ee0525d736242411c2ec3d4bd5' - 'ca4c0bc2b26e3e07c29537cbecc8bade50212d5a0277ae1a834b7c33c36f7c8a' - '9dd041d482eb7f0479487c5fe127f4fd6bec1209d9cd7d77a73f87b205035238' - 'bd32fc7cc18345f027a4fe2af2b249d27470f7b120b1e521dd26008cc34e1e8d' - '02d720e3fffae018fc8719a98135e63c84e8b7f39769bcdee45ddf973a567732' - '6225348b31112a67f5e37a9075363b36c103ba6144083cf4e2d7d83edc77513c' - 'ea0016af33f682a0353738b00c39407b7e1a96a9ccfb04b3904b58143c1953ee' - '19fb55efe921ac95376ec2169b09add1846aa3bdf9eff368d74f2cc1f25364d5' - '1e620fa5bb90d7a8d4a57d0dc1552b096762a4da4cf1a5ce461d54c3c9b9885f' - '7693cf1df35a47ec8702543d9665f9010a5c7db18a39d9506649e892fd36954f') +sha256sums=('9526800a611ea4a46e8410686c89bab5a92df4b556b853e8933caa0d6cca835a' + 'd0ce1ee11ca0bc6a817c3c17a2651076409bd9fd6c0ab9e744aae2131ab654ce' + '233a1c4d898b7720dd623421b694cc030075317b01cba6444b53faefc7b2ca55' + '008e37de7e6cf8b9479ef0f0f425a25092646e85d741aedd056e5ff3e1096361' + '25457105444c91e4de57c6938f4e36f98e2ce1d6f079b401ae8d6c2ada689c06' + 'a813b8fda4a4eadd13ad78fac982aba0abcad72398cd9989eaafcc8372d3d39b' + 'a421e02dcbad81ec20e7c0e9c83a26ea018b203d0da96090cce25f8da6c63982' + 'e3022dbf12cf2e24fe0be1777da129808d9827be89e09d34f4971c5bfda53972' + '4b2972e73d311ac9f603e9229491464439a36537666f426797dcc70ecc611a4b' + '7b908157f3c88a16106555513d36c00a20ceec78314600b9ad042c8e62a7a90a' + '33347c5a7c7112ad08d83dbb54d0841c1044c62c9695a40a3cf09a54a65c4684' + 'c77726fee303a204f05385cacd6644a3e3452580056391d11873919b18ca5efb' + 'e40a928da09249e35f40072bee54f61a0b5d9aea12917674fa9c4a3944072e2b' + 'b6badd94dc5157c88ed64d34a38a4333d0cc6b19190d179a57a5a2fb3248bc24' + '3722b78f24f19d653849864fc2ae24c782a2c83519620551191b96bcaac1a3cb' + 'aa9601d0e3e8c5268c3e975632e8de93e001515b25b8c0caf2c0428907783c68' + 'aaabc6fe397ad534e0d1654ffafefe8aebd9b8cbb52d05b0abb0c394e1dce650' + 'beff0cf807dfbcbe58505f9d101a910679e360a11bb82ce9194db367c844ea7c' + '34ce59aa1f32cdcbb48307cbdc06de27376c909938fb6f8647c3dc47f859aac6' + 'bb2c5d4718eba576037d7d8c59dbd0227cadf5184b644071e26d441b38911e77' + '4e5876d308e391fc2055a6b1671d8af0bb394e81e05bede7edcc829ab6ba3059') export KBUILD_BUILD_HOST=archlinux @@ -134,12 +146,14 @@ _package() { kmod ) optdepends=( - 'wireless-regdb: to set the correct wireless channels of your country' 'linux-firmware: firmware images needed for some devices' + 'scx-scheds: to use sched-ext schedulers' + 'wireless-regdb: to set the correct wireless channels of your country' ) provides=( linux=${_shortver} KSMBD-MODULE + NTSYNC-MODULE VIRTUALBOX-GUEST-MODULES WIREGUARD-MODULE ) @@ -167,10 +181,10 @@ _package() { echo "$pkgbase" | install -Dm644 /dev/stdin "$modulesdir/pkgbase" echo "Installing modules..." - _make INSTALL_MOD_PATH="$pkgdir/usr" INSTALL_MOD_STRIP=1 \ + ZSTD_CLEVEL=19 _make INSTALL_MOD_PATH="$pkgdir/usr" INSTALL_MOD_STRIP=1 \ DEPMOD=/doesnt/exist modules_install # Suppress depmod - # remove build and source links + # remove build link rm "$modulesdir"/build } @@ -188,6 +202,7 @@ _package-headers() { install -Dt "$builddir/kernel" -m644 kernel/Makefile install -Dt "$builddir/arch/x86" -m644 arch/x86/Makefile cp -t "$builddir" -a scripts + ln -srt "$builddir" "$builddir/scripts/gdb/vmlinux-gdb.py" # required when STACK_VALIDATION is enabled install -Dt "$builddir/tools/objtool" tools/objtool/objtool @@ -200,10 +215,10 @@ _package-headers() { install -Dt "$builddir/drivers/md" -m644 drivers/md/*.h install -Dt "$builddir/net/mac80211" -m644 net/mac80211/*.h - # http://bugs.archlinux.org/task/13146 + # https://bugs.archlinux.org/task/13146 install -Dt "$builddir/drivers/media/i2c" -m644 drivers/media/i2c/msp3400-driver.h - # http://bugs.archlinux.org/task/20402 + # https://bugs.archlinux.org/task/20402 install -Dt "$builddir/drivers/media/usb/dvb-usb" -m644 drivers/media/usb/dvb-usb/*.h install -Dt "$builddir/drivers/media/dvb-frontends" -m644 drivers/media/dvb-frontends/*.h install -Dt "$builddir/drivers/media/tuners" -m644 drivers/media/tuners/*.h @@ -214,6 +229,14 @@ _package-headers() { echo "Installing KConfig files..." find . -name 'Kconfig*' -exec install -Dm644 {} "$builddir/{}" \; + echo "Installing Rust files..." + install -Dt "$builddir/rust" -m644 rust/*.rmeta + install -Dt "$builddir/rust" rust/*.so + + echo "Installing unstripped VDSO..." + make INSTALL_MOD_PATH="$pkgdir/usr" vdso_install \ + link= # Suppress build-id symlinks + echo "Removing unneeded architectures..." local arch for arch in "$builddir"/arch/*/; do @@ -234,7 +257,7 @@ _package-headers() { echo "Stripping build tools..." local file while read -rd '' file; do - case "$(file -bi "$file")" in + case "$(file -Sib "$file")" in application/x-sharedlib\;*) # Libraries (.so) strip -v $STRIP_SHARED "$file" ;; application/x-archive\;*) # Libraries (.a) diff --git a/pkg/arch/kernel/arch.config b/pkg/arch/kernel/arch.config index e18e444ab7..f2912ab075 100644 --- a/pkg/arch/kernel/arch.config +++ b/pkg/arch/kernel/arch.config @@ -24,3 +24,16 @@ CONFIG_ANDROID=y CONFIG_ANDROID_BINDER_IPC=y CONFIG_ANDROID_BINDERFS=y CONFIG_ANDROID_BINDER_DEVICES="" + +## +## Build-in basic pinctrl modules to prevent race-conditions in drivers +## relying on them (like soc-button-array) +## + +CONFIG_PINCTRL_INTEL=y +CONFIG_PINCTRL_ALDERLAKE=y +CONFIG_PINCTRL_CANNONLAKE=y +CONFIG_PINCTRL_ICELAKE=y +CONFIG_PINCTRL_METEORLAKE=y +CONFIG_PINCTRL_SUNRISEPOINT=y +CONFIG_PINCTRL_TIGERLAKE=y diff --git a/pkg/arch/kernel/config b/pkg/arch/kernel/config index 4d684210b9..644f252ada 100644 --- a/pkg/arch/kernel/config +++ b/pkg/arch/kernel/config @@ -1,25 +1,35 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/x86 6.8.1-arch1 Kernel Configuration +# Linux/x86 6.18.2-arch2 Kernel Configuration # -CONFIG_CC_VERSION_TEXT="gcc (GCC) 13.2.1 20230801" +CONFIG_CC_VERSION_TEXT="gcc (GCC) 15.2.1 20251112" CONFIG_CC_IS_GCC=y -CONFIG_GCC_VERSION=130201 +CONFIG_GCC_VERSION=150201 CONFIG_CLANG_VERSION=0 CONFIG_AS_IS_GNU=y -CONFIG_AS_VERSION=24200 +CONFIG_AS_VERSION=24501 CONFIG_LD_IS_BFD=y -CONFIG_LD_VERSION=24200 +CONFIG_LD_VERSION=24501 CONFIG_LLD_VERSION=0 +CONFIG_RUSTC_VERSION=109200 +CONFIG_RUST_IS_AVAILABLE=y +CONFIG_RUSTC_LLVM_VERSION=210106 CONFIG_CC_CAN_LINK=y -CONFIG_CC_CAN_LINK_STATIC=y CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y CONFIG_CC_HAS_ASM_GOTO_TIED_OUTPUT=y -CONFIG_GCC_ASM_GOTO_OUTPUT_WORKAROUND=y CONFIG_TOOLS_SUPPORT_RELR=y CONFIG_CC_HAS_ASM_INLINE=y +CONFIG_CC_HAS_ASSUME=y CONFIG_CC_HAS_NO_PROFILE_FN_ATTR=y -CONFIG_PAHOLE_VERSION=126 +CONFIG_CC_HAS_COUNTED_BY=y +CONFIG_CC_HAS_MULTIDIMENSIONAL_NONSTRING=y +CONFIG_LD_CAN_USE_KEEP_IN_OVERLAY=y +CONFIG_RUSTC_HAS_COERCE_POINTEE=y +CONFIG_RUSTC_HAS_SPAN_FILE=y +CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES=y +CONFIG_RUSTC_HAS_FILE_WITH_NUL=y +CONFIG_RUSTC_HAS_FILE_AS_C_STR=y +CONFIG_PAHOLE_VERSION=131 CONFIG_IRQ_WORK=y CONFIG_BUILDTIME_TABLE_SORT=y CONFIG_THREAD_INFO_IN_TASK=y @@ -56,7 +66,6 @@ CONFIG_POSIX_MQUEUE=y CONFIG_POSIX_MQUEUE_SYSCTL=y CONFIG_WATCH_QUEUE=y CONFIG_CROSS_MEMORY_ATTACH=y -# CONFIG_USELIB is not set CONFIG_AUDIT=y CONFIG_HAVE_ARCH_AUDITSYSCALL=y CONFIG_AUDITSYSCALL=y @@ -76,9 +85,9 @@ CONFIG_IRQ_DOMAIN=y CONFIG_IRQ_SIM=y CONFIG_IRQ_DOMAIN_HIERARCHY=y CONFIG_GENERIC_MSI_IRQ=y -CONFIG_IRQ_MSI_IOMMU=y CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y CONFIG_GENERIC_IRQ_RESERVATION_MODE=y +CONFIG_GENERIC_IRQ_STAT_SNAPSHOT=y CONFIG_IRQ_FORCED_THREADING=y CONFIG_SPARSE_IRQ=y # CONFIG_GENERIC_IRQ_DEBUGFS is not set @@ -86,10 +95,10 @@ CONFIG_SPARSE_IRQ=y CONFIG_CLOCKSOURCE_WATCHDOG=y CONFIG_ARCH_CLOCKSOURCE_INIT=y -CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE=y CONFIG_GENERIC_TIME_VSYSCALL=y CONFIG_GENERIC_CLOCKEVENTS=y CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST_IDLE=y CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST=y CONFIG_GENERIC_CMOS_UPDATE=y CONFIG_HAVE_POSIX_CPU_TIMERS_TASK_WORK=y @@ -110,6 +119,7 @@ CONFIG_CONTEXT_TRACKING_USER=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_CLOCKSOURCE_WATCHDOG_MAX_SKEW_US=100 +CONFIG_POSIX_AUX_CLOCKS=y # end of Timers subsystem CONFIG_BPF=y @@ -129,13 +139,16 @@ CONFIG_BPF_LSM=y # end of BPF subsystem CONFIG_PREEMPT_BUILD=y +CONFIG_ARCH_HAS_PREEMPT_LAZY=y # CONFIG_PREEMPT_NONE is not set # CONFIG_PREEMPT_VOLUNTARY is not set CONFIG_PREEMPT=y +# CONFIG_PREEMPT_LAZY is not set CONFIG_PREEMPT_COUNT=y CONFIG_PREEMPTION=y CONFIG_PREEMPT_DYNAMIC=y CONFIG_SCHED_CORE=y +CONFIG_SCHED_CLASS_EXT=y # # CPU/Task time and stats accounting @@ -163,8 +176,10 @@ CONFIG_TREE_RCU=y CONFIG_PREEMPT_RCU=y CONFIG_RCU_EXPERT=y CONFIG_TREE_SRCU=y +# CONFIG_FORCE_NEED_SRCU_NMI_SAFE is not set CONFIG_TASKS_RCU_GENERIC=y # CONFIG_FORCE_TASKS_RCU is not set +CONFIG_NEED_TASKS_RCU=y CONFIG_TASKS_RCU=y # CONFIG_FORCE_TASKS_RUDE_RCU is not set CONFIG_TASKS_RUDE_RCU=y @@ -182,6 +197,7 @@ CONFIG_RCU_NOCB_CPU=y # CONFIG_RCU_NOCB_CPU_CB_BOOST is not set # CONFIG_TASKS_TRACE_RCU_READ_MB is not set CONFIG_RCU_LAZY=y +# CONFIG_RCU_LAZY_DEFAULT_OFF is not set CONFIG_RCU_DOUBLE_CHECK_CB_TIME=y # end of RCU Subsystem @@ -211,25 +227,30 @@ CONFIG_CC_NO_STRINGOP_OVERFLOW=y CONFIG_ARCH_SUPPORTS_INT128=y CONFIG_NUMA_BALANCING=y CONFIG_NUMA_BALANCING_DEFAULT_ENABLED=y +CONFIG_SLAB_OBJ_EXT=y CONFIG_CGROUPS=y CONFIG_PAGE_COUNTER=y # CONFIG_CGROUP_FAVOR_DYNMODS is not set CONFIG_MEMCG=y -CONFIG_MEMCG_KMEM=y +# CONFIG_MEMCG_V1 is not set CONFIG_BLK_CGROUP=y CONFIG_CGROUP_WRITEBACK=y CONFIG_CGROUP_SCHED=y +CONFIG_GROUP_SCHED_WEIGHT=y +CONFIG_GROUP_SCHED_BANDWIDTH=y CONFIG_FAIR_GROUP_SCHED=y CONFIG_CFS_BANDWIDTH=y # CONFIG_RT_GROUP_SCHED is not set +CONFIG_EXT_GROUP_SCHED=y CONFIG_SCHED_MM_CID=y CONFIG_UCLAMP_TASK_GROUP=y CONFIG_CGROUP_PIDS=y CONFIG_CGROUP_RDMA=y +CONFIG_CGROUP_DMEM=y CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_HUGETLB=y CONFIG_CPUSETS=y -CONFIG_PROC_PID_CPUSET=y +# CONFIG_CPUSETS_V1 is not set CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_CPUACCT=y CONFIG_CGROUP_PERF=y @@ -242,7 +263,6 @@ CONFIG_UTS_NS=y CONFIG_TIME_NS=y CONFIG_IPC_NS=y CONFIG_USER_NS=y -CONFIG_USER_NS_UNPRIVILEGED=y CONFIG_PID_NS=y CONFIG_NET_NS=y CONFIG_CHECKPOINT_RESTORE=y @@ -268,21 +288,22 @@ CONFIG_LD_ORPHAN_WARN_LEVEL="warn" CONFIG_SYSCTL=y CONFIG_HAVE_UID16=y CONFIG_SYSCTL_EXCEPTION_TRACE=y +# CONFIG_SYSFS_SYSCALL is not set CONFIG_HAVE_PCSPKR_PLATFORM=y # CONFIG_EXPERT is not set CONFIG_UID16=y CONFIG_MULTIUSER=y CONFIG_SGETMASK_SYSCALL=y -CONFIG_SYSFS_SYSCALL=y CONFIG_FHANDLE=y CONFIG_POSIX_TIMERS=y CONFIG_PRINTK=y CONFIG_BUG=y CONFIG_ELF_CORE=y CONFIG_PCSPKR_PLATFORM=y -CONFIG_BASE_FULL=y CONFIG_FUTEX=y CONFIG_FUTEX_PI=y +CONFIG_FUTEX_PRIVATE_HASH=y +CONFIG_FUTEX_MPOL=y CONFIG_EPOLL=y CONFIG_SIGNALFD=y CONFIG_TIMERFD=y @@ -298,9 +319,8 @@ CONFIG_CACHESTAT_SYSCALL=y CONFIG_KALLSYMS=y # CONFIG_KALLSYMS_SELFTEST is not set CONFIG_KALLSYMS_ALL=y -CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y -CONFIG_KALLSYMS_BASE_RELATIVE=y CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y +CONFIG_ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS=y CONFIG_HAVE_PERF_EVENTS=y CONFIG_GUEST_PERF_EVENTS=y @@ -313,12 +333,16 @@ CONFIG_PERF_EVENTS=y CONFIG_SYSTEM_DATA_VERIFICATION=y CONFIG_PROFILING=y +CONFIG_RUST=y +CONFIG_RUSTC_VERSION_TEXT="rustc 1.92.0 (ded5c06cf 2025-12-08) (Arch Linux rust 1:1.92.0-1)" +CONFIG_BINDGEN_VERSION_TEXT="bindgen 0.72.1" CONFIG_TRACEPOINTS=y # # Kexec and crash features # -CONFIG_CRASH_CORE=y +CONFIG_CRASH_RESERVE=y +CONFIG_VMCORE_INFO=y CONFIG_KEXEC_CORE=y CONFIG_KEXEC=y CONFIG_KEXEC_FILE=y @@ -326,7 +350,11 @@ CONFIG_KEXEC_SIG=y # CONFIG_KEXEC_SIG_FORCE is not set CONFIG_KEXEC_BZIMAGE_VERIFY_SIG=y CONFIG_KEXEC_JUMP=y +CONFIG_KEXEC_HANDOVER=y +# CONFIG_KEXEC_HANDOVER_DEBUG is not set CONFIG_CRASH_DUMP=y +CONFIG_CRASH_DM_CRYPT=y +CONFIG_CRASH_DM_CRYPT_CONFIGS=y CONFIG_CRASH_HOTPLUG=y CONFIG_CRASH_MAX_MEMORY_RANGES=8192 # end of Kexec and crash features @@ -354,21 +382,21 @@ CONFIG_ARCH_HIBERNATION_POSSIBLE=y CONFIG_ARCH_SUSPEND_POSSIBLE=y CONFIG_AUDIT_ARCH=y CONFIG_HAVE_INTEL_TXT=y -CONFIG_X86_64_SMP=y CONFIG_ARCH_SUPPORTS_UPROBES=y CONFIG_FIX_EARLYCON_MEM=y CONFIG_DYNAMIC_PHYSICAL_MASK=y CONFIG_PGTABLE_LEVELS=5 -CONFIG_CC_HAS_SANE_STACKPROTECTOR=y # # Processor type and features # CONFIG_SMP=y CONFIG_X86_X2APIC=y +CONFIG_AMD_SECURE_AVIC=y +CONFIG_X86_POSTED_MSI=y CONFIG_X86_MPPARSE=y -# CONFIG_GOLDFISH is not set CONFIG_X86_CPU_RESCTRL=y +CONFIG_X86_FRED=y # CONFIG_X86_EXTENDED_PLATFORM is not set CONFIG_X86_INTEL_LPSS=y CONFIG_X86_AMD_PLATFORM_DEVICE=y @@ -390,7 +418,6 @@ CONFIG_XEN_PV_DOM0=y CONFIG_XEN_PVHVM=y CONFIG_XEN_PVHVM_SMP=y CONFIG_XEN_PVHVM_GUEST=y -CONFIG_XEN_SAVE_RESTORE=y # CONFIG_XEN_DEBUG_FS is not set CONFIG_XEN_PVH=y CONFIG_XEN_DOM0=y @@ -402,22 +429,21 @@ CONFIG_PARAVIRT_TIME_ACCOUNTING=y CONFIG_PARAVIRT_CLOCK=y CONFIG_JAILHOUSE_GUEST=y CONFIG_ACRN_GUEST=y +CONFIG_BHYVE_GUEST=y CONFIG_INTEL_TDX_GUEST=y -# CONFIG_MK8 is not set -# CONFIG_MPSC is not set -# CONFIG_MCORE2 is not set -# CONFIG_MATOM is not set -CONFIG_GENERIC_CPU=y +CONFIG_CC_HAS_MARCH_NATIVE=y +# CONFIG_X86_NATIVE_CPU is not set CONFIG_X86_INTERNODE_CACHE_SHIFT=6 CONFIG_X86_L1_CACHE_SHIFT=6 CONFIG_X86_TSC=y CONFIG_X86_HAVE_PAE=y -CONFIG_X86_CMPXCHG64=y +CONFIG_X86_CX8=y CONFIG_X86_CMOV=y CONFIG_X86_MINIMUM_CPU_FAMILY=64 CONFIG_X86_DEBUGCTLMSR=y CONFIG_IA32_FEAT_CTL=y CONFIG_X86_VMX_FEATURE_NAMES=y +CONFIG_BROADCAST_TLB_FLUSH=y CONFIG_CPU_SUP_INTEL=y CONFIG_CPU_SUP_AMD=y CONFIG_CPU_SUP_HYGON=y @@ -428,16 +454,14 @@ CONFIG_HPET_EMULATE_RTC=y CONFIG_DMI=y # CONFIG_GART_IOMMU is not set CONFIG_BOOT_VESA_SUPPORT=y -# CONFIG_MAXSMP is not set -CONFIG_NR_CPUS_RANGE_BEGIN=2 -CONFIG_NR_CPUS_RANGE_END=512 -CONFIG_NR_CPUS_DEFAULT=64 -CONFIG_NR_CPUS=320 -CONFIG_SCHED_CLUSTER=y -CONFIG_SCHED_SMT=y -CONFIG_SCHED_MC=y +CONFIG_MAXSMP=y +CONFIG_NR_CPUS_RANGE_BEGIN=8192 +CONFIG_NR_CPUS_RANGE_END=8192 +CONFIG_NR_CPUS_DEFAULT=8192 +CONFIG_NR_CPUS=8192 CONFIG_SCHED_MC_PRIO=y CONFIG_X86_LOCAL_APIC=y +CONFIG_ACPI_MADT_WAKEUP=y CONFIG_X86_IO_APIC=y CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS=y CONFIG_X86_MCE=y @@ -464,19 +488,17 @@ CONFIG_X86_VSYSCALL_EMULATION=y CONFIG_X86_IOPL_IOPERM=y CONFIG_MICROCODE=y # CONFIG_MICROCODE_LATE_LOADING is not set +# CONFIG_MICROCODE_DBG is not set CONFIG_X86_MSR=y CONFIG_X86_CPUID=y -CONFIG_X86_5LEVEL=y CONFIG_X86_DIRECT_GBPAGES=y CONFIG_X86_CPA_STATISTICS=y CONFIG_X86_MEM_ENCRYPT=y CONFIG_AMD_MEM_ENCRYPT=y -# CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT is not set CONFIG_NUMA=y CONFIG_AMD_NUMA=y CONFIG_X86_64_ACPI_NUMA=y -# CONFIG_NUMA_EMU is not set -CONFIG_NODES_SHIFT=5 +CONFIG_NODES_SHIFT=10 CONFIG_ARCH_SPARSEMEM_ENABLE=y CONFIG_ARCH_SPARSEMEM_DEFAULT=y # CONFIG_ARCH_MEMORY_PROBE is not set @@ -491,28 +513,28 @@ CONFIG_MTRR_SANITIZER=y CONFIG_MTRR_SANITIZER_ENABLE_DEFAULT=1 CONFIG_MTRR_SANITIZER_SPARE_REG_NR_DEFAULT=0 CONFIG_X86_PAT=y -CONFIG_ARCH_USES_PG_UNCACHED=y CONFIG_X86_UMIP=y CONFIG_CC_HAS_IBT=y CONFIG_X86_CET=y CONFIG_X86_KERNEL_IBT=y CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS=y +CONFIG_ARCH_PKEY_BITS=4 # CONFIG_X86_INTEL_TSX_MODE_OFF is not set # CONFIG_X86_INTEL_TSX_MODE_ON is not set CONFIG_X86_INTEL_TSX_MODE_AUTO=y CONFIG_X86_SGX=y CONFIG_X86_USER_SHADOW_STACK=y +CONFIG_INTEL_TDX_HOST=y CONFIG_EFI=y CONFIG_EFI_STUB=y CONFIG_EFI_HANDOVER_PROTOCOL=y CONFIG_EFI_MIXED=y -# CONFIG_EFI_FAKE_MEMMAP is not set CONFIG_EFI_RUNTIME_MAP=y # CONFIG_HZ_100 is not set # CONFIG_HZ_250 is not set -CONFIG_HZ_300=y -# CONFIG_HZ_1000 is not set -CONFIG_HZ=300 +# CONFIG_HZ_300 is not set +CONFIG_HZ_1000=y +CONFIG_HZ=1000 CONFIG_SCHED_HRTICK=y CONFIG_ARCH_SUPPORTS_KEXEC=y CONFIG_ARCH_SUPPORTS_KEXEC_FILE=y @@ -522,7 +544,9 @@ CONFIG_ARCH_SUPPORTS_KEXEC_SIG=y CONFIG_ARCH_SUPPORTS_KEXEC_SIG_FORCE=y CONFIG_ARCH_SUPPORTS_KEXEC_BZIMAGE_VERIFY_SIG=y CONFIG_ARCH_SUPPORTS_KEXEC_JUMP=y +CONFIG_ARCH_SUPPORTS_KEXEC_HANDOVER=y CONFIG_ARCH_SUPPORTS_CRASH_DUMP=y +CONFIG_ARCH_DEFAULT_CRASH_DUMP=y CONFIG_ARCH_SUPPORTS_CRASH_HOTPLUG=y CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION=y CONFIG_PHYSICAL_START=0x1000000 @@ -530,10 +554,8 @@ CONFIG_RELOCATABLE=y CONFIG_RANDOMIZE_BASE=y CONFIG_X86_NEED_RELOCS=y CONFIG_PHYSICAL_ALIGN=0x200000 -CONFIG_DYNAMIC_MEMORY_LAYOUT=y CONFIG_RANDOMIZE_MEMORY=y CONFIG_RANDOMIZE_MEMORY_PHYSICAL_PADDING=0xa -CONFIG_ADDRESS_MASKING=y CONFIG_HOTPLUG_CPU=y # CONFIG_COMPAT_VDSO is not set CONFIG_LEGACY_VSYSCALL_XONLY=y @@ -543,8 +565,12 @@ CONFIG_MODIFY_LDT_SYSCALL=y # CONFIG_STRICT_SIGALTSTACK_SIZE is not set CONFIG_HAVE_LIVEPATCH=y # CONFIG_LIVEPATCH is not set +CONFIG_X86_BUS_LOCK_DETECT=y # end of Processor type and features +CONFIG_CC_HAS_NAMED_AS=y +CONFIG_CC_HAS_NAMED_AS_FIXED_SANITIZERS=y +CONFIG_USE_X86_SEG_SUPPORT=y CONFIG_CC_HAS_SLS=y CONFIG_CC_HAS_RETURN_THUNK=y CONFIG_CC_HAS_ENTRY_PADDING=y @@ -554,19 +580,32 @@ CONFIG_CALL_PADDING=y CONFIG_HAVE_CALL_THUNKS=y CONFIG_CALL_THUNKS=y CONFIG_PREFIX_SYMBOLS=y -CONFIG_SPECULATION_MITIGATIONS=y -CONFIG_PAGE_TABLE_ISOLATION=y -CONFIG_RETPOLINE=y -CONFIG_RETHUNK=y -CONFIG_CPU_UNRET_ENTRY=y -CONFIG_CALL_DEPTH_TRACKING=y +CONFIG_CPU_MITIGATIONS=y +CONFIG_MITIGATION_PAGE_TABLE_ISOLATION=y +CONFIG_MITIGATION_RETPOLINE=y +CONFIG_MITIGATION_RETHUNK=y +CONFIG_MITIGATION_UNRET_ENTRY=y +CONFIG_MITIGATION_CALL_DEPTH_TRACKING=y # CONFIG_CALL_THUNKS_DEBUG is not set -CONFIG_CPU_IBPB_ENTRY=y -CONFIG_CPU_IBRS_ENTRY=y -CONFIG_CPU_SRSO=y -CONFIG_SLS=y -# CONFIG_GDS_FORCE_MITIGATION is not set +CONFIG_MITIGATION_IBPB_ENTRY=y +CONFIG_MITIGATION_IBRS_ENTRY=y +CONFIG_MITIGATION_SRSO=y +CONFIG_MITIGATION_SLS=y +CONFIG_MITIGATION_GDS=y CONFIG_MITIGATION_RFDS=y +CONFIG_MITIGATION_SPECTRE_BHI=y +CONFIG_MITIGATION_MDS=y +CONFIG_MITIGATION_TAA=y +CONFIG_MITIGATION_MMIO_STALE_DATA=y +CONFIG_MITIGATION_L1TF=y +CONFIG_MITIGATION_RETBLEED=y +CONFIG_MITIGATION_SPECTRE_V1=y +CONFIG_MITIGATION_SPECTRE_V2=y +CONFIG_MITIGATION_SRBDS=y +CONFIG_MITIGATION_SSB=y +CONFIG_MITIGATION_ITS=y +CONFIG_MITIGATION_TSA=y +CONFIG_MITIGATION_VMSCAPE=y CONFIG_ARCH_HAS_ADD_PAGES=y # @@ -578,6 +617,9 @@ CONFIG_SUSPEND_FREEZER=y CONFIG_HIBERNATE_CALLBACKS=y CONFIG_HIBERNATION=y CONFIG_HIBERNATION_SNAPSHOT_DEV=y +CONFIG_HIBERNATION_COMP_LZO=y +# CONFIG_HIBERNATION_COMP_LZ4 is not set +CONFIG_HIBERNATION_DEF_COMP="lzo" CONFIG_PM_STD_PARTITION="" CONFIG_PM_SLEEP=y CONFIG_PM_SLEEP_SMP=y @@ -609,6 +651,7 @@ CONFIG_ACPI_FPDT=y CONFIG_ACPI_LPIT=y CONFIG_ACPI_SLEEP=y CONFIG_ACPI_REV_OVERRIDE_POSSIBLE=y +CONFIG_ACPI_EC=y CONFIG_ACPI_EC_DEBUGFS=m CONFIG_ACPI_AC=y CONFIG_ACPI_BATTERY=y @@ -636,8 +679,8 @@ CONFIG_ACPI_HOTPLUG_MEMORY=y CONFIG_ACPI_HOTPLUG_IOAPIC=y CONFIG_ACPI_SBS=m CONFIG_ACPI_HED=y -CONFIG_ACPI_CUSTOM_METHOD=m CONFIG_ACPI_BGRT=y +CONFIG_ACPI_NHLT=y CONFIG_ACPI_NFIT=m # CONFIG_NFIT_SECURITY_DEBUG is not set CONFIG_ACPI_NUMA=y @@ -649,6 +692,7 @@ CONFIG_ACPI_APEI_GHES=y CONFIG_ACPI_APEI_PCIEAER=y CONFIG_ACPI_APEI_MEMORY_FAILURE=y CONFIG_ACPI_APEI_EINJ=m +CONFIG_ACPI_APEI_EINJ_CXL=y CONFIG_ACPI_APEI_ERST_DEBUG=m CONFIG_ACPI_DPTF=y CONFIG_DPTF_POWER=m @@ -660,6 +704,7 @@ CONFIG_ACPI_CONFIGFS=m CONFIG_ACPI_PFRUT=m CONFIG_ACPI_PCC=y CONFIG_ACPI_FFH=y +CONFIG_ACPI_MRRM=y CONFIG_PMIC_OPREGION=y CONFIG_BYTCRC_PMIC_OPREGION=y CONFIG_CHTCRC_PMIC_OPREGION=y @@ -709,6 +754,7 @@ CONFIG_X86_P4_CLOCKMOD=m # shared options # CONFIG_X86_SPEEDSTEP_LIB=m +CONFIG_CPUFREQ_ARCH_CUR_FREQ=y # end of CPU Frequency scaling # @@ -734,6 +780,7 @@ CONFIG_PCI_XEN=y CONFIG_MMCONF_FAM10H=y CONFIG_ISA_DMA_API=y CONFIG_AMD_NB=y +CONFIG_AMD_NODE=y # end of Bus options (PCI etc.) # @@ -741,13 +788,12 @@ CONFIG_AMD_NB=y # CONFIG_IA32_EMULATION=y # CONFIG_IA32_EMULATION_DEFAULT_DISABLED is not set -# CONFIG_X86_X32_ABI is not set +CONFIG_X86_X32_ABI=y CONFIG_COMPAT_32=y CONFIG_COMPAT=y CONFIG_COMPAT_FOR_U64_ALIGNMENT=y # end of Binary Emulations -CONFIG_HAVE_KVM=y CONFIG_KVM_COMMON=y CONFIG_HAVE_KVM_PFNCACHE=y CONFIG_HAVE_KVM_IRQCHIP=y @@ -758,43 +804,77 @@ CONFIG_HAVE_KVM_DIRTY_RING_ACQ_REL=y CONFIG_KVM_MMIO=y CONFIG_KVM_ASYNC_PF=y CONFIG_HAVE_KVM_MSI=y +CONFIG_HAVE_KVM_READONLY_MEM=y CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT=y CONFIG_KVM_VFIO=y CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT=y +CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY=y CONFIG_KVM_COMPAT=y -CONFIG_HAVE_KVM_IRQ_BYPASS=y +CONFIG_HAVE_KVM_IRQ_BYPASS=m CONFIG_HAVE_KVM_NO_POLL=y -CONFIG_KVM_XFER_TO_GUEST_WORK=y +CONFIG_VIRT_XFER_TO_GUEST_WORK=y CONFIG_HAVE_KVM_PM_NOTIFIER=y CONFIG_KVM_GENERIC_HARDWARE_ENABLING=y CONFIG_KVM_GENERIC_MMU_NOTIFIER=y +CONFIG_KVM_ELIDE_TLB_FLUSH_IF_YOUNG=y +CONFIG_KVM_MMU_LOCKLESS_AGING=y +CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES=y +CONFIG_KVM_GUEST_MEMFD=y +CONFIG_HAVE_KVM_ARCH_GMEM_PREPARE=y +CONFIG_HAVE_KVM_ARCH_GMEM_INVALIDATE=y +CONFIG_HAVE_KVM_ARCH_GMEM_POPULATE=y CONFIG_VIRTUALIZATION=y +CONFIG_KVM_X86=m CONFIG_KVM=m CONFIG_KVM_INTEL=m CONFIG_X86_SGX_KVM=y +CONFIG_KVM_INTEL_TDX=y CONFIG_KVM_AMD=m CONFIG_KVM_AMD_SEV=y +CONFIG_KVM_IOAPIC=y CONFIG_KVM_SMM=y CONFIG_KVM_HYPERV=y CONFIG_KVM_XEN=y CONFIG_KVM_EXTERNAL_WRITE_TRACKING=y CONFIG_KVM_MAX_NR_VCPUS=1024 -CONFIG_AS_AVX512=y -CONFIG_AS_SHA1_NI=y -CONFIG_AS_SHA256_NI=y -CONFIG_AS_TPAUSE=y -CONFIG_AS_GFNI=y +CONFIG_X86_REQUIRED_FEATURE_ALWAYS=y +CONFIG_X86_REQUIRED_FEATURE_NOPL=y +CONFIG_X86_REQUIRED_FEATURE_CX8=y +CONFIG_X86_REQUIRED_FEATURE_CMOV=y +CONFIG_X86_REQUIRED_FEATURE_CPUID=y +CONFIG_X86_REQUIRED_FEATURE_FPU=y +CONFIG_X86_REQUIRED_FEATURE_PAE=y +CONFIG_X86_REQUIRED_FEATURE_MSR=y +CONFIG_X86_REQUIRED_FEATURE_FXSR=y +CONFIG_X86_REQUIRED_FEATURE_XMM=y +CONFIG_X86_REQUIRED_FEATURE_XMM2=y +CONFIG_X86_REQUIRED_FEATURE_LM=y +CONFIG_X86_DISABLED_FEATURE_VME=y +CONFIG_X86_DISABLED_FEATURE_K6_MTRR=y +CONFIG_X86_DISABLED_FEATURE_CYRIX_ARR=y +CONFIG_X86_DISABLED_FEATURE_CENTAUR_MCR=y +CONFIG_X86_DISABLED_FEATURE_LAM=y CONFIG_AS_WRUSS=y +CONFIG_ARCH_CONFIGURES_CPU_MITIGATIONS=y +CONFIG_ARCH_HAS_DMA_OPS=y # # General architecture-dependent options # CONFIG_HOTPLUG_SMT=y +CONFIG_ARCH_SUPPORTS_SCHED_SMT=y +CONFIG_ARCH_SUPPORTS_SCHED_CLUSTER=y +CONFIG_ARCH_SUPPORTS_SCHED_MC=y +CONFIG_SCHED_SMT=y +CONFIG_SCHED_CLUSTER=y +CONFIG_SCHED_MC=y CONFIG_HOTPLUG_CORE_SYNC=y CONFIG_HOTPLUG_CORE_SYNC_DEAD=y CONFIG_HOTPLUG_CORE_SYNC_FULL=y CONFIG_HOTPLUG_SPLIT_STARTUP=y CONFIG_HOTPLUG_PARALLEL=y +CONFIG_GENERIC_IRQ_ENTRY=y +CONFIG_GENERIC_SYSCALL=y CONFIG_GENERIC_ENTRY=y CONFIG_KPROBES=y CONFIG_JUMP_LABEL=y @@ -846,8 +926,10 @@ CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y CONFIG_MMU_GATHER_TABLE_FREE=y CONFIG_MMU_GATHER_RCU_TABLE_FREE=y CONFIG_MMU_GATHER_MERGE_VMAS=y +CONFIG_ARCH_WANT_IRQS_OFF_ACTIVATE_MM=y CONFIG_MMU_LAZY_TLB_REFCOUNT=y CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y +CONFIG_ARCH_HAVE_EXTRA_ELF_NOTES=y CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS=y CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y CONFIG_HAVE_CMPXCHG_LOCAL=y @@ -859,14 +941,16 @@ CONFIG_HAVE_ARCH_SECCOMP_FILTER=y CONFIG_SECCOMP=y CONFIG_SECCOMP_FILTER=y # CONFIG_SECCOMP_CACHE_DEBUG is not set -CONFIG_HAVE_ARCH_STACKLEAK=y +CONFIG_HAVE_ARCH_KSTACK_ERASE=y CONFIG_HAVE_STACKPROTECTOR=y CONFIG_STACKPROTECTOR=y CONFIG_STACKPROTECTOR_STRONG=y CONFIG_ARCH_SUPPORTS_LTO_CLANG=y CONFIG_ARCH_SUPPORTS_LTO_CLANG_THIN=y CONFIG_LTO_NONE=y -CONFIG_ARCH_SUPPORTS_CFI_CLANG=y +CONFIG_ARCH_SUPPORTS_AUTOFDO_CLANG=y +CONFIG_ARCH_SUPPORTS_PROPELLER_CLANG=y +CONFIG_ARCH_SUPPORTS_CFI=y CONFIG_HAVE_ARCH_WITHIN_STACK_FRAMES=y CONFIG_HAVE_CONTEXT_TRACKING_USER=y CONFIG_HAVE_CONTEXT_TRACKING_USER_OFFSTACK=y @@ -883,18 +967,22 @@ CONFIG_ARCH_WANT_PMD_MKWRITE=y CONFIG_HAVE_ARCH_SOFT_DIRTY=y CONFIG_HAVE_MOD_ARCH_SPECIFIC=y CONFIG_MODULES_USE_ELF_RELA=y +CONFIG_ARCH_HAS_EXECMEM_ROX=y CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK=y CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK=y CONFIG_SOFTIRQ_ON_OWN_STACK=y CONFIG_ARCH_HAS_ELF_RANDOMIZE=y CONFIG_HAVE_ARCH_MMAP_RND_BITS=y CONFIG_HAVE_EXIT_THREAD=y -CONFIG_ARCH_MMAP_RND_BITS=32 +CONFIG_ARCH_MMAP_RND_BITS=28 CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS=y -CONFIG_ARCH_MMAP_RND_COMPAT_BITS=16 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS=8 CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES=y +CONFIG_HAVE_PAGE_SIZE_4KB=y +CONFIG_PAGE_SIZE_4KB=y CONFIG_PAGE_SIZE_LESS_THAN_64KB=y CONFIG_PAGE_SIZE_LESS_THAN_256KB=y +CONFIG_PAGE_SHIFT=12 CONFIG_HAVE_OBJTOOL=y CONFIG_HAVE_JUMP_LABEL_HACK=y CONFIG_HAVE_NOINSTR_HACK=y @@ -906,6 +994,7 @@ CONFIG_ISA_BUS_API=y CONFIG_OLD_SIGSUSPEND3=y CONFIG_COMPAT_OLD_SIGACTION=y CONFIG_COMPAT_32BIT_TIME=y +CONFIG_ARCH_SUPPORTS_RT=y CONFIG_HAVE_ARCH_VMAP_STACK=y CONFIG_VMAP_STACK=y CONFIG_HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET=y @@ -915,6 +1004,7 @@ CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y CONFIG_STRICT_KERNEL_RWX=y CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y CONFIG_STRICT_MODULE_RWX=y +CONFIG_ARCH_HAS_CPU_RESCTRL=y CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y CONFIG_ARCH_USE_MEMREMAP_PROT=y CONFIG_LOCK_EVENT_COUNTS=y @@ -933,6 +1023,9 @@ CONFIG_DYNAMIC_SIGFRAME=y CONFIG_HAVE_ARCH_NODE_DEV_GROUP=y CONFIG_ARCH_HAS_HW_PTE_YOUNG=y CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG=y +CONFIG_ARCH_HAS_KERNEL_FPU_SUPPORT=y +CONFIG_ARCH_VMLINUX_NEEDS_RELOCS=y +CONFIG_HAVE_GENERIC_TIF_BITS=y # # GCOV-based kernel profiling @@ -942,15 +1035,16 @@ CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y # end of GCOV-based kernel profiling CONFIG_HAVE_GCC_PLUGINS=y -CONFIG_GCC_PLUGINS=y -# CONFIG_GCC_PLUGIN_LATENT_ENTROPY is not set +# CONFIG_GCC_PLUGINS is not set CONFIG_FUNCTION_ALIGNMENT_4B=y CONFIG_FUNCTION_ALIGNMENT_16B=y CONFIG_FUNCTION_ALIGNMENT=16 +CONFIG_CC_HAS_MIN_FUNCTION_ALIGNMENT=y +CONFIG_CC_HAS_SANE_FUNCTION_ALIGNMENT=y +CONFIG_ARCH_HAS_CPU_ATTACK_VECTORS=y # end of General architecture-dependent options CONFIG_RT_MUTEXES=y -CONFIG_BASE_SMALL=0 CONFIG_MODULE_SIG_FORMAT=y CONFIG_MODULES=y CONFIG_MODULE_DEBUGFS=y @@ -964,6 +1058,7 @@ CONFIG_MODULE_SRCVERSION_ALL=y CONFIG_MODULE_SIG=y # CONFIG_MODULE_SIG_FORCE is not set CONFIG_MODULE_SIG_ALL=y +# CONFIG_MODULE_SIG_SHA1 is not set # CONFIG_MODULE_SIG_SHA256 is not set # CONFIG_MODULE_SIG_SHA384 is not set CONFIG_MODULE_SIG_SHA512=y @@ -971,13 +1066,15 @@ CONFIG_MODULE_SIG_SHA512=y # CONFIG_MODULE_SIG_SHA3_384 is not set # CONFIG_MODULE_SIG_SHA3_512 is not set CONFIG_MODULE_SIG_HASH="sha512" -# CONFIG_MODULE_COMPRESS_NONE is not set +CONFIG_MODULE_COMPRESS=y # CONFIG_MODULE_COMPRESS_GZIP is not set # CONFIG_MODULE_COMPRESS_XZ is not set CONFIG_MODULE_COMPRESS_ZSTD=y +CONFIG_MODULE_COMPRESS_ALL=y CONFIG_MODULE_DECOMPRESS=y CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS=y CONFIG_MODPROBE_PATH="/sbin/modprobe" +# CONFIG_TRIM_UNUSED_KSYMS is not set CONFIG_MODULES_TREE_LOOKUP=y CONFIG_BLOCK=y CONFIG_BLOCK_LEGACY_AUTOLOAD=y @@ -988,11 +1085,9 @@ CONFIG_BLK_DEV_BSG_COMMON=y CONFIG_BLK_ICQ=y CONFIG_BLK_DEV_BSGLIB=y CONFIG_BLK_DEV_INTEGRITY=y -CONFIG_BLK_DEV_INTEGRITY_T10=y CONFIG_BLK_DEV_WRITE_MOUNTED=y CONFIG_BLK_DEV_ZONED=y CONFIG_BLK_DEV_THROTTLING=y -CONFIG_BLK_DEV_THROTTLING_LOW=y CONFIG_BLK_WBT=y CONFIG_BLK_WBT_MQ=y CONFIG_BLK_CGROUP_IOLATENCY=y @@ -1000,7 +1095,6 @@ CONFIG_BLK_CGROUP_FC_APPID=y CONFIG_BLK_CGROUP_IOCOST=y CONFIG_BLK_CGROUP_IOPRIO=y CONFIG_BLK_DEBUG_FS=y -CONFIG_BLK_DEBUG_FS_ZONED=y CONFIG_BLK_SED_OPAL=y CONFIG_BLK_INLINE_ENCRYPTION=y CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y @@ -1031,8 +1125,6 @@ CONFIG_EFI_PARTITION=y # CONFIG_CMDLINE_PARTITION is not set # end of Partition Types -CONFIG_BLK_MQ_PCI=y -CONFIG_BLK_MQ_VIRTIO=y CONFIG_BLK_PM=y CONFIG_BLOCK_HOLDER_DEPRECATED=y CONFIG_BLK_MQ_STACKING=y @@ -1079,11 +1171,9 @@ CONFIG_COREDUMP=y # # Memory Management options # -CONFIG_ZPOOL=y CONFIG_SWAP=y CONFIG_ZSWAP=y CONFIG_ZSWAP_DEFAULT_ON=y -# CONFIG_ZSWAP_EXCLUSIVE_LOADS_DEFAULT_ON is not set CONFIG_ZSWAP_SHRINKER_DEFAULT_ON=y # CONFIG_ZSWAP_COMPRESSOR_DEFAULT_DEFLATE is not set # CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZO is not set @@ -1092,23 +1182,28 @@ CONFIG_ZSWAP_SHRINKER_DEFAULT_ON=y # CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZ4HC is not set CONFIG_ZSWAP_COMPRESSOR_DEFAULT_ZSTD=y CONFIG_ZSWAP_COMPRESSOR_DEFAULT="zstd" -# CONFIG_ZSWAP_ZPOOL_DEFAULT_ZBUD is not set -# CONFIG_ZSWAP_ZPOOL_DEFAULT_Z3FOLD is not set -CONFIG_ZSWAP_ZPOOL_DEFAULT_ZSMALLOC=y -CONFIG_ZSWAP_ZPOOL_DEFAULT="zsmalloc" -CONFIG_ZBUD=y -CONFIG_Z3FOLD=y CONFIG_ZSMALLOC=y + +# +# Zsmalloc allocator options +# + +# +# Zsmalloc is a common backend allocator for zswap & zram +# CONFIG_ZSMALLOC_STAT=y CONFIG_ZSMALLOC_CHAIN_SIZE=8 +# end of Zsmalloc allocator options # # Slab allocator options # CONFIG_SLUB=y +CONFIG_KVFREE_RCU_BATCHED=y CONFIG_SLAB_MERGE_DEFAULT=y CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_SLAB_FREELIST_HARDENED=y +CONFIG_SLAB_BUCKETS=y # CONFIG_SLUB_STATS is not set CONFIG_SLUB_CPU_PARTIAL=y # CONFIG_RANDOM_KMALLOC_CACHES is not set @@ -1120,9 +1215,13 @@ CONFIG_SPARSEMEM=y CONFIG_SPARSEMEM_EXTREME=y CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y CONFIG_SPARSEMEM_VMEMMAP=y +CONFIG_SPARSEMEM_VMEMMAP_PREINIT=y CONFIG_ARCH_WANT_OPTIMIZE_DAX_VMEMMAP=y CONFIG_ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP=y -CONFIG_HAVE_FAST_GUP=y +CONFIG_ARCH_WANT_HUGETLB_VMEMMAP_PREINIT=y +CONFIG_HAVE_GUP_FAST=y +CONFIG_MEMBLOCK_KHO_SCRATCH=y +CONFIG_ARCH_KEEP_MEMBLOCK=y CONFIG_NUMA_KEEP_MEMINFO=y CONFIG_MEMORY_ISOLATION=y CONFIG_EXCLUSIVE_SYSTEM_RAM=y @@ -1130,12 +1229,16 @@ CONFIG_HAVE_BOOTMEM_INFO_NODE=y CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y CONFIG_MEMORY_HOTPLUG=y -CONFIG_MEMORY_HOTPLUG_DEFAULT_ONLINE=y +# CONFIG_MHP_DEFAULT_ONLINE_TYPE_OFFLINE is not set +CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_AUTO=y +# CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_KERNEL is not set +# CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_MOVABLE is not set CONFIG_MEMORY_HOTREMOVE=y CONFIG_MHP_MEMMAP_ON_MEMORY=y CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y -CONFIG_SPLIT_PTLOCK_CPUS=4 +CONFIG_SPLIT_PTE_PTLOCKS=y CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK=y +CONFIG_SPLIT_PMD_PTLOCKS=y CONFIG_MEMORY_BALLOON=y CONFIG_BALLOON_COMPACTION=y CONFIG_COMPACTION=y @@ -1156,21 +1259,30 @@ CONFIG_MEMORY_FAILURE=y CONFIG_HWPOISON_INJECT=m CONFIG_ARCH_WANT_GENERAL_HUGETLB=y CONFIG_ARCH_WANTS_THP_SWAP=y +CONFIG_PERSISTENT_HUGE_ZERO_FOLIO=y +CONFIG_MM_ID=y CONFIG_TRANSPARENT_HUGEPAGE=y CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y # CONFIG_TRANSPARENT_HUGEPAGE_MADVISE is not set # CONFIG_TRANSPARENT_HUGEPAGE_NEVER is not set CONFIG_THP_SWAP=y CONFIG_READ_ONLY_THP_FOR_FS=y +# CONFIG_NO_PAGE_MAPCOUNT is not set +CONFIG_PAGE_MAPCOUNT=y +CONFIG_PGTABLE_HAS_HUGE_LEAVES=y +CONFIG_HAVE_GIGANTIC_FOLIOS=y +CONFIG_ARCH_SUPPORTS_HUGE_PFNMAP=y +CONFIG_ARCH_SUPPORTS_PMD_PFNMAP=y +CONFIG_ARCH_SUPPORTS_PUD_PFNMAP=y CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y CONFIG_USE_PERCPU_NUMA_NODE_ID=y CONFIG_HAVE_SETUP_PER_CPU_AREA=y CONFIG_CMA=y -# CONFIG_CMA_DEBUG is not set CONFIG_CMA_DEBUGFS=y CONFIG_CMA_SYSFS=y CONFIG_CMA_AREAS=7 +CONFIG_PAGE_BLOCK_MAX_ORDER=10 CONFIG_MEM_SOFT_DIRTY=y CONFIG_GENERIC_EARLY_IOREMAP=y # CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set @@ -1178,7 +1290,6 @@ CONFIG_PAGE_IDLE_FLAG=y CONFIG_IDLE_PAGE_TRACKING=y CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y CONFIG_ARCH_HAS_CURRENT_STACK_POINTER=y -CONFIG_ARCH_HAS_PTE_DEVMAP=y CONFIG_ZONE_DMA=y CONFIG_ZONE_DMA32=y CONFIG_ZONE_DEVICE=y @@ -1188,6 +1299,7 @@ CONFIG_DEVICE_PRIVATE=y CONFIG_VMAP_PFN=y CONFIG_ARCH_USES_HIGH_VMA_FLAGS=y CONFIG_ARCH_HAS_PKEYS=y +CONFIG_ARCH_USES_PG_ARCH_2=y CONFIG_VM_EVENT_COUNTERS=y # CONFIG_PERCPU_STATS is not set # CONFIG_GUP_TEST is not set @@ -1209,6 +1321,13 @@ CONFIG_ARCH_SUPPORTS_PER_VMA_LOCK=y CONFIG_PER_VMA_LOCK=y CONFIG_LOCK_MM_AND_FIND_VMA=y CONFIG_IOMMU_MM_DATA=y +CONFIG_EXECMEM=y +CONFIG_NUMA_MEMBLKS=y +# CONFIG_NUMA_EMU is not set +CONFIG_ARCH_HAS_USER_SHADOW_STACK=y +CONFIG_ARCH_SUPPORTS_PT_RECLAIM=y +CONFIG_PT_RECLAIM=y +CONFIG_FIND_NORMAL_PAGE=y # # Data Access Monitoring @@ -1217,9 +1336,10 @@ CONFIG_DAMON=y CONFIG_DAMON_VADDR=y CONFIG_DAMON_PADDR=y CONFIG_DAMON_SYSFS=y -CONFIG_DAMON_DBGFS=y CONFIG_DAMON_RECLAIM=y CONFIG_DAMON_LRU_SORT=y +CONFIG_DAMON_STAT=y +# CONFIG_DAMON_STAT_ENABLED_DEFAULT is not set # end of Data Access Monitoring # end of Memory Management options @@ -1229,15 +1349,19 @@ CONFIG_NET_INGRESS=y CONFIG_NET_EGRESS=y CONFIG_NET_XGRESS=y CONFIG_NET_REDIRECT=y +CONFIG_SKB_DECRYPTED=y CONFIG_SKB_EXTENSIONS=y +CONFIG_NET_DEVMEM=y +CONFIG_NET_SHAPER=y +CONFIG_NET_CRC32C=y # # Networking options # CONFIG_PACKET=y CONFIG_PACKET_DIAG=m +CONFIG_INET_PSP=y CONFIG_UNIX=y -CONFIG_UNIX_SCM=y CONFIG_AF_UNIX_OOB=y CONFIG_UNIX_DIAG=m CONFIG_TLS=m @@ -1257,9 +1381,12 @@ CONFIG_XFRM_ESP=m CONFIG_XFRM_IPCOMP=m CONFIG_NET_KEY=m CONFIG_NET_KEY_MIGRATE=y +CONFIG_XFRM_IPTFS=m CONFIG_XFRM_ESPINTCP=y CONFIG_SMC=m CONFIG_SMC_DIAG=m +CONFIG_DIBS=m +CONFIG_DIBS_LO=y CONFIG_XDP_SOCKETS=y CONFIG_XDP_SOCKETS_DIAG=m CONFIG_NET_HANDSHAKE=y @@ -1392,7 +1519,6 @@ CONFIG_NF_CONNTRACK_TIMEOUT=y CONFIG_NF_CONNTRACK_TIMESTAMP=y CONFIG_NF_CONNTRACK_LABELS=y CONFIG_NF_CONNTRACK_OVS=y -CONFIG_NF_CT_PROTO_DCCP=y CONFIG_NF_CT_PROTO_GRE=y CONFIG_NF_CT_PROTO_SCTP=y CONFIG_NF_CT_PROTO_UDPLITE=y @@ -1426,6 +1552,7 @@ CONFIG_NF_TABLES_INET=y CONFIG_NF_TABLES_NETDEV=y CONFIG_NFT_NUMGEN=m CONFIG_NFT_CT=m +# CONFIG_NFT_EXTHDR_DCCP is not set CONFIG_NFT_FLOW_OFFLOAD=m CONFIG_NFT_CONNLIMIT=m CONFIG_NFT_LOG=m @@ -1457,6 +1584,7 @@ CONFIG_NF_FLOW_TABLE=m CONFIG_NF_FLOW_TABLE_PROCFS=y CONFIG_NETFILTER_XTABLES=m CONFIG_NETFILTER_XTABLES_COMPAT=y +CONFIG_NETFILTER_XTABLES_LEGACY=y # # Xtables combined modules @@ -1619,6 +1747,7 @@ CONFIG_IP_VS_PE_SIP=m # IP: Netfilter Configuration # CONFIG_NF_DEFRAG_IPV4=m +CONFIG_IP_NF_IPTABLES_LEGACY=m CONFIG_NF_SOCKET_IPV4=m CONFIG_NF_TPROXY_IPV4=m CONFIG_NF_TABLES_IPV4=y @@ -1651,6 +1780,7 @@ CONFIG_IP_NF_TARGET_TTL=m CONFIG_IP_NF_RAW=m CONFIG_IP_NF_SECURITY=m CONFIG_IP_NF_ARPTABLES=m +CONFIG_NFT_COMPAT_ARP=m CONFIG_IP_NF_ARPFILTER=m CONFIG_IP_NF_ARP_MANGLE=m # end of IP: Netfilter Configuration @@ -1658,6 +1788,7 @@ CONFIG_IP_NF_ARP_MANGLE=m # # IPv6: Netfilter Configuration # +CONFIG_IP6_NF_IPTABLES_LEGACY=m CONFIG_NF_SOCKET_IPV6=m CONFIG_NF_TPROXY_IPV6=m CONFIG_NF_TABLES_IPV6=y @@ -1695,6 +1826,7 @@ CONFIG_NF_TABLES_BRIDGE=m CONFIG_NFT_BRIDGE_META=m CONFIG_NFT_BRIDGE_REJECT=m CONFIG_NF_CONNTRACK_BRIDGE=m +CONFIG_BRIDGE_NF_EBTABLES_LEGACY=m CONFIG_BRIDGE_NF_EBTABLES=m CONFIG_BRIDGE_EBT_BROUTE=m CONFIG_BRIDGE_EBT_T_FILTER=m @@ -1716,14 +1848,10 @@ CONFIG_BRIDGE_EBT_REDIRECT=m CONFIG_BRIDGE_EBT_SNAT=m CONFIG_BRIDGE_EBT_LOG=m CONFIG_BRIDGE_EBT_NFLOG=m -# CONFIG_IP_DCCP is not set CONFIG_IP_SCTP=m # CONFIG_SCTP_DBG_OBJCNT is not set -# CONFIG_SCTP_DEFAULT_COOKIE_HMAC_MD5 is not set -CONFIG_SCTP_DEFAULT_COOKIE_HMAC_SHA1=y +CONFIG_SCTP_DEFAULT_COOKIE_HMAC_SHA256=y # CONFIG_SCTP_DEFAULT_COOKIE_HMAC_NONE is not set -CONFIG_SCTP_COOKIE_HMAC_MD5=y -CONFIG_SCTP_COOKIE_HMAC_SHA1=y CONFIG_INET_SCTP_DIAG=m CONFIG_RDS=m CONFIG_RDS_RDMA=m @@ -1760,6 +1888,7 @@ CONFIG_NET_DSA_TAG_AR9331=m CONFIG_NET_DSA_TAG_BRCM_COMMON=m CONFIG_NET_DSA_TAG_BRCM=m CONFIG_NET_DSA_TAG_BRCM_LEGACY=m +CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS=m CONFIG_NET_DSA_TAG_BRCM_PREPEND=m CONFIG_NET_DSA_TAG_HELLCREEK=m CONFIG_NET_DSA_TAG_GSWIP=m @@ -1777,6 +1906,7 @@ CONFIG_NET_DSA_TAG_RZN1_A5PSW=m CONFIG_NET_DSA_TAG_LAN9303=m CONFIG_NET_DSA_TAG_SJA1105=m CONFIG_NET_DSA_TAG_TRAILER=m +CONFIG_NET_DSA_TAG_VSC73XX_8021Q=m CONFIG_NET_DSA_TAG_XRS700X=m CONFIG_VLAN_8021Q=m CONFIG_VLAN_8021Q_GVRP=y @@ -1843,6 +1973,8 @@ CONFIG_NET_SCH_FQ_PIE=m CONFIG_NET_SCH_INGRESS=m CONFIG_NET_SCH_PLUG=m CONFIG_NET_SCH_ETS=m +CONFIG_NET_SCH_BPF=y +CONFIG_NET_SCH_DUALPI2=m CONFIG_NET_SCH_DEFAULT=y # CONFIG_DEFAULT_FQ is not set # CONFIG_DEFAULT_CODEL is not set @@ -1883,7 +2015,6 @@ CONFIG_NET_ACT_GACT=m CONFIG_GACT_PROB=y CONFIG_NET_ACT_MIRRED=m CONFIG_NET_ACT_SAMPLE=m -CONFIG_NET_ACT_IPT=m CONFIG_NET_ACT_NAT=m CONFIG_NET_ACT_PEDIT=m CONFIG_NET_ACT_SIMP=m @@ -1910,7 +2041,6 @@ CONFIG_BATMAN_ADV=m CONFIG_BATMAN_ADV_BATMAN_V=y CONFIG_BATMAN_ADV_BLA=y CONFIG_BATMAN_ADV_DAT=y -CONFIG_BATMAN_ADV_NC=y CONFIG_BATMAN_ADV_MCAST=y # CONFIG_BATMAN_ADV_DEBUG is not set # CONFIG_BATMAN_ADV_TRACING is not set @@ -1997,9 +2127,7 @@ CONFIG_BT_RFCOMM_TTY=y CONFIG_BT_BNEP=m CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y -CONFIG_BT_CMTP=m CONFIG_BT_HIDP=m -# CONFIG_BT_HS is not set CONFIG_BT_LE=y CONFIG_BT_LE_L2CAP_ECRED=y CONFIG_BT_6LOWPAN=m @@ -2038,6 +2166,7 @@ CONFIG_BT_HCIUART_RTL=y CONFIG_BT_HCIUART_QCA=y CONFIG_BT_HCIUART_AG6XX=y CONFIG_BT_HCIUART_MRVL=y +CONFIG_BT_HCIUART_AML=y CONFIG_BT_HCIBCM203X=m CONFIG_BT_HCIBCM4377=m CONFIG_BT_HCIBPA10X=m @@ -2054,6 +2183,7 @@ CONFIG_BT_MTKUART=m CONFIG_BT_HCIRSI=m CONFIG_BT_VIRTIO=m CONFIG_BT_NXPUART=m +CONFIG_BT_INTEL_PCIE=m # end of Bluetooth device drivers CONFIG_AF_RXRPC=m @@ -2062,6 +2192,7 @@ CONFIG_AF_RXRPC_IPV6=y # CONFIG_AF_RXRPC_INJECT_RX_DELAY is not set CONFIG_AF_RXRPC_DEBUG=y CONFIG_RXKAD=y +CONFIG_RXGK=y # CONFIG_RXPERF is not set CONFIG_AF_KCM=m CONFIG_STREAM_PARSER=y @@ -2072,7 +2203,6 @@ CONFIG_WIRELESS=y CONFIG_WIRELESS_EXT=y CONFIG_WEXT_CORE=y CONFIG_WEXT_PROC=y -CONFIG_WEXT_SPY=y CONFIG_WEXT_PRIV=y CONFIG_CFG80211=m # CONFIG_NL80211_TESTMODE is not set @@ -2083,12 +2213,6 @@ CONFIG_CFG80211_DEFAULT_PS=y CONFIG_CFG80211_DEBUGFS=y CONFIG_CFG80211_CRDA_SUPPORT=y CONFIG_CFG80211_WEXT=y -CONFIG_CFG80211_WEXT_EXPORT=y -CONFIG_LIB80211=m -CONFIG_LIB80211_CRYPT_WEP=m -CONFIG_LIB80211_CRYPT_CCMP=m -CONFIG_LIB80211_CRYPT_TKIP=m -# CONFIG_LIB80211_DEBUG is not set CONFIG_MAC80211=m CONFIG_MAC80211_HAS_RC=y CONFIG_MAC80211_RC_MINSTREL=y @@ -2108,6 +2232,7 @@ CONFIG_NET_9P=m CONFIG_NET_9P_FD=m CONFIG_NET_9P_VIRTIO=m CONFIG_NET_9P_XEN=m +CONFIG_NET_9P_USBG=m CONFIG_NET_9P_RDMA=m # CONFIG_NET_9P_DEBUG is not set # CONFIG_CAIF is not set @@ -2167,6 +2292,7 @@ CONFIG_LWTUNNEL_BPF=y CONFIG_DST_CACHE=y CONFIG_GRO_CELLS=y CONFIG_SOCK_VALIDATE_XMIT=y +CONFIG_NET_IEEE8021Q_HELPERS=y CONFIG_NET_SELFTESTS=m CONFIG_NET_SOCK_MSG=y CONFIG_NET_DEVLINK=y @@ -2178,9 +2304,8 @@ CONFIG_ETHTOOL_NETLINK=y # # Device Drivers # -CONFIG_HAVE_EISA=y -# CONFIG_EISA is not set CONFIG_HAVE_PCI=y +CONFIG_GENERIC_PCI_IOMAP=y CONFIG_PCI=y CONFIG_PCI_DOMAINS=y CONFIG_PCIEPORTBUS=y @@ -2207,10 +2332,13 @@ CONFIG_PCI_PF_STUB=m CONFIG_XEN_PCIDEV_FRONTEND=m CONFIG_PCI_ATS=y CONFIG_PCI_DOE=y +CONFIG_PCI_ECAM=y CONFIG_PCI_LOCKLESS_CONFIG=y CONFIG_PCI_IOV=y +CONFIG_PCI_NPEM=y CONFIG_PCI_PRI=y CONFIG_PCI_PASID=y +CONFIG_PCIE_TPH=y CONFIG_PCI_P2PDMA=y CONFIG_PCI_LABEL=y CONFIG_PCI_HYPERV=m @@ -2222,11 +2350,13 @@ CONFIG_HOTPLUG_PCI_ACPI_IBM=m CONFIG_HOTPLUG_PCI_CPCI=y CONFIG_HOTPLUG_PCI_CPCI_ZT5550=m CONFIG_HOTPLUG_PCI_CPCI_GENERIC=m +# CONFIG_HOTPLUG_PCI_OCTEONEP is not set CONFIG_HOTPLUG_PCI_SHPC=y # # PCI controller drivers # +CONFIG_PCI_HOST_COMMON=y CONFIG_VMD=m CONFIG_PCI_HYPERV_INTERFACE=m @@ -2239,6 +2369,7 @@ CONFIG_PCI_HYPERV_INTERFACE=m # DesignWare-based PCIe controllers # CONFIG_PCIE_DW=y +CONFIG_PCIE_DW_DEBUGFS=y CONFIG_PCIE_DW_HOST=y CONFIG_PCI_MESON=m CONFIG_PCIE_DW_PLAT=y @@ -2249,6 +2380,11 @@ CONFIG_PCIE_DW_PLAT_HOST=y # Mobiveil-based PCIe controllers # # end of Mobiveil-based PCIe controllers + +# +# PLDA-based PCIe controllers +# +# end of PLDA-based PCIe controllers # end of PCI controller drivers # @@ -2263,17 +2399,20 @@ CONFIG_PCIE_DW_PLAT_HOST=y CONFIG_PCI_SW_SWITCHTEC=m # end of PCI switch controller drivers +CONFIG_PCI_PWRCTRL=m +CONFIG_PCI_PWRCTRL_SLOT=m CONFIG_CXL_BUS=m CONFIG_CXL_PCI=m # CONFIG_CXL_MEM_RAW_COMMANDS is not set CONFIG_CXL_ACPI=m CONFIG_CXL_PMEM=m CONFIG_CXL_MEM=m +CONFIG_CXL_FEATURES=y CONFIG_CXL_PORT=m CONFIG_CXL_SUSPEND=y CONFIG_CXL_REGION=y # CONFIG_CXL_REGION_INVALIDATION_TEST is not set -CONFIG_CXL_PMU=m +CONFIG_CXL_MCE=y CONFIG_PCCARD=m CONFIG_PCMCIA=m CONFIG_PCMCIA_LOAD_CIS=y @@ -2309,6 +2448,7 @@ CONFIG_PREVENT_FIRMWARE_BUILD=y # CONFIG_FW_LOADER=y CONFIG_FW_LOADER_DEBUG=y +CONFIG_RUST_FW_LOADER_ABSTRACTIONS=y CONFIG_FW_LOADER_PAGED_BUF=y CONFIG_FW_LOADER_SYSFS=y CONFIG_EXTRA_FIRMWARE="" @@ -2419,8 +2559,10 @@ CONFIG_EFI_EARLYCON=y CONFIG_EFI_CUSTOM_SSDT_OVERLAYS=y # CONFIG_EFI_DISABLE_RUNTIME is not set CONFIG_EFI_COCO_SECRET=y +CONFIG_OVMF_DEBUG_LOG=y CONFIG_UNACCEPTED_MEMORY=y CONFIG_EFI_EMBEDDED_FIRMWARE=y +CONFIG_EFI_SBAT_FILE="" # end of EFI (Extensible Firmware Interface) Support CONFIG_UEFI_CPER=y @@ -2429,6 +2571,10 @@ CONFIG_UEFI_CPER_X86=y # # Qualcomm firmware drivers # +CONFIG_QCOM_SCM=m +CONFIG_QCOM_TZMEM=m +CONFIG_QCOM_TZMEM_MODE_GENERIC=y +# CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE is not set # end of Qualcomm firmware drivers # @@ -2437,6 +2583,9 @@ CONFIG_UEFI_CPER_X86=y # end of Tegra firmware driver # end of Firmware Drivers +CONFIG_FWCTL=m +CONFIG_FWCTL_MLX5=m +CONFIG_FWCTL_PDS=m CONFIG_GNSS=m CONFIG_GNSS_SERIAL=m CONFIG_GNSS_MTK_SERIAL=m @@ -2494,7 +2643,6 @@ CONFIG_MTD_ROM=m # # CONFIG_MTD_COMPLEX_MAPPINGS is not set # CONFIG_MTD_PHYSMAP is not set -# CONFIG_MTD_INTEL_VR_NOR is not set # CONFIG_MTD_PLATRAM is not set # end of Mapping drivers for chip access @@ -2512,6 +2660,7 @@ CONFIG_MTD_MTDRAM=m CONFIG_MTDRAM_TOTAL_SIZE=4096 CONFIG_MTDRAM_ERASE_SIZE=128 CONFIG_MTD_BLOCK2MTD=m +CONFIG_MTD_INTEL_DG=m # # Disk-On-Chip Device Drivers @@ -2572,6 +2721,7 @@ CONFIG_MTD_UBI_BEB_LIMIT=20 # CONFIG_MTD_UBI_FASTMAP is not set # CONFIG_MTD_UBI_GLUEBI is not set # CONFIG_MTD_UBI_BLOCK is not set +CONFIG_MTD_UBI_NVMEM=m # CONFIG_MTD_HYPERBUS is not set # CONFIG_OF is not set CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y @@ -2592,16 +2742,24 @@ CONFIG_PNP_DEBUG_MESSAGES=y CONFIG_PNPACPI=y CONFIG_BLK_DEV=y CONFIG_BLK_DEV_NULL_BLK=m +# CONFIG_BLK_DEV_RUST_NULL is not set CONFIG_BLK_DEV_FD=m # CONFIG_BLK_DEV_FD_RAWCMD is not set CONFIG_CDROM=m CONFIG_BLK_DEV_PCIESSD_MTIP32XX=m CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_LZ4HC=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_DEFLATE=y +CONFIG_ZRAM_BACKEND_842=y +CONFIG_ZRAM_BACKEND_LZO=y # CONFIG_ZRAM_DEF_COMP_LZORLE is not set -CONFIG_ZRAM_DEF_COMP_ZSTD=y -# CONFIG_ZRAM_DEF_COMP_LZ4 is not set # CONFIG_ZRAM_DEF_COMP_LZO is not set +# CONFIG_ZRAM_DEF_COMP_LZ4 is not set # CONFIG_ZRAM_DEF_COMP_LZ4HC is not set +CONFIG_ZRAM_DEF_COMP_ZSTD=y +# CONFIG_ZRAM_DEF_COMP_DEFLATE is not set # CONFIG_ZRAM_DEF_COMP_842 is not set CONFIG_ZRAM_DEF_COMP="zstd" CONFIG_ZRAM_WRITEBACK=y @@ -2616,19 +2774,17 @@ CONFIG_BLK_DEV_NBD=m CONFIG_BLK_DEV_RAM=m CONFIG_BLK_DEV_RAM_COUNT=16 CONFIG_BLK_DEV_RAM_SIZE=16384 -CONFIG_CDROM_PKTCDVD=m -CONFIG_CDROM_PKTCDVD_BUFFERS=8 -# CONFIG_CDROM_PKTCDVD_WCACHE is not set CONFIG_ATA_OVER_ETH=m CONFIG_XEN_BLKDEV_FRONTEND=m CONFIG_XEN_BLKDEV_BACKEND=m -CONFIG_VIRTIO_BLK=m +CONFIG_VIRTIO_BLK=y CONFIG_BLK_DEV_RBD=m CONFIG_BLK_DEV_UBLK=m CONFIG_BLKDEV_UBLK_LEGACY_OPCODES=y CONFIG_BLK_DEV_RNBD=y CONFIG_BLK_DEV_RNBD_CLIENT=m CONFIG_BLK_DEV_RNBD_SERVER=m +CONFIG_BLK_DEV_ZONED_LOOP=m # # NVME Support @@ -2647,6 +2803,7 @@ CONFIG_NVME_TCP=m CONFIG_NVME_TCP_TLS=y CONFIG_NVME_HOST_AUTH=y CONFIG_NVME_TARGET=m +CONFIG_NVME_TARGET_DEBUGFS=y CONFIG_NVME_TARGET_PASSTHRU=y CONFIG_NVME_TARGET_LOOP=m CONFIG_NVME_TARGET_RDMA=m @@ -2667,6 +2824,8 @@ CONFIG_AD525X_DPOT_SPI=m # CONFIG_DUMMY_IRQ is not set CONFIG_IBM_ASM=m CONFIG_PHANTOM=m +CONFIG_RPMB=m +CONFIG_TI_FPC202=m CONFIG_TIFM_CORE=m CONFIG_TIFM_7XX1=m CONFIG_ICS932S401=m @@ -2689,6 +2848,7 @@ CONFIG_DW_XDATA_PCIE=m CONFIG_PCI_ENDPOINT_TEST=m CONFIG_XILINX_SDFEC=m CONFIG_MISC_RTSX=m +CONFIG_NTSYNC=m CONFIG_TPS6594_ESM=m CONFIG_TPS6594_PFSM=m CONFIG_NSM=m @@ -2705,18 +2865,12 @@ CONFIG_EEPROM_93CX6=m # CONFIG_EEPROM_93XX46 is not set CONFIG_EEPROM_IDT_89HPESX=m CONFIG_EEPROM_EE1004=m +CONFIG_EEPROM_M24LR=m # end of EEPROM support CONFIG_CB710_CORE=m # CONFIG_CB710_DEBUG is not set CONFIG_CB710_DEBUG_ASSUMPTIONS=y - -# -# Texas Instruments shared transport line discipline -# -CONFIG_TI_ST=m -# end of Texas Instruments shared transport line discipline - CONFIG_SENSORS_LIS3_I2C=m CONFIG_ALTERA_STAPL=m CONFIG_INTEL_MEI=m @@ -2725,13 +2879,13 @@ CONFIG_INTEL_MEI_TXE=m CONFIG_INTEL_MEI_GSC=m CONFIG_INTEL_MEI_VSC_HW=m CONFIG_INTEL_MEI_VSC=m +CONFIG_INTEL_MEI_LB=m CONFIG_INTEL_MEI_HDCP=m CONFIG_INTEL_MEI_PXP=m CONFIG_INTEL_MEI_GSC_PROXY=m CONFIG_VMWARE_VMCI=m CONFIG_GENWQE=m CONFIG_GENWQE_PLATFORM_ERROR_RECOVERY=0 -CONFIG_ECHO=m CONFIG_BCM_VK=m CONFIG_BCM_VK_TTY=y CONFIG_MISC_ALCOR_PCI=m @@ -2742,6 +2896,8 @@ CONFIG_PVPANIC=y CONFIG_PVPANIC_MMIO=m CONFIG_PVPANIC_PCI=m CONFIG_GP_PCI1XXXX=m +CONFIG_KEBA_CP500=m +CONFIG_KEBA_LAN9252=m # end of Misc devices # @@ -3014,7 +3170,10 @@ CONFIG_ATA_GENERIC=m CONFIG_PATA_LEGACY=m CONFIG_MD=y CONFIG_BLK_DEV_MD=m +CONFIG_MD_BITMAP=y +CONFIG_MD_LLBITMAP=y CONFIG_MD_BITMAP_FILE=y +CONFIG_MD_LINEAR=m CONFIG_MD_RAID0=m CONFIG_MD_RAID1=m CONFIG_MD_RAID10=m @@ -3057,12 +3216,15 @@ CONFIG_DM_FLAKEY=m CONFIG_DM_VERITY=m CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING=y +CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG_PLATFORM_KEYRING=y CONFIG_DM_VERITY_FEC=y CONFIG_DM_SWITCH=m CONFIG_DM_LOG_WRITES=m CONFIG_DM_INTEGRITY=m CONFIG_DM_ZONED=m CONFIG_DM_AUDIT=y +CONFIG_DM_VDO=m +CONFIG_DM_PCACHE=m CONFIG_TARGET_CORE=m CONFIG_TCM_IBLOCK=m CONFIG_TCM_FILEIO=m @@ -3102,6 +3264,7 @@ CONFIG_BONDING=m CONFIG_DUMMY=m CONFIG_WIREGUARD=m # CONFIG_WIREGUARD_DEBUG is not set +CONFIG_OVPN=m CONFIG_EQUALIZER=m CONFIG_NET_FC=y CONFIG_IFB=m @@ -3120,6 +3283,7 @@ CONFIG_VXLAN=m CONFIG_GENEVE=m CONFIG_BAREUDP=m CONFIG_GTP=m +CONFIG_PFCP=m CONFIG_AMT=m CONFIG_MACSEC=m CONFIG_NETCONSOLE=m @@ -3188,6 +3352,7 @@ CONFIG_NET_DSA_MICROCHIP_KSZ_PTP=y CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI=m CONFIG_NET_DSA_MV88E6XXX=m CONFIG_NET_DSA_MV88E6XXX_PTP=y +CONFIG_NET_DSA_MV88E6XXX_LEDS=y CONFIG_NET_DSA_MSCC_FELIX_DSA_LIB=m CONFIG_NET_DSA_MSCC_OCELOT_EXT=m CONFIG_NET_DSA_MSCC_SEVILLE=m @@ -3202,10 +3367,7 @@ CONFIG_NET_DSA_XRS700X=m CONFIG_NET_DSA_XRS700X_I2C=m CONFIG_NET_DSA_XRS700X_MDIO=m CONFIG_NET_DSA_REALTEK=m -# CONFIG_NET_DSA_REALTEK_MDIO is not set -# CONFIG_NET_DSA_REALTEK_SMI is not set -CONFIG_NET_DSA_REALTEK_RTL8365MB=m -CONFIG_NET_DSA_REALTEK_RTL8366RB=m +CONFIG_NET_DSA_KS8995=m CONFIG_NET_DSA_SMSC_LAN9303=m CONFIG_NET_DSA_SMSC_LAN9303_I2C=m CONFIG_NET_DSA_SMSC_LAN9303_MDIO=m @@ -3272,6 +3434,7 @@ CONFIG_BNXT_SRIOV=y CONFIG_BNXT_FLOWER_OFFLOAD=y CONFIG_BNXT_DCB=y CONFIG_BNXT_HWMON=y +CONFIG_BNGE=m CONFIG_NET_VENDOR_CADENCE=y CONFIG_MACB=m CONFIG_MACB_USE_HWSTAMP=y @@ -3338,10 +3501,18 @@ CONFIG_FUN_CORE=m CONFIG_FUN_ETH=m CONFIG_NET_VENDOR_GOOGLE=y CONFIG_GVE=m +CONFIG_NET_VENDOR_HISILICON=y +CONFIG_HIBMCGE=m CONFIG_NET_VENDOR_HUAWEI=y CONFIG_HINIC=m +CONFIG_HINIC3=m CONFIG_NET_VENDOR_I825XX=y CONFIG_NET_VENDOR_INTEL=y +CONFIG_LIBETH=m +CONFIG_LIBETH_XDP=m +CONFIG_LIBIE=m +CONFIG_LIBIE_ADMINQ=m +CONFIG_LIBIE_FWLOG=m CONFIG_E100=m CONFIG_E1000=m CONFIG_E1000E=m @@ -3367,7 +3538,9 @@ CONFIG_ICE_SWITCHDEV=y CONFIG_ICE_HWTS=y CONFIG_FM10K=m CONFIG_IGC=m +CONFIG_IGC_LEDS=y CONFIG_IDPF=m +# CONFIG_IDPF_SINGLEQ is not set CONFIG_JME=m CONFIG_NET_VENDOR_ADI=y CONFIG_ADIN1110=m @@ -3380,6 +3553,7 @@ CONFIG_SKGE_GENESIS=y CONFIG_SKY2=m # CONFIG_SKY2_DEBUG is not set CONFIG_OCTEON_EP=m +CONFIG_OCTEON_EP_VF=m CONFIG_PRESTERA=m CONFIG_PRESTERA_PCI=m CONFIG_NET_VENDOR_MELLANOX=y @@ -3405,9 +3579,11 @@ CONFIG_MLX5_MACSEC=y CONFIG_MLX5_EN_IPSEC=y CONFIG_MLX5_EN_TLS=y CONFIG_MLX5_SW_STEERING=y +CONFIG_MLX5_HW_STEERING=y CONFIG_MLX5_SF=y CONFIG_MLX5_SF_MANAGER=y CONFIG_MLX5_DPLL=m +CONFIG_MLX5_EN_PSP=y CONFIG_MLXSW_CORE=m CONFIG_MLXSW_CORE_HWMON=y CONFIG_MLXSW_CORE_THERMAL=y @@ -3417,6 +3593,8 @@ CONFIG_MLXSW_SPECTRUM=m CONFIG_MLXSW_SPECTRUM_DCB=y CONFIG_MLXSW_MINIMAL=m CONFIG_MLXFW=m +CONFIG_NET_VENDOR_META=y +CONFIG_FBNIC=m CONFIG_NET_VENDOR_MICREL=y CONFIG_KS8842=m CONFIG_KS8851=m @@ -3427,6 +3605,7 @@ CONFIG_ENC28J60=m # CONFIG_ENC28J60_WRITEVERIFY is not set CONFIG_ENCX24J600=m CONFIG_LAN743X=m +CONFIG_LAN865X=m CONFIG_VCAP=y CONFIG_NET_VENDOR_MICROSEMI=y CONFIG_MSCC_OCELOT_SWITCH_LIB=m @@ -3457,6 +3636,7 @@ CONFIG_NET_VENDOR_NVIDIA=y CONFIG_FORCEDETH=m CONFIG_NET_VENDOR_OKI=y CONFIG_ETHOC=m +CONFIG_OA_TC6=m CONFIG_NET_VENDOR_PACKET_ENGINES=y CONFIG_HAMACHI=m CONFIG_YELLOWFIN=m @@ -3494,6 +3674,7 @@ CONFIG_8139TOO_8129=y # CONFIG_8139_OLD_RX_RESET is not set CONFIG_R8169=m CONFIG_R8169_LEDS=y +CONFIG_RTASE=m CONFIG_NET_VENDOR_RENESAS=y CONFIG_NET_VENDOR_ROCKER=y CONFIG_ROCKER=m @@ -3541,6 +3722,7 @@ CONFIG_DWC_XLGMAC=m CONFIG_DWC_XLGMAC_PCI=m CONFIG_NET_VENDOR_TEHUTI=y CONFIG_TEHUTI=m +CONFIG_TEHUTI_TN40=m CONFIG_NET_VENDOR_TI=y # CONFIG_TI_CPSW_PHY_SEL is not set CONFIG_TLAN=m @@ -3554,6 +3736,8 @@ CONFIG_NET_VENDOR_WANGXUN=y CONFIG_LIBWX=m CONFIG_NGBE=m CONFIG_TXGBE=m +CONFIG_TXGBEVF=m +CONFIG_NGBEVF=m CONFIG_NET_VENDOR_WIZNET=y CONFIG_WIZNET_W5100=m CONFIG_WIZNET_W5300=m @@ -3571,17 +3755,21 @@ CONFIG_FDDI=m CONFIG_DEFXX=m CONFIG_SKFP=m # CONFIG_HIPPI is not set -CONFIG_NET_SB1000=m +CONFIG_MDIO_BUS=m CONFIG_PHYLINK=m CONFIG_PHYLIB=m CONFIG_SWPHY=y +CONFIG_PHY_PACKAGE=m CONFIG_LED_TRIGGER_PHY=y +CONFIG_OPEN_ALLIANCE_HELPERS=y CONFIG_FIXED_PHY=m CONFIG_SFP=m # # MII PHY device drivers # +CONFIG_AS21XXX_PHY=m +CONFIG_AIR_EN8811H_PHY=m CONFIG_AMD_PHY=m CONFIG_ADIN_PHY=m CONFIG_ADIN1100_PHY=m @@ -3606,12 +3794,15 @@ CONFIG_MARVELL_10G_PHY=m CONFIG_MARVELL_88Q2XXX_PHY=m CONFIG_MARVELL_88X2222_PHY=m CONFIG_MAXLINEAR_GPHY=m +CONFIG_MAXLINEAR_86110_PHY=m CONFIG_MEDIATEK_GE_PHY=m # CONFIG_MEDIATEK_GE_SOC_PHY is not set +CONFIG_MTK_NET_PHYLIB=m CONFIG_MICREL_PHY=m CONFIG_MICROCHIP_T1S_PHY=m CONFIG_MICROCHIP_PHY=m CONFIG_MICROCHIP_T1_PHY=m +CONFIG_MICROCHIP_PHY_RDS_PTP=m CONFIG_MICROSEMI_PHY=m CONFIG_MOTORCOMM_PHY=m CONFIG_NATIONAL_PHY=m @@ -3619,9 +3810,13 @@ CONFIG_NXP_CBTX_PHY=m CONFIG_NXP_C45_TJA11XX_PHY=m CONFIG_NXP_TJA11XX_PHY=m CONFIG_NCN26000_PHY=m +CONFIG_QCOM_NET_PHYLIB=m CONFIG_AT803X_PHY=m +CONFIG_QCA83XX_PHY=m +CONFIG_QCA808X_PHY=m CONFIG_QSEMI_PHY=m CONFIG_REALTEK_PHY=m +CONFIG_REALTEK_PHY_HWMON=y CONFIG_RENESAS_PHY=m # CONFIG_ROCKCHIP_PHY is not set CONFIG_SMSC_PHY=m @@ -3636,9 +3831,11 @@ CONFIG_DP83TD510_PHY=m CONFIG_DP83TG720_PHY=m CONFIG_VITESSE_PHY=m CONFIG_XILINX_GMII2RGMII=m -CONFIG_MICREL_KS8995MA=m CONFIG_PSE_CONTROLLER=y CONFIG_PSE_REGULATOR=m +CONFIG_PSE_PD692X0=m +CONFIG_PSE_SI3474=m +CONFIG_PSE_TPS23881=m CONFIG_CAN_DEV=m CONFIG_CAN_VCAN=m CONFIG_CAN_VXCAN=m @@ -3653,10 +3850,11 @@ CONFIG_CAN_C_CAN=m CONFIG_CAN_C_CAN_PLATFORM=m CONFIG_CAN_C_CAN_PCI=m CONFIG_CAN_CC770=m -# CONFIG_CAN_CC770_ISA is not set +CONFIG_CAN_CC770_ISA=m CONFIG_CAN_CC770_PLATFORM=m CONFIG_CAN_CTUCANFD=m CONFIG_CAN_CTUCANFD_PCI=m +CONFIG_CAN_ESD_402_PCI=m CONFIG_CAN_IFI_CANFD=m CONFIG_CAN_M_CAN=m CONFIG_CAN_M_CAN_PCI=m @@ -3672,7 +3870,7 @@ CONFIG_CAN_PEAK_PCI=m CONFIG_CAN_PEAK_PCIEC=y CONFIG_CAN_PEAK_PCMCIA=m CONFIG_CAN_PLX_PCI=m -# CONFIG_CAN_SJA1000_ISA is not set +CONFIG_CAN_SJA1000_ISA=m CONFIG_CAN_SJA1000_PLATFORM=m CONFIG_CAN_SOFTING=m CONFIG_CAN_SOFTING_CS=m @@ -3697,6 +3895,7 @@ CONFIG_CAN_F81604=m CONFIG_CAN_GS_USB=m CONFIG_CAN_KVASER_USB=m CONFIG_CAN_MCBA_USB=m +CONFIG_CAN_NCT6694=m CONFIG_CAN_PEAK_USB=m CONFIG_CAN_UCAN=m # end of CAN USB interfaces @@ -3708,13 +3907,11 @@ CONFIG_CAN_UCAN=m # CONFIG_MCTP_SERIAL=m CONFIG_MCTP_TRANSPORT_I2C=m +CONFIG_MCTP_TRANSPORT_USB=m # end of MCTP Device Drivers -CONFIG_MDIO_DEVICE=m -CONFIG_MDIO_BUS=m CONFIG_FWNODE_MDIO=m CONFIG_ACPI_MDIO=m -CONFIG_MDIO_DEVRES=m CONFIG_MDIO_BITBANG=m CONFIG_MDIO_BCM_UNIMAC=m CONFIG_MDIO_CAVIUM=m @@ -3857,6 +4054,7 @@ CONFIG_ATH10K_SDIO=m CONFIG_ATH10K_USB=m CONFIG_ATH10K_DEBUG=y CONFIG_ATH10K_DEBUGFS=y +CONFIG_ATH10K_LEDS=y CONFIG_ATH10K_SPECTRAL=y CONFIG_ATH10K_TRACING=y CONFIG_WCN36XX=m @@ -3869,8 +4067,11 @@ CONFIG_ATH11K_DEBUGFS=y # CONFIG_ATH11K_TRACING is not set CONFIG_ATH11K_SPECTRAL=y CONFIG_ATH12K=m +CONFIG_ATH12K_AHB=y CONFIG_ATH12K_DEBUG=y +CONFIG_ATH12K_DEBUGFS=y CONFIG_ATH12K_TRACING=y +CONFIG_ATH12K_COREDUMP=y CONFIG_WLAN_VENDOR_ATMEL=y CONFIG_AT76C50X_USB=m CONFIG_WLAN_VENDOR_BROADCOM=y @@ -3941,6 +4142,7 @@ CONFIG_IWLWIFI=m CONFIG_IWLWIFI_LEDS=y CONFIG_IWLDVM=m CONFIG_IWLMVM=m +CONFIG_IWLMLD=m CONFIG_IWLWIFI_OPMODE_MODULAR=y # @@ -4056,11 +4258,13 @@ CONFIG_RTL8188EE=m CONFIG_RTL8192EE=m CONFIG_RTL8821AE=m CONFIG_RTL8192CU=m +CONFIG_RTL8192DU=m CONFIG_RTLWIFI=m CONFIG_RTLWIFI_PCI=m CONFIG_RTLWIFI_USB=m CONFIG_RTLWIFI_DEBUG=y CONFIG_RTL8192C_COMMON=m +CONFIG_RTL8192D_COMMON=m CONFIG_RTL8723_COMMON=m CONFIG_RTLBTCOEXIST=m CONFIG_RTL8XXXU=m @@ -4072,8 +4276,14 @@ CONFIG_RTW88_SDIO=m CONFIG_RTW88_USB=m CONFIG_RTW88_8822B=m CONFIG_RTW88_8822C=m +CONFIG_RTW88_8723X=m +CONFIG_RTW88_8703B=m CONFIG_RTW88_8723D=m CONFIG_RTW88_8821C=m +CONFIG_RTW88_88XXA=m +CONFIG_RTW88_8821A=m +CONFIG_RTW88_8812A=m +CONFIG_RTW88_8814A=m CONFIG_RTW88_8822BE=m CONFIG_RTW88_8822BS=m CONFIG_RTW88_8822BU=m @@ -4082,23 +4292,37 @@ CONFIG_RTW88_8822CS=m CONFIG_RTW88_8822CU=m CONFIG_RTW88_8723DE=m CONFIG_RTW88_8723DS=m +CONFIG_RTW88_8723CS=m CONFIG_RTW88_8723DU=m CONFIG_RTW88_8821CE=m CONFIG_RTW88_8821CS=m CONFIG_RTW88_8821CU=m +CONFIG_RTW88_8821AU=m +CONFIG_RTW88_8812AU=m +CONFIG_RTW88_8814AE=m +CONFIG_RTW88_8814AU=m CONFIG_RTW88_DEBUG=y CONFIG_RTW88_DEBUGFS=y +CONFIG_RTW88_LEDS=y CONFIG_RTW89=m CONFIG_RTW89_CORE=m CONFIG_RTW89_PCI=m +CONFIG_RTW89_USB=m CONFIG_RTW89_8851B=m CONFIG_RTW89_8852A=m +CONFIG_RTW89_8852B_COMMON=m CONFIG_RTW89_8852B=m +CONFIG_RTW89_8852BT=m CONFIG_RTW89_8852C=m +CONFIG_RTW89_8922A=m CONFIG_RTW89_8851BE=m +CONFIG_RTW89_8851BU=m CONFIG_RTW89_8852AE=m CONFIG_RTW89_8852BE=m +CONFIG_RTW89_8852BU=m +CONFIG_RTW89_8852BTE=m CONFIG_RTW89_8852CE=m +CONFIG_RTW89_8922AE=m CONFIG_RTW89_DEBUG=y CONFIG_RTW89_DEBUGMSG=y CONFIG_RTW89_DEBUGFS=y @@ -4166,8 +4390,6 @@ CONFIG_NETDEVSIM=m CONFIG_NET_FAILOVER=m CONFIG_ISDN=y CONFIG_ISDN_CAPI=y -CONFIG_CAPI_TRACE=y -CONFIG_ISDN_CAPI_MIDDLEWARE=y CONFIG_MISDN=m CONFIG_MISDN_DSP=m CONFIG_MISDN_L1OIP=m @@ -4195,7 +4417,7 @@ CONFIG_INPUT_LEDS=y CONFIG_INPUT_FF_MEMLESS=m CONFIG_INPUT_SPARSEKMAP=m CONFIG_INPUT_MATRIXKMAP=m -CONFIG_INPUT_VIVALDIFMAP=m +CONFIG_INPUT_VIVALDIFMAP=y # # Userland interfaces @@ -4206,7 +4428,6 @@ CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 CONFIG_INPUT_JOYDEV=m CONFIG_INPUT_EVDEV=y -# CONFIG_INPUT_EVBUG is not set # # Input Device Drivers @@ -4215,9 +4436,8 @@ CONFIG_INPUT_KEYBOARD=y CONFIG_KEYBOARD_ADC=m CONFIG_KEYBOARD_ADP5520=m CONFIG_KEYBOARD_ADP5588=m -CONFIG_KEYBOARD_ADP5589=m CONFIG_KEYBOARD_APPLESPI=m -CONFIG_KEYBOARD_ATKBD=m +CONFIG_KEYBOARD_ATKBD=y CONFIG_KEYBOARD_QT1050=m CONFIG_KEYBOARD_QT1070=m CONFIG_KEYBOARD_QT2160=m @@ -4225,13 +4445,12 @@ CONFIG_KEYBOARD_DLINK_DIR685=m CONFIG_KEYBOARD_LKKBD=m CONFIG_KEYBOARD_GPIO=m CONFIG_KEYBOARD_GPIO_POLLED=m -CONFIG_KEYBOARD_TCA6416=m CONFIG_KEYBOARD_TCA8418=m CONFIG_KEYBOARD_MATRIX=m CONFIG_KEYBOARD_LM8323=m CONFIG_KEYBOARD_LM8333=m CONFIG_KEYBOARD_MAX7359=m -CONFIG_KEYBOARD_MCS=m +CONFIG_KEYBOARD_MAX7360=m CONFIG_KEYBOARD_MPR121=m CONFIG_KEYBOARD_NEWTON=m CONFIG_KEYBOARD_OPENCORES=m @@ -4339,9 +4558,6 @@ CONFIG_TOUCHSCREEN_CY8CTMG110=m CONFIG_TOUCHSCREEN_CYTTSP_CORE=m CONFIG_TOUCHSCREEN_CYTTSP_I2C=m CONFIG_TOUCHSCREEN_CYTTSP_SPI=m -CONFIG_TOUCHSCREEN_CYTTSP4_CORE=m -CONFIG_TOUCHSCREEN_CYTTSP4_I2C=m -CONFIG_TOUCHSCREEN_CYTTSP4_SPI=m CONFIG_TOUCHSCREEN_CYTTSP5=m CONFIG_TOUCHSCREEN_DA9034=m CONFIG_TOUCHSCREEN_DA9052=m @@ -4352,9 +4568,14 @@ CONFIG_TOUCHSCREEN_EGALAX_SERIAL=m CONFIG_TOUCHSCREEN_EXC3000=m CONFIG_TOUCHSCREEN_FUJITSU=m CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_GOODIX_BERLIN_CORE=m +CONFIG_TOUCHSCREEN_GOODIX_BERLIN_I2C=m +CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI=m CONFIG_TOUCHSCREEN_HIDEEP=m +CONFIG_TOUCHSCREEN_HIMAX_HX852X=m CONFIG_TOUCHSCREEN_HYCON_HY46XX=m CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX=m +CONFIG_TOUCHSCREEN_HYNITRON_CST816X=m CONFIG_TOUCHSCREEN_ILI210X=m CONFIG_TOUCHSCREEN_ILITEK=m CONFIG_TOUCHSCREEN_S6SY761=m @@ -4365,7 +4586,6 @@ CONFIG_TOUCHSCREEN_ELO=m CONFIG_TOUCHSCREEN_WACOM_W8001=m CONFIG_TOUCHSCREEN_WACOM_I2C=m CONFIG_TOUCHSCREEN_MAX11801=m -CONFIG_TOUCHSCREEN_MCS5000=m CONFIG_TOUCHSCREEN_MMS114=m CONFIG_TOUCHSCREEN_MELFAS_MIP4=m CONFIG_TOUCHSCREEN_MSG2638=m @@ -4437,9 +4657,12 @@ CONFIG_INPUT_AD714X_I2C=m CONFIG_INPUT_AD714X_SPI=m CONFIG_INPUT_ARIZONA_HAPTICS=m CONFIG_INPUT_ATC260X_ONKEY=m +CONFIG_INPUT_AW86927=m CONFIG_INPUT_BMA150=m +# CONFIG_INPUT_CS40L50_VIBRA is not set CONFIG_INPUT_E3X0_BUTTON=m CONFIG_INPUT_PCSPKR=m +CONFIG_INPUT_MAX7360_ROTARY=m CONFIG_INPUT_MAX77693_HAPTIC=m CONFIG_INPUT_MAX8925_ONKEY=m CONFIG_INPUT_MAX8997_HAPTIC=m @@ -4458,13 +4681,13 @@ CONFIG_INPUT_YEALINK=m CONFIG_INPUT_CM109=m CONFIG_INPUT_REGULATOR_HAPTIC=m CONFIG_INPUT_RETU_PWRBUTTON=m +CONFIG_INPUT_TPS6594_PWRBUTTON=m CONFIG_INPUT_AXP20X_PEK=m CONFIG_INPUT_TWL4030_PWRBUTTON=m CONFIG_INPUT_TWL4030_VIBRA=m CONFIG_INPUT_TWL6040_VIBRA=m CONFIG_INPUT_UINPUT=m CONFIG_INPUT_PALMAS_PWRBUTTON=m -CONFIG_INPUT_PCF50633_PMU=m CONFIG_INPUT_PCF8574=m CONFIG_INPUT_PWM_BEEPER=m CONFIG_INPUT_PWM_VIBRA=m @@ -4491,6 +4714,7 @@ CONFIG_INPUT_SOC_BUTTON_ARRAY=m CONFIG_INPUT_DRV260X_HAPTICS=m CONFIG_INPUT_DRV2665_HAPTICS=m CONFIG_INPUT_DRV2667_HAPTICS=m +CONFIG_INPUT_QNAP_MCU=m CONFIG_INPUT_RAVE_SP_PWRBUTTON=m CONFIG_INPUT_RT5120_PWRKEY=m CONFIG_RMI4_CORE=m @@ -4502,6 +4726,8 @@ CONFIG_RMI4_F03_SERIO=m CONFIG_RMI4_2D_SENSOR=y CONFIG_RMI4_F11=y CONFIG_RMI4_F12=y +CONFIG_RMI4_F1A=y +CONFIG_RMI4_F21=y CONFIG_RMI4_F30=y CONFIG_RMI4_F34=y CONFIG_RMI4_F3A=y @@ -4511,14 +4737,14 @@ CONFIG_RMI4_F55=y # # Hardware I/O ports # -CONFIG_SERIO=m +CONFIG_SERIO=y CONFIG_ARCH_MIGHT_HAVE_PC_SERIO=y -CONFIG_SERIO_I8042=m +CONFIG_SERIO_I8042=y CONFIG_SERIO_SERPORT=m CONFIG_SERIO_CT82C710=m CONFIG_SERIO_PARKBD=m CONFIG_SERIO_PCIPS2=m -CONFIG_SERIO_LIBPS2=m +CONFIG_SERIO_LIBPS2=y CONFIG_SERIO_RAW=m CONFIG_SERIO_ALTERA_PS2=m CONFIG_SERIO_PS2MULT=m @@ -4540,7 +4766,6 @@ CONFIG_VT=y CONFIG_CONSOLE_TRANSLATIONS=y CONFIG_VT_CONSOLE=y CONFIG_VT_CONSOLE_SLEEP=y -CONFIG_HW_CONSOLE=y CONFIG_VT_HW_CONSOLE_BINDING=y CONFIG_UNIX98_PTYS=y # CONFIG_LEGACY_PTYS is not set @@ -4578,6 +4803,7 @@ CONFIG_SERIAL_8250_RT288X=y CONFIG_SERIAL_8250_LPSS=m CONFIG_SERIAL_8250_MID=m CONFIG_SERIAL_8250_PERICOM=m +CONFIG_SERIAL_8250_NI=m # # Non-8250 serial port support @@ -4591,10 +4817,9 @@ CONFIG_SERIAL_CORE_CONSOLE=y CONFIG_SERIAL_JSM=m # CONFIG_SERIAL_LANTIQ is not set CONFIG_SERIAL_SCCNXP=m -CONFIG_SERIAL_SC16IS7XX_CORE=m CONFIG_SERIAL_SC16IS7XX=m -CONFIG_SERIAL_SC16IS7XX_I2C=y -CONFIG_SERIAL_SC16IS7XX_SPI=y +CONFIG_SERIAL_SC16IS7XX_I2C=m +CONFIG_SERIAL_SC16IS7XX_SPI=m CONFIG_SERIAL_ALTERA_JTAGUART=m CONFIG_SERIAL_ALTERA_UART=m CONFIG_SERIAL_ALTERA_UART_MAXPORTS=4 @@ -4615,7 +4840,7 @@ CONFIG_MOXA_INTELLIO=m CONFIG_MOXA_SMARTIO=m CONFIG_N_HDLC=m CONFIG_IPWIRELESS=m -CONFIG_N_GSM=m +# CONFIG_N_GSM is not set CONFIG_NOZOMI=m CONFIG_NULL_TTY=m CONFIG_HVC_DRIVER=y @@ -4628,7 +4853,7 @@ CONFIG_SERIAL_DEV_CTRL_TTYPORT=y CONFIG_PRINTER=m CONFIG_LP_CONSOLE=y CONFIG_PPDEV=m -CONFIG_VIRTIO_CONSOLE=m +CONFIG_VIRTIO_CONSOLE=y CONFIG_IPMI_HANDLER=m CONFIG_IPMI_DMI_DECODE=y CONFIG_IPMI_PLAT_DATA=y @@ -4658,6 +4883,7 @@ CONFIG_HPET=y # CONFIG_HPET_MMAP is not set CONFIG_HANGCHECK_TIMER=m CONFIG_TCG_TPM=y +CONFIG_TCG_TPM2_HMAC=y CONFIG_HW_RANDOM_TPM=y CONFIG_TCG_TIS_CORE=y CONFIG_TCG_TIS=y @@ -4674,6 +4900,7 @@ CONFIG_TCG_INFINEON=m CONFIG_TCG_XEN=m CONFIG_TCG_CRB=y CONFIG_TCG_VTPM_PROXY=m +CONFIG_TCG_SVSM=m CONFIG_TCG_TIS_ST33ZP24=m CONFIG_TCG_TIS_ST33ZP24_I2C=m CONFIG_TCG_TIS_ST33ZP24_SPI=m @@ -4690,7 +4917,6 @@ CONFIG_XILLYUSB=m CONFIG_I2C=y CONFIG_ACPI_I2C_OPREGION=y CONFIG_I2C_BOARDINFO=y -CONFIG_I2C_COMPAT=y CONFIG_I2C_CHARDEV=m CONFIG_I2C_MUX=m @@ -4705,6 +4931,7 @@ CONFIG_I2C_MUX_REG=m CONFIG_I2C_MUX_MLXCPLD=m # end of Multiplexer I2C Chip support +CONFIG_I2C_ATR=m CONFIG_I2C_HELPER_AUTO=y CONFIG_I2C_SMBUS=m CONFIG_I2C_ALGOBIT=m @@ -4722,22 +4949,23 @@ CONFIG_I2C_ALI1535=m CONFIG_I2C_ALI1563=m CONFIG_I2C_ALI15X3=m CONFIG_I2C_AMD756=m -CONFIG_I2C_AMD756_S4882=m CONFIG_I2C_AMD8111=m CONFIG_I2C_AMD_MP2=m +CONFIG_I2C_AMD_ASF=m CONFIG_I2C_I801=m +CONFIG_I2C_I801_MUX=y CONFIG_I2C_ISCH=m CONFIG_I2C_ISMT=m CONFIG_I2C_PIIX4=m CONFIG_I2C_CHT_WC=m CONFIG_I2C_NFORCE2=m -CONFIG_I2C_NFORCE2_S4985=m CONFIG_I2C_NVIDIA_GPU=m CONFIG_I2C_SIS5595=m CONFIG_I2C_SIS630=m CONFIG_I2C_SIS96X=m CONFIG_I2C_VIA=m CONFIG_I2C_VIAPRO=m +CONFIG_I2C_ZHAOXIN=m # # ACPI drivers @@ -4748,14 +4976,17 @@ CONFIG_I2C_SCMI=m # I2C system bus drivers (mostly embedded / system-on-chip) # CONFIG_I2C_CBUS_GPIO=m +CONFIG_I2C_CGBC=m CONFIG_I2C_DESIGNWARE_CORE=y CONFIG_I2C_DESIGNWARE_SLAVE=y CONFIG_I2C_DESIGNWARE_PLATFORM=y +CONFIG_I2C_DESIGNWARE_AMDISP=m CONFIG_I2C_DESIGNWARE_BAYTRAIL=y CONFIG_I2C_DESIGNWARE_PCI=y CONFIG_I2C_EMEV2=m CONFIG_I2C_GPIO=m # CONFIG_I2C_GPIO_FAULT_INJECTOR is not set +CONFIG_I2C_KEBA=m CONFIG_I2C_KEMPLD=m CONFIG_I2C_OCORES=m CONFIG_I2C_PCA_PLATFORM=m @@ -4768,6 +4999,8 @@ CONFIG_I2C_XILINX=m CONFIG_I2C_DIOLAN_U2C=m CONFIG_I2C_DLN2=m CONFIG_I2C_LJCA=m +CONFIG_I2C_NCT6694=m +CONFIG_I2C_USBIO=m CONFIG_I2C_CP2615=m CONFIG_I2C_PARPORT=m CONFIG_I2C_PCI1XXXX=m @@ -4798,6 +5031,7 @@ CONFIG_SPI=y # CONFIG_SPI_DEBUG is not set CONFIG_SPI_MASTER=y CONFIG_SPI_MEM=y +CONFIG_SPI_OFFLOAD=y # # SPI Master Controller Drivers @@ -4809,6 +5043,7 @@ CONFIG_SPI_AXI_SPI_ENGINE=m CONFIG_SPI_BITBANG=m CONFIG_SPI_BUTTERFLY=m CONFIG_SPI_CADENCE=m +CONFIG_SPI_CH341=m CONFIG_SPI_CS42L43=m CONFIG_SPI_DESIGNWARE=m CONFIG_SPI_DW_DMA=y @@ -4819,6 +5054,7 @@ CONFIG_SPI_GPIO=m CONFIG_SPI_INTEL=m CONFIG_SPI_INTEL_PCI=m CONFIG_SPI_INTEL_PLATFORM=m +CONFIG_SPI_KSPI2=m CONFIG_SPI_LM70_LLP=m CONFIG_SPI_LJCA=m CONFIG_SPI_MICROCHIP_CORE=m @@ -4831,6 +5067,7 @@ CONFIG_SPI_PXA2XX_PCI=m CONFIG_SPI_SC18IS602=m CONFIG_SPI_SIFIVE=m CONFIG_SPI_MXIC=m +CONFIG_SPI_VIRTIO=m CONFIG_SPI_XCOMM=m CONFIG_SPI_XILINX=m CONFIG_SPI_ZYNQMP_GQSPI=m @@ -4851,6 +5088,12 @@ CONFIG_SPI_SLAVE=y CONFIG_SPI_SLAVE_TIME=m CONFIG_SPI_SLAVE_SYSTEM_CONTROL=m CONFIG_SPI_DYNAMIC=y + +# +# SPI Offload triggers +# +CONFIG_SPI_OFFLOAD_TRIGGER_ADI_UTIL_SD=m +CONFIG_SPI_OFFLOAD_TRIGGER_PWM=m # CONFIG_SPMI is not set # CONFIG_HSI is not set CONFIG_PPS=m @@ -4863,10 +5106,9 @@ CONFIG_PPS_CLIENT_KTIMER=m CONFIG_PPS_CLIENT_LDISC=m CONFIG_PPS_CLIENT_PARPORT=m CONFIG_PPS_CLIENT_GPIO=m - -# -# PPS generators support -# +CONFIG_PPS_GENERATOR=m +# CONFIG_PPS_GENERATOR_DUMMY is not set +CONFIG_PPS_GENERATOR_TIO=m # # PTP clock support @@ -4876,26 +5118,43 @@ CONFIG_PTP_1588_CLOCK_OPTIONAL=m CONFIG_DP83640_PHY=m CONFIG_PTP_1588_CLOCK_INES=m CONFIG_PTP_1588_CLOCK_KVM=m +CONFIG_PTP_1588_CLOCK_VMCLOCK=m CONFIG_PTP_1588_CLOCK_IDT82P33=m CONFIG_PTP_1588_CLOCK_IDTCM=m +CONFIG_PTP_1588_CLOCK_FC3W=m CONFIG_PTP_1588_CLOCK_MOCK=m CONFIG_PTP_1588_CLOCK_VMW=m CONFIG_PTP_1588_CLOCK_OCP=m CONFIG_PTP_DFL_TOD=m +CONFIG_PTP_NETC_V4_TIMER=m # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y +CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y +CONFIG_GENERIC_PINMUX_FUNCTIONS=y CONFIG_PINCONF=y CONFIG_GENERIC_PINCONF=y # CONFIG_DEBUG_PINCTRL is not set CONFIG_PINCTRL_AMD=y +CONFIG_PINCTRL_AMDISP=m CONFIG_PINCTRL_CY8C95X0=m CONFIG_PINCTRL_DA9062=m +CONFIG_PINCTRL_MAX7360=m CONFIG_PINCTRL_MCP23S08_I2C=m CONFIG_PINCTRL_MCP23S08_SPI=m CONFIG_PINCTRL_MCP23S08=m CONFIG_PINCTRL_SX150X=y +CONFIG_PINCTRL_UPBOARD=m CONFIG_PINCTRL_CS42L43=m CONFIG_PINCTRL_MADERA=m CONFIG_PINCTRL_CS47L15=y @@ -4935,6 +5194,7 @@ CONFIG_PINCTRL_TIGERLAKE=m # # end of Renesas pinctrl drivers +CONFIG_GPIOLIB_LEGACY=y CONFIG_GPIOLIB=y CONFIG_GPIOLIB_FASTPATH_LIMIT=512 CONFIG_GPIO_ACPI=y @@ -4944,21 +5204,26 @@ CONFIG_GPIO_CDEV=y CONFIG_GPIO_CDEV_V1=y CONFIG_GPIO_GENERIC=m CONFIG_GPIO_REGMAP=m +CONFIG_GPIO_SWNODE_UNDEFINED=y CONFIG_GPIO_MAX730X=m CONFIG_GPIO_IDIO_16=m # # Memory mapped GPIO drivers # +CONFIG_GPIO_ALTERA=m CONFIG_GPIO_AMDPT=m CONFIG_GPIO_DWAPB=m CONFIG_GPIO_EXAR=m CONFIG_GPIO_GENERIC_PLATFORM=m +CONFIG_GPIO_GRANITERAPIDS=m CONFIG_GPIO_ICH=m CONFIG_GPIO_MB86S7X=m CONFIG_GPIO_MENZ127=m +# CONFIG_GPIO_POLARFIRE_SOC is not set CONFIG_GPIO_SIOX=m CONFIG_GPIO_TANGIER=m +CONFIG_GPIO_XILINX=m CONFIG_GPIO_AMD_FCH=m # end of Memory mapped GPIO drivers @@ -4994,7 +5259,9 @@ CONFIG_GPIO_TPIC2810=m CONFIG_GPIO_ADP5520=m CONFIG_GPIO_ARIZONA=m CONFIG_GPIO_BD9571MWV=m -CONFIG_GPIO_CRYSTAL_COVE=m +CONFIG_GPIO_CGBC=m +CONFIG_GPIO_CROS_EC=m +CONFIG_GPIO_CRYSTAL_COVE=y CONFIG_GPIO_DA9052=m CONFIG_GPIO_DA9055=m CONFIG_GPIO_DLN2=m @@ -5005,7 +5272,9 @@ CONFIG_GPIO_LJCA=m CONFIG_GPIO_LP3943=m CONFIG_GPIO_LP873X=m CONFIG_GPIO_MADERA=m -CONFIG_GPIO_PALMAS=y +CONFIG_GPIO_MAX7360=m +CONFIG_GPIO_NCT6694=m +CONFIG_GPIO_PALMAS=m CONFIG_GPIO_RC5T583=y CONFIG_GPIO_TPS65086=m CONFIG_GPIO_TPS6586X=y @@ -5034,6 +5303,7 @@ CONFIG_GPIO_RDC321X=m # # SPI GPIO expanders # +CONFIG_GPIO_74X164=m CONFIG_GPIO_MAX3191X=m CONFIG_GPIO_MAX7301=m CONFIG_GPIO_MC33880=m @@ -5045,6 +5315,8 @@ CONFIG_GPIO_XRA1403=m # USB GPIO expanders # CONFIG_GPIO_VIPERBOARD=m +CONFIG_GPIO_MPSSE=m +CONFIG_GPIO_USBIO=m # end of USB GPIO expanders # @@ -5057,6 +5329,13 @@ CONFIG_GPIO_VIRTIO=m CONFIG_GPIO_SIM=m # end of Virtual GPIO drivers +# +# GPIO Debugging utilities +# +# CONFIG_GPIO_VIRTUSER is not set +# end of GPIO Debugging utilities + +CONFIG_DEV_SYNC_PROBE=m CONFIG_W1=m CONFIG_W1_CON=y @@ -5069,6 +5348,7 @@ CONFIG_W1_MASTER_DS2490=m CONFIG_W1_MASTER_DS2482=m CONFIG_W1_MASTER_GPIO=m CONFIG_W1_MASTER_SGI=m +CONFIG_W1_MASTER_UART=m # end of 1-wire Bus Masters # @@ -5100,9 +5380,11 @@ CONFIG_POWER_RESET_ATC260X=m CONFIG_POWER_RESET_MT6323=y CONFIG_POWER_RESET_RESTART=y CONFIG_POWER_RESET_TPS65086=y +CONFIG_POWER_SEQUENCING=m CONFIG_POWER_SUPPLY=y # CONFIG_POWER_SUPPLY_DEBUG is not set CONFIG_POWER_SUPPLY_HWMON=y +CONFIG_ADC_BATTERY_HELPER=m CONFIG_GENERIC_ADC_BATTERY=m CONFIG_IP5XXX_POWER=m CONFIG_MAX8925_POWER=m @@ -5112,12 +5394,14 @@ CONFIG_WM8350_POWER=m CONFIG_TEST_POWER=m CONFIG_BATTERY_88PM860X=m CONFIG_CHARGER_ADP5061=m +CONFIG_BATTERY_CHAGALL=m CONFIG_BATTERY_CW2015=m CONFIG_BATTERY_DS2760=m CONFIG_BATTERY_DS2780=m CONFIG_BATTERY_DS2781=m CONFIG_BATTERY_DS2782=m # CONFIG_BATTERY_SAMSUNG_SDI is not set +CONFIG_BATTERY_INTEL_DC_TI=m CONFIG_BATTERY_SBS=m CONFIG_CHARGER_SBS=m CONFIG_MANAGER_SBS=m @@ -5136,14 +5420,15 @@ CONFIG_AXP288_CHARGER=m CONFIG_AXP288_FUEL_GAUGE=m CONFIG_BATTERY_MAX17040=m CONFIG_BATTERY_MAX17042=m +CONFIG_BATTERY_MAX1720X=m CONFIG_BATTERY_MAX1721X=m CONFIG_BATTERY_TWL4030_MADC=m CONFIG_CHARGER_88PM860X=m -CONFIG_CHARGER_PCF50633=m CONFIG_BATTERY_RX51=m CONFIG_CHARGER_ISP1704=m CONFIG_CHARGER_MAX8903=m CONFIG_CHARGER_TWL4030=m +CONFIG_CHARGER_TWL6030=m CONFIG_CHARGER_LP8727=m CONFIG_CHARGER_LP8788=m CONFIG_CHARGER_GPIO=m @@ -5152,7 +5437,9 @@ CONFIG_CHARGER_LT3651=m CONFIG_CHARGER_LTC4162L=m CONFIG_CHARGER_MAX14577=m CONFIG_CHARGER_MAX77693=m +CONFIG_CHARGER_MAX77705=m CONFIG_CHARGER_MAX77976=m +CONFIG_CHARGER_MAX8971=m CONFIG_CHARGER_MAX8997=m CONFIG_CHARGER_MAX8998=m CONFIG_CHARGER_MP2629=m @@ -5163,6 +5450,7 @@ CONFIG_CHARGER_BQ24190=m CONFIG_CHARGER_BQ24257=m CONFIG_CHARGER_BQ24735=m CONFIG_CHARGER_BQ2515X=m +CONFIG_CHARGER_BQ257XX=m CONFIG_CHARGER_BQ25890=m CONFIG_CHARGER_BQ25980=m CONFIG_CHARGER_BQ256XX=m @@ -5177,6 +5465,8 @@ CONFIG_CHARGER_RT9467=m CONFIG_CHARGER_RT9471=m CONFIG_CHARGER_CROS_USBPD=m CONFIG_CHARGER_CROS_PCHG=m +CONFIG_CHARGER_CROS_CONTROL=m +CONFIG_FUEL_GAUGE_STC3117=m CONFIG_CHARGER_BD99954=m CONFIG_CHARGER_WILCO=m CONFIG_BATTERY_SURFACE=m @@ -5213,15 +5503,21 @@ CONFIG_SENSORS_AHT10=m CONFIG_SENSORS_AQUACOMPUTER_D5NEXT=m CONFIG_SENSORS_AS370=m CONFIG_SENSORS_ASC7621=m +CONFIG_SENSORS_ASUS_ROG_RYUJIN=m CONFIG_SENSORS_AXI_FAN_CONTROL=m CONFIG_SENSORS_K8TEMP=m CONFIG_SENSORS_K10TEMP=m +CONFIG_SENSORS_KBATT=m +CONFIG_SENSORS_KFAN=m CONFIG_SENSORS_FAM15H_POWER=m CONFIG_SENSORS_APPLESMC=m CONFIG_SENSORS_ASB100=m CONFIG_SENSORS_ATXP1=m +CONFIG_SENSORS_CGBC=m +CONFIG_SENSORS_CHIPCAP2=m CONFIG_SENSORS_CORSAIR_CPRO=m CONFIG_SENSORS_CORSAIR_PSU=m +CONFIG_SENSORS_CROS_EC=m CONFIG_SENSORS_DRIVETEMP=m CONFIG_SENSORS_DS620=m CONFIG_SENSORS_DS1621=m @@ -5239,19 +5535,23 @@ CONFIG_SENSORS_FTSTEUTATES=m CONFIG_SENSORS_GIGABYTE_WATERFORCE=m CONFIG_SENSORS_GL518SM=m CONFIG_SENSORS_GL520SM=m +CONFIG_SENSORS_GPD=m CONFIG_SENSORS_G760A=m CONFIG_SENSORS_G762=m CONFIG_SENSORS_HIH6130=m CONFIG_SENSORS_HS3001=m +CONFIG_SENSORS_HTU31=m CONFIG_SENSORS_IBMAEM=m CONFIG_SENSORS_IBMPEX=m CONFIG_SENSORS_IIO_HWMON=m CONFIG_SENSORS_I5500=m CONFIG_SENSORS_CORETEMP=m +CONFIG_SENSORS_ISL28022=m CONFIG_SENSORS_IT87=m CONFIG_SENSORS_JC42=m CONFIG_SENSORS_POWERZ=m CONFIG_SENSORS_POWR1220=m +CONFIG_SENSORS_LENOVO_EC=m CONFIG_SENSORS_LINEAGE=m CONFIG_SENSORS_LTC2945=m CONFIG_SENSORS_LTC2947=m @@ -5266,6 +5566,7 @@ CONFIG_SENSORS_LTC4222=m CONFIG_SENSORS_LTC4245=m CONFIG_SENSORS_LTC4260=m CONFIG_SENSORS_LTC4261=m +CONFIG_SENSORS_LTC4282=m CONFIG_SENSORS_MAX1111=m CONFIG_SENSORS_MAX127=m CONFIG_SENSORS_MAX16065=m @@ -5282,6 +5583,7 @@ CONFIG_SENSORS_MAX6639=m CONFIG_SENSORS_MAX6650=m CONFIG_SENSORS_MAX6697=m CONFIG_SENSORS_MAX31790=m +CONFIG_SENSORS_MAX77705=m CONFIG_SENSORS_MC34VR500=m CONFIG_SENSORS_MCP3021=m CONFIG_SENSORS_MLXREG_FAN=m @@ -5310,29 +5612,35 @@ CONFIG_SENSORS_PC87360=m CONFIG_SENSORS_PC87427=m CONFIG_SENSORS_NTC_THERMISTOR=m CONFIG_SENSORS_NCT6683=m +CONFIG_SENSORS_NCT6694=m CONFIG_SENSORS_NCT6775_CORE=m CONFIG_SENSORS_NCT6775=m CONFIG_SENSORS_NCT6775_I2C=m +CONFIG_SENSORS_NCT7363=m CONFIG_SENSORS_NCT7802=m CONFIG_SENSORS_NCT7904=m CONFIG_SENSORS_NPCM7XX=m CONFIG_SENSORS_NZXT_KRAKEN2=m +CONFIG_SENSORS_NZXT_KRAKEN3=m CONFIG_SENSORS_NZXT_SMART2=m CONFIG_SENSORS_OCC_P8_I2C=m CONFIG_SENSORS_OCC=m -CONFIG_SENSORS_OXP=m CONFIG_SENSORS_PCF8591=m CONFIG_PMBUS=m CONFIG_SENSORS_PMBUS=m CONFIG_SENSORS_ACBEL_FSG032=m CONFIG_SENSORS_ADM1266=m CONFIG_SENSORS_ADM1275=m +CONFIG_SENSORS_ADP1050=m +CONFIG_SENSORS_ADP1050_REGULATOR=y CONFIG_SENSORS_BEL_PFE=m CONFIG_SENSORS_BPA_RS600=m +CONFIG_SENSORS_CRPS=m CONFIG_SENSORS_DELTA_AHE50DC_FAN=m CONFIG_SENSORS_FSP_3Y=m CONFIG_SENSORS_IBM_CFFPS=m CONFIG_SENSORS_DPS920AB=m +CONFIG_SENSORS_INA233=m CONFIG_SENSORS_INSPUR_IPSPS=m CONFIG_SENSORS_IR35221=m CONFIG_SENSORS_IR36021=m @@ -5342,6 +5650,8 @@ CONFIG_SENSORS_IRPS5401=m CONFIG_SENSORS_ISL68137=m CONFIG_SENSORS_LM25066=m CONFIG_SENSORS_LM25066_REGULATOR=y +CONFIG_SENSORS_LT3074=m +CONFIG_SENSORS_LT3074_REGULATOR=m CONFIG_SENSORS_LT7182S=m CONFIG_SENSORS_LTC2978=m # CONFIG_SENSORS_LTC2978_REGULATOR is not set @@ -5356,13 +5666,20 @@ CONFIG_SENSORS_MAX31785=m CONFIG_SENSORS_MAX34440=m CONFIG_SENSORS_MAX8688=m CONFIG_SENSORS_MP2856=m +CONFIG_SENSORS_MP2869=m CONFIG_SENSORS_MP2888=m +CONFIG_SENSORS_MP2891=m +CONFIG_SENSORS_MP29502=m CONFIG_SENSORS_MP2975=m +CONFIG_SENSORS_MP2993=m CONFIG_SENSORS_MP2975_REGULATOR=y CONFIG_SENSORS_MP5023=m +CONFIG_SENSORS_MP5920=m CONFIG_SENSORS_MP5990=m +CONFIG_SENSORS_MP9941=m CONFIG_SENSORS_MPQ7932_REGULATOR=y CONFIG_SENSORS_MPQ7932=m +CONFIG_SENSORS_MPQ8785=m CONFIG_SENSORS_PIM4328=m CONFIG_SENSORS_PLI1209BC=m CONFIG_SENSORS_PLI1209BC_REGULATOR=y @@ -5372,17 +5689,22 @@ CONFIG_SENSORS_Q54SJ108A2=m CONFIG_SENSORS_STPDDC60=m CONFIG_SENSORS_TDA38640=m CONFIG_SENSORS_TDA38640_REGULATOR=y +CONFIG_SENSORS_TPS25990=m +CONFIG_SENSORS_TPS25990_REGULATOR=y CONFIG_SENSORS_TPS40422=m CONFIG_SENSORS_TPS53679=m CONFIG_SENSORS_TPS546D24=m CONFIG_SENSORS_UCD9000=m CONFIG_SENSORS_UCD9200=m +CONFIG_SENSORS_XDP710=m CONFIG_SENSORS_XDPE152=m CONFIG_SENSORS_XDPE122=m CONFIG_SENSORS_XDPE122_REGULATOR=y CONFIG_SENSORS_ZL6100=m +CONFIG_SENSORS_PT5161L=m +CONFIG_SENSORS_PWM_FAN=m +CONFIG_SENSORS_QNAP_MCU_HWMON=m CONFIG_SENSORS_SBTSI=m -CONFIG_SENSORS_SBRMI=m CONFIG_SENSORS_SHT15=m CONFIG_SENSORS_SHT21=m CONFIG_SENSORS_SHT3x=m @@ -5402,6 +5724,8 @@ CONFIG_SENSORS_SCH56XX_COMMON=m CONFIG_SENSORS_SCH5627=m CONFIG_SENSORS_SCH5636=m CONFIG_SENSORS_STTS751=m +CONFIG_SENSORS_SURFACE_FAN=m +CONFIG_SENSORS_SURFACE_TEMP=m CONFIG_SENSORS_ADC128D818=m CONFIG_SENSORS_ADS7828=m CONFIG_SENSORS_ADS7871=m @@ -5410,6 +5734,8 @@ CONFIG_SENSORS_INA209=m CONFIG_SENSORS_INA2XX=m CONFIG_SENSORS_INA238=m CONFIG_SENSORS_INA3221=m +CONFIG_SENSORS_SPD5118=m +CONFIG_SENSORS_SPD5118_DETECT=y CONFIG_SENSORS_TC74=m CONFIG_SENSORS_THMC50=m CONFIG_SENSORS_TMP102=m @@ -5451,9 +5777,9 @@ CONFIG_THERMAL=y CONFIG_THERMAL_NETLINK=y # CONFIG_THERMAL_STATISTICS is not set # CONFIG_THERMAL_DEBUGFS is not set +CONFIG_THERMAL_CORE_TESTING=m CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=100 CONFIG_THERMAL_HWMON=y -CONFIG_THERMAL_WRITABLE_TRIPS=y CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y # CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set # CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set @@ -5465,6 +5791,7 @@ CONFIG_THERMAL_GOV_BANG_BANG=y CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y CONFIG_DEVFREQ_THERMAL=y +# CONFIG_PCIE_THERMAL is not set # CONFIG_THERMAL_EMULATION is not set # @@ -5516,10 +5843,13 @@ CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC=y # CONFIG_SOFT_WATCHDOG=m # CONFIG_SOFT_WATCHDOG_PRETIMEOUT is not set +CONFIG_CROS_EC_WATCHDOG=m CONFIG_DA9052_WATCHDOG=m CONFIG_DA9055_WATCHDOG=m CONFIG_DA9063_WATCHDOG=m CONFIG_DA9062_WATCHDOG=m +CONFIG_LENOVO_SE10_WDT=m +CONFIG_LENOVO_SE30_WDT=m CONFIG_MENF21BMC_WATCHDOG=m CONFIG_MENZ069_WATCHDOG=m CONFIG_WDAT_WDT=m @@ -5533,12 +5863,14 @@ CONFIG_CADENCE_WATCHDOG=m CONFIG_DW_WATCHDOG=m CONFIG_TWL4030_WATCHDOG=m CONFIG_MAX63XX_WATCHDOG=m +CONFIG_NCT6694_WATCHDOG=m CONFIG_RETU_WATCHDOG=m CONFIG_ACQUIRE_WDT=m CONFIG_ADVANTECH_WDT=m CONFIG_ADVANTECH_EC_WDT=m CONFIG_ALIM1535_WDT=m CONFIG_ALIM7101_WDT=m +CONFIG_CGBC_WDT=m CONFIG_EBC_C384_WDT=m CONFIG_EXAR_WDT=m CONFIG_F71808E_WDT=m @@ -5550,6 +5882,7 @@ CONFIG_IBMASR=m CONFIG_WAFER_WDT=m CONFIG_I6300ESB_WDT=m CONFIG_IE6XX_WDT=m +CONFIG_INTEL_OC_WATCHDOG=m CONFIG_ITCO_WDT=m CONFIG_ITCO_VENDOR_SUPPORT=y CONFIG_IT8712F_WDT=m @@ -5561,7 +5894,6 @@ CONFIG_SC1200_WDT=m CONFIG_PC87413_WDT=m CONFIG_NV_TCO=m CONFIG_60XX_WDT=m -CONFIG_CPU5_WDT=m CONFIG_SMSC_SCH311X_WDT=m CONFIG_SMSC37B787_WDT=m CONFIG_TQMX86_WDT=m @@ -5625,7 +5957,11 @@ CONFIG_MFD_BCM590XX=m CONFIG_MFD_BD9571MWV=m CONFIG_MFD_AXP20X=m CONFIG_MFD_AXP20X_I2C=m +CONFIG_MFD_CGBC=m CONFIG_MFD_CROS_EC_DEV=m +CONFIG_MFD_CS40L50_CORE=m +CONFIG_MFD_CS40L50_I2C=m +CONFIG_MFD_CS40L50_SPI=m CONFIG_MFD_CS42L43=m CONFIG_MFD_CS42L43_I2C=m CONFIG_MFD_CS42L43_SDW=m @@ -5671,6 +6007,7 @@ CONFIG_MFD_88PM860X=y CONFIG_MFD_MAX14577=m CONFIG_MFD_MAX77541=m CONFIG_MFD_MAX77693=m +CONFIG_MFD_MAX77705=m CONFIG_MFD_MAX77843=y CONFIG_MFD_MAX8907=m CONFIG_MFD_MAX8925=y @@ -5680,13 +6017,11 @@ CONFIG_MFD_MT6360=m CONFIG_MFD_MT6370=m CONFIG_MFD_MT6397=m CONFIG_MFD_MENF21BMC=m +CONFIG_MFD_NCT6694=m CONFIG_MFD_OCELOT=m CONFIG_EZX_PCAP=y CONFIG_MFD_VIPERBOARD=m CONFIG_MFD_RETU=m -CONFIG_MFD_PCF50633=m -CONFIG_PCF50633_ADC=m -CONFIG_PCF50633_GPIO=m CONFIG_MFD_SY7636A=m CONFIG_MFD_RDC321X=m CONFIG_MFD_RT4831=m @@ -5702,6 +6037,7 @@ CONFIG_MFD_SYSCON=y CONFIG_MFD_LP3943=m CONFIG_MFD_LP8788=y CONFIG_MFD_TI_LMU=m +CONFIG_MFD_BQ257XX=m CONFIG_MFD_PALMAS=m CONFIG_TPS6105X=m CONFIG_TPS65010=m @@ -5746,6 +6082,9 @@ CONFIG_RAVE_SP_CORE=m CONFIG_MFD_INTEL_M10_BMC_CORE=m CONFIG_MFD_INTEL_M10_BMC_SPI=m CONFIG_MFD_INTEL_M10_BMC_PMCI=m +CONFIG_MFD_QNAP_MCU=m +CONFIG_MFD_UPBOARD_FPGA=m +CONFIG_MFD_MAX7360=m # end of Multifunction device drivers CONFIG_REGULATOR=y @@ -5759,6 +6098,7 @@ CONFIG_REGULATOR_88PM800=m CONFIG_REGULATOR_88PM8607=m CONFIG_REGULATOR_ACT8865=m CONFIG_REGULATOR_AD5398=m +CONFIG_REGULATOR_ADP5055=m CONFIG_REGULATOR_AAT2870=m CONFIG_REGULATOR_ARIZONA_LDO1=m CONFIG_REGULATOR_ARIZONA_MICSUPP=m @@ -5768,6 +6108,7 @@ CONFIG_REGULATOR_AW37503=m CONFIG_REGULATOR_AXP20X=m CONFIG_REGULATOR_BCM590XX=m CONFIG_REGULATOR_BD9571MWV=m +CONFIG_REGULATOR_BQ257XX=m CONFIG_REGULATOR_DA903X=m CONFIG_REGULATOR_DA9052=m CONFIG_REGULATOR_DA9055=m @@ -5803,6 +6144,7 @@ CONFIG_REGULATOR_MAX20086=m CONFIG_REGULATOR_MAX20411=m CONFIG_REGULATOR_MAX77693=m CONFIG_REGULATOR_MAX77826=m +CONFIG_REGULATOR_MAX77838=m CONFIG_REGULATOR_MC13XXX_CORE=m CONFIG_REGULATOR_MC13783=m CONFIG_REGULATOR_MC13892=m @@ -5819,8 +6161,9 @@ CONFIG_REGULATOR_MT6370=m CONFIG_REGULATOR_MT6397=m CONFIG_REGULATOR_PALMAS=m CONFIG_REGULATOR_PCA9450=m +CONFIG_REGULATOR_PF9453=m CONFIG_REGULATOR_PCAP=m -CONFIG_REGULATOR_PCF50633=m +CONFIG_REGULATOR_PF0900=m CONFIG_REGULATOR_PV88060=m CONFIG_REGULATOR_PV88080=m CONFIG_REGULATOR_PV88090=m @@ -5910,10 +6253,12 @@ CONFIG_MEDIA_CEC_RC=y # CONFIG_CEC_PIN_ERROR_INJ is not set CONFIG_MEDIA_CEC_SUPPORT=y CONFIG_CEC_CH7322=m +CONFIG_CEC_NXP_TDA9950=m CONFIG_CEC_CROS_EC=m CONFIG_CEC_GPIO=m CONFIG_CEC_SECO=m CONFIG_CEC_SECO_RC=y +CONFIG_USB_EXTRON_DA_HD_4K_PLUS_CEC=m CONFIG_USB_PULSE8_CEC=m CONFIG_USB_RAINSHADOW_CEC=m # end of CEC support @@ -6200,6 +6545,7 @@ CONFIG_DVB_BUDGET=m CONFIG_DVB_BUDGET_CI=m CONFIG_DVB_BUDGET_AV=m CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_VIDEO_INTEL_IPU6=m CONFIG_INTEL_VSC=m CONFIG_IPU_BRIDGE=m CONFIG_RADIO_ADAPTERS=m @@ -6212,7 +6558,6 @@ CONFIG_RADIO_SI476X=m CONFIG_RADIO_TEA575X=m CONFIG_RADIO_TEA5764=m CONFIG_RADIO_TEF6862=m -CONFIG_RADIO_WL1273=m CONFIG_USB_DSBR=m CONFIG_USB_KEENE=m CONFIG_USB_MA901=m @@ -6224,7 +6569,6 @@ CONFIG_I2C_SI470X=m CONFIG_USB_SI4713=m CONFIG_PLATFORM_SI4713=m CONFIG_I2C_SI4713=m -CONFIG_RADIO_WL128X=m CONFIG_MEDIA_PLATFORM_DRIVERS=y CONFIG_V4L_PLATFORM_DRIVERS=y CONFIG_DVB_PLATFORM_DRIVERS=y @@ -6294,6 +6638,11 @@ CONFIG_VIDEO_CAFE_CCIC=m # Qualcomm media platform drivers # +# +# Raspberry Pi media platform drivers +# +CONFIG_VIDEO_RP1_CFE=m + # # Renesas media platform drivers # @@ -6340,6 +6689,7 @@ CONFIG_VIDEO_VICODEC=m CONFIG_VIDEO_VIMC=m CONFIG_VIDEO_VIVID=m CONFIG_VIDEO_VIVID_CEC=y +CONFIG_VIDEO_VIVID_OSD=y CONFIG_VIDEO_VIVID_MAX_DEVS=64 CONFIG_VIDEO_VISL=m # CONFIG_VISL_DEBUGFS is not set @@ -6394,6 +6744,9 @@ CONFIG_VIDEO_CCS_PLL=m CONFIG_VIDEO_ALVIUM_CSI2=m CONFIG_VIDEO_AR0521=m CONFIG_VIDEO_GC0308=m +CONFIG_VIDEO_GC0310=m +CONFIG_VIDEO_GC05A2=m +CONFIG_VIDEO_GC08A3=m CONFIG_VIDEO_GC2145=m CONFIG_VIDEO_HI556=m CONFIG_VIDEO_HI846=m @@ -6403,6 +6756,7 @@ CONFIG_VIDEO_IMX214=m CONFIG_VIDEO_IMX219=m CONFIG_VIDEO_IMX258=m CONFIG_VIDEO_IMX274=m +CONFIG_VIDEO_IMX283=m CONFIG_VIDEO_IMX290=m CONFIG_VIDEO_IMX296=m CONFIG_VIDEO_IMX319=m @@ -6414,11 +6768,13 @@ CONFIG_VIDEO_MT9M114=m CONFIG_VIDEO_MT9P031=m CONFIG_VIDEO_MT9T112=m CONFIG_VIDEO_MT9V011=m -CONFIG_VIDEO_MT9V032=m CONFIG_VIDEO_MT9V111=m CONFIG_VIDEO_OG01A1B=m +CONFIG_VIDEO_OG0VE1B=m CONFIG_VIDEO_OV01A10=m CONFIG_VIDEO_OV02A10=m +CONFIG_VIDEO_OV02E10=m +CONFIG_VIDEO_OV02C10=m CONFIG_VIDEO_OV08D10=m CONFIG_VIDEO_OV08X40=m CONFIG_VIDEO_OV13858=m @@ -6427,6 +6783,7 @@ CONFIG_VIDEO_OV2640=m CONFIG_VIDEO_OV2659=m CONFIG_VIDEO_OV2680=m CONFIG_VIDEO_OV2685=m +CONFIG_VIDEO_OV2735=m CONFIG_VIDEO_OV2740=m CONFIG_VIDEO_OV4689=m CONFIG_VIDEO_OV5647=m @@ -6435,8 +6792,8 @@ CONFIG_VIDEO_OV5670=m CONFIG_VIDEO_OV5675=m CONFIG_VIDEO_OV5693=m CONFIG_VIDEO_OV5695=m +CONFIG_VIDEO_OV6211=m CONFIG_VIDEO_OV64A40=m -CONFIG_VIDEO_OV6650=m CONFIG_VIDEO_OV7251=m CONFIG_VIDEO_OV7640=m CONFIG_VIDEO_OV7670=m @@ -6454,6 +6811,8 @@ CONFIG_VIDEO_RJ54N1=m CONFIG_VIDEO_S5C73M3=m CONFIG_VIDEO_S5K5BAF=m CONFIG_VIDEO_S5K6A3=m +CONFIG_VIDEO_VD55G1=m +CONFIG_VIDEO_VD56G3=m CONFIG_VIDEO_CCS=m CONFIG_VIDEO_ET8EK8=m @@ -6463,16 +6822,13 @@ CONFIG_VIDEO_ET8EK8=m CONFIG_VIDEO_THP7312=m # end of Camera ISPs -# -# Lens drivers -# +CONFIG_VIDEO_CAMERA_LENS=y CONFIG_VIDEO_AD5820=m CONFIG_VIDEO_AK7375=m CONFIG_VIDEO_DW9714=m CONFIG_VIDEO_DW9719=m CONFIG_VIDEO_DW9768=m CONFIG_VIDEO_DW9807_VCM=m -# end of Lens drivers # # Flash devices @@ -6520,6 +6876,7 @@ CONFIG_VIDEO_M52790=m # # Video serializers and deserializers # +CONFIG_VIDEO_MAX96717=m # end of Video serializers and deserializers # @@ -6742,8 +7099,8 @@ CONFIG_DVB_DUMMY_FE=m # Graphics support # CONFIG_APERTURE_HELPERS=y -CONFIG_VIDEO_CMDLINE=y -CONFIG_VIDEO_NOMODESET=y +CONFIG_SCREEN_INFO=y +CONFIG_VIDEO=y # CONFIG_AUXDISPLAY is not set # CONFIG_PANEL is not set CONFIG_AGP=y @@ -6753,23 +7110,57 @@ CONFIG_AGP_SIS=m CONFIG_AGP_VIA=m CONFIG_INTEL_GTT=m CONFIG_VGA_SWITCHEROO=y +# CONFIG_NOVA_CORE is not set CONFIG_DRM=y + +# +# DRM debugging options +# +# CONFIG_DRM_DEBUG_MM is not set +# end of DRM debugging options + CONFIG_DRM_MIPI_DBI=m CONFIG_DRM_MIPI_DSI=y -# CONFIG_DRM_DEBUG_MM is not set CONFIG_DRM_KMS_HELPER=y +CONFIG_DRM_DRAW=y +CONFIG_DRM_PANIC=y +CONFIG_DRM_PANIC_FOREGROUND_COLOR=0xffffff +CONFIG_DRM_PANIC_BACKGROUND_COLOR=0x0000aa +# CONFIG_DRM_PANIC_DEBUG is not set +CONFIG_DRM_PANIC_SCREEN="qr_code" +CONFIG_DRM_PANIC_SCREEN_QR_CODE=y +CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL="https://panic.archlinux.org/panic_report#" +CONFIG_DRM_PANIC_SCREEN_QR_VERSION=40 +CONFIG_DRM_CLIENT=y +CONFIG_DRM_CLIENT_LIB=y +CONFIG_DRM_CLIENT_SELECTION=y +CONFIG_DRM_CLIENT_SETUP=y + +# +# Supported DRM clients +# CONFIG_DRM_FBDEV_EMULATION=y CONFIG_DRM_FBDEV_OVERALLOC=100 +CONFIG_DRM_CLIENT_LOG=y +CONFIG_DRM_CLIENT_DEFAULT_FBDEV=y +# CONFIG_DRM_CLIENT_DEFAULT_LOG is not set +CONFIG_DRM_CLIENT_DEFAULT="fbdev" +# end of Supported DRM clients + CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_DISPLAY_HELPER=m +CONFIG_DRM_DISPLAY_DP_AUX_CEC=y +CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV=y CONFIG_DRM_DISPLAY_DP_HELPER=y +CONFIG_DRM_DISPLAY_DP_TUNNEL=y +CONFIG_DRM_DISPLAY_DSC_HELPER=y CONFIG_DRM_DISPLAY_HDCP_HELPER=y +CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER=y CONFIG_DRM_DISPLAY_HDMI_HELPER=y -CONFIG_DRM_DP_AUX_CHARDEV=y -CONFIG_DRM_DP_CEC=y CONFIG_DRM_TTM=m CONFIG_DRM_EXEC=m CONFIG_DRM_GPUVM=m +CONFIG_DRM_GPUSVM=m CONFIG_DRM_BUDDY=m CONFIG_DRM_VRAM_HELPER=m CONFIG_DRM_TTM_HELPER=m @@ -6779,13 +7170,11 @@ CONFIG_DRM_SUBALLOC_HELPER=m CONFIG_DRM_SCHED=m # -# I2C encoder or helper chips +# Drivers for system framebuffers # -CONFIG_DRM_I2C_CH7006=m -CONFIG_DRM_I2C_SIL164=m -CONFIG_DRM_I2C_NXP_TDA998X=m -CONFIG_DRM_I2C_NXP_TDA9950=m -# end of I2C encoder or helper chips +CONFIG_DRM_SYSFB_HELPER=y +CONFIG_DRM_SIMPLEDRM=y +# end of Drivers for system framebuffers # # ARM devices @@ -6798,6 +7187,7 @@ CONFIG_DRM_AMDGPU=m CONFIG_DRM_AMDGPU_SI=y CONFIG_DRM_AMDGPU_CIK=y CONFIG_DRM_AMDGPU_USERPTR=y +CONFIG_DRM_AMD_ISP=y # # ACP (Audio CoProcessor) Configuration @@ -6823,7 +7213,9 @@ CONFIG_NOUVEAU_DEBUG_DEFAULT=3 # CONFIG_NOUVEAU_DEBUG_PUSH is not set CONFIG_DRM_NOUVEAU_BACKLIGHT=y CONFIG_DRM_NOUVEAU_SVM=y -CONFIG_DRM_NOUVEAU_GSP_DEFAULT=y +CONFIG_DRM_NOUVEAU_CH7006=m +CONFIG_DRM_NOUVEAU_SIL164=m +# CONFIG_DRM_NOVA is not set CONFIG_DRM_I915=m CONFIG_DRM_I915_FORCE_PROBE="*" CONFIG_DRM_I915_CAPTURE_ERROR=y @@ -6831,6 +7223,7 @@ CONFIG_DRM_I915_COMPRESS_ERROR=y CONFIG_DRM_I915_USERPTR=y CONFIG_DRM_I915_GVT_KVMGT=m CONFIG_DRM_I915_PXP=y +CONFIG_DRM_I915_DP_TUNNEL=y CONFIG_DRM_I915_REQUEST_TIMEOUT=20000 CONFIG_DRM_I915_FENCE_TIMEOUT=10000 CONFIG_DRM_I915_USERFAULT_AUTOSUSPEND=250 @@ -6843,6 +7236,9 @@ CONFIG_DRM_I915_TIMESLICE_DURATION=1 CONFIG_DRM_I915_GVT=y CONFIG_DRM_XE=m CONFIG_DRM_XE_DISPLAY=y +CONFIG_DRM_XE_DP_TUNNEL=y +CONFIG_DRM_XE_GPUSVM=y +CONFIG_DRM_XE_PAGEMAP=y CONFIG_DRM_XE_FORCE_PROBE="" CONFIG_DRM_XE_JOB_TIMEOUT_MAX=10000 CONFIG_DRM_XE_JOB_TIMEOUT_MIN=1 @@ -6869,6 +7265,7 @@ CONFIG_DRM_PANEL=y # Display Panels # CONFIG_DRM_PANEL_AUO_A030JTN01=m +CONFIG_DRM_PANEL_ILITEK_ILI9341=m CONFIG_DRM_PANEL_ORISETECH_OTA5601A=m CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m CONFIG_DRM_PANEL_WIDECHIPS_WS2401=m @@ -6880,16 +7277,19 @@ CONFIG_DRM_PANEL_BRIDGE=y # # Display Interface Bridges # +CONFIG_DRM_I2C_NXP_TDA998X=m CONFIG_DRM_ANALOGIX_ANX78XX=m CONFIG_DRM_ANALOGIX_DP=m # end of Display Interface Bridges # CONFIG_DRM_ETNAVIV is not set +CONFIG_DRM_HISI_HIBMC=m +CONFIG_DRM_APPLETBDRM=m CONFIG_DRM_BOCHS=m CONFIG_DRM_CIRRUS_QEMU=m CONFIG_DRM_GM12U320=m CONFIG_DRM_PANEL_MIPI_DBI=m -CONFIG_DRM_SIMPLEDRM=y +CONFIG_DRM_PIXPAPER=m CONFIG_TINYDRM_HX8357D=m CONFIG_TINYDRM_ILI9163=m CONFIG_TINYDRM_ILI9225=m @@ -6897,18 +7297,21 @@ CONFIG_TINYDRM_ILI9341=m CONFIG_TINYDRM_ILI9486=m CONFIG_TINYDRM_MI0283QT=m CONFIG_TINYDRM_REPAPER=m -CONFIG_TINYDRM_ST7586=m -CONFIG_TINYDRM_ST7735R=m +CONFIG_TINYDRM_SHARP_MEMORY=m CONFIG_DRM_XEN=y CONFIG_DRM_XEN_FRONTEND=m CONFIG_DRM_VBOXVIDEO=m CONFIG_DRM_GUD=m +CONFIG_DRM_ST7571_I2C=m +CONFIG_DRM_ST7586=m +CONFIG_DRM_ST7735R=m CONFIG_DRM_SSD130X=m CONFIG_DRM_SSD130X_I2C=m CONFIG_DRM_SSD130X_SPI=m CONFIG_DRM_HYPERV=m -CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y +CONFIG_DRM_PANEL_BACKLIGHT_QUIRKS=m CONFIG_DRM_PRIVACY_SCREEN=y +CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y # # Frame buffer Devices @@ -6961,7 +7364,6 @@ CONFIG_XEN_FBDEV_FRONTEND=m # CONFIG_FB_SM712 is not set CONFIG_FB_CORE=y CONFIG_FB_NOTIFY=y -# CONFIG_FIRMWARE_EDID is not set CONFIG_FB_DEVICE=y CONFIG_FB_CFB_FILLRECT=y CONFIG_FB_CFB_COPYAREA=y @@ -6973,6 +7375,7 @@ CONFIG_FB_SYS_IMAGEBLIT=y CONFIG_FB_SYSMEM_FOPS=y CONFIG_FB_DEFERRED_IO=y CONFIG_FB_DMAMEM_HELPERS=y +CONFIG_FB_DMAMEM_HELPERS_DEFERRED=y CONFIG_FB_IOMEM_FOPS=y CONFIG_FB_IOMEM_HELPERS=y CONFIG_FB_SYSMEM_HELPERS=y @@ -6999,6 +7402,7 @@ CONFIG_LCD_HX8357=m CONFIG_LCD_OTM3225A=m CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BACKLIGHT_KTD253=m +CONFIG_BACKLIGHT_KTD2801=m CONFIG_BACKLIGHT_KTZ8866=m CONFIG_BACKLIGHT_LM3533=m CONFIG_BACKLIGHT_PWM=m @@ -7015,8 +7419,8 @@ CONFIG_BACKLIGHT_ADP5520=m CONFIG_BACKLIGHT_ADP8860=m CONFIG_BACKLIGHT_ADP8870=m CONFIG_BACKLIGHT_88PM860X=m -CONFIG_BACKLIGHT_PCF50633=m CONFIG_BACKLIGHT_AAT2870=m +CONFIG_BACKLIGHT_LM3509=m CONFIG_BACKLIGHT_LM3630A=m CONFIG_BACKLIGHT_LM3639=m CONFIG_BACKLIGHT_LP855X=m @@ -7034,6 +7438,7 @@ CONFIG_BACKLIGHT_RAVE_SP=m CONFIG_VIDEOMODE_HELPERS=y CONFIG_HDMI=y +# CONFIG_FIRMWARE_EDID is not set # # Console display driver support @@ -7050,11 +7455,15 @@ CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y # end of Console display driver support # CONFIG_LOGO is not set +CONFIG_TRACE_GPU_MEM=y # end of Graphics support CONFIG_DRM_ACCEL=y +CONFIG_DRM_ACCEL_AMDXDNA=m CONFIG_DRM_ACCEL_HABANALABS=m +CONFIG_HL_HLDIO=y CONFIG_DRM_ACCEL_IVPU=m +# CONFIG_DRM_ACCEL_IVPU_DEBUG is not set CONFIG_DRM_ACCEL_QAIC=m CONFIG_SOUND=m CONFIG_SOUND_OSS_CORE=y @@ -7084,7 +7493,6 @@ CONFIG_SND_MAX_CARDS=32 # CONFIG_SND_SUPPORT_OLD_API is not set CONFIG_SND_PROC_FS=y CONFIG_SND_VERBOSE_PROCFS=y -CONFIG_SND_VERBOSE_PRINTK=y CONFIG_SND_CTL_FAST_LOOKUP=y CONFIG_SND_DEBUG=y # CONFIG_SND_DEBUG_VERBOSE is not set @@ -7092,6 +7500,7 @@ CONFIG_SND_DEBUG=y CONFIG_SND_CTL_INPUT_VALIDATION=y # CONFIG_SND_CTL_DEBUG is not set # CONFIG_SND_JACK_INJECTION_DEBUG is not set +CONFIG_SND_UTIMER=y CONFIG_SND_VMASTER=y CONFIG_SND_DMA_SGBUF=y CONFIG_SND_CTL_LED=m @@ -7202,51 +7611,76 @@ CONFIG_SND_YMFPCI=m # HD-Audio # CONFIG_SND_HDA=m -CONFIG_SND_HDA_GENERIC_LEDS=y -CONFIG_SND_HDA_INTEL=m CONFIG_SND_HDA_HWDEP=y CONFIG_SND_HDA_RECONFIG=y CONFIG_SND_HDA_INPUT_BEEP=y CONFIG_SND_HDA_INPUT_BEEP_MODE=0 CONFIG_SND_HDA_PATCH_LOADER=y -CONFIG_SND_HDA_CIRRUS_SCODEC=m -CONFIG_SND_HDA_SCODEC_CS35L41=m -CONFIG_SND_HDA_CS_DSP_CONTROLS=m -CONFIG_SND_HDA_SCODEC_CS35L41_I2C=m -CONFIG_SND_HDA_SCODEC_CS35L41_SPI=m -CONFIG_SND_HDA_SCODEC_CS35L56=m -CONFIG_SND_HDA_SCODEC_CS35L56_I2C=m -CONFIG_SND_HDA_SCODEC_CS35L56_SPI=m -CONFIG_SND_HDA_SCODEC_TAS2781_I2C=m -CONFIG_SND_HDA_CODEC_REALTEK=m +CONFIG_SND_HDA_POWER_SAVE_DEFAULT=10 +# CONFIG_SND_HDA_CTL_DEV_ID is not set +CONFIG_SND_HDA_PREALLOC_SIZE=0 +CONFIG_SND_HDA_INTEL=m +CONFIG_SND_HDA_ACPI=m +CONFIG_SND_HDA_GENERIC_LEDS=y CONFIG_SND_HDA_CODEC_ANALOG=m CONFIG_SND_HDA_CODEC_SIGMATEL=m CONFIG_SND_HDA_CODEC_VIA=m -CONFIG_SND_HDA_CODEC_HDMI=m -CONFIG_SND_HDA_CODEC_CIRRUS=m -CONFIG_SND_HDA_CODEC_CS8409=m CONFIG_SND_HDA_CODEC_CONEXANT=m +CONFIG_SND_HDA_CODEC_SENARYTECH=m CONFIG_SND_HDA_CODEC_CA0110=m CONFIG_SND_HDA_CODEC_CA0132=m CONFIG_SND_HDA_CODEC_CA0132_DSP=y CONFIG_SND_HDA_CODEC_CMEDIA=m +CONFIG_SND_HDA_CODEC_CM9825=m CONFIG_SND_HDA_CODEC_SI3054=m CONFIG_SND_HDA_GENERIC=m -CONFIG_SND_HDA_POWER_SAVE_DEFAULT=10 +CONFIG_SND_HDA_CODEC_REALTEK=m +CONFIG_SND_HDA_CODEC_REALTEK_LIB=m +CONFIG_SND_HDA_CODEC_ALC260=m +CONFIG_SND_HDA_CODEC_ALC262=m +CONFIG_SND_HDA_CODEC_ALC268=m +CONFIG_SND_HDA_CODEC_ALC269=m +CONFIG_SND_HDA_CODEC_ALC662=m +CONFIG_SND_HDA_CODEC_ALC680=m +CONFIG_SND_HDA_CODEC_ALC861=m +CONFIG_SND_HDA_CODEC_ALC861VD=m +CONFIG_SND_HDA_CODEC_ALC880=m +CONFIG_SND_HDA_CODEC_ALC882=m +CONFIG_SND_HDA_CODEC_CIRRUS=m +CONFIG_SND_HDA_CODEC_CS420X=m +CONFIG_SND_HDA_CODEC_CS421X=m +CONFIG_SND_HDA_CODEC_CS8409=m +CONFIG_SND_HDA_CODEC_HDMI=m +CONFIG_SND_HDA_CODEC_HDMI_GENERIC=m +CONFIG_SND_HDA_CODEC_HDMI_SIMPLE=m +CONFIG_SND_HDA_CODEC_HDMI_INTEL=m CONFIG_SND_HDA_INTEL_HDMI_SILENT_STREAM=y -# CONFIG_SND_HDA_CTL_DEV_ID is not set -# end of HD-Audio - +CONFIG_SND_HDA_CODEC_HDMI_ATI=m +CONFIG_SND_HDA_CODEC_HDMI_NVIDIA=m +CONFIG_SND_HDA_CODEC_HDMI_NVIDIA_MCP=m +CONFIG_SND_HDA_CODEC_HDMI_TEGRA=m +CONFIG_SND_HDA_CIRRUS_SCODEC=m +CONFIG_SND_HDA_SCODEC_CS35L41=m +CONFIG_SND_HDA_SCODEC_COMPONENT=m +CONFIG_SND_HDA_SCODEC_CS35L41_I2C=m +CONFIG_SND_HDA_SCODEC_CS35L41_SPI=m +CONFIG_SND_HDA_SCODEC_CS35L56=m +CONFIG_SND_HDA_SCODEC_CS35L56_I2C=m +CONFIG_SND_HDA_SCODEC_CS35L56_SPI=m +CONFIG_SND_HDA_SCODEC_TAS2781=m +CONFIG_SND_HDA_SCODEC_TAS2781_I2C=m +CONFIG_SND_HDA_SCODEC_TAS2781_SPI=m CONFIG_SND_HDA_CORE=m CONFIG_SND_HDA_DSP_LOADER=y CONFIG_SND_HDA_COMPONENT=y CONFIG_SND_HDA_I915=y CONFIG_SND_HDA_EXT_CORE=m -CONFIG_SND_HDA_PREALLOC_SIZE=0 CONFIG_SND_INTEL_NHLT=y CONFIG_SND_INTEL_DSP_CONFIG=m CONFIG_SND_INTEL_SOUNDWIRE_ACPI=m # CONFIG_SND_INTEL_BYT_PREFER_SOF is not set +# end of HD-Audio + CONFIG_SND_SPI=y CONFIG_SND_USB=y CONFIG_SND_USB_AUDIO=m @@ -7257,9 +7691,11 @@ CONFIG_SND_USB_USX2Y=m CONFIG_SND_USB_CAIAQ=m CONFIG_SND_USB_CAIAQ_INPUT=y CONFIG_SND_USB_US122L=m +CONFIG_SND_USB_US144MKII=m CONFIG_SND_USB_6FIRE=m CONFIG_SND_USB_HIFACE=m CONFIG_SND_BCD2000=m +CONFIG_SND_USB_AUDIO_QMI=m CONFIG_SND_USB_LINE6=m CONFIG_SND_USB_POD=m CONFIG_SND_USB_PODHD=m @@ -7285,9 +7721,18 @@ CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y CONFIG_SND_SOC_COMPRESS=y CONFIG_SND_SOC_TOPOLOGY=y CONFIG_SND_SOC_ACPI=m -CONFIG_SND_SOC_ADI=m +CONFIG_SND_SOC_USB=m + +# +# Analog Devices +# CONFIG_SND_SOC_ADI_AXI_I2S=m CONFIG_SND_SOC_ADI_AXI_SPDIF=m +# end of Analog Devices + +# +# AMD +# CONFIG_SND_SOC_AMD_ACP=m CONFIG_SND_SOC_AMD_CZ_DA7219MX98357_MACH=m CONFIG_SND_SOC_AMD_CZ_RT5645_MACH=m @@ -7302,9 +7747,11 @@ CONFIG_SND_SOC_AMD_ACP6x=m CONFIG_SND_SOC_AMD_YC_MACH=m CONFIG_SND_AMD_ACP_CONFIG=m CONFIG_SND_SOC_AMD_ACP_COMMON=m +CONFIG_SND_SOC_ACPI_AMD_MATCH=m CONFIG_SND_SOC_AMD_ACP_PDM=m CONFIG_SND_SOC_AMD_ACP_LEGACY_COMMON=m CONFIG_SND_SOC_AMD_ACP_I2S=m +CONFIG_SND_SOC_AMD_ACPI_MACH=m CONFIG_SND_SOC_AMD_ACP_PCM=m CONFIG_SND_SOC_AMD_ACP_PCI=m CONFIG_SND_AMD_ASOC_RENOIR=m @@ -7314,16 +7761,53 @@ CONFIG_SND_AMD_ASOC_ACP70=m CONFIG_SND_SOC_AMD_MACH_COMMON=m CONFIG_SND_SOC_AMD_LEGACY_MACH=m CONFIG_SND_SOC_AMD_SOF_MACH=m +CONFIG_SND_SOC_AMD_SDW_MACH_COMMON=m +CONFIG_SND_SOC_AMD_SOF_SDW_MACH=m +CONFIG_SND_SOC_AMD_LEGACY_SDW_MACH=m +CONFIG_SND_AMD_SOUNDWIRE_ACPI=m CONFIG_SND_SOC_AMD_RPL_ACP6x=m +CONFIG_SND_SOC_AMD_ACP63_TOPLEVEL=m +CONFIG_SND_SOC_AMD_SOUNDWIRE_LINK_BASELINE=m +CONFIG_SND_SOC_AMD_SOUNDWIRE=m CONFIG_SND_SOC_AMD_PS=m CONFIG_SND_SOC_AMD_PS_MACH=m -CONFIG_SND_ATMEL_SOC=m +# end of AMD + +# +# Apple +# +# end of Apple + +# +# Atmel +# +# end of Atmel + +# +# Au1x +# +# end of Au1x + +# +# Broadcom +# # CONFIG_SND_BCM63XX_I2S_WHISTLER is not set +# end of Broadcom + +# +# Cirrus Logic +# +# end of Cirrus Logic + +# +# DesignWare +# CONFIG_SND_DESIGNWARE_I2S=m CONFIG_SND_DESIGNWARE_PCM=y +# end of DesignWare # -# SoC Audio for Freescale CPUs +# Freescale # # @@ -7337,38 +7821,47 @@ CONFIG_SND_DESIGNWARE_PCM=y # CONFIG_SND_SOC_FSL_ESAI is not set # CONFIG_SND_SOC_FSL_MICFIL is not set CONFIG_SND_SOC_FSL_XCVR=m +CONFIG_SND_SOC_FSL_UTILS=m # CONFIG_SND_SOC_IMX_AUDMUX is not set -# end of SoC Audio for Freescale CPUs +# end of Freescale +# +# Google +# CONFIG_SND_SOC_CHV3_I2S=m +# end of Google + +# +# Hisilicon +# CONFIG_SND_I2S_HI6210_I2S=m -CONFIG_SND_SOC_IMG=y -CONFIG_SND_SOC_IMG_I2S_IN=m -CONFIG_SND_SOC_IMG_I2S_OUT=m -CONFIG_SND_SOC_IMG_PARALLEL_OUT=m -CONFIG_SND_SOC_IMG_SPDIF_IN=m -CONFIG_SND_SOC_IMG_SPDIF_OUT=m -CONFIG_SND_SOC_IMG_PISTACHIO_INTERNAL_DAC=m +# end of Hisilicon + +# +# JZ4740 +# +# end of JZ4740 + +# +# Kirkwood +# +# end of Kirkwood + +# +# Loongson +# +# end of Loongson + +# +# Intel +# CONFIG_SND_SOC_INTEL_SST_TOPLEVEL=y -CONFIG_SND_SOC_INTEL_SST=m CONFIG_SND_SOC_INTEL_CATPT=m CONFIG_SND_SST_ATOM_HIFI2_PLATFORM=m CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_PCI=m CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI=m -CONFIG_SND_SOC_INTEL_SKYLAKE=m -CONFIG_SND_SOC_INTEL_SKL=m -CONFIG_SND_SOC_INTEL_APL=m -CONFIG_SND_SOC_INTEL_KBL=m -CONFIG_SND_SOC_INTEL_GLK=m -CONFIG_SND_SOC_INTEL_CNL=m -CONFIG_SND_SOC_INTEL_CFL=m -CONFIG_SND_SOC_INTEL_CML_H=m -CONFIG_SND_SOC_INTEL_CML_LP=m -CONFIG_SND_SOC_INTEL_SKYLAKE_FAMILY=m -CONFIG_SND_SOC_INTEL_SKYLAKE_SSP_CLK=m -CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC=y -CONFIG_SND_SOC_INTEL_SKYLAKE_COMMON=m CONFIG_SND_SOC_ACPI_INTEL_MATCH=m +CONFIG_SND_SOC_ACPI_INTEL_SDCA_QUIRKS=m CONFIG_SND_SOC_INTEL_AVS=m # @@ -7378,6 +7871,7 @@ CONFIG_SND_SOC_INTEL_AVS=m # # Available DSP configurations # +# CONFIG_SND_SOC_INTEL_AVS_CARDNAME_OBSOLETE is not set CONFIG_SND_SOC_INTEL_AVS_MACH_DA7219=m CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC=m CONFIG_SND_SOC_INTEL_AVS_MACH_ES8336=m @@ -7387,11 +7881,13 @@ CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98927=m CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98357A=m CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98373=m CONFIG_SND_SOC_INTEL_AVS_MACH_NAU8825=m +CONFIG_SND_SOC_INTEL_AVS_MACH_PCM3168A=m CONFIG_SND_SOC_INTEL_AVS_MACH_PROBE=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT274=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT286=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT298=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT5514=m +CONFIG_SND_SOC_INTEL_AVS_MACH_RT5640=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT5663=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT5682=m CONFIG_SND_SOC_INTEL_AVS_MACH_SSM4567=m @@ -7404,7 +7900,6 @@ CONFIG_SND_SOC_INTEL_SOF_MAXIM_COMMON=m CONFIG_SND_SOC_INTEL_SOF_REALTEK_COMMON=m CONFIG_SND_SOC_INTEL_SOF_CIRRUS_COMMON=m CONFIG_SND_SOC_INTEL_SOF_NUVOTON_COMMON=m -CONFIG_SND_SOC_INTEL_SOF_SSP_COMMON=m CONFIG_SND_SOC_INTEL_SOF_BOARD_HELPERS=m CONFIG_SND_SOC_INTEL_HASWELL_MACH=m CONFIG_SND_SOC_INTEL_BDW_RT5650_MACH=m @@ -7421,19 +7916,7 @@ CONFIG_SND_SOC_INTEL_BYT_CHT_CX2072X_MACH=m CONFIG_SND_SOC_INTEL_BYT_CHT_DA7213_MACH=m CONFIG_SND_SOC_INTEL_BYT_CHT_ES8316_MACH=m # CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH is not set -CONFIG_SND_SOC_INTEL_SKL_RT286_MACH=m -CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH=m -CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH=m -CONFIG_SND_SOC_INTEL_DA7219_MAX98357A_GENERIC=m -CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON=m -CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH=m -CONFIG_SND_SOC_INTEL_BXT_RT298_MACH=m CONFIG_SND_SOC_INTEL_SOF_WM8804_MACH=m -CONFIG_SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH=m -CONFIG_SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH=m -CONFIG_SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH=m -CONFIG_SND_SOC_INTEL_KBL_DA7219_MAX98927_MACH=m -CONFIG_SND_SOC_INTEL_KBL_RT5660_MACH=m CONFIG_SND_SOC_INTEL_GLK_DA7219_MAX98357A_MACH=m CONFIG_SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH=m CONFIG_SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH=m @@ -7448,7 +7931,62 @@ CONFIG_SND_SOC_INTEL_SOF_DA7219_MACH=m CONFIG_SND_SOC_INTEL_SOF_SSP_AMP_MACH=m CONFIG_SND_SOC_INTEL_EHL_RT5660_MACH=m CONFIG_SND_SOC_INTEL_SOUNDWIRE_SOF_MACH=m +# end of Intel + +# +# Mediatek +# CONFIG_SND_SOC_MTK_BTCVSD=m +# end of Mediatek + +# +# PXA +# +# end of PXA + +# +# SoundWire (SDCA) +# +CONFIG_SND_SOC_SDCA=m +CONFIG_SND_SOC_SDCA_HID=y +CONFIG_SND_SOC_SDCA_IRQ=y +CONFIG_SND_SOC_SDCA_OPTIONAL=m +# end of SoundWire (SDCA) + +# +# ST SPEAr +# +# end of ST SPEAr + +# +# Spreadtrum +# +# end of Spreadtrum + +# +# STMicroelectronics STM32 +# +# end of STMicroelectronics STM32 + +# +# Tegra +# +# end of Tegra + +# +# Xilinx +# +CONFIG_SND_SOC_XILINX_I2S=m +CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER=m +CONFIG_SND_SOC_XILINX_SPDIF=m +# end of Xilinx + +# +# Xtensa +# +CONFIG_SND_SOC_XTFPGA_I2S=m +# end of Xtensa + CONFIG_SND_SOC_SOF_TOPLEVEL=y CONFIG_SND_SOC_SOF_PCI_DEV=m CONFIG_SND_SOC_SOF_PCI=m @@ -7466,7 +8004,10 @@ CONFIG_SND_SOC_SOF_AMD_RENOIR=m CONFIG_SND_SOC_SOF_AMD_VANGOGH=m CONFIG_SND_SOC_SOF_AMD_REMBRANDT=m CONFIG_SND_SOC_SOF_ACP_PROBES=m +CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE=m +CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE=m CONFIG_SND_SOC_SOF_AMD_ACP63=m +CONFIG_SND_SOC_SOF_AMD_ACP70=m CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL=y CONFIG_SND_SOC_SOF_INTEL_HIFI_EP_IPC=m CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP=m @@ -7494,26 +8035,20 @@ CONFIG_SND_SOC_SOF_INTEL_MTL=m CONFIG_SND_SOC_SOF_METEORLAKE=m CONFIG_SND_SOC_SOF_INTEL_LNL=m CONFIG_SND_SOC_SOF_LUNARLAKE=m +CONFIG_SND_SOC_SOF_INTEL_PTL=m +CONFIG_SND_SOC_SOF_PANTHERLAKE=m CONFIG_SND_SOC_SOF_HDA_COMMON=m +CONFIG_SND_SOC_SOF_HDA_GENERIC=m CONFIG_SND_SOC_SOF_HDA_MLINK=m CONFIG_SND_SOC_SOF_HDA_LINK=y CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC=y +CONFIG_SND_SOF_SOF_HDA_SDW_BPT=m CONFIG_SND_SOC_SOF_HDA_LINK_BASELINE=m CONFIG_SND_SOC_SOF_HDA=m CONFIG_SND_SOC_SOF_HDA_PROBES=m CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE=m CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE=m CONFIG_SND_SOC_SOF_XTENSA=m - -# -# STMicroelectronics STM32 SOC audio support -# -# end of STMicroelectronics STM32 SOC audio support - -CONFIG_SND_SOC_XILINX_I2S=m -CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER=m -CONFIG_SND_SOC_XILINX_SPDIF=m -CONFIG_SND_SOC_XTFPGA_I2S=m CONFIG_SND_SOC_I2C_AND_SPI=m # @@ -7526,6 +8061,7 @@ CONFIG_SND_SOC_ADAU_UTILS=m CONFIG_SND_SOC_ADAU1372=m CONFIG_SND_SOC_ADAU1372_I2C=m CONFIG_SND_SOC_ADAU1372_SPI=m +CONFIG_SND_SOC_ADAU1373=m CONFIG_SND_SOC_ADAU1701=m CONFIG_SND_SOC_ADAU17X1=m CONFIG_SND_SOC_ADAU1761=m @@ -7541,6 +8077,7 @@ CONFIG_SND_SOC_AK4375=m CONFIG_SND_SOC_AK4458=m CONFIG_SND_SOC_AK4554=m CONFIG_SND_SOC_AK4613=m +CONFIG_SND_SOC_AK4619=m CONFIG_SND_SOC_AK4642=m CONFIG_SND_SOC_AK5386=m CONFIG_SND_SOC_AK5558=m @@ -7549,13 +8086,16 @@ CONFIG_SND_SOC_AUDIO_IIO_AUX=m CONFIG_SND_SOC_AW8738=m CONFIG_SND_SOC_AW88395_LIB=m CONFIG_SND_SOC_AW88395=m +CONFIG_SND_SOC_AW88166=m CONFIG_SND_SOC_AW88261=m +CONFIG_SND_SOC_AW88081=m CONFIG_SND_SOC_AW87390=m CONFIG_SND_SOC_AW88399=m CONFIG_SND_SOC_BD28623=m # CONFIG_SND_SOC_BT_SCO is not set CONFIG_SND_SOC_CHV3_CODEC=m CONFIG_SND_SOC_CROS_EC_CODEC=m +CONFIG_SND_SOC_CS_AMP_LIB=m CONFIG_SND_SOC_CS35L32=m CONFIG_SND_SOC_CS35L33=m CONFIG_SND_SOC_CS35L34=m @@ -7573,6 +8113,7 @@ CONFIG_SND_SOC_CS35L56_SHARED=m CONFIG_SND_SOC_CS35L56_I2C=m CONFIG_SND_SOC_CS35L56_SPI=m CONFIG_SND_SOC_CS35L56_SDW=m +CONFIG_SND_SOC_CS40L50=m CONFIG_SND_SOC_CS42L42_CORE=m CONFIG_SND_SOC_CS42L42=m CONFIG_SND_SOC_CS42L42_SDW=m @@ -7584,6 +8125,7 @@ CONFIG_SND_SOC_CS42L52=m CONFIG_SND_SOC_CS42L56=m CONFIG_SND_SOC_CS42L73=m CONFIG_SND_SOC_CS42L83=m +CONFIG_SND_SOC_CS42L84=m CONFIG_SND_SOC_CS4234=m CONFIG_SND_SOC_CS4265=m CONFIG_SND_SOC_CS4270=m @@ -7595,7 +8137,10 @@ CONFIG_SND_SOC_CS42XX8_I2C=m CONFIG_SND_SOC_CS43130=m CONFIG_SND_SOC_CS4341=m CONFIG_SND_SOC_CS4349=m +CONFIG_SND_SOC_CS48L32=m CONFIG_SND_SOC_CS53L30=m +CONFIG_SND_SOC_CS530X=m +CONFIG_SND_SOC_CS530X_I2C=m CONFIG_SND_SOC_CX2072X=m CONFIG_SND_SOC_DA7213=m CONFIG_SND_SOC_DA7219=m @@ -7604,18 +8149,22 @@ CONFIG_SND_SOC_HDMI_CODEC=m CONFIG_SND_SOC_ES7134=m CONFIG_SND_SOC_ES7241=m CONFIG_SND_SOC_ES83XX_DSM_COMMON=m +CONFIG_SND_SOC_ES8311=m CONFIG_SND_SOC_ES8316=m +CONFIG_SND_SOC_ES8323=m CONFIG_SND_SOC_ES8326=m CONFIG_SND_SOC_ES8328=m CONFIG_SND_SOC_ES8328_I2C=m CONFIG_SND_SOC_ES8328_SPI=m +CONFIG_SND_SOC_ES8375=m +CONFIG_SND_SOC_ES8389=m +CONFIG_SND_SOC_FS_AMP_LIB=m +CONFIG_SND_SOC_FS210X=m CONFIG_SND_SOC_GTM601=m -CONFIG_SND_SOC_HDAC_HDMI=m CONFIG_SND_SOC_HDAC_HDA=m CONFIG_SND_SOC_HDA=m CONFIG_SND_SOC_ICS43432=m CONFIG_SND_SOC_IDT821034=m -CONFIG_SND_SOC_INNO_RK3036=m CONFIG_SND_SOC_MAX98088=m CONFIG_SND_SOC_MAX98090=m CONFIG_SND_SOC_MAX98357A=m @@ -7633,6 +8182,7 @@ CONFIG_SND_SOC_MAX98396=m CONFIG_SND_SOC_MAX9860=m CONFIG_SND_SOC_MSM8916_WCD_DIGITAL=m CONFIG_SND_SOC_PCM1681=m +CONFIG_SND_SOC_PCM1754=m CONFIG_SND_SOC_PCM1789=m CONFIG_SND_SOC_PCM1789_I2C=m CONFIG_SND_SOC_PCM179X=m @@ -7651,9 +8201,12 @@ CONFIG_SND_SOC_PCM5102A=m CONFIG_SND_SOC_PCM512x=m CONFIG_SND_SOC_PCM512x_I2C=m CONFIG_SND_SOC_PCM512x_SPI=m +CONFIG_SND_SOC_PCM6240=m CONFIG_SND_SOC_PEB2466=m -CONFIG_SND_SOC_RK3328=m +CONFIG_SND_SOC_PM4125=m +CONFIG_SND_SOC_PM4125_SDW=m CONFIG_SND_SOC_RL6231=m +CONFIG_SND_SOC_RT_SDW_COMMON=m CONFIG_SND_SOC_RL6347A=m CONFIG_SND_SOC_RT274=m CONFIG_SND_SOC_RT286=m @@ -7667,8 +8220,8 @@ CONFIG_SND_SOC_RT1308=m CONFIG_SND_SOC_RT1308_SDW=m CONFIG_SND_SOC_RT1316_SDW=m CONFIG_SND_SOC_RT1318_SDW=m +CONFIG_SND_SOC_RT1320_SDW=m CONFIG_SND_SOC_RT5514=m -CONFIG_SND_SOC_RT5514_SPI=m CONFIG_SND_SOC_RT5616=m CONFIG_SND_SOC_RT5631=m CONFIG_SND_SOC_RT5640=m @@ -7691,11 +8244,15 @@ CONFIG_SND_SOC_RT711_SDW=m CONFIG_SND_SOC_RT711_SDCA_SDW=m CONFIG_SND_SOC_RT712_SDCA_SDW=m CONFIG_SND_SOC_RT712_SDCA_DMIC_SDW=m +CONFIG_SND_SOC_RT721_SDCA_SDW=m CONFIG_SND_SOC_RT722_SDCA_SDW=m CONFIG_SND_SOC_RT715=m CONFIG_SND_SOC_RT715_SDW=m CONFIG_SND_SOC_RT715_SDCA_SDW=m CONFIG_SND_SOC_RT9120=m +CONFIG_SND_SOC_RT9123=m +CONFIG_SND_SOC_RT9123P=m +CONFIG_SND_SOC_RTQ9124=m CONFIG_SND_SOC_RTQ9128=m # CONFIG_SND_SOC_SDW_MOCKUP is not set CONFIG_SND_SOC_SGTL5000=m @@ -7706,6 +8263,7 @@ CONFIG_SND_SOC_SIGMADSP_REGMAP=m CONFIG_SND_SOC_SIMPLE_AMPLIFIER=m CONFIG_SND_SOC_SIMPLE_MUX=m CONFIG_SND_SOC_SMA1303=m +CONFIG_SND_SOC_SMA1307=m CONFIG_SND_SOC_SPDIF=m CONFIG_SND_SOC_SRC4XXX_I2C=m CONFIG_SND_SOC_SRC4XXX=m @@ -7724,8 +8282,10 @@ CONFIG_SND_SOC_TAS2764=m CONFIG_SND_SOC_TAS2770=m CONFIG_SND_SOC_TAS2780=m CONFIG_SND_SOC_TAS2781_COMLIB=m +CONFIG_SND_SOC_TAS2781_COMLIB_I2C=m CONFIG_SND_SOC_TAS2781_FMWLIB=m CONFIG_SND_SOC_TAS2781_I2C=m +CONFIG_SND_SOC_TAS2783_SDW=m CONFIG_SND_SOC_TAS5086=m CONFIG_SND_SOC_TAS571X=m CONFIG_SND_SOC_TAS5720=m @@ -7750,12 +8310,18 @@ CONFIG_SND_SOC_TS3A227E=m CONFIG_SND_SOC_TSCS42XX=m CONFIG_SND_SOC_TSCS454=m CONFIG_SND_SOC_UDA1334=m +CONFIG_SND_SOC_UDA1342=m CONFIG_SND_SOC_WCD_CLASSH=m +CONFIG_SND_SOC_WCD_COMMON=m CONFIG_SND_SOC_WCD9335=m CONFIG_SND_SOC_WCD_MBHC=m CONFIG_SND_SOC_WCD934X=m +CONFIG_SND_SOC_WCD937X=m +CONFIG_SND_SOC_WCD937X_SDW=m CONFIG_SND_SOC_WCD938X=m CONFIG_SND_SOC_WCD938X_SDW=m +CONFIG_SND_SOC_WCD939X=m +CONFIG_SND_SOC_WCD939X_SDW=m CONFIG_SND_SOC_WM5102=m CONFIG_SND_SOC_WM8510=m CONFIG_SND_SOC_WM8523=m @@ -7785,21 +8351,27 @@ CONFIG_SND_SOC_WM8962=m CONFIG_SND_SOC_WM8974=m CONFIG_SND_SOC_WM8978=m CONFIG_SND_SOC_WM8985=m +CONFIG_SND_SOC_WM8998=m CONFIG_SND_SOC_WSA881X=m CONFIG_SND_SOC_WSA883X=m CONFIG_SND_SOC_WSA884X=m CONFIG_SND_SOC_ZL38060=m CONFIG_SND_SOC_MAX9759=m CONFIG_SND_SOC_MT6351=m +CONFIG_SND_SOC_MT6357=m CONFIG_SND_SOC_MT6358=m CONFIG_SND_SOC_MT6660=m CONFIG_SND_SOC_NAU8315=m +CONFIG_SND_SOC_NAU8325=m CONFIG_SND_SOC_NAU8540=m CONFIG_SND_SOC_NAU8810=m CONFIG_SND_SOC_NAU8821=m CONFIG_SND_SOC_NAU8822=m CONFIG_SND_SOC_NAU8824=m CONFIG_SND_SOC_NAU8825=m +CONFIG_SND_SOC_NTPFW=m +CONFIG_SND_SOC_NTP8918=m +CONFIG_SND_SOC_NTP8835=m CONFIG_SND_SOC_TPA6130A2=m CONFIG_SND_SOC_LPASS_MACRO_COMMON=m CONFIG_SND_SOC_LPASS_WSA_MACRO=m @@ -7808,8 +8380,15 @@ CONFIG_SND_SOC_LPASS_RX_MACRO=m CONFIG_SND_SOC_LPASS_TX_MACRO=m # end of CODEC drivers +CONFIG_SND_SOC_SDW_UTILS=m + +# +# Generic drivers +# CONFIG_SND_SIMPLE_CARD_UTILS=m CONFIG_SND_SIMPLE_CARD=m +# end of Generic drivers + CONFIG_SND_X86=y CONFIG_HDMI_LPE_AUDIO=m CONFIG_SND_SYNTH_EMUX=m @@ -7821,7 +8400,8 @@ CONFIG_HID=y CONFIG_HID_BATTERY_STRENGTH=y CONFIG_HIDRAW=y CONFIG_UHID=m -CONFIG_HID_GENERIC=m +CONFIG_HID_GENERIC=y +CONFIG_HID_HAPTIC=y # # Special HID drivers @@ -7832,6 +8412,8 @@ CONFIG_HID_ACRUX=m CONFIG_HID_ACRUX_FF=y CONFIG_HID_APPLE=m CONFIG_HID_APPLEIR=m +CONFIG_HID_APPLETB_BL=m +CONFIG_HID_APPLETB_KBD=m CONFIG_HID_ASUS=m CONFIG_HID_AUREAL=m CONFIG_HID_BELKIN=m @@ -7862,12 +8444,14 @@ CONFIG_HID_GLORIOUS=m CONFIG_HID_HOLTEK=m CONFIG_HOLTEK_FF=y CONFIG_HID_VIVALDI_COMMON=m +CONFIG_HID_GOODIX_SPI=m CONFIG_HID_GOOGLE_HAMMER=m CONFIG_HID_GOOGLE_STADIA_FF=m CONFIG_HID_VIVALDI=m CONFIG_HID_GT683R=m CONFIG_HID_KEYTOUCH=m CONFIG_HID_KYE=m +CONFIG_HID_KYSONA=m CONFIG_HID_UCLOGIC=m CONFIG_HID_WALTOP=m CONFIG_HID_VIEWSONIC=m @@ -7948,8 +8532,10 @@ CONFIG_HID_THRUSTMASTER=m CONFIG_THRUSTMASTER_FF=y CONFIG_HID_UDRAW_PS3=m CONFIG_HID_U2FZERO=m +CONFIG_HID_UNIVERSAL_PIDFF=m CONFIG_HID_WACOM=m CONFIG_HID_WIIMOTE=m +CONFIG_HID_WINWING=m CONFIG_HID_XINMO=m CONFIG_HID_ZEROPLUS=m CONFIG_ZEROPLUS_FF=y @@ -7967,14 +8553,6 @@ CONFIG_HID_MCP2221=m CONFIG_HID_BPF=y # end of HID-BPF support -# -# USB HID support -# -CONFIG_USB_HID=m -CONFIG_HID_PID=y -CONFIG_USB_HIDDEV=y -# end of USB HID support - CONFIG_I2C_HID=m CONFIG_I2C_HID_ACPI=m CONFIG_I2C_HID_OF=m @@ -8001,6 +8579,23 @@ CONFIG_SURFACE_KBD=m # end of Surface System Aggregator Module HID support CONFIG_SURFACE_HID_CORE=m + +# +# Intel THC HID Support +# +CONFIG_INTEL_THC_HID=m +CONFIG_INTEL_QUICKSPI=m +CONFIG_INTEL_QUICKI2C=m +# end of Intel THC HID Support + +# +# USB HID support +# +CONFIG_USB_HID=y +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +# end of USB HID support + CONFIG_USB_OHCI_LITTLE_ENDIAN=y CONFIG_USB_SUPPORT=y CONFIG_USB_COMMON=y @@ -8023,6 +8618,7 @@ CONFIG_USB_DEFAULT_PERSIST=y # CONFIG_USB_OTG_PRODUCTLIST is not set CONFIG_USB_LEDS_TRIGGER_USBPORT=m CONFIG_USB_AUTOSUSPEND_DELAY=2 +CONFIG_USB_DEFAULT_AUTHORIZATION_MODE=1 CONFIG_USB_MON=m # @@ -8030,10 +8626,11 @@ CONFIG_USB_MON=m # CONFIG_USB_C67X00_HCD=m CONFIG_USB_XHCI_HCD=y -# CONFIG_USB_XHCI_DBGCAP is not set -CONFIG_USB_XHCI_PCI=m +CONFIG_USB_XHCI_DBGCAP=y +CONFIG_USB_XHCI_PCI=y CONFIG_USB_XHCI_PCI_RENESAS=m CONFIG_USB_XHCI_PLATFORM=m +CONFIG_USB_XHCI_SIDEBAND=y CONFIG_USB_EHCI_HCD=y CONFIG_USB_EHCI_ROOT_HUB_TT=y CONFIG_USB_EHCI_TT_NEWSCHED=y @@ -8065,11 +8662,7 @@ CONFIG_USB_WDM=m CONFIG_USB_TMC=m # -# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may -# - -# -# also be needed; see USB_STORAGE Help for more info +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; see USB_STORAGE Help for more info # CONFIG_USB_STORAGE=m # CONFIG_USB_STORAGE_DEBUG is not set @@ -8240,6 +8833,7 @@ CONFIG_USB_IDMOUSE=m CONFIG_USB_APPLEDISPLAY=m CONFIG_APPLE_MFI_FASTCHARGE=m CONFIG_USB_LJCA=m +CONFIG_USB_USBIO=m CONFIG_USB_SISUSBVGA=m CONFIG_USB_LD=m CONFIG_USB_TRANCEVIBRATOR=m @@ -8285,14 +8879,10 @@ CONFIG_U_SERIAL_CONSOLE=y CONFIG_USB_GR_UDC=m CONFIG_USB_R8A66597=m CONFIG_USB_PXA27X=m -CONFIG_USB_MV_UDC=m -CONFIG_USB_MV_U3D=m CONFIG_USB_SNP_CORE=m CONFIG_USB_M66592=m CONFIG_USB_BDC_UDC=m CONFIG_USB_AMD5536UDC=m -CONFIG_USB_NET2272=m -# CONFIG_USB_NET2272_DMA is not set CONFIG_USB_NET2280=m CONFIG_USB_GOKU=m CONFIG_USB_EG20T=m @@ -8396,6 +8986,7 @@ CONFIG_TYPEC_UCSI=m CONFIG_UCSI_CCG=m CONFIG_UCSI_ACPI=m CONFIG_UCSI_STM32G0=m +CONFIG_CROS_EC_UCSI=m CONFIG_TYPEC_TPS6598X=m CONFIG_TYPEC_ANX7411=m CONFIG_TYPEC_RT1719=m @@ -8410,8 +9001,11 @@ CONFIG_TYPEC_MUX_FSA4480=m CONFIG_TYPEC_MUX_GPIO_SBU=m CONFIG_TYPEC_MUX_PI3USB30532=m CONFIG_TYPEC_MUX_INTEL_PMC=m +CONFIG_TYPEC_MUX_IT5205=m CONFIG_TYPEC_MUX_NB7VPQ904M=m +CONFIG_TYPEC_MUX_PS883X=m CONFIG_TYPEC_MUX_PTN36502=m +CONFIG_TYPEC_MUX_TUSB1046=m CONFIG_TYPEC_MUX_WCD939X_USBSS=m # end of USB Type-C Multiplexer/DeMultiplexer Switch support @@ -8420,6 +9014,7 @@ CONFIG_TYPEC_MUX_WCD939X_USBSS=m # CONFIG_TYPEC_DP_ALTMODE=m CONFIG_TYPEC_NVIDIA_ALTMODE=m +CONFIG_TYPEC_TBT_ALTMODE=m # end of USB Type-C Alternate Mode drivers CONFIG_USB_ROLE_SWITCH=m @@ -8437,6 +9032,7 @@ CONFIG_MMC_CRYPTO=y # CONFIG_MMC_DEBUG is not set CONFIG_MMC_SDHCI=m CONFIG_MMC_SDHCI_IO_ACCESSORS=y +CONFIG_MMC_SDHCI_UHS2=m CONFIG_MMC_SDHCI_PCI=m CONFIG_MMC_RICOH_MMC=y CONFIG_MMC_SDHCI_ACPI=m @@ -8483,8 +9079,8 @@ CONFIG_MS_BLOCK=m CONFIG_MEMSTICK_TIFM_MS=m CONFIG_MEMSTICK_JMICRON_38X=m CONFIG_MEMSTICK_R592=m -CONFIG_MEMSTICK_REALTEK_PCI=m CONFIG_MEMSTICK_REALTEK_USB=m +CONFIG_LEDS_EXPRESSWIRE=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_CLASS_FLASH=m @@ -8498,6 +9094,7 @@ CONFIG_LEDS_88PM860X=m CONFIG_LEDS_APU=m CONFIG_LEDS_AW200XX=m CONFIG_LEDS_CHT_WCOVE=m +CONFIG_LEDS_CROS_EC=m CONFIG_LEDS_LM3530=m CONFIG_LEDS_LM3532=m CONFIG_LEDS_LM3533=m @@ -8514,6 +9111,7 @@ CONFIG_LEDS_PCA955X=m CONFIG_LEDS_PCA955X_GPIO=y CONFIG_LEDS_PCA963X=m CONFIG_LEDS_PCA995X=m +CONFIG_LEDS_QNAP_MCU=m CONFIG_LEDS_WM831X_STATUS=m CONFIG_LEDS_WM8350=m CONFIG_LEDS_DA903X=m @@ -8529,19 +9127,23 @@ CONFIG_LEDS_ADP5520=m CONFIG_LEDS_MC13783=m CONFIG_LEDS_TCA6507=m CONFIG_LEDS_TLC591XX=m +CONFIG_LEDS_MAX77705=m CONFIG_LEDS_MAX8997=m CONFIG_LEDS_LM355x=m CONFIG_LEDS_MENF21BMC=m CONFIG_LEDS_IS31FL319X=m +CONFIG_LEDS_UPBOARD=m # # LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM) # CONFIG_LEDS_BLINKM=m +CONFIG_LEDS_BLINKM_MULTICOLOR=y CONFIG_LEDS_MLXCPLD=m CONFIG_LEDS_MLXREG=m CONFIG_LEDS_USER=m CONFIG_LEDS_NIC78BX=m +CONFIG_LEDS_SPI_BYTE=m CONFIG_LEDS_TI_LMU_COMMON=m CONFIG_LEDS_LM36274=m CONFIG_LEDS_TPS6105X=m @@ -8558,6 +9160,7 @@ CONFIG_LEDS_SGM3140=m # # RGB LED drivers # +CONFIG_LEDS_KTD202X=m CONFIG_LEDS_PWM_MULTICOLOR=m CONFIG_LEDS_MT6370_RGB=m @@ -8584,11 +9187,11 @@ CONFIG_LEDS_TRIGGER_CAMERA=m CONFIG_LEDS_TRIGGER_PANIC=y CONFIG_LEDS_TRIGGER_NETDEV=m CONFIG_LEDS_TRIGGER_PATTERN=m -CONFIG_LEDS_TRIGGER_AUDIO=m CONFIG_LEDS_TRIGGER_TTY=m +CONFIG_LEDS_TRIGGER_INPUT_EVENTS=m # -# Simple LED drivers +# Simatic LED drivers # CONFIG_LEDS_SIEMENS_SIMATIC_IPC=m CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE=m @@ -8629,6 +9232,7 @@ CONFIG_INFINIBAND_ERDMA=m CONFIG_INFINIBAND_HFI1=m # CONFIG_HFI1_DEBUG_SDMA_ORDER is not set # CONFIG_SDMA_VERBOSITY is not set +CONFIG_INFINIBAND_IONIC=m CONFIG_INFINIBAND_IRDMA=m CONFIG_MANA_INFINIBAND=m CONFIG_MLX4_INFINIBAND=m @@ -8637,8 +9241,6 @@ CONFIG_INFINIBAND_MTHCA=m CONFIG_INFINIBAND_MTHCA_DEBUG=y CONFIG_INFINIBAND_OCRDMA=m CONFIG_INFINIBAND_QEDR=m -CONFIG_INFINIBAND_QIB=m -CONFIG_INFINIBAND_QIB_DCA=y CONFIG_INFINIBAND_USNIC=m CONFIG_INFINIBAND_VMWARE_PVRDMA=m CONFIG_INFINIBAND_RDMAVT=m @@ -8663,6 +9265,9 @@ CONFIG_EDAC_LEGACY_SYSFS=y # CONFIG_EDAC_DEBUG is not set CONFIG_EDAC_DECODE_MCE=y CONFIG_EDAC_GHES=y +CONFIG_EDAC_SCRUB=y +CONFIG_EDAC_ECS=y +CONFIG_EDAC_MEM_REPAIR=y CONFIG_EDAC_AMD64=m CONFIG_EDAC_E752X=m CONFIG_EDAC_I82975X=m @@ -8718,12 +9323,12 @@ CONFIG_RTC_DRV_MAX8925=m CONFIG_RTC_DRV_MAX8998=m CONFIG_RTC_DRV_MAX8997=m CONFIG_RTC_DRV_MAX31335=m +CONFIG_RTC_DRV_NCT6694=m CONFIG_RTC_DRV_RS5C372=m CONFIG_RTC_DRV_ISL1208=m CONFIG_RTC_DRV_ISL12022=m CONFIG_RTC_DRV_X1205=m CONFIG_RTC_DRV_PCF8523=m -CONFIG_RTC_DRV_PCF85063=m CONFIG_RTC_DRV_PCF85363=m CONFIG_RTC_DRV_PCF8563=m CONFIG_RTC_DRV_PCF8583=m @@ -8738,12 +9343,14 @@ CONFIG_RTC_DRV_RC5T583=m CONFIG_RTC_DRV_S35390A=m CONFIG_RTC_DRV_FM3130=m CONFIG_RTC_DRV_RX8010=m +CONFIG_RTC_DRV_RX8111=m CONFIG_RTC_DRV_RX8581=m CONFIG_RTC_DRV_RX8025=m CONFIG_RTC_DRV_EM3027=m CONFIG_RTC_DRV_RV3028=m CONFIG_RTC_DRV_RV3032=m CONFIG_RTC_DRV_RV8803=m +CONFIG_RTC_DRV_SD2405AL=m CONFIG_RTC_DRV_SD3078=m # @@ -8771,6 +9378,7 @@ CONFIG_RTC_I2C_AND_SPI=y CONFIG_RTC_DRV_DS3232=m CONFIG_RTC_DRV_DS3232_HWMON=y CONFIG_RTC_DRV_PCF2127=m +CONFIG_RTC_DRV_PCF85063=m CONFIG_RTC_DRV_RV3029C2=m CONFIG_RTC_DRV_RV3029_HWMON=y CONFIG_RTC_DRV_RX6110=m @@ -8801,7 +9409,6 @@ CONFIG_RTC_DRV_MSM6242=m CONFIG_RTC_DRV_RP5C01=m CONFIG_RTC_DRV_WM831X=m CONFIG_RTC_DRV_WM8350=m -CONFIG_RTC_DRV_PCF50633=m CONFIG_RTC_DRV_CROS_EC=m # @@ -8811,13 +9418,13 @@ CONFIG_RTC_DRV_FTRTC010=m CONFIG_RTC_DRV_PCAP=m CONFIG_RTC_DRV_MC13XXX=m CONFIG_RTC_DRV_MT6397=m +CONFIG_RTC_DRV_GOLDFISH=m +CONFIG_RTC_DRV_WILCO_EC=m # # HID Sensor RTC drivers # CONFIG_RTC_DRV_HID_SENSOR_TIME=m -CONFIG_RTC_DRV_GOLDFISH=m -CONFIG_RTC_DRV_WILCO_EC=m CONFIG_DMADEVICES=y # CONFIG_DMADEVICES_DEBUG is not set @@ -8838,7 +9445,9 @@ CONFIG_INTEL_IOATDMA=m CONFIG_PLX_DMA=m CONFIG_XILINX_DMA=m CONFIG_XILINX_XDMA=m +CONFIG_AMD_AE4DMA=m CONFIG_AMD_PTDMA=m +CONFIG_AMD_QDMA=m CONFIG_QCOM_HIDMA_MGMT=m CONFIG_QCOM_HIDMA=m CONFIG_DW_DMAC_CORE=y @@ -8870,6 +9479,7 @@ CONFIG_DMABUF_HEAPS=y CONFIG_DMABUF_SYSFS_STATS=y CONFIG_DMABUF_HEAPS_SYSTEM=y CONFIG_DMABUF_HEAPS_CMA=y +CONFIG_DMABUF_HEAPS_CMA_LEGACY=y # end of DMABUF options CONFIG_DCA=m @@ -8881,7 +9491,6 @@ CONFIG_UIO_AEC=m CONFIG_UIO_SERCOS3=m CONFIG_UIO_PCI_GENERIC=m CONFIG_UIO_NETX=m -CONFIG_UIO_PRUSS=m CONFIG_UIO_MF624=m CONFIG_UIO_HV_GENERIC=m CONFIG_UIO_DFL=m @@ -8898,7 +9507,6 @@ CONFIG_VFIO_DEBUGFS=y # VFIO support for PCI devices # CONFIG_VFIO_PCI_CORE=m -CONFIG_VFIO_PCI_MMAP=y CONFIG_VFIO_PCI_INTX=y CONFIG_VFIO_PCI=m CONFIG_VFIO_PCI_VGA=y @@ -8906,6 +9514,8 @@ CONFIG_VFIO_PCI_IGD=y CONFIG_MLX5_VFIO_PCI=m CONFIG_PDS_VFIO_PCI=m CONFIG_VIRTIO_VFIO_PCI=m +CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY=y +CONFIG_QAT_VFIO_PCI=m # end of VFIO support for PCI devices CONFIG_VFIO_MDEV=m @@ -8915,16 +9525,18 @@ CONFIG_VMGENID=y CONFIG_VBOXGUEST=m CONFIG_NITRO_ENCLAVES=m CONFIG_ACRN_HSM=m -CONFIG_TSM_REPORTS=m CONFIG_EFI_SECRET=m CONFIG_SEV_GUEST=m CONFIG_TDX_GUEST_DRIVER=m +CONFIG_TSM_GUEST=y +CONFIG_TSM_REPORTS=m +CONFIG_TSM_MEASUREMENTS=y CONFIG_VIRTIO_ANCHOR=y CONFIG_VIRTIO=y -CONFIG_VIRTIO_PCI_LIB=m -CONFIG_VIRTIO_PCI_LIB_LEGACY=m +CONFIG_VIRTIO_PCI_LIB=y +CONFIG_VIRTIO_PCI_LIB_LEGACY=y CONFIG_VIRTIO_MENU=y -CONFIG_VIRTIO_PCI=m +CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO_PCI_ADMIN_LEGACY=y CONFIG_VIRTIO_PCI_LEGACY=y CONFIG_VIRTIO_VDPA=m @@ -8935,6 +9547,10 @@ CONFIG_VIRTIO_INPUT=m CONFIG_VIRTIO_MMIO=m CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y CONFIG_VIRTIO_DMA_SHARED_BUFFER=m +CONFIG_VIRTIO_DEBUG=y +CONFIG_VIRTIO_RTC=m +CONFIG_VIRTIO_RTC_PTP=y +CONFIG_VIRTIO_RTC_CLASS=y CONFIG_VDPA=m CONFIG_VDPA_SIM=m CONFIG_VDPA_SIM_NET=m @@ -8948,6 +9564,7 @@ CONFIG_VP_VDPA=m CONFIG_ALIBABA_ENI_VDPA=m CONFIG_SNET_VDPA=m CONFIG_PDS_VDPA=m +CONFIG_OCTEONEP_VDPA=m CONFIG_VHOST_IOTLB=m CONFIG_VHOST_RING=m CONFIG_VHOST_TASK=y @@ -8958,15 +9575,18 @@ CONFIG_VHOST_SCSI=m CONFIG_VHOST_VSOCK=m CONFIG_VHOST_VDPA=m # CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set +CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL=y # # Microsoft Hyper-V guest support # -CONFIG_HYPERV=m +CONFIG_HYPERV=y # CONFIG_HYPERV_VTL_MODE is not set CONFIG_HYPERV_TIMER=y CONFIG_HYPERV_UTILS=m CONFIG_HYPERV_BALLOON=m +CONFIG_HYPERV_VMBUS=m +CONFIG_MSHV_ROOT=m # end of Microsoft Hyper-V guest support # @@ -9012,17 +9632,7 @@ CONFIG_XEN_VIRTIO=y # CONFIG_GREYBUS is not set # CONFIG_COMEDI is not set CONFIG_STAGING=y -CONFIG_PRISM2_USB=m -CONFIG_RTLLIB=m -CONFIG_RTLLIB_CRYPTO_CCMP=m -CONFIG_RTLLIB_CRYPTO_TKIP=m -CONFIG_RTLLIB_CRYPTO_WEP=m -CONFIG_RTL8192E=m CONFIG_RTL8723BS=m -CONFIG_R8712U=m -CONFIG_RTS5208=m -CONFIG_VT6655=m -CONFIG_VT6656=m # # IIO staging drivers @@ -9032,7 +9642,6 @@ CONFIG_VT6656=m # Accelerometers # CONFIG_ADIS16203=m -CONFIG_ADIS16240=m # end of Accelerometers # @@ -9069,24 +9678,33 @@ CONFIG_STAGING_MEDIA=y CONFIG_DVB_AV7110_IR=y CONFIG_DVB_AV7110=m CONFIG_DVB_AV7110_OSD=y -CONFIG_DVB_BUDGET_PATCH=m CONFIG_DVB_SP8870=m CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_INTEL_IPU7=m # # StarFive media platform drivers # # CONFIG_STAGING_MEDIA_DEPRECATED is not set -CONFIG_LTE_GDM724X=m # CONFIG_FB_TFT is not set CONFIG_MOST_COMPONENTS=m CONFIG_MOST_NET=m CONFIG_MOST_VIDEO=m -CONFIG_MOST_I2C=m -CONFIG_KS7010=m -CONFIG_PI433=m -CONFIG_FIELDBUS_DEV=m # CONFIG_VME_BUS is not set +CONFIG_GPIB=m +CONFIG_GPIB_COMMON=m +CONFIG_GPIB_AGILENT_82350B=m +CONFIG_GPIB_AGILENT_82357A=m +CONFIG_GPIB_CEC_PCI=m +CONFIG_GPIB_NI_PCI_ISA=m +CONFIG_GPIB_CB7210=m +CONFIG_GPIB_NI_USB=m +CONFIG_GPIB_INES=m +CONFIG_GPIB_PCMCIA=y +CONFIG_GPIB_LPVO=m +CONFIG_GPIB_TMS9914=m +CONFIG_GPIB_NEC7210=m +# CONFIG_GOLDFISH is not set CONFIG_CHROME_PLATFORMS=y CONFIG_CHROMEOS_ACPI=m CONFIG_CHROMEOS_LAPTOP=m @@ -9098,13 +9716,14 @@ CONFIG_CROS_EC_ISHTP=m CONFIG_CROS_EC_SPI=m CONFIG_CROS_EC_UART=m CONFIG_CROS_EC_LPC=m -CONFIG_CROS_EC_PROTO=y +CONFIG_CROS_EC_PROTO=m CONFIG_CROS_KBD_LED_BACKLIGHT=m CONFIG_CROS_EC_CHARDEV=m CONFIG_CROS_EC_LIGHTBAR=m CONFIG_CROS_EC_DEBUGFS=m CONFIG_CROS_EC_SENSORHUB=m CONFIG_CROS_EC_SYSFS=m +CONFIG_CROS_EC_TYPEC_ALTMODES=y CONFIG_CROS_EC_TYPEC=m CONFIG_CROS_HPS_I2C=m CONFIG_CROS_USBPD_LOGGER=m @@ -9116,6 +9735,8 @@ CONFIG_WILCO_EC_DEBUGFS=m CONFIG_WILCO_EC_EVENTS=m CONFIG_WILCO_EC_TELEMETRY=m CONFIG_MELLANOX_PLATFORM=y +CONFIG_MLX_PLATFORM=m +CONFIG_MLXREG_DPU=m CONFIG_MLXREG_HOTPLUG=m CONFIG_MLXREG_IO=m CONFIG_MLXREG_LC=m @@ -9138,21 +9759,34 @@ CONFIG_SURFACE_AGGREGATOR_BUS=y # CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION is not set CONFIG_X86_PLATFORM_DEVICES=y CONFIG_ACPI_WMI=m +# CONFIG_ACPI_WMI_LEGACY_DEVICE_NAMES is not set CONFIG_WMI_BMOF=m CONFIG_HUAWEI_WMI=m CONFIG_MXM_WMI=m CONFIG_NVIDIA_WMI_EC_BACKLIGHT=m CONFIG_XIAOMI_WMI=m +CONFIG_REDMI_WMI=m CONFIG_GIGABYTE_WMI=m -CONFIG_YOGABOOK=m CONFIG_ACERHDF=m CONFIG_ACER_WIRELESS=m CONFIG_ACER_WMI=m +CONFIG_AMD_HSMP=m + +# +# AMD HSMP Driver +# +CONFIG_AMD_HSMP_ACPI=m +CONFIG_AMD_HSMP_PLAT=m +# end of AMD HSMP Driver + CONFIG_AMD_PMF=m # CONFIG_AMD_PMF_DEBUG is not set CONFIG_AMD_PMC=m -CONFIG_AMD_HSMP=m +CONFIG_AMD_MP2_STB=y +CONFIG_AMD_HFI=y +CONFIG_AMD_3D_VCACHE=m CONFIG_AMD_WBRF=y +CONFIG_AMD_ISP_PLATFORM=m CONFIG_ADV_SWBUTTON=m CONFIG_APPLE_GMUX=m CONFIG_ASUS_LAPTOP=m @@ -9165,14 +9799,18 @@ CONFIG_EEEPC_LAPTOP=m CONFIG_EEEPC_WMI=m CONFIG_X86_PLATFORM_DRIVERS_DELL=y CONFIG_ALIENWARE_WMI=m +CONFIG_ALIENWARE_WMI_LEGACY=y +CONFIG_ALIENWARE_WMI_WMAX=y CONFIG_DCDBAS=m CONFIG_DELL_LAPTOP=m # CONFIG_DELL_RBU is not set CONFIG_DELL_RBTN=m +CONFIG_DELL_PC=m CONFIG_DELL_SMBIOS=m CONFIG_DELL_SMBIOS_WMI=y CONFIG_DELL_SMBIOS_SMM=y CONFIG_DELL_SMO8800=m +CONFIG_DELL_UART_BACKLIGHT=m CONFIG_DELL_WMI=m CONFIG_DELL_WMI_PRIVACY=y CONFIG_DELL_WMI_AIO=m @@ -9190,17 +9828,7 @@ CONFIG_HP_WMI=m CONFIG_HP_BIOSCFG=m CONFIG_WIRELESS_HOTKEY=m CONFIG_IBM_RTL=m -CONFIG_IDEAPAD_LAPTOP=m -CONFIG_LENOVO_YMC=m CONFIG_SENSORS_HDAPS=m -CONFIG_THINKPAD_ACPI=m -CONFIG_THINKPAD_ACPI_ALSA_SUPPORT=y -# CONFIG_THINKPAD_ACPI_DEBUGFACILITIES is not set -# CONFIG_THINKPAD_ACPI_DEBUG is not set -# CONFIG_THINKPAD_ACPI_UNSAFE_LEDS is not set -CONFIG_THINKPAD_ACPI_VIDEO=y -CONFIG_THINKPAD_ACPI_HOTKEY_POLL=y -CONFIG_THINKPAD_LMI=m CONFIG_INTEL_ATOMISP2_PDX86=y CONFIG_INTEL_ATOMISP2_LED=m CONFIG_INTEL_ATOMISP2_PM=m @@ -9208,9 +9836,11 @@ CONFIG_INTEL_IFS=m CONFIG_INTEL_SAR_INT1092=m CONFIG_INTEL_SKL_INT3472=m CONFIG_INTEL_PMC_CORE=m +CONFIG_INTEL_PMC_SSRAM_TELEMETRY=m CONFIG_INTEL_PMT_CLASS=m CONFIG_INTEL_PMT_TELEMETRY=m CONFIG_INTEL_PMT_CRASHLOG=m +CONFIG_INTEL_PMT_DISCOVERY=m # # Intel Speed Select Technology interface support @@ -9245,14 +9875,40 @@ CONFIG_INTEL_PUNIT_IPC=m CONFIG_INTEL_RST=m CONFIG_INTEL_SDSI=m CONFIG_INTEL_SMARTCONNECT=m +CONFIG_INTEL_TPMI_POWER_DOMAINS=m CONFIG_INTEL_TPMI=m +CONFIG_INTEL_PLR_TPMI=m CONFIG_INTEL_TURBO_MAX_3=y CONFIG_INTEL_VSEC=m +CONFIG_IDEAPAD_LAPTOP=m +CONFIG_LENOVO_WMI_HOTKEY_UTILITIES=m +CONFIG_LENOVO_WMI_CAMERA=m +CONFIG_LENOVO_YMC=m +CONFIG_THINKPAD_ACPI=m +CONFIG_THINKPAD_ACPI_ALSA_SUPPORT=y +# CONFIG_THINKPAD_ACPI_DEBUGFACILITIES is not set +# CONFIG_THINKPAD_ACPI_DEBUG is not set +# CONFIG_THINKPAD_ACPI_UNSAFE_LEDS is not set +CONFIG_THINKPAD_ACPI_VIDEO=y +CONFIG_THINKPAD_ACPI_HOTKEY_POLL=y +CONFIG_THINKPAD_LMI=m +CONFIG_YOGABOOK=m +CONFIG_YT2_1380=m +CONFIG_LENOVO_WMI_DATA01=m +CONFIG_LENOVO_WMI_EVENTS=m +CONFIG_LENOVO_WMI_HELPERS=m +CONFIG_LENOVO_WMI_GAMEZONE=m +CONFIG_LENOVO_WMI_TUNING=m +CONFIG_ACPI_QUICKSTART=m +CONFIG_MEEGOPAD_ANX7428=m CONFIG_MSI_EC=m CONFIG_MSI_LAPTOP=m CONFIG_MSI_WMI=m +CONFIG_MSI_WMI_PLATFORM=m CONFIG_PCENGINES_APU2=m +CONFIG_PORTWELL_EC=m CONFIG_BARCO_P50_GPIO=m +CONFIG_SAMSUNG_GALAXYBOOK=m CONFIG_SAMSUNG_LAPTOP=m CONFIG_SAMSUNG_Q10=m CONFIG_ACPI_TOSHIBA=m @@ -9268,9 +9924,9 @@ CONFIG_SONYPI_COMPAT=y CONFIG_SYSTEM76_ACPI=m CONFIG_TOPSTAR_LAPTOP=m CONFIG_SERIAL_MULTI_INSTANTIATE=m -CONFIG_MLX_PLATFORM=m CONFIG_TOUCHSCREEN_DMI=y CONFIG_INSPUR_PLATFORM_PROFILE=m +CONFIG_DASHARO_ACPI=m CONFIG_X86_ANDROID_TABLETS=m CONFIG_FW_ATTR_CLASS=m CONFIG_INTEL_IPS=m @@ -9287,6 +9943,8 @@ CONFIG_SIEMENS_SIMATIC_IPC_BATT_F7188X=m CONFIG_SILICOM_PLATFORM=m CONFIG_WINMATE_FM07_KEYS=m CONFIG_SEL3350_PLATFORM=m +CONFIG_OXP_EC=m +CONFIG_TUXEDO_NB04_WMI_AB=m CONFIG_P2SB=y CONFIG_HAVE_CLK=y CONFIG_HAVE_CLK_PREPARE=y @@ -9335,6 +9993,7 @@ CONFIG_IOMMU_DEFAULT_DMA_LAZY=y # CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set CONFIG_IOMMU_DMA=y CONFIG_IOMMU_SVA=y +CONFIG_IOMMU_IOPF=y CONFIG_AMD_IOMMU=y CONFIG_DMAR_TABLE=y CONFIG_INTEL_IOMMU=y @@ -9343,6 +10002,7 @@ CONFIG_INTEL_IOMMU_SVM=y CONFIG_INTEL_IOMMU_FLOPPY_WA=y CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON=y CONFIG_INTEL_IOMMU_PERF_EVENTS=y +CONFIG_IOMMUFD_DRIVER_CORE=y CONFIG_IOMMUFD=m CONFIG_IRQ_REMAP=y CONFIG_HYPERV_IOMMU=y @@ -9417,6 +10077,7 @@ CONFIG_WPCM450_SOC=m # # Qualcomm SoC drivers # +CONFIG_QCOM_MDT_LOADER=m CONFIG_QCOM_PMIC_PDCHARGER_ULOG=m CONFIG_QCOM_QMI_HELPERS=m # end of Qualcomm SoC drivers @@ -9481,11 +10142,13 @@ CONFIG_EXTCON_GPIO=m CONFIG_EXTCON_INTEL_INT3496=m CONFIG_EXTCON_INTEL_CHT_WC=m CONFIG_EXTCON_INTEL_MRFLD=m +CONFIG_EXTCON_LC824206XA=m CONFIG_EXTCON_MAX14577=m CONFIG_EXTCON_MAX3355=m CONFIG_EXTCON_MAX77693=m CONFIG_EXTCON_MAX77843=m CONFIG_EXTCON_MAX8997=m +CONFIG_EXTCON_MAX14526=m CONFIG_EXTCON_PALMAS=m CONFIG_EXTCON_PTN5150=m CONFIG_EXTCON_RT8973A=m @@ -9510,6 +10173,7 @@ CONFIG_IIO_CONSUMERS_PER_TRIGGER=2 CONFIG_IIO_SW_DEVICE=m CONFIG_IIO_SW_TRIGGER=m CONFIG_IIO_TRIGGERED_EVENT=m +CONFIG_IIO_BACKEND=m # # Accelerometers @@ -9528,6 +10192,9 @@ CONFIG_ADXL367_I2C=m CONFIG_ADXL372=m CONFIG_ADXL372_SPI=m CONFIG_ADXL372_I2C=m +CONFIG_ADXL380=m +CONFIG_ADXL380_SPI=m +CONFIG_ADXL380_I2C=m CONFIG_BMA220=m CONFIG_BMA400=m CONFIG_BMA400_I2C=m @@ -9536,6 +10203,7 @@ CONFIG_BMC150_ACCEL=m CONFIG_BMC150_ACCEL_I2C=m CONFIG_BMC150_ACCEL_SPI=m CONFIG_BMI088_ACCEL=m +CONFIG_BMI088_ACCEL_I2C=m CONFIG_BMI088_ACCEL_SPI=m CONFIG_DA280=m CONFIG_DA311=m @@ -9578,40 +10246,58 @@ CONFIG_STK8BA50=m # # Analog to digital converters # +CONFIG_IIO_ADC_HELPER=m CONFIG_AD_SIGMA_DELTA=m +CONFIG_AD4000=m +CONFIG_AD4030=m +CONFIG_AD4080=m CONFIG_AD4130=m +CONFIG_AD4170_4=m +CONFIG_AD4695=m +CONFIG_AD4851=m CONFIG_AD7091R=m CONFIG_AD7091R5=m CONFIG_AD7091R8=m CONFIG_AD7124=m +CONFIG_AD7173=m +CONFIG_AD7191=m CONFIG_AD7192=m CONFIG_AD7266=m CONFIG_AD7280=m CONFIG_AD7291=m CONFIG_AD7292=m CONFIG_AD7298=m +CONFIG_AD7380=m +CONFIG_AD7405=m CONFIG_AD7476=m CONFIG_AD7606=m CONFIG_AD7606_IFACE_PARALLEL=m CONFIG_AD7606_IFACE_SPI=m +CONFIG_AD7625=m CONFIG_AD7766=m CONFIG_AD7768_1=m +CONFIG_AD7779=m CONFIG_AD7780=m CONFIG_AD7791=m CONFIG_AD7793=m CONFIG_AD7887=m CONFIG_AD7923=m +CONFIG_AD7944=m CONFIG_AD7949=m CONFIG_AD799X=m +CONFIG_AD9467=m +CONFIG_ADE9000=m CONFIG_AXP20X_ADC=m CONFIG_AXP288_ADC=m CONFIG_CC10001_ADC=m CONFIG_DA9150_GPADC=m CONFIG_DLN2_ADC=m CONFIG_ENVELOPE_DETECTOR=m +CONFIG_GEHC_PMC_ADC=m CONFIG_HI8435=m CONFIG_HX711=m CONFIG_INA2XX_ADC=m +CONFIG_INTEL_DC_TI_ADC=m CONFIG_INTEL_MRFLD_ADC=m CONFIG_LP8788_ADC=m CONFIG_LTC2309=m @@ -9633,29 +10319,38 @@ CONFIG_MCP320X=m CONFIG_MCP3422=m CONFIG_MCP3564=m CONFIG_MCP3911=m +CONFIG_MEDIATEK_MT6359_AUXADC=m CONFIG_MEDIATEK_MT6360_ADC=m CONFIG_MEDIATEK_MT6370_ADC=m CONFIG_MEN_Z188_ADC=m CONFIG_MP2629_ADC=m CONFIG_NAU7802=m +CONFIG_NCT7201=m +CONFIG_PAC1921=m +CONFIG_PAC1934=m CONFIG_PALMAS_GPADC=m +CONFIG_ROHM_BD79112=m +CONFIG_ROHM_BD79124=m CONFIG_RICHTEK_RTQ6056=m CONFIG_SD_ADC_MODULATOR=m CONFIG_TI_ADC081C=m CONFIG_TI_ADC0832=m CONFIG_TI_ADC084S021=m -CONFIG_TI_ADC12138=m CONFIG_TI_ADC108S102=m +CONFIG_TI_ADC12138=m CONFIG_TI_ADC128S052=m CONFIG_TI_ADC161S626=m CONFIG_TI_ADS1015=m -CONFIG_TI_ADS7924=m CONFIG_TI_ADS1100=m +CONFIG_TI_ADS1119=m +CONFIG_TI_ADS124S08=m +CONFIG_TI_ADS1298=m +CONFIG_TI_ADS131E08=m +CONFIG_TI_ADS7138=m +CONFIG_TI_ADS7924=m CONFIG_TI_ADS7950=m CONFIG_TI_ADS8344=m CONFIG_TI_ADS8688=m -CONFIG_TI_ADS124S08=m -CONFIG_TI_ADS131E08=m CONFIG_TI_LMP92064=m CONFIG_TI_TLC4541=m CONFIG_TI_TSC2046=m @@ -9704,12 +10399,17 @@ CONFIG_BME680=m CONFIG_BME680_I2C=m CONFIG_BME680_SPI=m CONFIG_CCS811=m +CONFIG_ENS160=m +CONFIG_ENS160_I2C=m +CONFIG_ENS160_SPI=m CONFIG_IAQCORE=m +CONFIG_MHZ19B=m CONFIG_PMS7003=m CONFIG_SCD30_CORE=m CONFIG_SCD30_I2C=m CONFIG_SCD30_SERIAL=m CONFIG_SCD4X=m +CONFIG_SEN0322=m CONFIG_SENSIRION_SGP30=m CONFIG_SENSIRION_SGP40=m CONFIG_SPS30=m @@ -9722,6 +10422,7 @@ CONFIG_VZ89X=m CONFIG_IIO_CROS_EC_SENSORS_CORE=m CONFIG_IIO_CROS_EC_SENSORS=m CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE=m +CONFIG_IIO_CROS_EC_ACTIVITY=m # # Hid Sensor IIO Common @@ -9752,6 +10453,9 @@ CONFIG_IIO_ST_SENSORS_CORE=m # # Digital to analog converters # +CONFIG_AD3530R=m +CONFIG_AD3552R_HS=m +CONFIG_AD3552R_LIB=m CONFIG_AD3552R=m CONFIG_AD5064=m CONFIG_AD5360=m @@ -9764,6 +10468,7 @@ CONFIG_AD5592R=m CONFIG_AD5593R=m CONFIG_AD5504=m CONFIG_AD5624R_SPI=m +CONFIG_AD9739A=m CONFIG_LTC2688=m CONFIG_AD5686=m CONFIG_AD5686_SPI=m @@ -9777,11 +10482,14 @@ CONFIG_AD5770R=m CONFIG_AD5791=m CONFIG_AD7293=m CONFIG_AD7303=m +CONFIG_AD8460=m CONFIG_AD8801=m +CONFIG_BD79703=m CONFIG_DPOT_DAC=m CONFIG_DS4424=m CONFIG_LTC1660=m CONFIG_LTC2632=m +CONFIG_LTC2664=m CONFIG_M62332=m CONFIG_MAX517=m CONFIG_MAX5522=m @@ -9825,6 +10533,7 @@ CONFIG_AD9523=m CONFIG_ADF4350=m CONFIG_ADF4371=m CONFIG_ADF4377=m +CONFIG_ADMFM2000=m CONFIG_ADMV1013=m CONFIG_ADMV1014=m CONFIG_ADMV4420=m @@ -9875,6 +10584,7 @@ CONFIG_MAX30102=m # CONFIG_AM2315=m CONFIG_DHT11=m +CONFIG_ENS210=m CONFIG_HDC100X=m CONFIG_HDC2010=m CONFIG_HDC3020=m @@ -9894,9 +10604,13 @@ CONFIG_ADIS16400=m CONFIG_ADIS16460=m CONFIG_ADIS16475=m CONFIG_ADIS16480=m +CONFIG_ADIS16550=m CONFIG_BMI160=m CONFIG_BMI160_I2C=m CONFIG_BMI160_SPI=m +CONFIG_BMI270=m +CONFIG_BMI270_I2C=m +CONFIG_BMI270_SPI=m CONFIG_BMI323=m CONFIG_BMI323_I2C=m CONFIG_BMI323_SPI=m @@ -9913,6 +10627,7 @@ CONFIG_INV_ICM42600_SPI=m CONFIG_INV_MPU6050_IIO=m CONFIG_INV_MPU6050_I2C=m CONFIG_INV_MPU6050_SPI=m +CONFIG_SMI240=m CONFIG_IIO_ST_LSM6DSX=m CONFIG_IIO_ST_LSM6DSX_I2C=m CONFIG_IIO_ST_LSM6DSX_SPI=m @@ -9930,11 +10645,15 @@ CONFIG_IIO_ADIS_LIB_BUFFER=y CONFIG_ACPI_ALS=m CONFIG_ADJD_S311=m CONFIG_ADUX1020=m +CONFIG_AL3000A=m CONFIG_AL3010=m CONFIG_AL3320A=m +CONFIG_APDS9160=m CONFIG_APDS9300=m +CONFIG_APDS9306=m CONFIG_APDS9960=m CONFIG_AS73211=m +CONFIG_BH1745=m CONFIG_BH1750=m CONFIG_BH1780=m CONFIG_CM32181=m @@ -9953,7 +10672,6 @@ CONFIG_ISL76682=m CONFIG_HID_SENSOR_ALS=m CONFIG_HID_SENSOR_PROX=m CONFIG_JSA1212=m -CONFIG_ROHM_BU27008=m CONFIG_ROHM_BU27034=m CONFIG_RPR0521=m CONFIG_SENSORS_LM3533=m @@ -9966,6 +10684,7 @@ CONFIG_MAX44009=m CONFIG_NOA1305=m CONFIG_OPT3001=m CONFIG_OPT4001=m +CONFIG_OPT4060=m CONFIG_PA12203001=m CONFIG_SI1133=m CONFIG_SI1145=m @@ -9983,7 +10702,10 @@ CONFIG_TSL4531=m CONFIG_US5182D=m CONFIG_VCNL4000=m CONFIG_VCNL4035=m +CONFIG_VEML3235=m CONFIG_VEML6030=m +CONFIG_VEML6040=m +CONFIG_VEML6046X00=m CONFIG_VEML6070=m CONFIG_VEML6075=m CONFIG_VL6180=m @@ -9996,6 +10718,7 @@ CONFIG_ZOPT2201=m CONFIG_AK8974=m CONFIG_AK8975=m CONFIG_AK09911=m +CONFIG_ALS31300=m CONFIG_BMC150_MAGN=m CONFIG_BMC150_MAGN_I2C=m CONFIG_BMC150_MAGN_SPI=m @@ -10005,12 +10728,14 @@ CONFIG_MMC35240=m CONFIG_IIO_ST_MAGN_3AXIS=m CONFIG_IIO_ST_MAGN_I2C_3AXIS=m CONFIG_IIO_ST_MAGN_SPI_3AXIS=m +CONFIG_INFINEON_TLV493D=m CONFIG_SENSORS_HMC5843=m CONFIG_SENSORS_HMC5843_I2C=m CONFIG_SENSORS_HMC5843_SPI=m CONFIG_SENSORS_RM3100=m CONFIG_SENSORS_RM3100_I2C=m CONFIG_SENSORS_RM3100_SPI=m +CONFIG_SI7210=m CONFIG_TI_TMAG5273=m CONFIG_YAMAHA_YAS530=m # end of Magnetometer sensors @@ -10089,10 +10814,13 @@ CONFIG_MPL115_I2C=m CONFIG_MPL115_SPI=m CONFIG_MPL3115=m CONFIG_MPRLS0025PA=m +CONFIG_MPRLS0025PA_I2C=m +CONFIG_MPRLS0025PA_SPI=m CONFIG_MS5611=m CONFIG_MS5611_I2C=m CONFIG_MS5611_SPI=m CONFIG_MS5637=m +CONFIG_SDP500=m CONFIG_IIO_ST_PRESS=m CONFIG_IIO_ST_PRESS_I2C=m CONFIG_IIO_ST_PRESS_SPI=m @@ -10113,6 +10841,8 @@ CONFIG_AS3935=m # Proximity and distance sensors # CONFIG_CROS_EC_MKBP_PROXIMITY=m +CONFIG_D3323AA=m +CONFIG_HX9023S=m CONFIG_IRSD200=m CONFIG_ISL29501=m CONFIG_LIDAR_LITE_V2=m @@ -10128,6 +10858,7 @@ CONFIG_SX9500=m CONFIG_SRF08=m CONFIG_VCNL3020=m CONFIG_VL53L0X_I2C=m +CONFIG_AW96103=m # end of Proximity and distance sensors # @@ -10172,18 +10903,20 @@ CONFIG_NTB_SWITCHTEC=m # CONFIG_NTB_MSI_TEST is not set CONFIG_NTB_TRANSPORT=m CONFIG_PWM=y -CONFIG_PWM_SYSFS=y # CONFIG_PWM_DEBUG is not set +CONFIG_PWM_PROVIDE_GPIO=y CONFIG_PWM_CLK=m -CONFIG_PWM_CRC=m +CONFIG_PWM_CRC=y CONFIG_PWM_CROS_EC=m CONFIG_PWM_DWC_CORE=m CONFIG_PWM_DWC=m +CONFIG_PWM_GPIO=m CONFIG_PWM_IQS620A=m CONFIG_PWM_LP3943=m CONFIG_PWM_LPSS=m CONFIG_PWM_LPSS_PCI=m CONFIG_PWM_LPSS_PLATFORM=m +CONFIG_PWM_MAX7360=m CONFIG_PWM_PCA9685=m CONFIG_PWM_TWL=m CONFIG_PWM_TWL_LED=m @@ -10191,6 +10924,7 @@ CONFIG_PWM_TWL_LED=m # # IRQ chip support # +CONFIG_IRQ_MSI_LIB=y CONFIG_MADERA_IRQ=m # end of IRQ chip support @@ -10198,6 +10932,7 @@ CONFIG_IPACK_BUS=m CONFIG_BOARD_TPCI200=m CONFIG_SERIAL_IPOCTAL=m CONFIG_RESET_CONTROLLER=y +CONFIG_RESET_GPIO=m CONFIG_RESET_TI_SYSCON=m CONFIG_RESET_TI_TPS380X=m @@ -10238,11 +10973,15 @@ CONFIG_MCB_LPC=m # Performance monitor support # CONFIG_DWC_PCIE_PMU=m +CONFIG_CXL_PMU=m # end of Performance monitor support CONFIG_RAS=y CONFIG_RAS_CEC=y # CONFIG_RAS_CEC_DEBUG is not set +CONFIG_AMD_ATL=m +CONFIG_AMD_ATL_PRM=y +CONFIG_RAS_FMPM=m CONFIG_USB4=m # CONFIG_USB4_DEBUGFS_WRITE is not set # CONFIG_USB4_DMA_TEST is not set @@ -10251,6 +10990,8 @@ CONFIG_USB4=m # Android # # CONFIG_ANDROID_BINDER_IPC is not set +CONFIG_ANDROID_BINDER_IPC_RUST=y +CONFIG_ANDROID_BINDER_DEVICES="" # end of Android CONFIG_LIBNVDIMM=m @@ -10300,6 +11041,8 @@ CONFIG_FPGA=m CONFIG_ALTERA_PR_IP_CORE=m CONFIG_FPGA_MGR_ALTERA_PS_SPI=m CONFIG_FPGA_MGR_ALTERA_CVP=m +CONFIG_FPGA_MGR_XILINX_CORE=m +CONFIG_FPGA_MGR_XILINX_SELECTMAP=m CONFIG_FPGA_MGR_XILINX_SPI=m CONFIG_FPGA_MGR_MACHXO2_SPI=m CONFIG_FPGA_BRIDGE=m @@ -10319,6 +11062,8 @@ CONFIG_FPGA_MGR_MICROCHIP_SPI=m CONFIG_FPGA_MGR_LATTICE_SYSCONFIG=m CONFIG_FPGA_MGR_LATTICE_SYSCONFIG_SPI=m CONFIG_TEE=m +CONFIG_TEE_DMABUF_HEAPS=y +CONFIG_OPTEE_STATIC_PROTMEM_POOL=y CONFIG_AMDTEE=m CONFIG_MULTIPLEXER=m @@ -10334,7 +11079,6 @@ CONFIG_PM_OPP=y CONFIG_SIOX=m CONFIG_SIOX_BUS_GPIO=m CONFIG_SLIMBUS=m -CONFIG_SLIM_QCOM_CTRL=m CONFIG_INTERCONNECT=y CONFIG_COUNTER=m CONFIG_INTEL_QEP=m @@ -10345,7 +11089,6 @@ CONFIG_MOST_CDEV=m CONFIG_MOST_SND=m # CONFIG_PECI is not set CONFIG_HTE=y -CONFIG_DPLL=y # end of Device Drivers # @@ -10358,21 +11101,14 @@ CONFIG_FS_STACK=y CONFIG_BUFFER_HEAD=y CONFIG_LEGACY_DIRECT_IO=y # CONFIG_EXT2_FS is not set -# CONFIG_EXT3_FS is not set -CONFIG_EXT4_FS=m +CONFIG_EXT4_FS=y CONFIG_EXT4_USE_FOR_EXT2=y CONFIG_EXT4_FS_POSIX_ACL=y CONFIG_EXT4_FS_SECURITY=y # CONFIG_EXT4_DEBUG is not set -CONFIG_JBD2=m +CONFIG_JBD2=y # CONFIG_JBD2_DEBUG is not set -CONFIG_FS_MBCACHE=m -CONFIG_REISERFS_FS=m -# CONFIG_REISERFS_CHECK is not set -CONFIG_REISERFS_PROC_INFO=y -CONFIG_REISERFS_FS_XATTR=y -CONFIG_REISERFS_FS_POSIX_ACL=y -CONFIG_REISERFS_FS_SECURITY=y +CONFIG_FS_MBCACHE=y CONFIG_JFS_FS=m CONFIG_JFS_POSIX_ACL=y CONFIG_JFS_SECURITY=y @@ -10385,6 +11121,9 @@ CONFIG_XFS_QUOTA=y CONFIG_XFS_POSIX_ACL=y CONFIG_XFS_RT=y CONFIG_XFS_DRAIN_INTENTS=y +CONFIG_XFS_LIVE_HOOKS=y +CONFIG_XFS_MEMORY_BUFS=y +CONFIG_XFS_BTREE_IN_MEM=y CONFIG_XFS_ONLINE_SCRUB=y # CONFIG_XFS_ONLINE_SCRUB_STATS is not set CONFIG_XFS_ONLINE_REPAIR=y @@ -10398,12 +11137,12 @@ CONFIG_OCFS2_FS_USERSPACE_CLUSTER=m CONFIG_OCFS2_FS_STATS=y CONFIG_OCFS2_DEBUG_MASKLOG=y # CONFIG_OCFS2_DEBUG_FS is not set -CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS=y CONFIG_BTRFS_FS_POSIX_ACL=y # CONFIG_BTRFS_FS_RUN_SANITY_TESTS is not set # CONFIG_BTRFS_DEBUG is not set # CONFIG_BTRFS_ASSERT is not set -# CONFIG_BTRFS_FS_REF_VERIFY is not set +# CONFIG_BTRFS_EXPERIMENTAL is not set CONFIG_NILFS2_FS=m CONFIG_F2FS_FS=m CONFIG_F2FS_STAT_FS=y @@ -10420,15 +11159,6 @@ CONFIG_F2FS_FS_LZ4HC=y CONFIG_F2FS_FS_ZSTD=y CONFIG_F2FS_IOSTAT=y CONFIG_F2FS_UNFAIR_RWSEM=y -CONFIG_BCACHEFS_FS=m -CONFIG_BCACHEFS_QUOTA=y -# CONFIG_BCACHEFS_ERASURE_CODING is not set -CONFIG_BCACHEFS_POSIX_ACL=y -# CONFIG_BCACHEFS_DEBUG is not set -# CONFIG_BCACHEFS_TESTS is not set -CONFIG_BCACHEFS_LOCK_TIME_STATS=y -# CONFIG_BCACHEFS_NO_LATENCY_ACCT is not set -CONFIG_BCACHEFS_SIX_OPTIMISTIC_SPIN=y CONFIG_ZONEFS_FS=m CONFIG_FS_DAX=y CONFIG_FS_DAX_PMD=y @@ -10437,7 +11167,7 @@ CONFIG_EXPORTFS=y CONFIG_EXPORTFS_BLOCK_OPS=y CONFIG_FILE_LOCKING=y CONFIG_FS_ENCRYPTION=y -CONFIG_FS_ENCRYPTION_ALGS=m +CONFIG_FS_ENCRYPTION_ALGS=y CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y CONFIG_FS_VERITY=y CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y @@ -10454,10 +11184,12 @@ CONFIG_QFMT_V1=m CONFIG_QFMT_V2=m CONFIG_QUOTACTL=y CONFIG_AUTOFS_FS=y -CONFIG_FUSE_FS=m +CONFIG_FUSE_FS=y CONFIG_CUSE=m -CONFIG_VIRTIO_FS=m +CONFIG_VIRTIO_FS=y CONFIG_FUSE_DAX=y +CONFIG_FUSE_PASSTHROUGH=y +CONFIG_FUSE_IO_URING=y CONFIG_OVERLAY_FS=m CONFIG_OVERLAY_FS_REDIRECT_DIR=y # CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW is not set @@ -10471,9 +11203,9 @@ CONFIG_OVERLAY_FS_METACOPY=y # CONFIG_NETFS_SUPPORT=m CONFIG_NETFS_STATS=y +CONFIG_NETFS_DEBUG=y CONFIG_FSCACHE=y CONFIG_FSCACHE_STATS=y -# CONFIG_FSCACHE_DEBUG is not set CONFIG_CACHEFILES=m # CONFIG_CACHEFILES_DEBUG is not set # CONFIG_CACHEFILES_ERROR_INJECTION is not set @@ -10500,11 +11232,11 @@ CONFIG_FAT_DEFAULT_IOCHARSET="ascii" CONFIG_FAT_DEFAULT_UTF8=y CONFIG_EXFAT_FS=m CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8" -# CONFIG_NTFS_FS is not set CONFIG_NTFS3_FS=m # CONFIG_NTFS3_64BIT_CLUSTER is not set CONFIG_NTFS3_LZX_XPRESS=y CONFIG_NTFS3_FS_POSIX_ACL=y +CONFIG_NTFS_FS=m # end of DOS/FAT/EXFAT/NT Filesystems # @@ -10526,10 +11258,12 @@ CONFIG_TMPFS_POSIX_ACL=y CONFIG_TMPFS_XATTR=y CONFIG_TMPFS_INODE64=y CONFIG_TMPFS_QUOTA=y +CONFIG_ARCH_SUPPORTS_HUGETLBFS=y CONFIG_HUGETLBFS=y # CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON is not set CONFIG_HUGETLB_PAGE=y CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y +CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING=y CONFIG_ARCH_HAS_GIGANTIC_PAGE=y CONFIG_CONFIGFS_FS=y CONFIG_EFIVAR_FS=y @@ -10579,6 +11313,7 @@ CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU=y CONFIG_SQUASHFS_CHOICE_DECOMP_BY_MOUNT=y CONFIG_SQUASHFS_MOUNT_DECOMP_THREADS=y CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_COMP_CACHE_FULL=y CONFIG_SQUASHFS_ZLIB=y CONFIG_SQUASHFS_LZ4=y CONFIG_SQUASHFS_LZO=y @@ -10593,6 +11328,8 @@ CONFIG_OMFS_FS=m # CONFIG_HPFS_FS is not set # CONFIG_QNX4FS_FS is not set # CONFIG_QNX6FS_FS is not set +CONFIG_RESCTRL_FS=y +CONFIG_RESCTRL_FS_PSEUDO_LOCK=y CONFIG_ROMFS_FS=m CONFIG_ROMFS_BACKED_BY_BLOCK=y # CONFIG_ROMFS_BACKED_BY_MTD is not set @@ -10610,7 +11347,6 @@ CONFIG_PSTORE_BLK=m CONFIG_PSTORE_BLK_BLKDEV="" CONFIG_PSTORE_BLK_KMSG_SIZE=64 CONFIG_PSTORE_BLK_MAX_REASON=2 -# CONFIG_SYSV_FS is not set CONFIG_UFS_FS=m # CONFIG_UFS_FS_WRITE is not set # CONFIG_UFS_DEBUG is not set @@ -10619,9 +11355,12 @@ CONFIG_EROFS_FS=m CONFIG_EROFS_FS_XATTR=y CONFIG_EROFS_FS_POSIX_ACL=y CONFIG_EROFS_FS_SECURITY=y +CONFIG_EROFS_FS_BACKED_BY_FILE=y CONFIG_EROFS_FS_ZIP=y CONFIG_EROFS_FS_ZIP_LZMA=y CONFIG_EROFS_FS_ZIP_DEFLATE=y +CONFIG_EROFS_FS_ZIP_ZSTD=y +CONFIG_EROFS_FS_ZIP_ACCEL=y CONFIG_EROFS_FS_ONDEMAND=y CONFIG_EROFS_FS_PCPU_KTHREAD=y CONFIG_EROFS_FS_PCPU_KTHREAD_HIPRI=y @@ -10658,11 +11397,14 @@ CONFIG_NFSD_SCSILAYOUT=y CONFIG_NFSD_V4_2_INTER_SSC=y CONFIG_NFSD_V4_SECURITY_LABEL=y # CONFIG_NFSD_LEGACY_CLIENT_TRACKING is not set +# CONFIG_NFSD_V4_DELEG_TIMESTAMPS is not set CONFIG_GRACE_PERIOD=m CONFIG_LOCKD=m CONFIG_LOCKD_V4=y CONFIG_NFS_ACL_SUPPORT=m CONFIG_NFS_COMMON=y +CONFIG_NFS_COMMON_LOCALIO_SUPPORT=m +CONFIG_NFS_LOCALIO=y CONFIG_NFS_V4_2_SSC_HELPER=y CONFIG_SUNRPC=m CONFIG_SUNRPC_GSS=m @@ -10673,6 +11415,7 @@ CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1=y CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA=y CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2=y CONFIG_SUNRPC_DEBUG=y +# CONFIG_SUNRPC_DEBUG_TRACE is not set CONFIG_SUNRPC_XPRT_RDMA=m CONFIG_CEPH_FS=m CONFIG_CEPH_FSCACHE=y @@ -10691,6 +11434,7 @@ CONFIG_CIFS_DFS_UPCALL=y CONFIG_CIFS_SWN_UPCALL=y CONFIG_CIFS_SMB_DIRECT=y CONFIG_CIFS_FSCACHE=y +CONFIG_CIFS_COMPRESSION=y CONFIG_SMB_SERVER=m CONFIG_SMB_SERVER_SMBDIRECT=y CONFIG_SMB_SERVER_CHECK_CAP_NET_ADMIN=y @@ -10760,7 +11504,6 @@ CONFIG_NLS_UCS2_UTILS=m CONFIG_DLM=m CONFIG_DLM_DEBUG=y CONFIG_UNICODE=y -# CONFIG_UNICODE_NORMALIZATION_SELFTEST is not set CONFIG_IO_WQ=y # end of File systems @@ -10770,7 +11513,9 @@ CONFIG_IO_WQ=y CONFIG_KEYS=y CONFIG_KEYS_REQUEST_CACHE=y CONFIG_PERSISTENT_KEYRINGS=y +CONFIG_BIG_KEYS=y CONFIG_TRUSTED_KEYS=m +CONFIG_HAVE_TRUSTED_KEYS=y CONFIG_TRUSTED_KEYS_TPM=y CONFIG_TRUSTED_KEYS_TEE=y CONFIG_ENCRYPTED_KEYS=m @@ -10778,7 +11523,11 @@ CONFIG_ENCRYPTED_KEYS=m CONFIG_KEY_DH_OPERATIONS=y CONFIG_KEY_NOTIFICATIONS=y CONFIG_SECURITY_DMESG_RESTRICT=y +CONFIG_PROC_MEM_ALWAYS_FORCE=y +# CONFIG_PROC_MEM_FORCE_PTRACE is not set +# CONFIG_PROC_MEM_NO_FORCE is not set CONFIG_SECURITY=y +CONFIG_HAS_SECURITY_AUDIT=y CONFIG_SECURITYFS=y CONFIG_SECURITY_NETWORK=y CONFIG_SECURITY_INFINIBAND=y @@ -10786,8 +11535,6 @@ CONFIG_SECURITY_NETWORK_XFRM=y CONFIG_SECURITY_PATH=y # CONFIG_INTEL_TXT is not set CONFIG_LSM_MMAP_MIN_ADDR=65536 -CONFIG_HARDENED_USERCOPY=y -CONFIG_FORTIFY_SOURCE=y # CONFIG_STATIC_USERMODEHELPER is not set CONFIG_SECURITY_SELINUX=y CONFIG_SECURITY_SELINUX_BOOTPARAM=y @@ -10824,6 +11571,20 @@ CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y # CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY is not set # CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is not set CONFIG_SECURITY_LANDLOCK=y +CONFIG_SECURITY_IPE=y +CONFIG_IPE_BOOT_POLICY="" +CONFIG_IPE_POLICY_SIG_SECONDARY_KEYRING=y +CONFIG_IPE_POLICY_SIG_PLATFORM_KEYRING=y + +# +# IPE Trust Providers +# +CONFIG_IPE_PROP_DM_VERITY=y +CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y +CONFIG_IPE_PROP_FS_VERITY=y +CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y +# end of IPE Trust Providers + CONFIG_INTEGRITY=y CONFIG_INTEGRITY_SIGNATURE=y CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y @@ -10856,27 +11617,33 @@ CONFIG_CC_HAS_AUTO_VAR_INIT_ZERO=y # CONFIG_INIT_STACK_NONE is not set # CONFIG_INIT_STACK_ALL_PATTERN is not set CONFIG_INIT_STACK_ALL_ZERO=y -# CONFIG_GCC_PLUGIN_STACKLEAK is not set CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y # CONFIG_INIT_ON_FREE_DEFAULT_ON is not set CONFIG_CC_HAS_ZERO_CALL_USED_REGS=y # CONFIG_ZERO_CALL_USED_REGS is not set # end of Memory initialization +# +# Bounds checking +# +CONFIG_FORTIFY_SOURCE=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_HARDENED_USERCOPY_DEFAULT_ON=y +# end of Bounds checking + # # Hardening of kernel data structures # CONFIG_LIST_HARDENED=y +# CONFIG_RUST_BITMAP_HARDENED is not set # CONFIG_BUG_ON_DATA_CORRUPTION is not set # end of Hardening of kernel data structures CONFIG_RANDSTRUCT_NONE=y -# CONFIG_RANDSTRUCT_FULL is not set -# CONFIG_RANDSTRUCT_PERFORMANCE is not set # end of Kernel hardening options # end of Security options -CONFIG_XOR_BLOCKS=m +CONFIG_XOR_BLOCKS=y CONFIG_ASYNC_CORE=m CONFIG_ASYNC_MEMCPY=m CONFIG_ASYNC_XOR=m @@ -10891,6 +11658,7 @@ CONFIG_CRYPTO_ALGAPI=y CONFIG_CRYPTO_ALGAPI2=y CONFIG_CRYPTO_AEAD=m CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_SIG=y CONFIG_CRYPTO_SIG2=y CONFIG_CRYPTO_SKCIPHER=y CONFIG_CRYPTO_SKCIPHER2=y @@ -10904,17 +11672,16 @@ CONFIG_CRYPTO_AKCIPHER=y CONFIG_CRYPTO_KPP2=y CONFIG_CRYPTO_KPP=y CONFIG_CRYPTO_ACOMP2=y +CONFIG_CRYPTO_HKDF=m CONFIG_CRYPTO_MANAGER=y CONFIG_CRYPTO_MANAGER2=y CONFIG_CRYPTO_USER=m -CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y CONFIG_CRYPTO_NULL=m -CONFIG_CRYPTO_NULL2=m CONFIG_CRYPTO_PCRYPT=m CONFIG_CRYPTO_CRYPTD=m CONFIG_CRYPTO_AUTHENC=m -CONFIG_CRYPTO_TEST=m -CONFIG_CRYPTO_SIMD=m +CONFIG_CRYPTO_KRB5ENC=m +# CONFIG_CRYPTO_BENCHMARK is not set CONFIG_CRYPTO_ENGINE=m # end of Crypto core or helper @@ -10925,11 +11692,9 @@ CONFIG_CRYPTO_RSA=y CONFIG_CRYPTO_DH=y CONFIG_CRYPTO_DH_RFC7919_GROUPS=y CONFIG_CRYPTO_ECC=y -CONFIG_CRYPTO_ECDH=m +CONFIG_CRYPTO_ECDH=y CONFIG_CRYPTO_ECDSA=y CONFIG_CRYPTO_ECRDSA=m -CONFIG_CRYPTO_SM2=m -CONFIG_CRYPTO_CURVE25519=m # end of Public-key cryptography # @@ -10958,16 +11723,15 @@ CONFIG_CRYPTO_TWOFISH_COMMON=m # CONFIG_CRYPTO_ADIANTUM=m CONFIG_CRYPTO_CHACHA20=m -CONFIG_CRYPTO_CBC=m +CONFIG_CRYPTO_CBC=y CONFIG_CRYPTO_CTR=y -CONFIG_CRYPTO_CTS=m +CONFIG_CRYPTO_CTS=y CONFIG_CRYPTO_ECB=y CONFIG_CRYPTO_HCTR2=m -CONFIG_CRYPTO_KEYWRAP=m CONFIG_CRYPTO_LRW=m CONFIG_CRYPTO_PCBC=m CONFIG_CRYPTO_XCTR=m -CONFIG_CRYPTO_XTS=m +CONFIG_CRYPTO_XTS=y CONFIG_CRYPTO_NHPOLY1305=m # end of Length-preserving ciphers and modes @@ -10987,7 +11751,7 @@ CONFIG_CRYPTO_ESSIV=m # # Hashes, digests, and MACs # -CONFIG_CRYPTO_BLAKE2B=m +CONFIG_CRYPTO_BLAKE2B=y CONFIG_CRYPTO_CMAC=m CONFIG_CRYPTO_GHASH=m CONFIG_CRYPTO_HMAC=y @@ -10995,35 +11759,30 @@ CONFIG_CRYPTO_MD4=m CONFIG_CRYPTO_MD5=y CONFIG_CRYPTO_MICHAEL_MIC=m CONFIG_CRYPTO_POLYVAL=m -CONFIG_CRYPTO_POLY1305=m CONFIG_CRYPTO_RMD160=m CONFIG_CRYPTO_SHA1=y CONFIG_CRYPTO_SHA256=y CONFIG_CRYPTO_SHA512=y CONFIG_CRYPTO_SHA3=y -CONFIG_CRYPTO_SM3=m CONFIG_CRYPTO_SM3_GENERIC=m CONFIG_CRYPTO_STREEBOG=m -CONFIG_CRYPTO_VMAC=m CONFIG_CRYPTO_WP512=m CONFIG_CRYPTO_XCBC=m -CONFIG_CRYPTO_XXHASH=m +CONFIG_CRYPTO_XXHASH=y # end of Hashes, digests, and MACs # # CRCs (cyclic redundancy checks) # -CONFIG_CRYPTO_CRC32C=m +CONFIG_CRYPTO_CRC32C=y CONFIG_CRYPTO_CRC32=m -CONFIG_CRYPTO_CRCT10DIF=y -CONFIG_CRYPTO_CRC64_ROCKSOFT=y # end of CRCs (cyclic redundancy checks) # # Compression # CONFIG_CRYPTO_DEFLATE=m -CONFIG_CRYPTO_LZO=m +CONFIG_CRYPTO_LZO=y CONFIG_CRYPTO_842=m CONFIG_CRYPTO_LZ4=m CONFIG_CRYPTO_LZ4HC=m @@ -11056,15 +11815,11 @@ CONFIG_CRYPTO_USER_API_RNG=m # CONFIG_CRYPTO_USER_API_RNG_CAVP is not set CONFIG_CRYPTO_USER_API_AEAD=m # CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE is not set -CONFIG_CRYPTO_STATS=y # end of Userspace interface -CONFIG_CRYPTO_HASH_INFO=y - # # Accelerated Cryptographic Algorithms for CPU (x86) # -CONFIG_CRYPTO_CURVE25519_X86=m CONFIG_CRYPTO_AES_NI_INTEL=m CONFIG_CRYPTO_BLOWFISH_X86_64=m CONFIG_CRYPTO_CAMELLIA_X86_64=m @@ -11084,21 +11839,12 @@ CONFIG_CRYPTO_TWOFISH_AVX_X86_64=m CONFIG_CRYPTO_ARIA_AESNI_AVX_X86_64=m CONFIG_CRYPTO_ARIA_AESNI_AVX2_X86_64=m CONFIG_CRYPTO_ARIA_GFNI_AVX512_X86_64=m -CONFIG_CRYPTO_CHACHA20_X86_64=m CONFIG_CRYPTO_AEGIS128_AESNI_SSE2=m CONFIG_CRYPTO_NHPOLY1305_SSE2=m CONFIG_CRYPTO_NHPOLY1305_AVX2=m -CONFIG_CRYPTO_BLAKE2S_X86=y CONFIG_CRYPTO_POLYVAL_CLMUL_NI=m -CONFIG_CRYPTO_POLY1305_X86_64=m -CONFIG_CRYPTO_SHA1_SSSE3=m -CONFIG_CRYPTO_SHA256_SSSE3=m -CONFIG_CRYPTO_SHA512_SSSE3=m CONFIG_CRYPTO_SM3_AVX_X86_64=m CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=m -CONFIG_CRYPTO_CRC32C_INTEL=m -CONFIG_CRYPTO_CRC32_PCLMUL=m -CONFIG_CRYPTO_CRCT10DIF_PCLMUL=m # end of Accelerated Cryptographic Algorithms for CPU (x86) CONFIG_CRYPTO_HW=y @@ -11122,9 +11868,11 @@ CONFIG_CRYPTO_DEV_QAT_C3XXX=m CONFIG_CRYPTO_DEV_QAT_C62X=m CONFIG_CRYPTO_DEV_QAT_4XXX=m CONFIG_CRYPTO_DEV_QAT_420XX=m +CONFIG_CRYPTO_DEV_QAT_6XXX=m CONFIG_CRYPTO_DEV_QAT_DH895xCCVF=m CONFIG_CRYPTO_DEV_QAT_C3XXXVF=m CONFIG_CRYPTO_DEV_QAT_C62XVF=m +# CONFIG_CRYPTO_DEV_QAT_ERROR_INJECTION is not set CONFIG_CRYPTO_DEV_IAA_CRYPTO=m # CONFIG_CRYPTO_DEV_IAA_CRYPTO_STATS is not set CONFIG_CRYPTO_DEV_CHELSIO=m @@ -11159,12 +11907,14 @@ CONFIG_SYSTEM_REVOCATION_KEYS="" CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE=y # end of Certificates for signature checking +CONFIG_CRYPTO_KRB5=m +# CONFIG_CRYPTO_KRB5_SELFTESTS is not set CONFIG_BINARY_PRINTF=y # # Library routines # -CONFIG_RAID6_PQ=m +CONFIG_RAID6_PQ=y # CONFIG_RAID6_PQ_BENCHMARK is not set CONFIG_LINEAR_RANGES=y CONFIG_PACKING=y @@ -11175,53 +11925,55 @@ CONFIG_GENERIC_NET_UTILS=y CONFIG_CORDIC=m # CONFIG_PRIME_NUMBERS is not set CONFIG_RATIONAL=y -CONFIG_GENERIC_PCI_IOMAP=y CONFIG_GENERIC_IOMAP=y CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y CONFIG_ARCH_HAS_FAST_MULTIPLIER=y CONFIG_ARCH_USE_SYM_ANNOTATIONS=y +CONFIG_CRC7=m +CONFIG_CRC8=m +CONFIG_CRC16=y +CONFIG_CRC_CCITT=y +CONFIG_CRC_ITU_T=m +CONFIG_CRC_T10DIF=y +CONFIG_CRC_T10DIF_ARCH=y +CONFIG_CRC32=y +CONFIG_CRC32_ARCH=y +CONFIG_CRC64=y +CONFIG_CRC64_ARCH=y +CONFIG_CRC_OPTIMIZATIONS=y # # Crypto library routines # +CONFIG_CRYPTO_HASH_INFO=y CONFIG_CRYPTO_LIB_UTILS=y CONFIG_CRYPTO_LIB_AES=y +CONFIG_CRYPTO_LIB_AESCFB=y +CONFIG_CRYPTO_LIB_AESGCM=y CONFIG_CRYPTO_LIB_ARC4=m -CONFIG_CRYPTO_LIB_GF128MUL=m -CONFIG_CRYPTO_ARCH_HAVE_LIB_BLAKE2S=y -CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y -CONFIG_CRYPTO_ARCH_HAVE_LIB_CHACHA=m -CONFIG_CRYPTO_LIB_CHACHA_GENERIC=m -CONFIG_CRYPTO_LIB_CHACHA=m -CONFIG_CRYPTO_ARCH_HAVE_LIB_CURVE25519=m -CONFIG_CRYPTO_LIB_CURVE25519_GENERIC=m +CONFIG_CRYPTO_LIB_GF128MUL=y +CONFIG_CRYPTO_LIB_BLAKE2S_ARCH=y +CONFIG_CRYPTO_LIB_CHACHA=y +CONFIG_CRYPTO_LIB_CHACHA_ARCH=y CONFIG_CRYPTO_LIB_CURVE25519=m +CONFIG_CRYPTO_LIB_CURVE25519_ARCH=y +CONFIG_CRYPTO_LIB_CURVE25519_GENERIC=y CONFIG_CRYPTO_LIB_DES=m +CONFIG_CRYPTO_LIB_MD5=y +CONFIG_CRYPTO_LIB_POLY1305=y +CONFIG_CRYPTO_LIB_POLY1305_ARCH=y +CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y CONFIG_CRYPTO_LIB_POLY1305_RSIZE=11 -CONFIG_CRYPTO_ARCH_HAVE_LIB_POLY1305=m -CONFIG_CRYPTO_LIB_POLY1305_GENERIC=m -CONFIG_CRYPTO_LIB_POLY1305=m -CONFIG_CRYPTO_LIB_CHACHA20POLY1305=m +CONFIG_CRYPTO_LIB_CHACHA20POLY1305=y CONFIG_CRYPTO_LIB_SHA1=y +CONFIG_CRYPTO_LIB_SHA1_ARCH=y CONFIG_CRYPTO_LIB_SHA256=y +CONFIG_CRYPTO_LIB_SHA256_ARCH=y +CONFIG_CRYPTO_LIB_SHA512=y +CONFIG_CRYPTO_LIB_SHA512_ARCH=y +CONFIG_CRYPTO_LIB_SM3=m # end of Crypto library routines -CONFIG_CRC_CCITT=y -CONFIG_CRC16=m -CONFIG_CRC_T10DIF=y -CONFIG_CRC64_ROCKSOFT=y -CONFIG_CRC_ITU_T=m -CONFIG_CRC32=y -# CONFIG_CRC32_SELFTEST is not set -CONFIG_CRC32_SLICEBY8=y -# CONFIG_CRC32_SLICEBY4 is not set -# CONFIG_CRC32_SARWATE is not set -# CONFIG_CRC32_BIT is not set -CONFIG_CRC64=y -CONFIG_CRC4=m -CONFIG_CRC7=m -CONFIG_LIBCRC32C=m -CONFIG_CRC8=m CONFIG_XXHASH=y # CONFIG_RANDOM32_SELFTEST is not set CONFIG_842_COMPRESS=m @@ -11241,7 +11993,9 @@ CONFIG_XZ_DEC_X86=y CONFIG_XZ_DEC_POWERPC=y CONFIG_XZ_DEC_ARM=y CONFIG_XZ_DEC_ARMTHUMB=y +CONFIG_XZ_DEC_ARM64=y CONFIG_XZ_DEC_SPARC=y +CONFIG_XZ_DEC_RISCV=y CONFIG_XZ_DEC_MICROLZMA=y CONFIG_XZ_DEC_BCJ=y # CONFIG_XZ_DEC_TEST is not set @@ -11271,7 +12025,7 @@ CONFIG_HAS_IOMEM=y CONFIG_HAS_IOPORT=y CONFIG_HAS_IOPORT_MAP=y CONFIG_HAS_DMA=y -CONFIG_DMA_OPS=y +CONFIG_DMA_OPS_HELPERS=y CONFIG_NEED_SG_DMA_FLAGS=y CONFIG_NEED_SG_DMA_LENGTH=y CONFIG_NEED_DMA_MAP_STATE=y @@ -11279,6 +12033,7 @@ CONFIG_ARCH_DMA_ADDR_T_64BIT=y CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y CONFIG_SWIOTLB=y # CONFIG_SWIOTLB_DYNAMIC is not set +CONFIG_DMA_NEED_SYNC=y CONFIG_DMA_COHERENT_POOL=y CONFIG_DMA_CMA=y # CONFIG_DMA_NUMA_CMA is not set @@ -11296,6 +12051,7 @@ CONFIG_CMA_ALIGNMENT=8 # CONFIG_DMA_MAP_BENCHMARK is not set CONFIG_SGL_ALLOC=y CONFIG_CHECK_SIGNATURE=y +CONFIG_CPUMASK_OFFSTACK=y CONFIG_CPU_RMAP=y CONFIG_DQL=y CONFIG_GLOB=y @@ -11307,14 +12063,16 @@ CONFIG_IRQ_POLL=y CONFIG_MPILIB=y CONFIG_SIGNATURE=y CONFIG_DIMLIB=y +CONFIG_LIBFDT=y CONFIG_OID_REGISTRY=y CONFIG_UCS2_STRING=y CONFIG_HAVE_GENERIC_VDSO=y CONFIG_GENERIC_GETTIMEOFDAY=y -CONFIG_GENERIC_VDSO_TIME_NS=y +CONFIG_GENERIC_VDSO_OVERFLOW_PROTECT=y +CONFIG_VDSO_GETRANDOM=y CONFIG_FONT_SUPPORT=y CONFIG_FONTS=y -# CONFIG_FONT_8x8 is not set +CONFIG_FONT_8x8=y CONFIG_FONT_8x16=y # CONFIG_FONT_6x11 is not set # CONFIG_FONT_7x14 is not set @@ -11346,6 +12104,8 @@ CONFIG_PLDMFW=y CONFIG_ASN1_ENCODER=m CONFIG_POLYNOMIAL=m CONFIG_FIRMWARE_TABLE=y +CONFIG_UNION_FIND=y +CONFIG_MIN_HEAP=y # # Kernel hacking @@ -11389,7 +12149,7 @@ CONFIG_PAHOLE_HAS_SPLIT_BTF=y CONFIG_PAHOLE_HAS_LANG_EXCLUDE=y CONFIG_DEBUG_INFO_BTF_MODULES=y # CONFIG_MODULE_ALLOW_BTF_MISMATCH is not set -# CONFIG_GDB_SCRIPTS is not set +CONFIG_GDB_SCRIPTS=y CONFIG_FRAME_WARN=2048 CONFIG_STRIP_ASM_SYMS=y # CONFIG_READABLE_ASM is not set @@ -11397,6 +12157,7 @@ CONFIG_STRIP_ASM_SYMS=y # CONFIG_DEBUG_SECTION_MISMATCH is not set CONFIG_SECTION_MISMATCH_WARN_ONLY=y CONFIG_OBJTOOL=y +# CONFIG_OBJTOOL_WERROR is not set # CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set # end of Compile-time checks and compiler options @@ -11413,7 +12174,7 @@ CONFIG_DEBUG_FS_ALLOW_ALL=y # CONFIG_DEBUG_FS_ALLOW_NONE is not set CONFIG_HAVE_ARCH_KGDB=y # CONFIG_KGDB is not set -CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y +CONFIG_ARCH_HAS_UBSAN=y # CONFIG_UBSAN is not set CONFIG_HAVE_ARCH_KCSAN=y CONFIG_HAVE_KCSAN_COMPILER=y @@ -11426,6 +12187,7 @@ CONFIG_HAVE_KCSAN_COMPILER=y # CONFIG_NET_DEV_REFCNT_TRACKER is not set # CONFIG_NET_NS_REFCNT_TRACKER is not set # CONFIG_DEBUG_NET is not set +# CONFIG_DEBUG_NET_SMALL_RTNL is not set # end of Networking Debugging # @@ -11442,8 +12204,8 @@ CONFIG_PAGE_POISONING=y CONFIG_DEBUG_RODATA_TEST=y CONFIG_ARCH_HAS_DEBUG_WX=y CONFIG_DEBUG_WX=y -CONFIG_GENERIC_PTDUMP=y -CONFIG_PTDUMP_CORE=y +CONFIG_ARCH_HAS_PTDUMP=y +CONFIG_PTDUMP=y # CONFIG_PTDUMP_DEBUGFS is not set CONFIG_HAVE_DEBUG_KMEMLEAK=y # CONFIG_DEBUG_KMEMLEAK is not set @@ -11453,17 +12215,18 @@ CONFIG_SHRINKER_DEBUG=y # CONFIG_DEBUG_STACK_USAGE is not set CONFIG_SCHED_STACK_END_CHECK=y CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y +# CONFIG_DEBUG_VFS is not set # CONFIG_DEBUG_VM is not set # CONFIG_DEBUG_VM_PGTABLE is not set CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y # CONFIG_DEBUG_VIRTUAL is not set CONFIG_DEBUG_MEMORY_INIT=y # CONFIG_DEBUG_PER_CPU_MAPS is not set -CONFIG_ARCH_SUPPORTS_KMAP_LOCAL_FORCE_MAP=y -# CONFIG_DEBUG_KMAP_LOCAL_FORCE_MAP is not set +# CONFIG_MEM_ALLOC_PROFILING is not set CONFIG_HAVE_ARCH_KASAN=y CONFIG_HAVE_ARCH_KASAN_VMALLOC=y CONFIG_CC_HAS_KASAN_GENERIC=y +CONFIG_CC_HAS_KASAN_SW_TAGS=y CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y # CONFIG_KASAN is not set CONFIG_HAVE_ARCH_KFENCE=y @@ -11481,10 +12244,10 @@ CONFIG_DEBUG_SHIRQ=y # Debug Oops, Lockups and Hangs # # CONFIG_PANIC_ON_OOPS is not set -CONFIG_PANIC_ON_OOPS_VALUE=0 CONFIG_PANIC_TIMEOUT=0 CONFIG_LOCKUP_DETECTOR=y CONFIG_SOFTLOCKUP_DETECTOR=y +CONFIG_SOFTLOCKUP_DETECTOR_INTR_STORM=y # CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set CONFIG_HAVE_HARDLOCKUP_DETECTOR_BUDDY=y CONFIG_HARDLOCKUP_DETECTOR=y @@ -11498,6 +12261,7 @@ CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y CONFIG_DETECT_HUNG_TASK=y CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 # CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_DETECT_HUNG_TASK_BLOCKER=y # CONFIG_WQ_WATCHDOG is not set # CONFIG_WQ_CPU_INTENSIVE_REPORT is not set # CONFIG_TEST_LOCKUP is not set @@ -11506,12 +12270,10 @@ CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 # # Scheduler Debugging # -CONFIG_SCHED_DEBUG=y CONFIG_SCHED_INFO=y CONFIG_SCHEDSTATS=y # end of Scheduler Debugging -# CONFIG_DEBUG_TIMEKEEPING is not set # CONFIG_DEBUG_PREEMPT is not set # @@ -11575,13 +12337,14 @@ CONFIG_HAVE_RETHOOK=y CONFIG_RETHOOK=y CONFIG_HAVE_FUNCTION_TRACER=y CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y -CONFIG_HAVE_FUNCTION_GRAPH_RETVAL=y +CONFIG_HAVE_FUNCTION_GRAPH_FREGS=y +CONFIG_HAVE_FTRACE_GRAPH_FUNC=y CONFIG_HAVE_DYNAMIC_FTRACE=y CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS=y +CONFIG_HAVE_FTRACE_REGS_HAVING_PT_REGS=y CONFIG_HAVE_DYNAMIC_FTRACE_NO_PATCHABLE=y -CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y CONFIG_HAVE_SYSCALL_TRACEPOINTS=y CONFIG_HAVE_FENTRY=y CONFIG_HAVE_OBJTOOL_MCOUNT=y @@ -11598,10 +12361,13 @@ CONFIG_TRACING=y CONFIG_GENERIC_TRACER=y CONFIG_TRACING_SUPPORT=y CONFIG_FTRACE=y +# CONFIG_TRACEFS_AUTOMOUNT_DEPRECATED is not set CONFIG_BOOTTIME_TRACING=y CONFIG_FUNCTION_TRACER=y CONFIG_FUNCTION_GRAPH_TRACER=y CONFIG_FUNCTION_GRAPH_RETVAL=y +CONFIG_FUNCTION_GRAPH_RETADDR=y +CONFIG_FUNCTION_TRACE_ARGS=y CONFIG_DYNAMIC_FTRACE=y CONFIG_DYNAMIC_FTRACE_WITH_REGS=y CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y @@ -11627,11 +12393,11 @@ CONFIG_PROBE_EVENTS_BTF_ARGS=y CONFIG_KPROBE_EVENTS=y # CONFIG_KPROBE_EVENTS_ON_NOTRACE is not set CONFIG_UPROBE_EVENTS=y +CONFIG_EPROBE_EVENTS=y CONFIG_BPF_EVENTS=y CONFIG_DYNAMIC_EVENTS=y CONFIG_PROBE_EVENTS=y CONFIG_BPF_KPROBE_OVERRIDE=y -CONFIG_FTRACE_MCOUNT_RECORD=y CONFIG_FTRACE_MCOUNT_USE_CC=y CONFIG_TRACING_MAP=y CONFIG_SYNTH_EVENTS=y @@ -11642,6 +12408,7 @@ CONFIG_HIST_TRIGGERS=y # CONFIG_RING_BUFFER_BENCHMARK is not set # CONFIG_TRACE_EVAL_MAP_FILE is not set # CONFIG_FTRACE_RECORD_RECURSION is not set +# CONFIG_FTRACE_VALIDATE_RCU_IS_WATCHING is not set # CONFIG_FTRACE_STARTUP_TEST is not set # CONFIG_FTRACE_SORT_STARTUP_TEST is not set # CONFIG_RING_BUFFER_STARTUP_TEST is not set @@ -11694,13 +12461,13 @@ CONFIG_UNWINDER_ORC=y CONFIG_FUNCTION_ERROR_INJECTION=y # CONFIG_FAULT_INJECTION is not set CONFIG_ARCH_HAS_KCOV=y -CONFIG_CC_HAS_SANCOV_TRACE_PC=y # CONFIG_KCOV is not set CONFIG_RUNTIME_TESTING_MENU=y # CONFIG_TEST_DHRY is not set # CONFIG_LKDTM is not set # CONFIG_TEST_MIN_HEAP is not set # CONFIG_TEST_DIV64 is not set +# CONFIG_TEST_MULDIV64 is not set # CONFIG_BACKTRACE_SELF_TEST is not set # CONFIG_TEST_REF_TRACKER is not set # CONFIG_RBTREE_TEST is not set @@ -11710,11 +12477,7 @@ CONFIG_RUNTIME_TESTING_MENU=y # CONFIG_ATOMIC64_SELFTEST is not set CONFIG_ASYNC_RAID6_TEST=m # CONFIG_TEST_HEXDUMP is not set -# CONFIG_STRING_SELFTEST is not set -# CONFIG_TEST_STRING_HELPERS is not set # CONFIG_TEST_KSTRTOX is not set -# CONFIG_TEST_PRINTF is not set -# CONFIG_TEST_SCANF is not set # CONFIG_TEST_BITMAP is not set # CONFIG_TEST_UUID is not set # CONFIG_TEST_XARRAY is not set @@ -11725,16 +12488,16 @@ CONFIG_ASYNC_RAID6_TEST=m # CONFIG_TEST_LKM is not set # CONFIG_TEST_BITOPS is not set # CONFIG_TEST_VMALLOC is not set -# CONFIG_TEST_USER_COPY is not set # CONFIG_TEST_BPF is not set -# CONFIG_TEST_BLACKHOLE_DEV is not set # CONFIG_FIND_BIT_BENCHMARK is not set +# CONFIG_FIND_BIT_BENCHMARK_RUST is not set # CONFIG_TEST_FIRMWARE is not set # CONFIG_TEST_SYSCTL is not set # CONFIG_TEST_UDELAY is not set # CONFIG_TEST_STATIC_KEYS is not set # CONFIG_TEST_DYNAMIC_DEBUG is not set # CONFIG_TEST_KMOD is not set +# CONFIG_TEST_KALLSYMS is not set # CONFIG_TEST_MEMCAT_P is not set # CONFIG_TEST_OBJAGG is not set # CONFIG_TEST_MEMINIT is not set @@ -11743,6 +12506,7 @@ CONFIG_ASYNC_RAID6_TEST=m # CONFIG_TEST_FPU is not set # CONFIG_TEST_CLOCKSOURCE_WATCHDOG is not set # CONFIG_TEST_OBJPOOL is not set +# CONFIG_TEST_KEXEC_HANDOVER is not set CONFIG_ARCH_USE_MEMTEST=y CONFIG_MEMTEST=y # CONFIG_HYPERV_TESTING is not set @@ -11751,5 +12515,10 @@ CONFIG_MEMTEST=y # # Rust hacking # +# CONFIG_RUST_DEBUG_ASSERTIONS is not set +CONFIG_RUST_OVERFLOW_CHECKS=y +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set # end of Rust hacking # end of Kernel hacking + +CONFIG_IO_URING_ZCRX=y diff --git a/pkg/arch/kernel/surface.config b/pkg/arch/kernel/surface.config index 8e963d737e..cb40098f0f 120000 --- a/pkg/arch/kernel/surface.config +++ b/pkg/arch/kernel/surface.config @@ -1 +1 @@ -../../../configs/surface-6.8.config \ No newline at end of file +../../../configs/surface-6.18.config \ No newline at end of file diff --git a/pkg/debian/kernel/0001-Add-secureboot-pre-signing-to-the-kernel.patch b/pkg/debian/kernel/0001-Add-secureboot-pre-signing-to-the-kernel.patch index 51deb180c9..d836848f71 100644 --- a/pkg/debian/kernel/0001-Add-secureboot-pre-signing-to-the-kernel.patch +++ b/pkg/debian/kernel/0001-Add-secureboot-pre-signing-to-the-kernel.patch @@ -1,4 +1,4 @@ -From 43a7e8759162fa25aea203ce732f177bc6f15cdb Mon Sep 17 00:00:00 2001 +From 652e5a5d5d7263f8eaabbfdd9f8e064d897d50a3 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Sun, 22 Sep 2019 22:44:16 +0200 Subject: [PATCH] Add secureboot pre-signing to the kernel @@ -21,10 +21,10 @@ Signed-off-by: Dorian Stoll create mode 100755 scripts/sign_kernel.sh diff --git a/.gitignore b/.gitignore -index 7f86e0837909..04aaae490610 100644 +index 86a1ba0d9035..9af120b9591b 100644 --- a/.gitignore +++ b/.gitignore -@@ -152,6 +152,9 @@ signing_key.priv +@@ -161,6 +161,9 @@ signing_key.priv signing_key.x509 x509.genkey @@ -35,10 +35,10 @@ index 7f86e0837909..04aaae490610 100644 /all.config /alldef.config diff --git a/arch/x86/Makefile b/arch/x86/Makefile -index fdc2e3abd615..c7a374c7ceaf 100644 +index 1a27efcf3c20..b62a8d5f6702 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile -@@ -283,6 +283,7 @@ endif +@@ -308,6 +308,7 @@ endif $(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE) $(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot $(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@ @@ -83,5 +83,5 @@ index 000000000000..d2526a279254 +sbsign --key $BUILDDIR/keys/MOK.key --cert $BUILDDIR/keys/MOK.crt \ + --output $VMLINUX $VMLINUX -- -2.41.0 +2.52.0 diff --git a/pkg/debian/kernel/0001-Partially-revert-integrity-Only-use-machine-keyring-.patch b/pkg/debian/kernel/0001-Partially-revert-integrity-Only-use-machine-keyring-.patch index f80dc33031..dd171ac092 100644 --- a/pkg/debian/kernel/0001-Partially-revert-integrity-Only-use-machine-keyring-.patch +++ b/pkg/debian/kernel/0001-Partially-revert-integrity-Only-use-machine-keyring-.patch @@ -1,4 +1,4 @@ -From fbfaff58fe821fa93ceeb17e034886a6d8447207 Mon Sep 17 00:00:00 2001 +From b829c89ebc5a4b80f5063317655dc220310dce5a Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 20 Nov 2023 22:54:05 +0100 Subject: [PATCH] Partially revert "integrity: Only use machine keyring when @@ -17,7 +17,7 @@ loading. Therefore, revert this change. 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/security/integrity/platform_certs/machine_keyring.c b/security/integrity/platform_certs/machine_keyring.c -index a401640a63cd1..a1ad244cbf86d 100644 +index a401640a63cd..a1ad244cbf86 100644 --- a/security/integrity/platform_certs/machine_keyring.c +++ b/security/integrity/platform_certs/machine_keyring.c @@ -51,14 +51,7 @@ void __init add_to_machine_keyring(const char *source, const void *data, size_t @@ -37,5 +37,5 @@ index a401640a63cd1..a1ad244cbf86d 100644 static bool __init trust_moklist(void) -- -2.42.1 +2.52.0 diff --git a/pkg/debian/kernel/0001-add-sysctl-to-disallow-unprivileged-CLONE_NEWUSER-by.patch b/pkg/debian/kernel/0001-add-sysctl-to-disallow-unprivileged-CLONE_NEWUSER-by.patch new file mode 100644 index 0000000000..27a9ecdb7f --- /dev/null +++ b/pkg/debian/kernel/0001-add-sysctl-to-disallow-unprivileged-CLONE_NEWUSER-by.patch @@ -0,0 +1,110 @@ +From a01ff076551b58aa0f69b2132018edb4ee432ab2 Mon Sep 17 00:00:00 2001 +From: Serge Hallyn +Date: Fri, 31 May 2013 19:12:12 +0100 +Subject: [PATCH] add sysctl to disallow unprivileged CLONE_NEWUSER by default + +add sysctl to disallow unprivileged CLONE_NEWUSER by default + +This is a short-term patch. Unprivileged use of CLONE_NEWUSER +is certainly an intended feature of user namespaces. However +for at least saucy we want to make sure that, if any security +issues are found, we have a fail-safe. + +Signed-off-by: Serge Hallyn +[bwh: Remove unneeded binary sysctl bits] +[bwh: Keep this sysctl, but change the default to enabled] +--- + kernel/fork.c | 16 ++++++++++++++++ + kernel/sysctl.c | 13 +++++++++++++ + kernel/user_namespace.c | 3 +++ + 3 files changed, 32 insertions(+) + +diff --git a/kernel/fork.c b/kernel/fork.c +index 3da0f08615a9..8974312080fe 100644 +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -123,6 +123,12 @@ + + #include + ++#ifdef CONFIG_USER_NS ++extern int unprivileged_userns_clone; ++#else ++#define unprivileged_userns_clone 0 ++#endif ++ + /* + * Minimum number of threads to boot the kernel + */ +@@ -1946,6 +1952,10 @@ __latent_entropy struct task_struct *copy_process( + if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS)) + return ERR_PTR(-EINVAL); + ++ if ((clone_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) ++ if (!capable(CAP_SYS_ADMIN)) ++ return ERR_PTR(-EPERM); ++ + /* + * Thread groups must share signals as well, and detached threads + * can only be started up within the thread group. +@@ -3107,6 +3117,12 @@ int ksys_unshare(unsigned long unshare_flags) + if (unshare_flags & CLONE_NEWNS) + unshare_flags |= CLONE_FS; + ++ if ((unshare_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ err = -EPERM; ++ if (!capable(CAP_SYS_ADMIN)) ++ goto bad_unshare_out; ++ } ++ + err = check_unshare_flags(unshare_flags); + if (err) + goto bad_unshare_out; +diff --git a/kernel/sysctl.c b/kernel/sysctl.c +index cb6196e3fa99..c955e49a4b7a 100644 +--- a/kernel/sysctl.c ++++ b/kernel/sysctl.c +@@ -36,6 +36,10 @@ EXPORT_SYMBOL_GPL(sysctl_long_vals); + static const int ngroups_max = NGROUPS_MAX; + static const int cap_last_cap = CAP_LAST_CAP; + ++#ifdef CONFIG_USER_NS ++extern int unprivileged_userns_clone; ++#endif ++ + #ifdef CONFIG_PROC_SYSCTL + + /** +@@ -1455,6 +1459,15 @@ int proc_do_static_key(const struct ctl_table *table, int write, + } + + static const struct ctl_table sysctl_subsys_table[] = { ++#ifdef CONFIG_USER_NS ++ { ++ .procname = "unprivileged_userns_clone", ++ .data = &unprivileged_userns_clone, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec, ++ }, ++#endif + #ifdef CONFIG_PROC_SYSCTL + { + .procname = "sysctl_writes_strict", +diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c +index 03cb63883d04..163817801c17 100644 +--- a/kernel/user_namespace.c ++++ b/kernel/user_namespace.c +@@ -23,6 +23,9 @@ + #include + #include + ++/* sysctl */ ++int unprivileged_userns_clone = 1; ++ + static struct kmem_cache *user_ns_cachep __ro_after_init; + static DEFINE_MUTEX(userns_state_mutex); + +-- +2.52.0 + diff --git a/pkg/debian/kernel/0001-kbuild-Copy-config-to-target-directory.patch b/pkg/debian/kernel/0001-kbuild-Copy-config-to-target-directory.patch new file mode 100644 index 0000000000..d605f73072 --- /dev/null +++ b/pkg/debian/kernel/0001-kbuild-Copy-config-to-target-directory.patch @@ -0,0 +1,24 @@ +From fa4618b49419f0c64e2f2e83c2f2c1a42ae6fb79 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 20 Apr 2025 03:02:20 +0200 +Subject: [PATCH] kbuild: Copy config to target directory + +--- + scripts/package/install-extmod-build | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/scripts/package/install-extmod-build b/scripts/package/install-extmod-build +index 2576cf7902db..7b30ee536a43 100755 +--- a/scripts/package/install-extmod-build ++++ b/scripts/package/install-extmod-build +@@ -68,4 +68,7 @@ if [ "${CC}" != "${HOSTCC}" ]; then + rm -f "${destdir}/scripts/Kbuild" + fi + ++# copy .config manually to be where it's expected to be ++cp "${KCONFIG_CONFIG}" "${destdir}/.config" ++ + find "${destdir}" \( -name '.*.cmd' -o -name '*.o' \) -delete +-- +2.52.0 + diff --git a/pkg/debian/kernel/0001-kbuild-Link-sign-file-statically.patch b/pkg/debian/kernel/0001-kbuild-Link-sign-file-statically.patch new file mode 100644 index 0000000000..e2fec6571e --- /dev/null +++ b/pkg/debian/kernel/0001-kbuild-Link-sign-file-statically.patch @@ -0,0 +1,25 @@ +From 1eaf0059243d5f02ef579758629b7f7b1e2777b5 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 30 Jun 2024 17:10:28 +0200 +Subject: [PATCH] kbuild: Link sign-file statically + +--- + scripts/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/scripts/Makefile b/scripts/Makefile +index 46f860529df5..7846e18ef5d8 100644 +--- a/scripts/Makefile ++++ b/scripts/Makefile +@@ -29,7 +29,7 @@ HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include + HOSTLDLIBS_sorttable = -lpthread + HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include + HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) +-HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) ++HOSTLDLIBS_sign-file = -Wl,-Bstatic $(shell $(HOSTPKG_CONFIG) --static --libs libcrypto | sed 's/-ldl//' 2> /dev/null || echo -lcrypto) -Wl,-Bdynamic -ldl + + ifdef CONFIG_UNWINDER_ORC + ifeq ($(ARCH),x86_64) +-- +2.52.0 + diff --git a/pkg/debian/kernel/ubuntu.config b/pkg/debian/kernel/ubuntu.config index a8577bec08..137419756b 100644 --- a/pkg/debian/kernel/ubuntu.config +++ b/pkg/debian/kernel/ubuntu.config @@ -12,10 +12,7 @@ CONFIG_DEBUG_INFO_NONE=y ## ## Disable module compression ## -CONFIG_MODULE_COMPRESS_NONE=y -# CONFIG_MODULE_COMPRESS_GZIP is not set -# CONFIG_MODULE_COMPRESS_XZ is not set -# CONFIG_MODULE_COMPRESS_ZSTD is not set +# CONFIG_MODULE_COMPRESS is not set ## ## Do not build samples and tests @@ -64,6 +61,14 @@ CONFIG_ACPI_I2C_OPREGION=y # CONFIG_VIDEO_ATOMISP_OV5693 is not set +## +## Disable GDS mitigation. Enabling GDS mitigations disables AVX +## instructions, which causes a bunch of invalid opcode errors because +## some programs assume AVX is present. +## + +# CONFIG_MITIGATION_GDS_FORCE is not set + ## ## Disable evbug. It's annoying and potentially even a security risk when ## posting kernel logs... diff --git a/pkg/debian/kernel/version.conf b/pkg/debian/kernel/version.conf index 36b476aca3..1a734eb33c 100644 --- a/pkg/debian/kernel/version.conf +++ b/pkg/debian/kernel/version.conf @@ -1,3 +1,3 @@ -KERNEL_VERSION="6.8.1" +KERNEL_VERSION="6.18.8" KERNEL_REVISION="1" KERNEL_LOCALVERSION="-surface" diff --git a/pkg/fedora/kernel-surface/build-ark.py b/pkg/fedora/kernel-surface/build-ark.py index 2fa9201044..861cc7963c 100755 --- a/pkg/fedora/kernel-surface/build-ark.py +++ b/pkg/fedora/kernel-surface/build-ark.py @@ -113,7 +113,7 @@ def system(cmd: str) -> None: # Apply patches for patch in patches: - system("git am '%s'" % patch) + system("git am -3 '%s'" % patch) # Copy files for file in files: diff --git a/pkg/fedora/kernel-surface/build-linux-surface.py b/pkg/fedora/kernel-surface/build-linux-surface.py index 49b950c998..ed2f4dcc19 100755 --- a/pkg/fedora/kernel-surface/build-linux-surface.py +++ b/pkg/fedora/kernel-surface/build-linux-surface.py @@ -4,6 +4,7 @@ import subprocess import sys from pathlib import Path +import logging ##################################################################### @@ -18,7 +19,7 @@ ## Fedora tags: kernel-X.Y.Z ## Upstream tags: vX.Y.Z ## -PACKAGE_TAG = "kernel-6.8.0-63" +PACKAGE_TAG = "kernel-6.18.8-0" ## ## The release number of the modified kernel package. @@ -86,7 +87,7 @@ # Check if the major version is supported. if not patches.exists() or not config.exists(): - print("ERROR: Could not find patches / configs for kernel %s!" % kernel_major) + logging.error("ERROR: Could not find patches / configs for kernel %s!" % kernel_major) sys.exit(1) # Check if Secure Boot keys are available. @@ -94,12 +95,11 @@ # If we are building without secureboot, require user input to continue. if not sb_avail: - print("") - print("Secure Boot keys were not configured! Using Red Hat testkeys.") - print("The compiled kernel will not boot with Secure Boot enabled!") - print("") + logging.warning("Secure Boot keys were not configured! Using Red Hat testkeys.") + logging.warning("The compiled kernel will not boot with Secure Boot enabled!") - input("Press enter to continue: ") + if sys.stdin.isatty(): + input("Press enter to continue: ") # Expand globs surface_patches = sorted(patches.glob("*.patch")) diff --git a/pkg/fedora/kernel-surface/configs/fedora.config b/pkg/fedora/kernel-surface/configs/fedora.config index 8ab727953a..f71a583746 100644 --- a/pkg/fedora/kernel-surface/configs/fedora.config +++ b/pkg/fedora/kernel-surface/configs/fedora.config @@ -12,3 +12,75 @@ CONFIG_VIDEO_V4L2_SUBDEV_API=y ## Disable (wrong) NX compatibility flag. ## # CONFIG_EFI_DXE_MEM_ATTRIBUTES is not set + +## +## UFS pulls in DEVFREQ and NLS modules. Fedora's packaging system complains +## and aborts if any options aren't specified, so specify them explicitly here. +## +CONFIG_DEVFREQ_THERMAL=y +CONFIG_SCSI_UFS_BSG=y +CONFIG_SCSI_UFS_CRYPTO=y +# CONFIG_SCSI_UFS_FAULT_INJECTION is not set +# CONFIG_SCSI_UFS_DWC_TC_PCI is not set +# CONFIG_SCSI_UFS_TI_J721E is not set +# CONFIG_SCSI_UFS_CDNS_PLATFORM is not set +# CONFIG_SCSI_UFS_DWC_TC_PLATFORM is not set +# CONFIG_SCSI_UFS_QCOM is not set +# CONFIG_SCSI_UFS_HISI is not set +CONFIG_SCSI_UFSHCD=y +CONFIG_SCSI_UFSHCD_PLATFORM=m +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +CONFIG_DEVFREQ_GOV_PERFORMANCE=y +CONFIG_DEVFREQ_GOV_POWERSAVE=y +CONFIG_DEVFREQ_GOV_USERSPACE=y +CONFIG_DEVFREQ_GOV_PASSIVE=y +CONFIG_PM_DEVFREQ=y +CONFIG_PM_DEVFREQ_EVENT=y +CONFIG_NLS=y +CONFIG_NLS_CODEPAGE_737=y +CONFIG_NLS_CODEPAGE_775=y +CONFIG_NLS_CODEPAGE_850=y +CONFIG_NLS_CODEPAGE_852=y +CONFIG_NLS_CODEPAGE_855=y +CONFIG_NLS_CODEPAGE_857=y +CONFIG_NLS_CODEPAGE_860=y +CONFIG_NLS_CODEPAGE_861=y +CONFIG_NLS_CODEPAGE_862=y +CONFIG_NLS_CODEPAGE_863=y +CONFIG_NLS_CODEPAGE_864=y +CONFIG_NLS_CODEPAGE_865=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_869=y +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_CODEPAGE_950=y +CONFIG_NLS_CODEPAGE_932=y +CONFIG_NLS_CODEPAGE_949=y +CONFIG_NLS_CODEPAGE_874=y +CONFIG_NLS_ISO8859_8=y +CONFIG_NLS_CODEPAGE_1250=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_2=y +CONFIG_NLS_ISO8859_3=y +CONFIG_NLS_ISO8859_4=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_ISO8859_6=y +CONFIG_NLS_ISO8859_7=y +CONFIG_NLS_ISO8859_9=y +CONFIG_NLS_ISO8859_13=y +CONFIG_NLS_ISO8859_14=y +CONFIG_NLS_ISO8859_15=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_KOI8_U=y +CONFIG_NLS_MAC_ROMAN=y +CONFIG_NLS_MAC_CELTIC=y +CONFIG_NLS_MAC_CENTEURO=y +CONFIG_NLS_MAC_CROATIAN=y +CONFIG_NLS_MAC_CYRILLIC=y +CONFIG_NLS_MAC_GAELIC=y +CONFIG_NLS_MAC_GREEK=y +CONFIG_NLS_MAC_ICELAND=y +CONFIG_NLS_MAC_INUIT=y +CONFIG_NLS_MAC_ROMANIAN=y +CONFIG_NLS_MAC_TURKISH=y +CONFIG_NLS_UTF8=y diff --git a/pkg/fedora/kernel-surface/patches/0001-iptsd.patch b/pkg/fedora/kernel-surface/patches/0001-Pull-in-iptsd-as-a-weak-dependency.patch similarity index 73% rename from pkg/fedora/kernel-surface/patches/0001-iptsd.patch rename to pkg/fedora/kernel-surface/patches/0001-Pull-in-iptsd-as-a-weak-dependency.patch index d35cffa88c..be10be8f3d 100644 --- a/pkg/fedora/kernel-surface/patches/0001-iptsd.patch +++ b/pkg/fedora/kernel-surface/patches/0001-Pull-in-iptsd-as-a-weak-dependency.patch @@ -1,7 +1,7 @@ -From 1cad947df5f2ec874863e42cce595a76dad64cc2 Mon Sep 17 00:00:00 2001 +From 5747933cc59471903b75a640b7adc3ee152cb92f Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Sat, 22 Jul 2023 10:33:03 +0200 -Subject: [PATCH] Pull in iptsd as a weak dependency +Subject: [PATCH 1/6] Pull in iptsd as a weak dependency Signed-off-by: Dorian Stoll --- @@ -9,10 +9,10 @@ Signed-off-by: Dorian Stoll 1 file changed, 1 insertion(+) diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template -index d3dafe56aa64..24823cd8b784 100644 +index 08ff3553cecc..042a9736e324 100644 --- a/redhat/kernel.spec.template +++ b/redhat/kernel.spec.template -@@ -1052,6 +1052,7 @@ Requires(pre): %{kernel_prereq}\ +@@ -1191,6 +1191,7 @@ Requires(pre): %{kernel_prereq}\ Requires(pre): %{initrd_prereq}\ Requires(pre): ((linux-firmware >= 20150904-56.git6ebf5d57) if linux-firmware)\ Recommends: linux-firmware\ @@ -21,5 +21,5 @@ index d3dafe56aa64..24823cd8b784 100644 Conflicts: xfsprogs < 4.3.0-1\ Conflicts: xorg-x11-drv-vmmouse < 13.0.99\ -- -2.41.0 +2.52.0 diff --git a/pkg/fedora/kernel-surface/patches/0002-Let-kernel-surface-provide-the-standard-package-name.patch b/pkg/fedora/kernel-surface/patches/0002-Let-kernel-surface-provide-the-standard-package-name.patch new file mode 100644 index 0000000000..d97e8f47fb --- /dev/null +++ b/pkg/fedora/kernel-surface/patches/0002-Let-kernel-surface-provide-the-standard-package-name.patch @@ -0,0 +1,183 @@ +From 3a68edfb0846e4fea4a947cb70bf3c2eb181ad47 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Tue, 30 Sep 2025 20:09:20 +0200 +Subject: [PATCH 2/6] Let kernel-surface provide the standard package names + +Signed-off-by: Dorian Stoll +--- + redhat/kernel.spec.template | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template +index 042a9736e324..23ed1c854ed4 100644 +--- a/redhat/kernel.spec.template ++++ b/redhat/kernel.spec.template +@@ -747,6 +747,7 @@ Requires: %{name}-modules-uname-r = %{KVERREL} + Requires: %{name}-modules-core-uname-r = %{KVERREL} + Requires: ((%{name}-modules-extra-uname-r = %{KVERREL}) if %{name}-modules-extra-matched) + Provides: installonlypkg(kernel) ++Provides: kernel = %{KVERREL} + %endif + + +@@ -1184,7 +1185,9 @@ The %{package_name} meta package + Provides: kernel = %{specversion}-%{pkg_release}\ + Provides: %{name} = %{specversion}-%{pkg_release}\ + %endif\ ++Provides: kernel-%{_target_cpu} = %{specrpmversion}-%{pkg_release}%{uname_suffix %{?1}}\ + Provides: %{name}-%{_target_cpu} = %{specrpmversion}-%{pkg_release}%{uname_suffix %{?1}}\ ++Provides: kernel-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires(pre): %{kernel_prereq}\ +@@ -1223,6 +1226,7 @@ Summary: Header files for the Linux kernel for use by glibc + Obsoletes: glibc-kernheaders < 3.0-46 + Provides: glibc-kernheaders = 3.0-46 + %if 0%{?gemini} ++Provides: kernel-headers = %{specversion}-%{release} + Provides: %{name}-headers = %{specversion}-%{release} + Obsoletes: kernel-headers < %{specversion} + %endif +@@ -1238,6 +1242,7 @@ glibc package. + %package cross-headers + Summary: Header files for the Linux kernel for use by cross-glibc + %if 0%{?gemini} ++Provides: kernel-cross-headers = %{specversion}-%{release} + Provides: %{name}-cross-headers = %{specversion}-%{release} + Obsoletes: kernel-cross-headers < %{specversion} + %endif +@@ -1477,6 +1482,7 @@ Linux kernel, suitable for the kabi-dw tool. + %package %{?1:%{1}-}debuginfo\ + Summary: Debug information for package %{name}%{?1:-%{1}}\ + Requires: %{name}-debuginfo-common-%{_target_cpu} = %{specrpmversion}-%{release}\ ++Provides: kernel%{?1:-%{1}}-debuginfo-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: %{name}%{?1:-%{1}}-debuginfo-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: installonlypkg(kernel)\ + AutoReqProv: no\ +@@ -1493,10 +1499,13 @@ This is required to use SystemTap with %{name}%{?1:-%{1}}-%{KVERREL}.\ + %define kernel_devel_package(m) \ + %package %{?1:%{1}-}devel\ + Summary: Development package for building kernel modules to match the %{?2:%{2} }kernel\ ++Provides: kernel%{?1:-%{1}}-devel-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: %{name}%{?1:-%{1}}-devel-%{_target_cpu} = %{specrpmversion}-%{release}\ ++Provides: kernel-devel-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}-devel-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: kernel-devel-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}-devel-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ ++Provides: kernel-devel = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel)\ + AutoReqProv: no\ + Requires(pre): findutils\ +@@ -1526,6 +1535,7 @@ against the %{?2:%{2} }kernel package.\ + Summary: Meta package to install matching core and devel packages for a given %{?2:%{2} }kernel\ + Requires: %{package_name}%{?1:-%{1}}-devel = %{specrpmversion}-%{release}\ + Requires: %{package_name}%{?1:-%{1}}-core = %{specrpmversion}-%{release}\ ++Provides: kernel-devel-matched = %{specrpmversion}-%{release}\ + %description %{?1:%{1}-}devel-matched\ + This meta package is used to install matching core and devel packages for a given %{?2:%{2} }kernel.\ + %{nil} +@@ -1545,10 +1555,14 @@ This meta package provides a single reference that other packages can Require to + %package %{?1:%{1}-}modules-internal\ + Summary: Extra kernel modules to match the %{?2:%{2} }kernel\ + Group: System Environment/Kernel\ ++Provides: kernel%{?1:-%{1}}-modules-internal-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: %{name}%{?1:-%{1}}-modules-internal-%{_target_cpu} = %{specrpmversion}-%{release}\ ++Provides: kernel%{?1:-%{1}}-modules-internal-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-internal-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ ++Provides: kernel%{?1:-%{1}}-modules-internal = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-internal = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel-module)\ ++Provides: kernel%{?1:-%{1}}-modules-internal-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-internal-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ +@@ -1566,10 +1580,14 @@ This package provides kernel modules for the %{?2:%{2} }kernel package for Red H + %define kernel_modules_extra_package(m) \ + %package %{?1:%{1}-}modules-extra\ + Summary: Extra kernel modules to match the %{?2:%{2} }kernel\ ++Provides: kernel%{?1:-%{1}}-modules-extra-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: %{name}%{?1:-%{1}}-modules-extra-%{_target_cpu} = %{specrpmversion}-%{release}\ ++Provides: kernel%{?1:-%{1}}-modules-extra-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-extra-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ ++Provides: kernel%{?1:-%{1}}-modules-extra = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-extra = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel-module)\ ++Provides: kernel%{?1:-%{1}}-modules-extra-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-extra-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ +@@ -1590,10 +1608,14 @@ This package provides less commonly used kernel modules for the %{?2:%{2} }kerne + %define kernel_modules_package(m) \ + %package %{?1:%{1}-}modules\ + Summary: kernel modules to match the %{?2:%{2}-}core kernel\ ++Provides: kernel%{?1:-%{1}}-modules-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: %{name}%{?1:-%{1}}-modules-%{_target_cpu} = %{specrpmversion}-%{release}\ ++Provides: kernel-modules-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}-modules-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ ++Provides: kernel-modules = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}-modules = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel-module)\ ++Provides: kernel%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ +@@ -1613,10 +1635,14 @@ This package provides commonly used kernel modules for the %{?2:%{2}-}core kerne + %define kernel_modules_core_package(m) \ + %package %{?1:%{1}-}modules-core\ + Summary: Core kernel modules to match the %{?2:%{2}-}core kernel\ ++Provides: kernel%{?1:-%{1}}-modules-core-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: %{name}%{?1:-%{1}}-modules-core-%{_target_cpu} = %{specrpmversion}-%{release}\ ++Provides: kernel-modules-core-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}-modules-core-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ ++Provides: kernel-modules-core = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}-modules-core = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel-module)\ ++Provides: kernel%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + %if %{-m:1}%{!-m:0}\ +@@ -1643,6 +1669,7 @@ Requires: ((%{name}-%{1}-modules-extra-uname-r = %{KVERREL}%{uname_suffix %{1}}) + Requires: realtime-setup\ + %endif\ + Provides: installonlypkg(kernel)\ ++Provides: kernel = %{KVERREL}+%{1}\ + %description %{1}\ + The meta-package for the %{1} kernel\ + %{nil} +@@ -1659,8 +1686,10 @@ The meta-package for the %{1} kernel\ + %define kernel_variant_package(n:mo) \ + %package %{?1:%{1}-}core\ + Summary: %{variant_summary}\ ++Provides: kernel-%{?1:%{1}-}core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}-%{?1:%{1}-}core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel)\ ++Provides: kernel-%{?1:%{1}-}core = %{KVERREL}%{uname_suffix %{?1}}\ + %if %{-m:1}%{!-m:0}\ + Requires: %{name}-core-uname-r = %{KVERREL}%{uname_variant %{?1}}\ + Requires: %{name}-%{?1:%{1}-}-modules-core-uname-r = %{KVERREL}%{uname_variant %{?1}}\ +@@ -1685,6 +1714,7 @@ Requires: %{name}-%{?1:%{1}-}-modules-core-uname-r = %{KVERREL}%{uname_variant % + %package %{?1:%{1}-}uki-virt\ + Summary: %{variant_summary} unified kernel image for virtual machines\ + Provides: installonlypkg(kernel)\ ++Provides: kernel-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires(pre): %{kernel_prereq}\ +@@ -1709,10 +1739,14 @@ Requires(pre): systemd >= 254-1\ + %package %{?1:%{1}-}modules-partner\ + Summary: Extra kernel modules to match the %{?2:%{2} }kernel\ + Group: System Environment/Kernel\ ++Provides: kernel%{?1:-%{1}}-modules-partner-%{_target_cpu} = %{specrpmversion}-%{release}\ + Provides: %{name}%{?1:-%{1}}-modules-partner-%{_target_cpu} = %{specrpmversion}-%{release}\ ++Provides: kernel%{?1:-%{1}}-modules-partner-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-partner-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ ++Provides: kernel%{?1:-%{1}}-modules-partner = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-partner = %{specrpmversion}-%{release}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel-module)\ ++Provides: kernel%{?1:-%{1}}-modules-partner-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-partner-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ +-- +2.52.0 + diff --git a/pkg/fedora/kernel-surface/patches/0002-provides.patch b/pkg/fedora/kernel-surface/patches/0002-provides.patch deleted file mode 100644 index 1f032adfc7..0000000000 --- a/pkg/fedora/kernel-surface/patches/0002-provides.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 17907fed744ae17414a1ad8bebc335e12dac7691 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Sat, 22 Jul 2023 10:34:38 +0200 -Subject: [PATCH] Let kernel-surface provide the standard package names - -Signed-off-by: Dorian Stoll ---- - redhat/kernel.spec.template | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template -index 24823cd8b784..ea7de5884a89 100644 ---- a/redhat/kernel.spec.template -+++ b/redhat/kernel.spec.template -@@ -697,6 +697,7 @@ Requires: kernel-core-uname-r = %{KVERREL} - Requires: kernel-modules-uname-r = %{KVERREL} - Requires: kernel-modules-core-uname-r = %{KVERREL} - Provides: installonlypkg(kernel) -+Provides: kernel = %{KVERREL} - %endif - - -@@ -1352,6 +1353,7 @@ Summary: Development package for building kernel modules to match the %{?2:%{2} - Provides: kernel%{?1:-%{1}}-devel-%{_target_cpu} = %{specrpmversion}-%{release}\ - Provides: kernel-devel-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suffix %{?1:+%{1}}}\ - Provides: kernel-devel-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ -+Provides: kernel-devel = %{KVERREL}%{?1:+%{1}}\ - Provides: installonlypkg(kernel)\ - AutoReqProv: no\ - Requires(pre): findutils\ -@@ -1381,6 +1383,7 @@ against the %{?2:%{2} }kernel package.\ - Summary: Meta package to install matching core and devel packages for a given %{?2:%{2} }kernel\ - Requires: %{package_name}%{?1:-%{1}}-devel = %{specrpmversion}-%{release}\ - Requires: %{package_name}%{?1:-%{1}}-core = %{specrpmversion}-%{release}\ -+Provides: kernel-devel-matched = %{specrpmversion}-%{release}\ - %description %{?1:%{1}-}devel-matched\ - This meta package is used to install matching core and devel packages for a given %{?2:%{2} }kernel.\ - %{nil} -@@ -1502,6 +1505,7 @@ Requires: kernel-%{1}-modules-core-uname-r = %{KVERREL}+%{uname_suffix %{1}}\ - Requires: realtime-setup\ - %endif\ - Provides: installonlypkg(kernel)\ -+Provides: kernel = %{KVERREL}+%{1}\ - %description %{1}\ - The meta-package for the %{1} kernel\ - %{nil} -@@ -1534,6 +1538,7 @@ This package provides KVM modules for package kernel%{?1:-%{1}}.\ - Summary: %{variant_summary}\ - Provides: kernel-%{?1:%{1}-}core-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Provides: installonlypkg(kernel)\ -+Provides: kernel-%{?1:%{1}-}core = %{KVERREL}%{?1:+%{1}}\ - %if %{-m:1}%{!-m:0}\ - Requires: kernel-core-uname-r = %{KVERREL}%{uname_variant %{?1:+%{1}}}\ - Requires: kernel-%{?1:%{1}-}-modules-core-uname-r = %{KVERREL}%{uname_variant %{?1:+%{1}}}\ --- -2.41.0 - diff --git a/pkg/fedora/kernel-surface/patches/0003-Let-kernel-surface-conflict-with-older-versions-of-t.patch b/pkg/fedora/kernel-surface/patches/0003-Let-kernel-surface-conflict-with-older-versions-of-t.patch new file mode 100644 index 0000000000..7f5234cd94 --- /dev/null +++ b/pkg/fedora/kernel-surface/patches/0003-Let-kernel-surface-conflict-with-older-versions-of-t.patch @@ -0,0 +1,92 @@ +From e36d14a3c95845d54befe766b15a89d9ca65e1ef Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Tue, 30 Sep 2025 20:10:38 +0200 +Subject: [PATCH 3/6] Let kernel-surface conflict with older versions of the + package + +Signed-off-by: Dorian Stoll +--- + redhat/kernel.spec.template | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template +index 23ed1c854ed4..81bb35bb8e36 100644 +--- a/redhat/kernel.spec.template ++++ b/redhat/kernel.spec.template +@@ -748,6 +748,10 @@ Requires: %{name}-modules-core-uname-r = %{KVERREL} + Requires: ((%{name}-modules-extra-uname-r = %{KVERREL}) if %{name}-modules-extra-matched) + Provides: installonlypkg(kernel) + Provides: kernel = %{KVERREL} ++ ++Conflicts: kernel-surface < 6.3.6-1 ++Obsoletes: kernel-surface < 6.3.6-1 ++ + %endif + + +@@ -1507,6 +1511,8 @@ Provides: kernel-devel-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}-devel-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: kernel-devel = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel)\ ++Conflicts: %{package_name}-devel < 6.3.7-2\ ++Obsoletes: %{package_name}-devel < 6.3.7-2\ + AutoReqProv: no\ + Requires(pre): findutils\ + Requires: findutils\ +@@ -1536,6 +1542,8 @@ Summary: Meta package to install matching core and devel packages for a given %{ + Requires: %{package_name}%{?1:-%{1}}-devel = %{specrpmversion}-%{release}\ + Requires: %{package_name}%{?1:-%{1}}-core = %{specrpmversion}-%{release}\ + Provides: kernel-devel-matched = %{specrpmversion}-%{release}\ ++Conflicts: %{package_name}-devel-matched < 6.3.7-2\ ++Obsoletes: %{package_name}-devel-matched < 6.3.7-2\ + %description %{?1:%{1}-}devel-matched\ + This meta package is used to install matching core and devel packages for a given %{?2:%{2} }kernel.\ + %{nil} +@@ -1592,6 +1600,8 @@ Provides: %{name}%{?1:-%{1}}-modules-extra-uname-r = %{KVERREL}%{uname_suffix %{ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ ++Conflicts: %{package_name}-modules-extra < 6.3.7-2\ ++Obsoletes: %{package_name}-modules-extra < 6.3.7-2\ + %if %{-m:1}%{!-m:0}\ + Requires: %{name}-modules-extra-uname-r = %{KVERREL}%{uname_variant %{?1}}\ + %endif\ +@@ -1619,6 +1629,8 @@ Provides: kernel%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ ++Conflicts: %{package_name}-modules < 6.3.7-2\ ++Obsoletes: %{package_name}-modules < 6.3.7-2\ + %if %{-m:1}%{!-m:0}\ + Requires: %{name}-modules-uname-r = %{KVERREL}%{uname_variant %{?1}}\ + %endif\ +@@ -1645,6 +1657,8 @@ Provides: installonlypkg(kernel-module)\ + Provides: kernel%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Requires: %{name}-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ ++Conflicts: %{package_name}-modules-core < 6.3.7-2\ ++Obsoletes: %{package_name}-modules-core < 6.3.7-2\ + %if %{-m:1}%{!-m:0}\ + Requires: %{name}-modules-core-uname-r = %{KVERREL}%{uname_variant %{?1}}\ + %endif\ +@@ -1668,6 +1682,8 @@ Requires: ((%{name}-%{1}-modules-extra-uname-r = %{KVERREL}%{uname_suffix %{1}}) + %if "%{1}" == "rt" || "%{1}" == "rt-debug" || "%{1}" == "rt-64k" || "%{1}" == "rt-64k-debug"\ + Requires: realtime-setup\ + %endif\ ++Conflicts: %{package_name} < 6.3.7-2\ ++Obsoletes: %{package_name} < 6.3.7-2\ + Provides: installonlypkg(kernel)\ + Provides: kernel = %{KVERREL}+%{1}\ + %description %{1}\ +@@ -1690,6 +1706,8 @@ Provides: kernel-%{?1:%{1}-}core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: %{name}-%{?1:%{1}-}core-uname-r = %{KVERREL}%{uname_suffix %{?1}}\ + Provides: installonlypkg(kernel)\ + Provides: kernel-%{?1:%{1}-}core = %{KVERREL}%{uname_suffix %{?1}}\ ++Conflicts: %{package_name}-core < 6.3.7-2\ ++Obsoletes: %{package_name}-core < 6.3.7-2\ + %if %{-m:1}%{!-m:0}\ + Requires: %{name}-core-uname-r = %{KVERREL}%{uname_variant %{?1}}\ + Requires: %{name}-%{?1:%{1}-}-modules-core-uname-r = %{KVERREL}%{uname_variant %{?1}}\ +-- +2.52.0 + diff --git a/pkg/fedora/kernel-surface/patches/0003-obsoletes.patch b/pkg/fedora/kernel-surface/patches/0003-obsoletes.patch deleted file mode 100644 index 2146a7b37f..0000000000 --- a/pkg/fedora/kernel-surface/patches/0003-obsoletes.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 819c1b06089925d6944bf2dad9dc29649b7510d5 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Sat, 22 Jul 2023 10:40:32 +0200 -Subject: [PATCH] Let kernel-surface conflict with older versions of the - package - -Signed-off-by: Dorian Stoll ---- - redhat/kernel.spec.template | 20 ++++++++++++++++++++ - 1 file changed, 20 insertions(+) - -diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template -index ea7de5884a89..43dce82a9d36 100644 ---- a/redhat/kernel.spec.template -+++ b/redhat/kernel.spec.template -@@ -698,6 +698,10 @@ Requires: kernel-modules-uname-r = %{KVERREL} - Requires: kernel-modules-core-uname-r = %{KVERREL} - Provides: installonlypkg(kernel) - Provides: kernel = %{KVERREL} -+ -+Conflicts: kernel-surface < 6.3.6-1 -+Obsoletes: kernel-surface < 6.3.6-1 -+ - %endif - - -@@ -1355,6 +1359,8 @@ Provides: kernel-devel-%{_target_cpu} = %{specrpmversion}-%{release}%{uname_suff - Provides: kernel-devel-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Provides: kernel-devel = %{KVERREL}%{?1:+%{1}}\ - Provides: installonlypkg(kernel)\ -+Conflicts: %{package_name}-devel < 6.3.7-2\ -+Obsoletes: %{package_name}-devel < 6.3.7-2\ - AutoReqProv: no\ - Requires(pre): findutils\ - Requires: findutils\ -@@ -1384,6 +1390,8 @@ Summary: Meta package to install matching core and devel packages for a given %{ - Requires: %{package_name}%{?1:-%{1}}-devel = %{specrpmversion}-%{release}\ - Requires: %{package_name}%{?1:-%{1}}-core = %{specrpmversion}-%{release}\ - Provides: kernel-devel-matched = %{specrpmversion}-%{release}\ -+Conflicts: %{package_name}-devel-matched < 6.3.7-2\ -+Obsoletes: %{package_name}-devel-matched < 6.3.7-2\ - %description %{?1:%{1}-}devel-matched\ - This meta package is used to install matching core and devel packages for a given %{?2:%{2} }kernel.\ - %{nil} -@@ -1416,6 +1424,8 @@ Provides: kernel%{?1:-%{1}}-modules-internal-uname-r = %{KVERREL}%{uname_suffix - Requires: kernel-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Requires: kernel%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Requires: kernel%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ -+Conflicts: %{package_name}-modules-internal < 6.3.7-2\ -+Obsoletes: %{package_name}-modules-internal < 6.3.7-2\ - AutoReq: no\ - AutoProv: yes\ - %description %{?1:%{1}-}modules-internal\ -@@ -1437,6 +1447,8 @@ Provides: kernel%{?1:-%{1}}-modules-extra-uname-r = %{KVERREL}%{uname_suffix %{? - Requires: kernel-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Requires: kernel%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Requires: kernel%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ -+Conflicts: %{package_name}-modules-extra < 6.3.7-2\ -+Obsoletes: %{package_name}-modules-extra < 6.3.7-2\ - %if %{-m:1}%{!-m:0}\ - Requires: kernel-modules-extra-uname-r = %{KVERREL}%{uname_variant %{?1:+%{1}}}\ - %endif\ -@@ -1460,6 +1472,8 @@ Provides: installonlypkg(kernel-module)\ - Provides: kernel%{?1:-%{1}}-modules-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Requires: kernel-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Requires: kernel%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ -+Conflicts: %{package_name}-modules < 6.3.7-2\ -+Obsoletes: %{package_name}-modules < 6.3.7-2\ - %if %{-m:1}%{!-m:0}\ - Requires: kernel-modules-uname-r = %{KVERREL}%{uname_variant %{?1:+%{1}}}\ - %endif\ -@@ -1482,6 +1496,8 @@ Provides: kernel-modules-core = %{specrpmversion}-%{release}%{uname_suffix %{?1: - Provides: installonlypkg(kernel-module)\ - Provides: kernel%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Requires: kernel-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ -+Conflicts: %{package_name}-modules-core < 6.3.7-2\ -+Obsoletes: %{package_name}-modules-core < 6.3.7-2\ - %if %{-m:1}%{!-m:0}\ - Requires: kernel-modules-core-uname-r = %{KVERREL}%{uname_variant %{?1:+%{1}}}\ - %endif\ -@@ -1504,6 +1520,8 @@ Requires: kernel-%{1}-modules-core-uname-r = %{KVERREL}+%{uname_suffix %{1}}\ - %if "%{1}" == "rt" || "%{1}" == "rt-debug"\ - Requires: realtime-setup\ - %endif\ -+Conflicts: %{package_name} < 6.3.7-2\ -+Obsoletes: %{package_name} < 6.3.7-2\ - Provides: installonlypkg(kernel)\ - Provides: kernel = %{KVERREL}+%{1}\ - %description %{1}\ -@@ -1539,6 +1557,8 @@ Summary: %{variant_summary}\ - Provides: kernel-%{?1:%{1}-}core-uname-r = %{KVERREL}%{uname_suffix %{?1:+%{1}}}\ - Provides: installonlypkg(kernel)\ - Provides: kernel-%{?1:%{1}-}core = %{KVERREL}%{?1:+%{1}}\ -+Conflicts: %{package_name}-core < 6.3.7-2\ -+Obsoletes: %{package_name}-core < 6.3.7-2\ - %if %{-m:1}%{!-m:0}\ - Requires: kernel-core-uname-r = %{KVERREL}%{uname_variant %{?1:+%{1}}}\ - Requires: kernel-%{?1:%{1}-}-modules-core-uname-r = %{KVERREL}%{uname_variant %{?1:+%{1}}}\ --- -2.41.0 - diff --git a/pkg/fedora/kernel-surface/patches/0004-default-kernel.patch b/pkg/fedora/kernel-surface/patches/0004-Install-scripts-and-service-files-for-keeping-the-su.patch similarity index 76% rename from pkg/fedora/kernel-surface/patches/0004-default-kernel.patch rename to pkg/fedora/kernel-surface/patches/0004-Install-scripts-and-service-files-for-keeping-the-su.patch index 7b273304ef..f85919bc53 100644 --- a/pkg/fedora/kernel-surface/patches/0004-default-kernel.patch +++ b/pkg/fedora/kernel-surface/patches/0004-Install-scripts-and-service-files-for-keeping-the-su.patch @@ -1,19 +1,19 @@ -From 81141a454f41cbc5fd41b778f85b10552c8676e8 Mon Sep 17 00:00:00 2001 +From f7f864a34eb97847097e2250129a8b04dfeb8bff Mon Sep 17 00:00:00 2001 From: Dorian Stoll -Date: Sat, 22 Jul 2023 10:41:11 +0200 -Subject: [PATCH] Install scripts and service files for keeping the surface +Date: Tue, 30 Sep 2025 20:12:29 +0200 +Subject: [PATCH 4/6] Install scripts and service files for keeping the surface kernel the default Signed-off-by: Dorian Stoll --- - redhat/kernel.spec.template | 34 ++++++++++++++++++++++++++++++++++ - 1 file changed, 34 insertions(+) + redhat/kernel.spec.template | 33 +++++++++++++++++++++++++++++++++ + 1 file changed, 33 insertions(+) diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template -index 43dce82a9d36..28df94e561d4 100644 +index 81bb35bb8e36..67aaf5883051 100644 --- a/redhat/kernel.spec.template +++ b/redhat/kernel.spec.template -@@ -1025,6 +1025,11 @@ Source4000: README.rst +@@ -1161,6 +1161,11 @@ Source4000: README.rst Source4001: rpminspect.yaml Source4002: gating.yaml @@ -25,7 +25,7 @@ index 43dce82a9d36..28df94e561d4 100644 ## Patches needed for building this package %if !%{nopatches} -@@ -1058,6 +1063,7 @@ Requires(pre): %{initrd_prereq}\ +@@ -1199,6 +1204,7 @@ Requires(pre): %{initrd_prereq}\ Requires(pre): ((linux-firmware >= 20150904-56.git6ebf5d57) if linux-firmware)\ Recommends: linux-firmware\ Recommends: iptsd\ @@ -33,7 +33,7 @@ index 43dce82a9d36..28df94e561d4 100644 Requires(preun): systemd >= 200\ Conflicts: xfsprogs < 4.3.0-1\ Conflicts: xorg-x11-drv-vmmouse < 13.0.99\ -@@ -1072,6 +1078,14 @@ AutoProv: yes\ +@@ -1213,6 +1219,13 @@ AutoProv: yes\ %{nil} @@ -43,12 +43,11 @@ index 43dce82a9d36..28df94e561d4 100644 +This package provides a systemd service that will automatically keep +the surface kernel as the default kernel in GRUB, even if a newer stock +kernel has been installed. -+ + %package doc Summary: Various documentation bits found in the kernel source Group: Documentation -@@ -2945,6 +2959,11 @@ find Documentation -type d | xargs chmod u+w +@@ -3475,6 +3488,11 @@ find Documentation -type d | xargs chmod u+w cd linux-%{KVERREL} @@ -57,10 +56,10 @@ index 43dce82a9d36..28df94e561d4 100644 +install -D -m755 "%{SOURCE4102}" -t "%{buildroot}%{_bindir}" +install -D -m755 "%{SOURCE4103}" -t "%{buildroot}%{_presetdir}" + - %if %{with_doc} - docdir=$RPM_BUILD_ROOT%{_datadir}/doc/kernel-doc-%{specversion}-%{pkgrelease} + # re-define RPM_VMLINUX_H, because it doesn't carry over from %build + RPM_VMLINUX_H="$(cat ../vmlinux_h_path)" -@@ -3197,6 +3216,15 @@ popd +@@ -3784,6 +3802,15 @@ popd ### scripts ### @@ -76,7 +75,7 @@ index 43dce82a9d36..28df94e561d4 100644 %if %{with_tools} %post -n %{package_name}-tools-libs /sbin/ldconfig -@@ -3609,6 +3637,12 @@ fi\ +@@ -4266,6 +4293,12 @@ fi\ %{_libexecdir}/kselftests %endif @@ -90,5 +89,5 @@ index 43dce82a9d36..28df94e561d4 100644 %if %{with_up_base} %ifnarch %nobuildarches noarch -- -2.41.0 +2.52.0 diff --git a/pkg/fedora/kernel-surface/patches/0006-ipu3-fw.patch b/pkg/fedora/kernel-surface/patches/0005-Pull-in-intel-vsc-firmware.patch similarity index 70% rename from pkg/fedora/kernel-surface/patches/0006-ipu3-fw.patch rename to pkg/fedora/kernel-surface/patches/0005-Pull-in-intel-vsc-firmware.patch index d497856260..53c5de73ac 100644 --- a/pkg/fedora/kernel-surface/patches/0006-ipu3-fw.patch +++ b/pkg/fedora/kernel-surface/patches/0005-Pull-in-intel-vsc-firmware.patch @@ -1,7 +1,7 @@ -From a82af933b2ee35e24140ee7fddf9a083596cda3f Mon Sep 17 00:00:00 2001 +From 86059570fd818aeb68dc7caf725a996e3183d0e1 Mon Sep 17 00:00:00 2001 From: Dorian Stoll -Date: Mon, 29 Jan 2024 19:26:41 +0100 -Subject: [PATCH] Pull in intel-vsc-firmware +Date: Tue, 30 Sep 2025 20:14:00 +0200 +Subject: [PATCH 5/6] Pull in intel-vsc-firmware This package contains the IPU3 firmware needed for camera support. --- @@ -9,10 +9,10 @@ This package contains the IPU3 firmware needed for camera support. 1 file changed, 1 insertion(+) diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template -index 4ece0fa3a56c..768316b2ec82 100644 +index 67aaf5883051..79e67a8b1440 100644 --- a/redhat/kernel.spec.template +++ b/redhat/kernel.spec.template -@@ -1028,6 +1028,7 @@ Requires(pre): %{kernel_prereq}\ +@@ -1203,6 +1203,7 @@ Requires(pre): %{kernel_prereq}\ Requires(pre): %{initrd_prereq}\ Requires(pre): ((linux-firmware >= 20150904-56.git6ebf5d57) if linux-firmware)\ Recommends: linux-firmware\ @@ -21,5 +21,5 @@ index 4ece0fa3a56c..768316b2ec82 100644 Recommends: %{package_name}-default-watchdog\ Requires(preun): systemd >= 200\ -- -2.43.0 +2.52.0 diff --git a/pkg/fedora/kernel-surface/patches/0005-filter-modules.patch b/pkg/fedora/kernel-surface/patches/0005-filter-modules.patch deleted file mode 100644 index 82bcfbbd72..0000000000 --- a/pkg/fedora/kernel-surface/patches/0005-filter-modules.patch +++ /dev/null @@ -1,26 +0,0 @@ -From bb654591f82503f9e3c319902af2a2b64c18ae50 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Sat, 13 Jan 2024 12:30:02 +0100 -Subject: [PATCH] HACK: Move surface_fan and surface_temp to kernel-modules - ---- - redhat/fedora_files/filter-modules.sh.fedora | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/redhat/fedora_files/filter-modules.sh.fedora b/redhat/fedora_files/filter-modules.sh.fedora -index 2e7b79eba1d9..7ef7614cbda2 100755 ---- a/redhat/fedora_files/filter-modules.sh.fedora -+++ b/redhat/fedora_files/filter-modules.sh.fedora -@@ -84,6 +84,9 @@ filter_ko() { - return 0 - } - -+# HACK: move surface_fan and surface_temp to kernel-modules -+singlemods="${singlemods} surface_fan surface_temp" -+ - # Filter the drivers/ subsystems - for subsys in ${driverdirs} - do --- -2.43.0 - diff --git a/pkg/fedora/kernel-surface/patches/0006-Drop-riscv64-configs.patch b/pkg/fedora/kernel-surface/patches/0006-Drop-riscv64-configs.patch new file mode 100644 index 0000000000..103d7ef4bf --- /dev/null +++ b/pkg/fedora/kernel-surface/patches/0006-Drop-riscv64-configs.patch @@ -0,0 +1,86 @@ +From ded050bca1992e57147e86986acbf2a6c46fd91c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 30 Dec 2025 23:42:38 +0100 +Subject: [PATCH 6/6] Drop riscv64 configs + +Those seem to make some problems... so drop them for now. +--- + redhat/configs/priority.common | 4 ---- + redhat/configs/priority.fedora | 6 ------ + redhat/configs/priority.rhel | 4 ---- + redhat/kernel.spec.template | 7 ------- + 4 files changed, 21 deletions(-) + +diff --git a/redhat/configs/priority.common b/redhat/configs/priority.common +index ea4b1122b1a1..cb1f09840cff 100644 +--- a/redhat/configs/priority.common ++++ b/redhat/configs/priority.common +@@ -19,7 +19,3 @@ aarch64=generic:generic-arm:generic-arm-aarch64 + aarch64-debug=generic:generic-arm:generic-arm-aarch64:debug:debug-arm-aarch64 + aarch64-16k-debug=generic:generic-arm:generic-arm-aarch64:generic-arm-aarch64-16k:debug:debug-arm-aarch64:debug-arm-aarch64-16k + aarch64-64k-debug=generic:generic-arm:generic-arm-aarch64:generic-arm-aarch64-64k:debug:debug-arm-aarch64:debug-arm-aarch64-64k +- +-# riscv64 +-riscv64=generic:generic-riscv:generic-riscv-riscv64 +-riscv64-debug=generic:generic-riscv:generic-riscv-riscv64:debug:debug-riscv-riscv64 +diff --git a/redhat/configs/priority.fedora b/redhat/configs/priority.fedora +index e72726642039..658970a57293 100644 +--- a/redhat/configs/priority.fedora ++++ b/redhat/configs/priority.fedora +@@ -42,9 +42,3 @@ aarch64-rt=generic:generic-arm:generic-arm-aarch64:rt-generic:rt-generic-arm:rt- + aarch64-rt-debug=generic:generic-arm:generic-arm-aarch64:debug:debug-arm:debug-arm-aarch64:rt-generic:rt-generic-arm:rt-generic-arm-aarch64:rt-debug:rt-debug-arm:rt-debug-arm-aarch64 + aarch64-rt-64k=generic:generic-arm:generic-arm-aarch64:rt-generic:rt-arm-aarch64:rt-arm-aarch64-64k + aarch64-rt-64k-debug=generic:generic-arm:generic-arm-aarch64:rt-generic:rt-arm-aarch64:rt-arm-aarch64-64k:debug:debug-arm-aarch64:rt-debug +- +-# riscv64 +-riscv64=generic:generic-riscv:generic-riscv-riscv64 +-riscv64-debug=generic:generic-riscv:generic-riscv-riscv64:debug:debug-riscv-riscv64 +-riscv64-rt=generic:generic-riscv:generic-riscv-riscv64:rt-generic:rt-generic-riscv-riscv64 +-riscv64-rt-debug=generic:generic-riscv:generic-riscv-riscv64:debug:debug-riscv-riscv64:rt-generic:rt-generic-riscv-riscv64:rt-debug:rt-debug-riscv-riscv64 +diff --git a/redhat/configs/priority.rhel b/redhat/configs/priority.rhel +index 2f739fc47f31..4e56dcad5f76 100644 +--- a/redhat/configs/priority.rhel ++++ b/redhat/configs/priority.rhel +@@ -46,7 +46,3 @@ aarch64-rt-64k=generic:generic-arm:generic-arm-aarch64:rt-generic:rt-arm-aarch64 + aarch64-rt-64k-debug=generic:generic-arm:generic-arm-aarch64:rt-generic:rt-arm-aarch64:rt-arm-aarch64-64k:debug:debug-arm-aarch64:rt-debug + aarch64-automotive=generic:generic-arm:generic-arm-aarch64:rt-generic:rt-generic-arm:rt-generic-arm-aarch64:automotive-generic:automotive-generic-arm:automotive-generic-arm-aarch64 + aarch64-automotive-debug=generic:generic-arm:generic-arm-aarch64:debug:debug-arm:debug-arm-aarch64:rt-generic:rt-generic-arm:rt-generic-arm-aarch64:rt-debug:rt-debug-arm:rt-debug-arm-aarch64:automotive-generic:automotive-generic-arm:automotive-generic-arm-aarch64:automotive-debug:automotive-debug-arm:automotive-debug-arm-aarch64 +- +-# riscv64 +-riscv64=generic:generic-riscv:generic-riscv-riscv64 +-riscv64-debug=generic:generic-riscv:generic-riscv-riscv64:debug:debug-riscv-riscv64 +diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template +index 79e67a8b1440..be4e401212b5 100644 +--- a/redhat/kernel.spec.template ++++ b/redhat/kernel.spec.template +@@ -1015,9 +1015,6 @@ Source33: %{name}-x86_64-debug-rhel.config + # ARM64 64K page-size kernel config + Source42: %{name}-aarch64-64k-rhel.config + Source43: %{name}-aarch64-64k-debug-rhel.config +- +-Source44: %{name}-riscv64-rhel.config +-Source45: %{name}-riscv64-debug-rhel.config + %endif + + %if %{include_rhel} || %{include_automotive} +@@ -1039,8 +1036,6 @@ Source58: %{name}-s390x-fedora.config + Source59: %{name}-s390x-debug-fedora.config + Source60: %{name}-x86_64-fedora.config + Source61: %{name}-x86_64-debug-fedora.config +-Source700: %{name}-riscv64-fedora.config +-Source701: %{name}-riscv64-debug-fedora.config + + Source62: def_variants.yaml.fedora + %endif +@@ -1125,8 +1120,6 @@ Source482: %{name}-aarch64-rt-64k-fedora.config + Source483: %{name}-aarch64-rt-64k-debug-fedora.config + Source484: %{name}-x86_64-rt-fedora.config + Source485: %{name}-x86_64-rt-debug-fedora.config +-Source486: %{name}-riscv64-rt-fedora.config +-Source487: %{name}-riscv64-rt-debug-fedora.config + %endif + %endif + +-- +2.52.0 + diff --git a/pkg/fedora/kernel-surface/secureboot/0001-secureboot.patch b/pkg/fedora/kernel-surface/secureboot/0001-secureboot.patch index 80dc88ebe2..2b2cb43403 100644 --- a/pkg/fedora/kernel-surface/secureboot/0001-secureboot.patch +++ b/pkg/fedora/kernel-surface/secureboot/0001-secureboot.patch @@ -1,26 +1,26 @@ -From d4bbfbfee98f8b117885cf88a48f686ac889d73e Mon Sep 17 00:00:00 2001 +From e59f7b88079429cd5c373e4518864a87135d2bf8 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Sat, 22 Jul 2023 10:45:33 +0200 Subject: [PATCH] Use a custom key and certificate for Secure Boot signing Signed-off-by: Dorian Stoll --- - redhat/kernel.spec.template | 15 +++++++++------ - 1 file changed, 9 insertions(+), 6 deletions(-) + redhat/kernel.spec.template | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/redhat/kernel.spec.template b/redhat/kernel.spec.template -index 0fb19cc23041..d7bd6013423c 100644 +index ca141beda4fd..bbe58bf955f0 100644 --- a/redhat/kernel.spec.template +++ b/redhat/kernel.spec.template -@@ -762,6 +762,7 @@ BuildRequires: system-sb-certs - %ifarch x86_64 aarch64 +@@ -849,6 +849,7 @@ BuildRequires: system-sb-certs + %ifarch x86_64 aarch64 riscv64 BuildRequires: nss-tools BuildRequires: pesign >= 0.10-4 +BuildRequires: sbsigntools %endif %endif %endif -@@ -821,6 +822,13 @@ Source2: kernel.changelog +@@ -942,6 +943,13 @@ Source13: redhatsecureboot501.cer %define signing_key_filename kernel-signing-s390.cer %endif @@ -31,30 +31,18 @@ index 0fb19cc23041..d7bd6013423c 100644 + +%endif + - %if %{?released_kernel} - - Source10: redhatsecurebootca5.cer -@@ -2201,9 +2209,7 @@ BuildKernel() { + # Fedora/ELN pesign macro expects to see these cert file names, see: + # https://github.com/rhboot/pesign/blob/main/src/pesign-rpmbuild-helper.in#L216 + %if 0%{?fedora}%{?eln} +@@ -2373,7 +2381,7 @@ BuildKernel() { %ifarch x86_64 aarch64 %{log_msg "Sign kernel image"} -- %pesign -s -i $SignImage -o vmlinuz.tmp -a %{secureboot_ca_0} -c %{secureboot_key_0} -n %{pesign_name_0} -- %pesign -s -i vmlinuz.tmp -o vmlinuz.signed -a %{secureboot_ca_1} -c %{secureboot_key_1} -n %{pesign_name_1} -- rm vmlinuz.tmp +- %pesign -s -i $SignImage -o vmlinuz.signed -a %{secureboot_ca_0} -c %{secureboot_key_0} -n %{pesign_name_0} + sbsign --key %{SOURCE7001} --cert %{SOURCE7002} --output vmlinuz.signed $SignImage %endif %ifarch s390x ppc64le if [ -x /usr/bin/rpm-sign ]; then -@@ -2783,9 +2789,6 @@ BuildKernel() { - %{log_msg "Install certs"} - mkdir -p $RPM_BUILD_ROOT%{_datadir}/doc/kernel-keys/$KernelVer - %ifarch x86_64 aarch64 -- install -m 0644 %{secureboot_ca_0} $RPM_BUILD_ROOT%{_datadir}/doc/kernel-keys/$KernelVer/kernel-signing-ca-20200609.cer -- install -m 0644 %{secureboot_ca_1} $RPM_BUILD_ROOT%{_datadir}/doc/kernel-keys/$KernelVer/kernel-signing-ca-20140212.cer -- ln -s kernel-signing-ca-20200609.cer $RPM_BUILD_ROOT%{_datadir}/doc/kernel-keys/$KernelVer/kernel-signing-ca.cer - %else - install -m 0644 %{secureboot_ca_0} $RPM_BUILD_ROOT%{_datadir}/doc/kernel-keys/$KernelVer/kernel-signing-ca.cer - %endif -- -2.44.0 +2.51.0 diff --git a/scripts/tag-release.sh b/scripts/tag-release.sh index 8f57f6f9c8..a1fb7f6473 100755 --- a/scripts/tag-release.sh +++ b/scripts/tag-release.sh @@ -88,6 +88,5 @@ getfedora() tag 'arch' "$(getarch)" tag 'debian' "$(getdebian)" -tag 'fedora-38' "$(getfedora)" -tag 'fedora-39' "$(getfedora)" tag 'fedora-40' "$(getfedora)" +tag 'fedora-41' "$(getfedora)"