diff --git a/.artifactignore b/.artifactignore index 1126a160d..af97bce55 100644 --- a/.artifactignore +++ b/.artifactignore @@ -1,2 +1,3 @@ **/* !*.deb +!kconfig-diff-*.rst diff --git a/.azure-pipelines/build-template.yml b/.azure-pipelines/build-template.yml index d9f88b191..d67409d52 100644 --- a/.azure-pipelines/build-template.yml +++ b/.azure-pipelines/build-template.yml @@ -6,43 +6,91 @@ parameters: - armhf - arm64 +- name: platform + type: string + values: + - vs + - pensando + default: vs + - name: pool type: string values: - - sonicbld - - sonicbld_8c - default: sonicbld + - sonicso1ES-amd64 + - sonicso1ES-arm64 + - sonicso1ES-armhf + default: sonicso1ES-amd64 - name: timeout type: number - default: 600 + default: 180 + +- name: old_artifact_name + type: string -- name: sonic_slave +- name: old_linux_deb_pattern type: string - name: artifact_name type: string +- name: linux_deb_pattern + type: string + +- name: secure_boot + type: boolean + default: false + jobs: - job: pool: ${{ parameters.pool }} - displayName: ${{ parameters.arch }} + displayName: ${{ parameters.platform }}-${{ parameters.arch }} (Secure boot ${{ parameters.secure_boot }}) timeoutInMinutes: ${{ parameters.timeout }} container: - image: sonicdev-microsoft.azurecr.io:443/${{ parameters.sonic_slave }}:latest + image: sonicdev-microsoft.azurecr.io:443/sonic-slave-trixie:$(BUILD_BRANCH)-${{ parameters.arch }} steps: - checkout: self clean: true - displayName: 'Checkout code' + displayName: 'Checkout code' + - task: DownloadPipelineArtifact@2 + inputs: + source: specific + project: build + pipelineId: 499053 + artifact: ${{ parameters.old_artifact_name }} + runVersion: 'specific' + itemPattern: ${{ parameters.old_linux_deb_pattern }} + targetPath: $(Agent.TempDirectory)/ + displayName: "Download the linux-image artifact from the last build with a different kernel version" - script: | - git config --global user.email "lguohan@build.com" - git config --global user.name "Guohan Lu" - export kernel_procure_method=build + openssl req -new -noenc -utf8 -sha256 -days 30 -batch -x509 -subj "/CN=Autogenerated pipeline build key" -outform PEM -out build_trusted_key.pem -keyout /dev/null + displayName: "Generate additional secure boot certificate" + condition: ${{ eq(parameters.secure_boot, true) }} + - script: | + set -x cat /proc/cpuinfo - CONFIGURED_ARCH=${{ parameters.arch }} make + if [[ ${{ parameters.secure_boot }} = "True" ]]; then + export SECURE_UPGRADE_MODE=dev + export SECURE_UPGRADE_SIGNING_CERT=$(System.DefaultWorkingDirectory)/build_trusted_key.pem + fi + CONFIGURED_ARCH=${{ parameters.arch }} CONFIGURED_PLATFORM=${{ parameters.platform }} make displayName: "Compile sonic kernel" + - script: | + set -e + dpkg-deb -x $(Agent.TempDirectory)/${{ parameters.old_linux_deb_pattern }} $(Agent.TempDirectory)/old + dpkg-deb -x $(System.DefaultWorkingDirectory)/${{ parameters.linux_deb_pattern }} $(Agent.TempDirectory)/new + pip3 install tabulate + python3 $(System.DefaultWorkingDirectory)/.azure-pipelines/kcfg-diff.py \ + --buildid $(Build.BuildId) \ + --ref_buildid 499053 \ + --arch ${{ parameters.arch }} \ + --old_kcfg $(Agent.TempDirectory)/old/boot/ \ + --new_kcfg $(Agent.TempDirectory)/new/boot/ \ + --output $(System.DefaultWorkingDirectory)/kconfig-diff-${{ parameters.platform }}-${{ parameters.arch }}.rst + cat $(System.DefaultWorkingDirectory)/kconfig-diff-${{ parameters.platform }}-${{ parameters.arch }}.rst + displayName: "Compute the kconfig diff" - publish: $(System.DefaultWorkingDirectory)/ artifact: ${{ parameters.artifact_name }} displayName: "Archive sonic kernel debian packages" diff --git a/.azure-pipelines/kcfg-diff.py b/.azure-pipelines/kcfg-diff.py new file mode 100644 index 000000000..3b4c8d814 --- /dev/null +++ b/.azure-pipelines/kcfg-diff.py @@ -0,0 +1,160 @@ +import argparse +import os +import glob +import sys +import re +from tabulate import tabulate + +COLUMN_WIDTH_MAX = 40 +MODIF_COLUMN_WIDTH_MAX = 80 + +RST_TEMPLATE = '''\ +SONiC-linux-kernel KConfig Difference: +========== + +Reference Kernel +------------ +- Version: {} +- BuildID: https://dev.azure.com/mssonic/build/_build/results?buildId={}&view=results + +Latest Kernel +------------ +- Version: {} +- BuildID: https://dev.azure.com/mssonic/build/_build/results?buildId={}&view=results + + +Additions & Deletions +------------ +{} + +Modifications +------------ +{} +''' + +def read_data(file1): + data = [] + try: + with open(file1, 'r') as f1: + data = f1.readlines() + except Exception as e: + print("ABORT: Reading failed from {}, {}".format(file1, str(e))) + sys.exit(1) + + data = filter(lambda line: not(line.startswith('#')), data) + ret = dict() + for line in data: + tokens = line.split('=') + if len(tokens) == 2: + key, val = tokens[0].strip(), tokens[-1].strip() + ret[key] = val + return ret + +def write_data(fname, data): + try: + with open(fname, 'w') as f: + f.write(data) + except Exception as e: + print("ABORT: Writing to the file {} failed {}".format(fname, str(e))) + sys.exit(1) + +def generate_diff(file1, file2): + data_f1 = read_data(file1) + data_f2 = read_data(file2) + + additions = [] + modifications = [] + deletions = [] + + for key_old, val_old in data_f1.items(): + val_new = data_f2.get(key_old, None) + if not val_new: + deletions.append("{}={}".format(key_old, val_old)) + elif val_old != val_new: + modifications.append("{}={}->{}".format(key_old, val_old, val_new)) + if val_new: + del data_f2[key_old] + + for key, val in data_f2.items(): + additions.append("{}={}".format(key, val)) + return additions, modifications, deletions + +def restrict_column_width(lis, width): + for i in range(0, len(lis)): + curr_width = len(lis[i]) + new_val = '' + num_newlines = int(curr_width/width) + for j in range(0, num_newlines+1): + if (j+1)*width < curr_width: + new_val += lis[i][j*width:(j+1)*width] + new_val += "\n" + else: + new_val += lis[i][j*width:] + lis[i] = new_val + +def format_diff_table(additions, modifications, deletions): + max_len = max(len(additions), len(deletions)) + additions += [''] * (max_len - len(additions)) + deletions += [''] * (max_len - len(deletions)) + + restrict_column_width(additions, COLUMN_WIDTH_MAX) + restrict_column_width(deletions, COLUMN_WIDTH_MAX) + restrict_column_width(modifications, MODIF_COLUMN_WIDTH_MAX) + + table_data = list(zip(additions, deletions)) + headers = ["ADDITIONS", "DELETIONS"] + + add_del = tabulate(table_data, headers=headers, tablefmt="grid") + mod = tabulate(list(zip(modifications)), headers=["MODIFICATIONS"], tablefmt="grid") + return add_del, mod + +def parse_kver(loc): + files = glob.glob(os.path.join(loc, "config-*")) + if len(files) > 1: + print("WARNING: Multiple config- files present under {}, {}".format(loc, files)) + result = re.search(r"config-(.*)", os.path.basename(files[-1])) + return result.group(1) + +def create_parser(): + # Create argument parser + parser = argparse.ArgumentParser() + + # Optional arguments + parser.add_argument("--arch", type=str, required=True) + parser.add_argument("--old_kcfg", type=str, required=True) + parser.add_argument("--new_kcfg", type=str, required=True) + parser.add_argument("--output", type=str, required=True) + parser.add_argument("--ref_buildid", type=str, required=True) + parser.add_argument("--buildid", type=str, required=True) + return parser + +def verify_args(args): + if not glob.glob(os.path.join(args.old_kcfg, "config-*")): + print("ABORT: config file missing under {}".format(args.old_kcfg)) + return False + + if not glob.glob(os.path.join(args.new_kcfg, "config-*")): + print("ABORT: config file missing under {}".format(args.new_kcfg)) + return False + + if not os.path.exists(os.path.dirname(args.output)): + print("ABORT: Output Folder {} doesn't exist".format(args.output)) + return False + return True + +if __name__ == "__main__": + parser = create_parser() + args = parser.parse_args() + if not verify_args(args): + sys.exit(1) + + ver_old = parse_kver(args.old_kcfg) + ver_new = parse_kver(args.new_kcfg) + f_old = os.path.join(args.old_kcfg, 'config-{}'.format(ver_old)) + f_new = os.path.join(args.new_kcfg, 'config-{}'.format(ver_new)) + + additions, modifications, deletions = generate_diff(f_old, f_new) + add_del, mod = format_diff_table(additions, modifications, deletions) + + diff = RST_TEMPLATE.format(ver_old, args.ref_buildid, ver_new, args.buildid, add_del, mod) + write_data(args.output, diff) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..2295e1ca9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,87 @@ +# Copilot Instructions for sonic-linux-kernel + +## Project Overview + +sonic-linux-kernel contains the build scripts, patches, and kernel configuration for the SONiC Linux kernel. SONiC uses a single shared kernel across all platforms, preferring out-of-tree kernel modules for platform-specific hardware support. This repo manages kernel patches, configuration options, and the Debian kernel package build. + +## Architecture + +``` +sonic-linux-kernel/ +├── Makefile # Top-level build — produces Debian kernel packages +├── patch/ # Kernel configuration and patch management +│ ├── kconfig-exclusions # Kernel options to remove (reduce build time) +│ ├── kconfig-inclusions # Kernel options to add +│ └── series # Patch application order +├── patches-sonic/ # SONiC-specific kernel patches +├── patches-debian/ # Debian-specific kernel patches +├── config.local # Local kernel config overrides +├── manage-config # Script to manage kernel configuration +├── .azure-pipelines/ # Azure DevOps CI +└── azure-pipelines.yml # CI pipeline +``` + +### Key Concepts +- **Single kernel for all platforms**: All SONiC platforms share the same kernel image +- **Out-of-tree modules**: Platform-specific drivers should be built as external modules +- **kconfig management**: Options excluded via `kconfig-exclusions`, included via `kconfig-inclusions` +- **Patch layers**: `patches-sonic/` for SONiC-specific, `patches-debian/` for Debian-derived patches + +## Language & Style + +- **Primary languages**: Shell scripts, Makefiles, C (kernel patches) +- **Kernel patches**: Must pass `checkpatch.pl` validation +- **Patch format**: Standard kernel patch format with upstream commit reference when applicable +- **Commit messages**: Include original upstream commit ID and message for backported patches + +## Build Instructions + +```bash +# Build kernel Debian packages +make DEST= + +# If DEST is not set, packages stay in current directory +make +``` + +## Kernel Configuration Changes + +### Excluding Options (speed up builds) +Add to `patch/kconfig-exclusions`: +``` +CONFIG_REISERFS_FS +CONFIG_JFS_FS +CONFIG_XFS_FS +``` + +### Including Options +Add to `patch/kconfig-inclusions` with the desired value: +``` +CONFIG_MY_DRIVER=m +``` + +## Patch Acceptance Criteria + +Kernel patches are accepted under these conditions: +1. **Enable existing modules**: Enabling built-in kernel modules for SONiC use +2. **Upstream patches**: Backported patches that are already accepted upstream (include upstream commit ID) +3. **Common platform modules**: New modules needed by all/most platforms +4. **Platform-specific (exception)**: Only when impossible or very difficult to build out-of-tree + +## PR Guidelines + +- **Signed-off-by**: Required on all commits +- **CLA**: Sign Linux Foundation EasyCLA +- **checkpatch.pl**: All kernel patches MUST pass `checkpatch.pl` +- **Upstream first**: Prefer upstream patches — include the original commit hash +- **Justification**: Explain why a patch is needed and why it can't be done out-of-tree +- **CI**: Azure pipeline checks must pass + +## Gotchas + +- **Build time**: Full kernel build is slow — use kconfig-exclusions to remove unnecessary drivers +- **All platforms affected**: Kernel changes impact every SONiC platform +- **ABI stability**: Kernel ABI changes can break out-of-tree platform modules +- **Patch rebasing**: During kernel version upgrades, all patches must be ported — minimize the patch set +- **Config conflicts**: `kconfig-exclusions` and `kconfig-inclusions` can conflict — exclusions take priority +- **Out-of-tree preference**: Reviewers will push back on patches that could be out-of-tree modules diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..6b3a788e3 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,11 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# rules are explained here +# https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @user will be requested for review when someone opens +# a pull request. +* @sonic-net/sonic-linux-kernel-maintainer diff --git a/Makefile b/Makefile index 26213a8c7..0b8f3fce3 100644 --- a/Makefile +++ b/Makefile @@ -1,63 +1,78 @@ .ONESHELL: SHELL = /bin/bash -.SHELLFLAGS += -e +.SHELLFLAGS += -ex -KERNEL_ABI_MINOR_VERSION = 2 -KVERSION_SHORT ?= 4.19.0-12-$(KERNEL_ABI_MINOR_VERSION) -KVERSION ?= $(KVERSION_SHORT)-amd64 -KERNEL_VERSION ?= 4.19.152 +KERNEL_VERSION ?= 6.12.94 +KERNEL_ABISUFFIX ?= +deb13 KERNEL_SUBVERSION ?= 1 -kernel_procure_method ?= build +KERNEL_FEATURESET ?= sonic CONFIGURED_ARCH ?= amd64 +CONFIGURED_PLATFORM ?= vs +CROSS_BUILD_ENVIRON ?= n +SONIC_CONFIG_MAKE_JOBS ?= $(shell nproc) +KVERSION_SHORT := $(KERNEL_VERSION)$(KERNEL_ABISUFFIX)-$(KERNEL_FEATURESET) +ifeq ($(CONFIGURED_ARCH), armhf) +# Override kernel version for ARMHF as it uses arm MP (multi-platform) for short version +KVERSION ?= $(KVERSION_SHORT)-armmp +else +KVERSION ?= $(KVERSION_SHORT)-$(CONFIGURED_ARCH) +endif +SECURE_UPGRADE_MODE ?= +SECURE_UPGRADE_SIGNING_CERT ?= +SECURE_UPGRADE_KERNEL_CAFILE ?= $(SECURE_UPGRADE_SIGNING_CERT) -LINUX_HEADER_COMMON = linux-headers-$(KVERSION_SHORT)-common_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_all.deb -LINUX_HEADER_AMD64 = linux-headers-$(KVERSION)_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_$(CONFIGURED_ARCH).deb +LINUX_HEADER_COMMON = linux-headers-$(KERNEL_VERSION)$(KERNEL_ABISUFFIX)-common-$(KERNEL_FEATURESET)_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_all.deb +LINUX_HEADER_ARCH = linux-headers-$(KVERSION)_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_$(CONFIGURED_ARCH).deb +LINUX_KBUILD = linux-kbuild-$(KERNEL_VERSION)$(KERNEL_ABISUFFIX)_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_$(CONFIGURED_ARCH).deb ifeq ($(CONFIGURED_ARCH), armhf) LINUX_IMAGE = linux-image-$(KVERSION)_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_$(CONFIGURED_ARCH).deb + KERNEL_FLAVOR_ARCH=armmp else LINUX_IMAGE = linux-image-$(KVERSION)-unsigned_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_$(CONFIGURED_ARCH).deb + KERNEL_FLAVOR_ARCH=$(CONFIGURED_ARCH) endif MAIN_TARGET = $(LINUX_HEADER_COMMON) -DERIVED_TARGETS = $(LINUX_HEADER_AMD64) $(LINUX_IMAGE) - -ifneq ($(kernel_procure_method), build) -# Downloading kernel - -# TBD, need upload the new kernel packages -LINUX_HEADER_COMMON_URL = "https://sonicstorage.blob.core.windows.net/packages/kernel-public/linux-headers-$(KVERSION_SHORT)-common_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_all.deb?sv=2015-04-05&sr=b&sig=JmF0asLzRh6btfK4xxfVqX%2F5ylqaY4wLkMb5JwBJOb8%3D&se=2128-12-23T19%3A05%3A28Z&sp=r" - -LINUX_HEADER_AMD64_URL = "https://sonicstorage.blob.core.windows.net/packages/kernel-public/linux-headers-$(KVERSION_SHORT)-amd64_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_amd64.deb?sv=2015-04-05&sr=b&sig=%2FD9a178J4L%2FN3Fi2uX%2FWJaddpYOZqGmQL4WAC7A7rbA%3D&se=2128-12-23T19%3A06%3A13Z&sp=r" - -LINUX_IMAGE_URL = "https://sonicstorage.blob.core.windows.net/packages/kernel-public/linux-image-$(KVERSION_SHORT)-amd64_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_amd64.deb?sv=2015-04-05&sr=b&sig=oRGGO9xJ6jmF31KGy%2BwoqEYMuTfCDcfILKIJbbaRFkU%3D&se=2128-12-23T19%3A06%3A47Z&sp=r" - -$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : - # Obtaining the Debian kernel packages - rm -rf $(BUILD_DIR) - wget --no-use-server-timestamps -O $(LINUX_HEADER_COMMON) $(LINUX_HEADER_COMMON_URL) - wget --no-use-server-timestamps -O $(LINUX_HEADER_AMD64) $(LINUX_HEADER_AMD64_URL) - wget --no-use-server-timestamps -O $(LINUX_IMAGE) $(LINUX_IMAGE_URL) - -ifneq ($(DEST),) - mv $(DERIVED_TARGETS) $* $(DEST)/ -endif - -$(addprefix $(DEST)/, $(DERIVED_TARGETS)): $(DEST)/% : $(DEST)/$(MAIN_TARGET) - -else -# Building kernel +DERIVED_TARGETS = $(LINUX_HEADER_ARCH) $(LINUX_IMAGE) $(LINUX_KBUILD) DSC_FILE = linux_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION).dsc DEBIAN_FILE = linux_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION).debian.tar.xz ORIG_FILE = linux_$(KERNEL_VERSION).orig.tar.xz BUILD_DIR=linux-$(KERNEL_VERSION) -SOURCE_FILE_BASE_URL="https://sonicstorage.blob.core.windows.net/debian-security/pool/updates/main/l/linux" +LINUX_SOURCE_BASE_URL=https://packages.trafficmanager.net/public/debian-security/pool/updates/main/l/linux -DSC_FILE_URL = "$(SOURCE_FILE_BASE_URL)/$(DSC_FILE)" -DEBIAN_FILE_URL = "$(SOURCE_FILE_BASE_URL)/$(DEBIAN_FILE)" -ORIG_FILE_URL = "$(SOURCE_FILE_BASE_URL)/$(ORIG_FILE)" +DSC_FILE_URL = "$(LINUX_SOURCE_BASE_URL)/$(DSC_FILE)" +DEBIAN_FILE_URL = "$(LINUX_SOURCE_BASE_URL)/$(DEBIAN_FILE)" +ORIG_FILE_URL = "$(LINUX_SOURCE_BASE_URL)/$(ORIG_FILE)" +NON_UP_DIR = /tmp/non_upstream_patches $(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : + # Include any non upstream patches + rm -rf $(NON_UP_DIR) + mkdir -p $(NON_UP_DIR) + + if [ x${INCLUDE_EXTERNAL_PATCHES} == xy ]; then + if [ ! -z ${EXTERNAL_KERNEL_PATCH_URL} ]; then + wget $(EXTERNAL_KERNEL_PATCH_URL) -O patches.tar + tar -xf patches.tar -C $(NON_UP_DIR) + else + if [ -d "$(EXTERNAL_KERNEL_PATCH_LOC)" ]; then + cp -r $(EXTERNAL_KERNEL_PATCH_LOC)/* $(NON_UP_DIR)/ + fi + fi + fi + + if [ -f "$(NON_UP_DIR)/external-changes.patch" ]; then + cat $(NON_UP_DIR)/external-changes.patch + git stash -- patch/ + git apply $(NON_UP_DIR)/external-changes.patch + fi + + if [ -d "$(NON_UP_DIR)/patches" ]; then + echo "Copy the non upstream patches" + cp $(NON_UP_DIR)/patches/*.patch patch/ + fi + # Obtaining the Debian kernel source rm -rf $(BUILD_DIR) wget -O $(DSC_FILE) $(DSC_FILE_URL) @@ -67,47 +82,26 @@ $(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : dpkg-source -x $(DSC_FILE) pushd $(BUILD_DIR) - git init - git add -f * - git commit -qm "check in all loose files and diffs" - # patching anything that could affect following configuration generation. - stg init - stg import -s ../patch/preconfig/series + cp -vr ../config.local ../patches-sonic debian/ + if [[ -f debian/config.local/$(CONFIGURED_ARCH)/config.sonic-$(CONFIGURED_PLATFORM) ]]; then + cp debian/config.local/$(CONFIGURED_ARCH)/config.sonic-$(CONFIGURED_PLATFORM) debian/config.local/$(CONFIGURED_ARCH)/config.sonic-platform-specific + fi + + patch -p1 -i ../patches-debian/disable-secureboot-config-checks.patch + + # Enable secure boot configs if needed + ../manage-config $(CONFIGURED_ARCH) $(SECURE_UPGRADE_MODE) $(SECURE_UPGRADE_KERNEL_CAFILE) - # re-generate debian/rules.gen, requires kernel-wedge + # re-generate debian packages and rules with SONiC customizations debian/bin/gencontrol.py - # generate linux build file for amd64_none_amd64 - fakeroot make -f debian/rules.gen DEB_HOST_ARCH=armhf setup_armhf_none_armmp - fakeroot make -f debian/rules.gen DEB_HOST_ARCH=arm64 setup_arm64_none_arm64 - fakeroot make -f debian/rules.gen DEB_HOST_ARCH=amd64 setup_amd64_none_amd64 - - # Applying patches and configuration changes - git add debian/build/build_armhf_none_armmp/.config -f - git add debian/build/build_arm64_none_arm64/.config -f - git add debian/build/build_amd64_none_amd64/.config -f - git add debian/config.defines.dump -f - git add debian/control -f - git add debian/rules.gen -f - git add debian/tests/control -f - git commit -m "unmodified debian source" - - # Learning new git repo head (above commit) by calling stg repair. - stg repair - stg import -s ../patch/series - - # Optionally add/remove kernel options - if [ -f ../manage-config ]; then - ../manage-config $(CONFIGURED_ARCH) $(CONFIGURED_PLATFORM) - fi + # TODO(trixie): Make a way to verify that our configs are being set - # Building a custom kernel from Debian kernel source - ARCH=$(CONFIGURED_ARCH) DEB_HOST_ARCH=$(CONFIGURED_ARCH) DEB_BUILD_PROFILES=nodoc fakeroot make -f debian/rules -j $(shell nproc) binary-indep -ifeq ($(CONFIGURED_ARCH), armhf) - ARCH=$(CONFIGURED_ARCH) DEB_HOST_ARCH=$(CONFIGURED_ARCH) fakeroot make -f debian/rules.gen -j $(shell nproc) binary-arch_$(CONFIGURED_ARCH)_none_armmp +ifeq ($(CROSS_BUILD_ENVIRON), y) + dpkg-buildpackage -b -us -uc -a$(CONFIGURED_ARCH) -Pcross,nocheck,nodoc -j$(SONIC_CONFIG_MAKE_JOBS) else - ARCH=$(CONFIGURED_ARCH) DEB_HOST_ARCH=$(CONFIGURED_ARCH) fakeroot make -f debian/rules.gen -j $(shell nproc) binary-arch_$(CONFIGURED_ARCH)_none_$(CONFIGURED_ARCH) + dpkg-buildpackage -b -us -uc -Pnodoc -j$(SONIC_CONFIG_MAKE_JOBS) endif popd @@ -116,5 +110,3 @@ ifneq ($(DEST),) endif $(addprefix $(DEST)/, $(DERIVED_TARGETS)): $(DEST)/% : $(DEST)/$(MAIN_TARGET) - -endif # building kernel diff --git a/README.md b/README.md index ada84ec6a..0e60a5d6e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ -[![Build Status](https://sonic-jenkins.westus2.cloudapp.azure.com/job/common/job/linux-kernel-build/badge/icon)](https://sonic-jenkins.westus2.cloudapp.azure.com/job/common/job/linux-kernel-build/) # SONiC - Kernel +## Build Status + +[![Master Branch](https://dev.azure.com/mssonic/build/_apis/build/status%2FAzure.sonic-linux-kernel?branchName=master&label=Master)](https://dev.azure.com/mssonic/build/_build/latest?definitionId=13&branchName=master) +[![202211 Branch](https://dev.azure.com/mssonic/build/_apis/build/status%2FAzure.sonic-linux-kernel?branchName=202211&label=202211)](https://dev.azure.com/mssonic/build/_build/latest?definitionId=13&branchName=202211) +[![202205 Branch](https://dev.azure.com/mssonic/build/_apis/build/status%2FAzure.sonic-linux-kernel?branchName=202205&label=202205)](https://dev.azure.com/mssonic/build/_build/latest?definitionId=13&branchName=202205) + ## Description This repository contains the scripts and patches to build the kernel for SONiC. SONiC uses the same kernel for all platforms. We prefer to out-of-tree kernel platform modules. We accept kernel patches on following conditions: @@ -45,3 +50,9 @@ If the files patch/kconfig-exclusions and patch/kconfig-inclusions exist, they w Also, the final kernel configuration will be checked to verify that: - all options asked to be excluded are effectively not present in the kernel, - and all options asked to be included are effectively present in the kernel, using the exact type (module or built-in) or string or number. + +## Kernel Configuration Difference during Upgrades + +During Kernel Upgrades, the maintainer must update the configuration diff in the wiki of this repo. This acts as a guide for keeping track of configuration changes between Kernel upgrades. Applies for minor version upgrades as well + +The diff is saved as kconfig-diff-{platform}-{arch}.rst under the artifacts of Azure.sonic-linux-kernel job runs. \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5be58a67d..205ce2cb6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,6 +7,12 @@ trigger: branches: include: - "*" +variables: + - name: BUILD_BRANCH + ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + value: $(System.PullRequest.TargetBranch) + ${{ else }}: + value: $(Build.SourceBranchName) stages: - stage: Build @@ -15,22 +21,47 @@ stages: - template: .azure-pipelines/build-template.yml parameters: arch: amd64 - sonic_slave: sonic-slave-buster - artifact_name: sonic-linux-kernel - - - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - - template: .azure-pipelines/build-template.yml - parameters: - arch: armhf - timeout: 1440 - pool: sonicbld_8c - sonic_slave: sonic-slave-buster-armhf - artifact_name: sonic-linux-kernel.armhf - - - template: .azure-pipelines/build-template.yml - parameters: - arch: arm64 - timeout: 1440 - pool: sonicbld_8c - sonic_slave: sonic-slave-buster-arm64 - artifact_name: sonic-linux-kernel.arm64 + pool: sonicso1ES-amd64 + old_artifact_name: sonic-linux-kernel + artifact_name: sonic-linux-kernel.amd64 + old_linux_deb_pattern: linux-image-*-unsigned_*deb + linux_deb_pattern: linux-image-*-unsigned_*deb + + - template: .azure-pipelines/build-template.yml + parameters: + arch: amd64 + pool: sonicso1ES-amd64 + old_artifact_name: sonic-linux-kernel + artifact_name: sonic-linux-kernel.amd64.secure + old_linux_deb_pattern: linux-image-*-unsigned_*deb + linux_deb_pattern: linux-image-*-unsigned_*deb + secure_boot: true + + - template: .azure-pipelines/build-template.yml + parameters: + arch: arm64 + pool: sonicso1ES-arm64 + old_artifact_name: sonic-linux-kernel.arm64 + artifact_name: sonic-linux-kernel.arm64 + old_linux_deb_pattern: linux-image-*-unsigned_*deb + linux_deb_pattern: linux-image-*-unsigned_*deb + + - template: .azure-pipelines/build-template.yml + parameters: + arch: armhf + pool: sonicso1ES-armhf + old_artifact_name: sonic-linux-kernel.armhf + artifact_name: sonic-linux-kernel.armhf + old_linux_deb_pattern: linux-image-*-armmp_*deb + linux_deb_pattern: linux-image-*+deb*-*-armmp_*deb + + - template: .azure-pipelines/build-template.yml + parameters: + arch: arm64 + platform: pensando + pool: sonicso1ES-arm64 + old_artifact_name: sonic-linux-kernel.pensando.arm64 + artifact_name: sonic-linux-kernel.pensando.arm64 + old_linux_deb_pattern: linux-image-*-unsigned_*deb + linux_deb_pattern: linux-image-*-unsigned_*deb + diff --git a/config.local/amd64/config.sonic b/config.local/amd64/config.sonic new file mode 100644 index 000000000..28bce424d --- /dev/null +++ b/config.local/amd64/config.sonic @@ -0,0 +1,184 @@ + +CONFIG_SATA_MOBILE_LPM_POLICY=1 +# For Arista +CONFIG_I2C_MUX_PCA9541=m +CONFIG_SENSORS_MAX6697=m +#CONFIG_SENSORS_DPS1900=m # TODO: enable once the DPS1900 patch is re-enabled +CONFIG_PCI_REASSIGN_PREF_MEM=y +# For cig-cs6436 +CONFIG_SERIAL_8250_LPSS=m +# SFF_8436 +CONFIG_EEPROM_SFF_8436=m +# For Dell S6000 +CONFIG_SENSORS_MAX6620=m +CONFIG_PMBUS=m +CONFIG_SENSORS_PMBUS=m +#CONFIG_SENSORS_DNI_DPS460=m # TODO: enable once the DPS460 patch is re-enabled +CONFIG_I2C_MUX_GPIO=m +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SCH=m +# For Dell Z9100 +CONFIG_I2C_MUX_PCA954x=m +# For Ingrasys S9100 +CONFIG_I2C_GPIO=m +CONFIG_GPIO_PCA953X=m +# For Inventec d7032 +CONFIG_GPIO_ICH=m +# For mitac ly1200 +CONFIG_SENSORS_MAX31790=m +# For optoe +CONFIG_EEPROM_OPTOE=m +###-> mellanox_amd64-start +CONFIG_X86_AMD_PLATFORM_DEVICE=y +CONFIG_MCTP=y +CONFIG_DMI_SYSFS=y +CONFIG_OF=y +CONFIG_EEPROM_AT24=m +CONFIG_AMD_XGBE=m +CONFIG_IGB=m +CONFIG_MLXSW_CORE=m +CONFIG_MARVELL_PHY=m +CONFIG_MARVELL_10G_PHY=m +CONFIG_SERIAL_8250_DW=y +CONFIG_HW_RANDOM_AMD=m +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX=m +CONFIG_I2C_SMBUS=m +CONFIG_I2C_I801=m +CONFIG_I2C_DESIGNWARE_CORE=m +CONFIG_I2C_MLXCPLD=m +CONFIG_I2C_SLAVE=y +CONFIG_PINCTRL=y +CONFIG_GPIOLIB=y +CONFIG_SENSORS_K10TEMP=m +CONFIG_SENSORS_DRIVETEMP=m +CONFIG_SENSORS_CORETEMP=m +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_POWR1220=m +CONFIG_SENSORS_LM75=m +CONFIG_PMBUS=m +CONFIG_SENSORS_EMC1403=m +CONFIG_SENSORS_EMC2305=m +CONFIG_SENSORS_STTS751=m +CONFIG_SENSORS_TMP102=m +CONFIG_SENSORS_TMP421=m +CONFIG_THERMAL_NETLINK=y +CONFIG_INTEL_PCH_THERMAL=m +CONFIG_WATCHDOG_SYSFS=y +CONFIG_LPC_ICH=m +CONFIG_MFD_INTEL_LPSS_PCI=y +CONFIG_LEDS_MLXREG=m +CONFIG_LEDS_TRIGGER_TIMER=m +CONFIG_DW_DMAC_PCI=y +CONFIG_MELLANOX_PLATFORM=y +CONFIG_MLX_PLATFORM=m +CONFIG_NET_DEVLINK=y +CONFIG_I2C_MUX_REG=m +CONFIG_I2C_MUX_MLXCPLD=m +CONFIG_REGMAP_I2C=m +CONFIG_GPIO_SYSFS=y +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_NVME_HWMON=y +CONFIG_MLXSW_CORE_HWMON=y +CONFIG_MLXSW_I2C=m +CONFIG_MLXSW_MINIMAL=m +CONFIG_MLXSW_CORE_THERMAL=n +CONFIG_NVSW_HOST_L1=m +CONFIG_NVSW_HOST_SPC5=m +CONFIG_NVSW_HOST_SPC6=m +CONFIG_MLXREG_HOTPLUG=m +CONFIG_MLXREG_IO=m +CONFIG_MLX_WDT=m +CONFIG_MLXREG_LC=m +CONFIG_MLXREG_DPU=m +CONFIG_THERMAL_WRITABLE_TRIPS=y +CONFIG_SENSORS_PMBUS=m +CONFIG_SENSORS_MLXREG_FAN=m +CONFIG_GPIO_GENERIC=m +CONFIG_MAX1363=m +CONFIG_SENSORS_TPS53679=m +CONFIG_SENSORS_XDPE122=m +CONFIG_SENSORS_MP2975=m +CONFIG_SENSORS_MP2855=m +CONFIG_SENSORS_MP2888=m +CONFIG_SENSORS_MP2891=m +CONFIG_SENSORS_MP2869=m +CONFIG_SENSORS_MP29502=m +CONFIG_SENSORS_MP2845=m +CONFIG_SENSORS_MP5926=m +CONFIG_SENSORS_XDPE1A2G7B=m +CONFIG_GPIO_ICH=m +CONFIG_CPU_THERMAL=y +CONFIG_IGB_HWMON=y +CONFIG_MFD_CORE=y +CONFIG_MFD_INTEL_LPSS=y +CONFIG_TI_ADS1015=m +CONFIG_PINCTRL_INTEL=y +CONFIG_PINCTRL_CANNONLAKE=m +CONFIG_PINCTRL_DENVERTON=m +CONFIG_NVSW_SN2201=m +CONFIG_I2C_DESIGNWARE_PLATFORM=m +CONFIG_I2C_DESIGNWARE_BAYTRAIL=y +CONFIG_I2C_DESIGNWARE_PCI=m +CONFIG_SPI_PXA2XX=m +CONFIG_SENSORS_XDPE152=m +CONFIG_SENSORS_IIO_HWMON=m +CONFIG_SENSORS_LM25066=m +CONFIG_SENSORS_UCD9000=m +CONFIG_SENSORS_UCD9200=m +CONFIG_THERMAL_OF=y +CONFIG_MCTP_FLOWS=y +CONFIG_MCTP_TRANSPORT_I2C=m +CONFIG_I2C_SLAVE_EEPROM=m +CONFIG_I2C_ASF=m +CONFIG_SPI_AMD=m +CONFIG_PINCTRL_AMD=y +CONFIG_EDAC_AMD64=m +CONFIG_AMD_XGBE_DCB=y +CONFIG_SENSORS_ISL68137=m +###-> mellanox_amd64-end +# For Cisco 8000 +CONFIG_PHYLIB=m +CONFIG_OF_GPIO=y +CONFIG_OF_MDIO=m +CONFIG_MDIO_BUS_MUX_GPIO=m +CONFIG_MDIO_BUS_MUX=m +CONFIG_REGMAP_MMIO=y +CONFIG_TI_TLC4541=m +CONFIG_SENSORS_LTC2978=m +CONFIG_SENSORS_LTC2945=m +CONFIG_SENSORS_TPS40422=m +CONFIG_SENSORS_ADM1275=m +CONFIG_SPI_INTEL_PLATFORM=m +CONFIG_SPI_INTEL=m +# For pensando +CONFIG_IONIC=m +# For AMD Ryzen Embedded processors +CONFIG_PCI_ENDPOINT=y +CONFIG_PCIE_DW=y +CONFIG_PCIE_DW_HOST=y +CONFIG_PCIE_DW_EP=y +CONFIG_PCIE_DW_PLAT=y +CONFIG_PCIE_DW_PLAT_HOST=y +CONFIG_PCIE_DW_PLAT_EP=y +# For Micas +CONFIG_MDIO_BITBANG=m +CONFIG_MDIO_GPIO=m +CONFIG_SPI_GPIO=m +CONFIG_SENSORS_INA3221=m +CONFIG_SENSORS_INA238=m +# For Nokia +CONFIG_RTC_DRV_DS1307=m +CONFIG_SENSORS_TMP464=m +# For Nexthop +CONFIG_I2C_XILINX=m +CONFIG_PCI_STUB=y +CONFIG_SENSORS_ADM1266=m +CONFIG_SENSORS_Q54SJ108A2=m +CONFIG_SENSORS_TMP464=m +CONFIG_SPI_XILINX=m + +CONFIG_AMD_MEM_ENCRYPT=n +# Unset X86_PAT according to Broadcom's requirement +CONFIG_X86_PAT=n +CONFIG_MLXSW_PCI=n diff --git a/config.local/amd64/config.sonic-secureboot b/config.local/amd64/config.sonic-secureboot new file mode 100644 index 000000000..70697c1ed --- /dev/null +++ b/config.local/amd64/config.sonic-secureboot @@ -0,0 +1,12 @@ +CONFIG_SYSTEM_TRUSTED_KEYS="debian/certs/debian-uefi-certs.pem" +CONFIG_MODULE_SIG_HASH="sha512" +CONFIG_MODULE_SIG_SHA512=y +CONFIG_KEXEC_SIG_FORCE=y +CONFIG_MODULE_SIG_FORCE=y + +CONFIG_MODULE_SIG_SHA256=n +# For mellanox +CONFIG_SECURITY_LOCKDOWN_LSM=n +CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=n +CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=n +CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT=n diff --git a/config.local/amd64/defines.toml b/config.local/amd64/defines.toml new file mode 100644 index 000000000..8092e5461 --- /dev/null +++ b/config.local/amd64/defines.toml @@ -0,0 +1,6 @@ +[[featureset]] +name = 'sonic' +[[featureset.flavour]] +name = 'amd64' +[featureset.flavour.build] +config = ['amd64/config.sonic'] diff --git a/config.local/arm64/config.sonic b/config.local/arm64/config.sonic new file mode 100644 index 000000000..2e5fb2069 --- /dev/null +++ b/config.local/arm64/config.sonic @@ -0,0 +1,43 @@ +# For enabling SDEI +CONFIG_ARM_SDE_INTERFACE=y +# For Marvell +CONFIG_EEPROM_SFF_8436=m +CONFIG_EEPROM_OPTOE=m +CONFIG_I2C_MUX_GPIO=m +CONFIG_MTD_RAW_NAND=m +CONFIG_MTD_NAND_ECC_SW_BCH=y +CONFIG_MTD_NAND_MARVELL=m +CONFIG_SENSORS_EMC2305=m +CONFIG_PHY_MVEBU_CP110_COMPHY=y +CONFIG_SPI_ORION=m +CONFIG_ARM_SMC_WATCHDOG=m +# For pensando +CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_EMBEDDED=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_GPIO_DWAPB=m +CONFIG_IP_PNP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IRQSOFF_TRACER=y +CONFIG_KALLSYMS_ALL=y +CONFIG_TRACER_SNAPSHOT_PER_CPU_SWAP=y +CONFIG_SCHED_TRACER=y +CONFIG_MEMTEST=y +CONFIG_MMC_DEBUG=y +CONFIG_MMC_SDHCI_CADENCE=m +CONFIG_PMBUS=m +CONFIG_SENSORS_PMBUS=m +CONFIG_SENSORS_LTC2978=m +CONFIG_SENSORS_TPS53679=m +CONFIG_RTC_DRV_PCF85363=m +CONFIG_RTC_CLASS=y +CONFIG_SPI_CADENCE_QUADSPI=m +CONFIG_SPI_DESIGNWARE=m +CONFIG_SPI_DW_MMIO=m +CONFIG_SQUASHFS_DECOMP_MULTI=y +CONFIG_PREEMPTIRQ_TRACEPOINTS=y +CONFIG_RING_BUFFER_ALLOW_SWAP=y +CONFIG_KEXEC_FILE=y +CONFIG_TCG_TIS_I2C=m diff --git a/config.local/arm64/config.sonic-aspeed b/config.local/arm64/config.sonic-aspeed new file mode 100644 index 000000000..b37cee88c --- /dev/null +++ b/config.local/arm64/config.sonic-aspeed @@ -0,0 +1,198 @@ +CONFIG_ARCH_ASPEED=y +CONFIG_NET_VENDOR_FARADAY=y +CONFIG_NET_NCSI=y +CONFIG_NCSI_OEM_CMD_KEEP_PHY=y +CONFIG_FTGMAC100=y +CONFIG_UNICODE=y +CONFIG_BLOCK=y +CONFIG_LBDAF=y +CONFIG_FS_MBCACHE=y +CONFIG_JBD2=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_USE_FOR_EXT2=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_MCTP=y +CONFIG_SERIAL_8250_ASPEED_VUART=y +CONFIG_SERIAL_8250_ASPEED=y +CONFIG_SERIAL_8250_NR_UARTS=24 +CONFIG_SERIAL_8250_RUNTIME_UARTS=24 +CONFIG_CAN_ASPEED=m +CONFIG_MDIO_DEVICE=m +CONFIG_MDIO_BUS=m +CONFIG_MDIO_ASPEED=m +CONFIG_ASPEED_KCS_IPMI_BMC=m +CONFIG_ASPEED_BT_IPMI_BMC=m +CONFIG_SPI_ASPEED_SMC=m +CONFIG_I2C_AST2600=m +CONFIG_SPI_ASPEED_TXRX=y +CONFIG_PINCTRL_ASPEED=y +CONFIG_PINCTRL_ASPEED_G7=y +CONFIG_GPIO_ASPEED=y +CONFIG_GPIO_ASPEED_SGPIO=y +CONFIG_GPIO_ASPEED_LTPI=y +CONFIG_SENSORS_ASPEED=y +CONFIG_SENSORS_ASPEED_G6=m +CONFIG_SENSORS_ASPEED_CHASSIS=m +CONFIG_ASPEED_WATCHDOG=y +CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_VIDEO_DEV=m +CONFIG_DVB_CORE=m +CONFIG_VIDEO_V4L2_I2C=m +CONFIG_VIDEO_V4L2_SUBDEV_API=y +CONFIG_MEDIA_CONTROLLER=m +CONFIG_V4L2_FWNODE=m +CONFIG_V4L2_ASYNC=m +CONFIG_DVB_NET=m +CONFIG_DVB_MAX_ADAPTERS=16 +CONFIG_DVB_DYNAMIC_MINORS=y +CONFIG_MEDIA_PLATFORM_DRIVERS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_ASPEED=m +CONFIG_VIDEOBUF2_CORE=m +CONFIG_VIDEOBUF2_V4L2=m +CONFIG_VIDEOBUF2_MEMOPS=m +CONFIG_VIDEOBUF2_DMA_CONTIG=m +CONFIG_VIDEOBUF2_VMALLOC=m +CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER=y +CONFIG_USB_UHCI_PLATFORM=y +CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC=y +CONFIG_USB_UHCI_ASPEED=y +# CONFIG_USB_ASPEED_UDC is not set +CONFIG_USB_ASPEED_VHUB=m +CONFIG_MMC_SDHCI=y +CONFIG_MMC_CQHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_OF_ASPEED=y +# CONFIG_EDAC_ASPEED is not set +CONFIG_EDAC_AST2700=y +CONFIG_RTC_DRV_ASPEED=y +CONFIG_COMMON_CLK_ASPEED=y +CONFIG_COMMON_CLK_AST2700=y +CONFIG_ASPEED_LPC_CTRL=y +CONFIG_ASPEED_LPC_SNOOP=y +CONFIG_ASPEED_UART_ROUTING=y +CONFIG_ASPEED_P2A_CTRL=y +CONFIG_ASPEED_SOCINFO=y +CONFIG_ASPEED_BMC_DEV=y +CONFIG_ASPEED_HOST_BMC_DEV=y +CONFIG_ASPEED_MCTP=y +CONFIG_ASPEED_LPC_MAILBOX=y +CONFIG_ASPEED_XDMA=y +CONFIG_ASPEED_SBC=y +CONFIG_AST2700_ESPI=y +CONFIG_AST2700_RTC_OVER_ESPI=y +CONFIG_ASPEED_LPC_PCC=y +CONFIG_ASPEED_UDMA=y +CONFIG_ASPEED_OTP=y +CONFIG_AST2700_OTP=y +CONFIG_ASPEED_DISP_INTF=y +CONFIG_PHY_ASPEED_SGMII=y +CONFIG_ASPEED_PCIE_MMBI=y +CONFIG_ASPEED_MBOX=y +CONFIG_ASPEED_ADC=m +CONFIG_IIO_HWMON=m +CONFIG_ASPEED_VIC=y +CONFIG_ASPEED_SCU_IC=y +CONFIG_ASPEED_I2C_IC=y +CONFIG_ASPEED_INTC=y +CONFIG_ASPEED_E2M_IC=y +CONFIG_RESET_ASPEED=y +CONFIG_SQUASHFS=y +CONFIG_CRYPTO_ENGINE=y +CONFIG_CRYPTO_DEV_ASPEED=y +# CONFIG_CRYPTO_DEV_ASPEED_DEBUG is not set +# CONFIG_CRYPTO_DEV_ASPEED_HACE is not set +# CONFIG_CRYPTO_DEV_ASPEED_RSSS is not set +# CONFIG_CRYPTO_DEV_ASPEED_ECDSA is not set + +###-> nvidia_aspeed_bmc-start +CONFIG_MODVERSIONS=y +CONFIG_MCTP=y +CONFIG_MTD_PHRAM=y +CONFIG_CHR_DEV_SG=y +CONFIG_REALTEK_PHY=m +CONFIG_USB_USBNET=y +CONFIG_USB_NET_CDCETHER=y +CONFIG_USB_NET_CDC_NCM=y +CONFIG_USB_NET_CDC_SUBSET_ENABLE=y +CONFIG_USB_NET_CDC_SUBSET=y +CONFIG_TCG_TIS_SPI=y +CONFIG_TCG_TIS_SPI_CR50=n +CONFIG_I2C_MUX_MLXCPLD=m +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_I3C=m +CONFIG_SPI_SPIDEV=y +CONFIG_SENSORS_ADT7475=y +CONFIG_SENSORS_SBTSI=y +CONFIG_SENSORS_SBRMI=y +CONFIG_SENSORS_EMC1403=m +CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +CONFIG_HIDRAW=y +CONFIG_USB_MON=y +CONFIG_USB_UHCI_HCD=y +CONFIG_USB_F_ECM=y +CONFIG_USB_F_RNDIS=y +CONFIG_USB_ETH=y +CONFIG_LEDS_MLXREG=m +CONFIG_MELLANOX_PLATFORM=y +CONFIG_MAX1363=y +CONFIG_TI_ADS1015=y +CONFIG_TI_ADS7924=y +CONFIG_MSDOS_FS=y +CONFIG_FAT_DEFAULT_UTF8=y +CONFIG_PSTORE_RAM=y +CONFIG_LSUSB=y +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_ASPEED=y +CONFIG_SPI_ASPEED_TXRX=y +CONFIG_SPI_ASPEED_SMC=y +CONFIG_SPI_FMC=m +CONFIG_FIXED_PHY_APPLY=y +CONFIG_FIXED_PHY_SPEED=100 +CONFIG_FIXED_PHY_FULL_DUPLEX=y +CONFIG_FTGMAC100_FIXED_PHY_SPEED=100 +CONFIG_FTGMAC100_FIXED_PHY_FULL_DUPLEX=1 +CONFIG_ASPEED_MCTP=y +CONFIG_JTAG=y +CONFIG_JTAG_ASPEED=y +CONFIG_COMMON_CLK_AST2700=y +CONFIG_AST2700_MBOX=y +CONFIG_RTL_RGMII_SGMII_3809=y +CONFIG_HID_CP2112=m +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_ASPEED_SGPIO=y +CONFIG_ASPEED_LPC_SNOOP=y +CONFIG_AST2700_ESPI=y +CONFIG_MISC_DEVICES=y +CONFIG_ASPEED_UART_ROUTING=y +CONFIG_FTGMAC100=m +CONFIG_SENSORS_LM25066=m +CONFIG_SENSORS_MP2855=m +CONFIG_SENSORS_MP2888=m +CONFIG_SENSORS_MP2975=m +CONFIG_MCTP_FLOWS=y +CONFIG_MCTP_TRANSPORT_I2C=y +CONFIG_I3C_MCTP=m +CONFIG_I3C_TARGET_MCTP=m +CONFIG_NVIDIA_AST27XX_IROT=m +CONFIG_PLDMFW=y +CONFIG_I2C_COMPAT=y +CONFIG_I2C_AST2600=y +CONFIG_I2C_AST2600_SLAVE_RX_WORKAROUND=y +CONFIG_DW_I3C_MASTER=m +CONFIG_MIPI_I3C_HCI=m +CONFIG_I3C_HUB=m +CONFIG_REGMAP_I3C=m +CONFIG_SENSORS_MLXREG_FAN=m +CONFIG_MLXREG_HOTPLUG=m +CONFIG_MLXREG_IO=m +CONFIG_MLX_WDT=m +CONFIG_RTC_DRV_PCF85053A=y +CONFIG_NVSW_BMC_HID162=m +CONFIG_I2C_MUX_REGMAP=y +CONFIG_PCIE_PME=n +###-> nvidia_aspeed_bmc-end + diff --git a/config.local/arm64/config.sonic-mellanox b/config.local/arm64/config.sonic-mellanox new file mode 100644 index 000000000..7155ccc94 --- /dev/null +++ b/config.local/arm64/config.sonic-mellanox @@ -0,0 +1,2 @@ +[mellanox-arm64] + diff --git a/config.local/arm64/config.sonic-pensando b/config.local/arm64/config.sonic-pensando new file mode 100644 index 000000000..bbd55bc44 --- /dev/null +++ b/config.local/arm64/config.sonic-pensando @@ -0,0 +1,20 @@ +CONFIG_ARCH_PENSANDO=y +CONFIG_ARCH_PENSANDO_ELBA_SOC=y +CONFIG_PENSANDO_SOC_PENFW=y +CONFIG_EDAC_ELBA=y +CONFIG_IRQ_PENSANDO=y +CONFIG_MFD_PENSANDO_ELBASR=y +CONFIG_RESET_ELBASR=y +CONFIG_UIO_PCIEMAC=m +CONFIG_UIO_PENMSI1=m +CONFIG_UIO_PENGIC=m +CONFIG_UIO_PENMSI=m +CONFIG_ARM64_VA_BITS_39=y +CONFIG_COMMON_CLK_CS2000_CP=y +CONFIG_I2C_RD1173=y +CONFIG_CONTEXT_TRACKING=y +CONFIG_NO_HZ_FULL=y +CONFIG_RCU_NOCB_CPU=y +CONFIG_VIRT_CPU_ACCOUNTING=y +CONFIG_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_RTC_DRV_ELBA_CPLD=y diff --git a/config.local/arm64/config.sonic-secureboot b/config.local/arm64/config.sonic-secureboot new file mode 100644 index 000000000..4eac105b3 --- /dev/null +++ b/config.local/arm64/config.sonic-secureboot @@ -0,0 +1,12 @@ +CONFIG_SYSTEM_TRUSTED_KEYS="debian/certs/debian-uefi-certs.pem" +CONFIG_MODULE_SIG_HASH="sha512" +CONFIG_MODULE_SIG_SHA512=y +CONFIG_KEXEC_SIG=y +CONFIG_MODULE_SIG_FORCE=y + +CONFIG_MODULE_SIG_SHA256=n +# For mellanox +CONFIG_SECURITY_LOCKDOWN_LSM=n +CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=n +CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=n +CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT=n diff --git a/config.local/arm64/defines.toml b/config.local/arm64/defines.toml new file mode 100644 index 000000000..c4c3bd185 --- /dev/null +++ b/config.local/arm64/defines.toml @@ -0,0 +1,6 @@ +[[featureset]] +name = 'sonic' +[[featureset.flavour]] +name = 'arm64' +[featureset.flavour.build] +config = ['arm64/config.sonic', 'arm64/config.sonic-platform-specific'] diff --git a/config.local/armhf/config.sonic b/config.local/armhf/config.sonic new file mode 100644 index 000000000..788a8dc0f --- /dev/null +++ b/config.local/armhf/config.sonic @@ -0,0 +1,3 @@ +CONFIG_EEPROM_SFF_8436=m +CONFIG_EEPROM_OPTOE=m +CONFIG_I2C_MUX_GPIO=m diff --git a/config.local/armhf/config.sonic-marvell-prestera b/config.local/armhf/config.sonic-marvell-prestera new file mode 100644 index 000000000..e8e09b5d2 --- /dev/null +++ b/config.local/armhf/config.sonic-marvell-prestera @@ -0,0 +1,22 @@ +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_OF_PARTS=y +CONFIG_ARCH_VIRT=n +CONFIG_ARCH_BCM=n +CONFIG_ARCH_EXYNOS=n +CONFIG_ARCH_HIGHBANK=n +CONFIG_ARCH_MXC=n +CONFIG_ARCH_MESON=n +CONFIG_ARCH_OMAP3=n +CONFIG_ARCH_OMAP4=n +CONFIG_ARCH_ROCKCHIP=n +CONFIG_ARCH_SOCFPGA=n +CONFIG_ARCH_SUNXI=n +CONFIG_ARCH_TEGRA=n +CONFIG_ARCH_VEXPRESS=n +CONFIG_ARCH_WM8850=n +CONFIG_SOC_OMAP5=n +CONFIG_SOC_AM33XX=n +CONFIG_SOC_DRA7XX=n +CONFIG_EFI=n +CONFIG_SECONDARY_TRUSTED_KEYRING=n +CONFIG_SYSTEM_BLACKLIST_KEYRING=n diff --git a/config.local/armhf/defines.toml b/config.local/armhf/defines.toml new file mode 100644 index 000000000..2cf233d0b --- /dev/null +++ b/config.local/armhf/defines.toml @@ -0,0 +1,6 @@ +[[featureset]] +name = 'sonic' +[[featureset.flavour]] +name = 'armmp' +[featureset.flavour.build] +config = ['armhf/config.sonic', 'armhf/config.sonic-platform-specific'] diff --git a/config.local/defines.toml b/config.local/defines.toml new file mode 100644 index 000000000..5e09e52b9 --- /dev/null +++ b/config.local/defines.toml @@ -0,0 +1,22 @@ +[packages] +docs = false +source = false +tools_unversioned = false + +[[featureset]] +name = 'none' +enable = false + +[[featureset]] +name = 'rt' +enable = false + +[[featureset]] +name = 'sonic' +enable = true + [featureset.description] + parts = ['sonic'] + [featureset.description.long] + sonic = 'This kernel includes patches that support SONiC.' + [featureset.description.short] + sonic = 'SONiC Kernel' diff --git a/config.local/featureset-sonic/config b/config.local/featureset-sonic/config new file mode 100644 index 000000000..9dc91ba57 --- /dev/null +++ b/config.local/featureset-sonic/config @@ -0,0 +1,22 @@ +CONFIG_LOG_BUF_SHIFT=20 +CONFIG_RTC_INTF_DEV_UIE_EMUL=y + +# Unset to enable UBIFS on flash +CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=n +###-> mellanox_common-start +###-> mellanox_common-end +CONFIG_DRM=n +CONFIG_VIDEO_DEV=n +CONFIG_MEDIA_SUPPORT=n +CONFIG_MEDIA_CONTROLLER=n +CONFIG_DVB_CORE=n +CONFIG_MEDIA_TUNER=n +CONFIG_MEDIA_ATTACH=n + +# Disable module compression, since squashfs will do compression anyways +CONFIG_MODULE_COMPRESS=n +# Disable strict /dev/mem access checks to allow mmap on PCI resource file +CONFIG_IO_STRICT_DEVMEM=n + +# Set THP default to madvise instead of always +CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y diff --git a/manage-config b/manage-config index 1d26574e1..31fcd7f66 100755 --- a/manage-config +++ b/manage-config @@ -5,126 +5,38 @@ # # Add / Remove options in kernel configuration, # and Verify the kernel configuration afterwards. -# -# To remove options from the kernel, add the options into the flat text file -# patch/kconfig-exclusions -# -# Example: -# CONFIG_SOUND -# CONFIG_ISDN -# -# To add options into the kernel, add the options into the flat text file -# patch/kconfig-inclusions -# -# Example: -# CONFIG_AD5064=y -# -# If the option is required on all architectures, add it to the common section; -# if the option is only relevant to a specific architecture, add it to the -# section of the corresponding architecture. -# Configuration file to change ARCH=amd64 -PLATFORM= +SECURE_UPGRADE_MODE="no_sign" +SECURE_UPGRADE_KERNEL_CAFILE= if [ $# -ge 1 ]; then ARCH=$1 fi if [ $# -ge 2 ]; then - PLATFORM=$2 + SECURE_UPGRADE_MODE=$2 +fi +if [ $# -ge 3 ]; then + SECURE_UPGRADE_KERNEL_CAFILE=$3 fi -case "$ARCH" in - amd64) - CONFIG_FILE_LOC=debian/build/build_amd64_none_amd64 - ;; - arm64) - CONFIG_FILE_LOC=debian/build/build_arm64_none_arm64 - ;; - armhf) - CONFIG_FILE_LOC=debian/build/build_armhf_none_armmp - ;; - *) - CONFIG_FILE_LOC=debian/build/build_amd64_none_amd64 - ;; -esac -CONFIG_FILE=${CONFIG_FILE_LOC}/.config - -function get_section_opts(){ - file=$1 - for((i=2;i<=$#;i++));do - eval section=\$$i - opts+=$(sed -n '/^\['${section}'\]/, /^\[.*\]/p' ${file} | grep -Ev '\[.*\]|^$|[#;]') - opts+=$'\n' - done - echo "$opts" -} - -ret=0 -exclusion_file="../patch/kconfig-exclusions" -inclusion_file="../patch/kconfig-inclusions" -if [ -e ${exclusion_file} -o -e ${inclusion_file} ]; then - - # Process any exclusions in the kernel - if [ -f ${exclusion_file} ]; then - exclusion_opts=$(get_section_opts ${exclusion_file} "common" ${ARCH} ${PLATFORM}) - while read -r opt; do - if [ ! -z "$opt" ] && [[ ! "$opt" =~ ^#.* ]]; then - scripts/config --file ${CONFIG_FILE} -d $opt - fi - done <<< ${exclusion_opts}; - fi - # Process any inclusions in the kernel - if [ -f ${inclusion_file} ]; then - inclusion_opts=$(get_section_opts ${inclusion_file} "common" ${ARCH} ${PLATFORM}) - while read -r opt; do - if [ ! -z "$opt" ] && [[ ! "$opt" =~ ^#.* ]]; then - echo $opt >> ${CONFIG_FILE} - fi - done <<< ${inclusion_opts}; - fi +# Secure Boot support +echo "Secure Boot params: SECURE_UPGRADE_MODE=${SECURE_UPGRADE_MODE}, SECURE_UPGRADE_KERNEL_CAFILE=${SECURE_UPGRADE_KERNEL_CAFILE}" +if [ ${SECURE_UPGRADE_MODE} == "dev" -o ${SECURE_UPGRADE_MODE} == "prod" ]; then + echo "Enable secure boot configs" - # Update the .config file to be sure it's consistent - make -C ${CONFIG_FILE_LOC} olddefconfig + if [ ! -f "${SECURE_UPGRADE_KERNEL_CAFILE}" ]; then + echo "ERROR: SECURE_UPGRADE_KERNEL_CAFILE=${SECURE_UPGRADE_KERNEL_CAFILE} file does not exist" + exit 1 + fi - # Verify that the kernel options we want to remove are not in the updated configuration - if [ -f ${exclusion_file} ]; then - echo - echo "Checking removed kernel options..." - while read -r opt; do - if [ ! -z "$opt" ] && [[ ! "$opt" =~ ^#.* ]]; then - s=$(scripts/config --file ${CONFIG_FILE} -k --state $opt) - if [ ! "$s" = "undef" -a ! "$s" = "n" ]; then - ret=1 - echo "Option $opt should not be set, but is set to [$s]" - fi - fi - done <<< ${exclusion_opts}; - if [ $ret = 0 ]; then - echo "No error" - fi - fi + if [ -f debian/config.local/${ARCH}/config.sonic-secureboot ]; then + cat debian/config.local/${ARCH}/config.sonic-secureboot >> debian/config.local/${ARCH}/config.sonic + fi - # Verify that the kernel options we want to add are now in the updated configuration - if [ -f ${inclusion_file} ]; then - echo - echo "Checking added kernel options..." - while read -r opt; do - if [ ! -z "$opt" ] && [[ ! "$opt" =~ ^#.* ]]; then - n=${opt%=*} - v="${opt#*=}" - s=$(scripts/config --file ${CONFIG_FILE} -k --state $n) - if [ ! "$s" = "$v" ]; then - ret=2 - echo "Option $n should be set to [$v] instead of [$s]" - fi - fi - done <<< ${inclusion_opts}; - if [ ! $ret = 2 ]; then - echo "No error" - fi - fi + # save the new pub key in kernel + sed -i "s|^CONFIG_SYSTEM_TRUSTED_KEYS=.*|CONFIG_SYSTEM_TRUSTED_KEYS=\"$SECURE_UPGRADE_KERNEL_CAFILE\"|g" debian/config.local/${ARCH}/config.sonic - echo + echo "Secure Boot kernel configuration done." +else + echo "No Secure Boot Kernel configuration required." fi - -exit $ret diff --git a/patch/0001-Mellanox-platform-Backport-patches-for-new-Mellanox-.patch b/patch/0001-Mellanox-platform-Backport-patches-for-new-Mellanox-.patch deleted file mode 100644 index 377457c8e..000000000 --- a/patch/0001-Mellanox-platform-Backport-patches-for-new-Mellanox-.patch +++ /dev/null @@ -1,7260 +0,0 @@ -From e4149afc593ef4bc14d5980a9a72413fd0406750 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Mon, 29 Jun 2020 18:48:23 +0300 -Subject: [backport 01/12] Mellanox platform: Backport patches for new Mellanox - systems - -It contains backport patches from v4.20 - v5.5 and below bug fixes and -optimizations. - -Fix wrong order in probe error flow, in case the firmware version is -incompatible with the driver. -Validation of firmware version should be provided at early stage, prior -of initialization of different driver components. - -Decrease i2c controller polling time from 2000usec to 200usec -for the performance improvement. - -Skip thermal zones setting for modules to reduce probing time. -It is to be read anyway during thermal zone operations. -Skip thermal zone setting and reading during initialization. - -Drop creation of "hwmon" interfaces from "thermal". These interfaces -are redundant, since they are created by "core_hwmon" component. -Creation of those interface from "thermal" just causes each temperature -input entry to by created twice in "hwmon" -Add thermal zone platform parameters definition with the field -"no_hwmon" set to true. Use it in thermal_zone_device_register(). -It will indicate that the "thermal" to "hwmon" sysfs interface is not - -Adding module for ethtool support. - -Changing Mellanox i2c bus poling time frequency. - -Adding watchdog Mellanox driver. - -Signed-off-by: Vadim Pasternak ---- - drivers/hwmon/mlxreg-fan.c | 90 +- - drivers/i2c/busses/i2c-mlxcpld.c | 2 +- - drivers/leds/leds-mlxreg.c | 21 +- - drivers/net/ethernet/mellanox/mlxsw/Kconfig | 9 + - drivers/net/ethernet/mellanox/mlxsw/Makefile | 3 +- - drivers/net/ethernet/mellanox/mlxsw/core.c | 82 +- - drivers/net/ethernet/mellanox/mlxsw/core.h | 36 + - drivers/net/ethernet/mellanox/mlxsw/core_env.c | 256 ++++ - drivers/net/ethernet/mellanox/mlxsw/core_env.h | 17 + - drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c | 361 +++++- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 696 +++++++++- - drivers/net/ethernet/mellanox/mlxsw/minimal.c | 328 ++++- - drivers/net/ethernet/mellanox/mlxsw/pci.c | 49 +- - drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c | 454 +++++++ - drivers/net/ethernet/mellanox/mlxsw/reg.h | 1221 +++++++++++++++++- - drivers/net/ethernet/mellanox/mlxsw/resources.h | 6 + - drivers/platform/mellanox/mlxreg-hotplug.c | 42 +- - drivers/platform/x86/mlx-platform.c | 1328 +++++++++++++++++--- - drivers/watchdog/Kconfig | 16 + - drivers/watchdog/Makefile | 1 + - drivers/watchdog/mlx_wdt.c | 289 +++++ - include/linux/platform_data/mlxreg.h | 27 +- - include/uapi/linux/ethtool.h | 3 + - 23 files changed, 5039 insertions(+), 298 deletions(-) - create mode 100644 drivers/net/ethernet/mellanox/mlxsw/core_env.c - create mode 100644 drivers/net/ethernet/mellanox/mlxsw/core_env.h - create mode 100644 drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c - create mode 100644 drivers/watchdog/mlx_wdt.c - -diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c -index d8fa4be..7028a4f 100644 ---- a/drivers/hwmon/mlxreg-fan.c -+++ b/drivers/hwmon/mlxreg-fan.c -@@ -27,7 +27,9 @@ - #define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2) - #define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ - #define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44 --#define MLXREG_FAN_TACHO_DIVIDER_DEF 1132 -+#define MLXREG_FAN_TACHO_DIV_MIN 283 -+#define MLXREG_FAN_TACHO_DIV_DEF (MLXREG_FAN_TACHO_DIV_MIN * 4) -+#define MLXREG_FAN_TACHO_DIV_SCALE_MAX 64 - /* - * FAN datasheet defines the formula for RPM calculations as RPM = 15/t-high. - * The logic in a programmable device measures the time t-high by sampling the -@@ -51,7 +53,7 @@ - */ - #define MLXREG_FAN_GET_RPM(rval, d, s) (DIV_ROUND_CLOSEST(15000000 * 100, \ - ((rval) + (s)) * (d))) --#define MLXREG_FAN_GET_FAULT(val, mask) (!((val) ^ (mask))) -+#define MLXREG_FAN_GET_FAULT(val, mask) ((val) == (mask)) - #define MLXREG_FAN_PWM_DUTY2STATE(duty) (DIV_ROUND_CLOSEST((duty) * \ - MLXREG_FAN_MAX_STATE, \ - MLXREG_FAN_MAX_DUTY)) -@@ -360,15 +362,57 @@ static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = { - .set_cur_state = mlxreg_fan_set_cur_state, - }; - -+static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan, -+ struct mlxreg_core_data *data) -+{ -+ u32 regval; -+ int err; -+ -+ err = regmap_read(fan->regmap, data->capability, ®val); -+ if (err) { -+ dev_err(fan->dev, "Failed to query capability register 0x%08x\n", -+ data->capability); -+ return err; -+ } -+ -+ return !!(regval & data->bit); -+} -+ -+static int mlxreg_fan_speed_divider_get(struct mlxreg_fan *fan, -+ struct mlxreg_core_data *data) -+{ -+ u32 regval; -+ int err; -+ -+ err = regmap_read(fan->regmap, data->capability, ®val); -+ if (err) { -+ dev_err(fan->dev, "Failed to query capability register 0x%08x\n", -+ data->capability); -+ return err; -+ } -+ -+ /* -+ * Set divider value according to the capability register, in case it -+ * contains valid value. Otherwise use default value. The purpose of -+ * this validation is to protect against the old hardware, in which -+ * this register can return zero. -+ */ -+ if (regval > 0 && regval <= MLXREG_FAN_TACHO_DIV_SCALE_MAX) -+ fan->divider = regval * MLXREG_FAN_TACHO_DIV_MIN; -+ -+ return 0; -+} -+ - static int mlxreg_fan_config(struct mlxreg_fan *fan, - struct mlxreg_core_platform_data *pdata) - { - struct mlxreg_core_data *data = pdata->data; - bool configured = false; - int tacho_num = 0, i; -+ int err; - - fan->samples = MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF; -- fan->divider = MLXREG_FAN_TACHO_DIVIDER_DEF; -+ fan->divider = MLXREG_FAN_TACHO_DIV_DEF; - for (i = 0; i < pdata->counter; i++, data++) { - if (strnstr(data->label, "tacho", sizeof(data->label))) { - if (tacho_num == MLXREG_FAN_MAX_TACHO) { -@@ -376,6 +420,17 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan, - data->label); - return -EINVAL; - } -+ -+ if (data->capability) { -+ err = mlxreg_fan_connect_verify(fan, data); -+ if (err < 0) -+ return err; -+ else if (!err) { -+ tacho_num++; -+ continue; -+ } -+ } -+ - fan->tacho[tacho_num].reg = data->reg; - fan->tacho[tacho_num].mask = data->mask; - fan->tacho[tacho_num++].connected = true; -@@ -394,13 +449,21 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan, - return -EINVAL; - } - /* Validate that conf parameters are not zeros. */ -- if (!data->mask || !data->bit) { -+ if (!data->mask && !data->bit && !data->capability) { - dev_err(fan->dev, "invalid conf entry params: %s\n", - data->label); - return -EINVAL; - } -- fan->samples = data->mask; -- fan->divider = data->bit; -+ if (data->capability) { -+ err = mlxreg_fan_speed_divider_get(fan, data); -+ if (err) -+ return err; -+ } else { -+ if (data->mask) -+ fan->samples = data->mask; -+ if (data->bit) -+ fan->divider = data->bit; -+ } - configured = true; - } else { - dev_err(fan->dev, "invalid label: %s\n", data->label); -@@ -420,21 +483,22 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan, - static int mlxreg_fan_probe(struct platform_device *pdev) - { - struct mlxreg_core_platform_data *pdata; -+ struct device *dev = &pdev->dev; - struct mlxreg_fan *fan; - struct device *hwm; - int err; - -- pdata = dev_get_platdata(&pdev->dev); -+ pdata = dev_get_platdata(dev); - if (!pdata) { -- dev_err(&pdev->dev, "Failed to get platform data.\n"); -+ dev_err(dev, "Failed to get platform data.\n"); - return -EINVAL; - } - -- fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); -+ fan = devm_kzalloc(dev, sizeof(*fan), GFP_KERNEL); - if (!fan) - return -ENOMEM; - -- fan->dev = &pdev->dev; -+ fan->dev = dev; - fan->regmap = pdata->regmap; - platform_set_drvdata(pdev, fan); - -@@ -442,12 +506,12 @@ static int mlxreg_fan_probe(struct platform_device *pdev) - if (err) - return err; - -- hwm = devm_hwmon_device_register_with_info(&pdev->dev, "mlxreg_fan", -+ hwm = devm_hwmon_device_register_with_info(dev, "mlxreg_fan", - fan, - &mlxreg_fan_hwmon_chip_info, - NULL); - if (IS_ERR(hwm)) { -- dev_err(&pdev->dev, "Failed to register hwmon device\n"); -+ dev_err(dev, "Failed to register hwmon device\n"); - return PTR_ERR(hwm); - } - -@@ -455,7 +519,7 @@ static int mlxreg_fan_probe(struct platform_device *pdev) - fan->cdev = thermal_cooling_device_register("mlxreg_fan", fan, - &mlxreg_fan_cooling_ops); - if (IS_ERR(fan->cdev)) { -- dev_err(&pdev->dev, "Failed to register cooling device\n"); -+ dev_err(dev, "Failed to register cooling device\n"); - return PTR_ERR(fan->cdev); - } - } -diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c -index 2fd717d..6da4b58 100644 ---- a/drivers/i2c/busses/i2c-mlxcpld.c -+++ b/drivers/i2c/busses/i2c-mlxcpld.c -@@ -51,7 +51,7 @@ - #define MLXCPLD_I2C_MAX_ADDR_LEN 4 - #define MLXCPLD_I2C_RETR_NUM 2 - #define MLXCPLD_I2C_XFER_TO 500000 /* usec */ --#define MLXCPLD_I2C_POLL_TIME 2000 /* usec */ -+#define MLXCPLD_I2C_POLL_TIME 400 /* usec */ - - /* LPC I2C registers */ - #define MLXCPLD_LPCI2C_CPBLTY_REG 0x0 -diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c -index 1ee48cb..781eb03 100644 ---- a/drivers/leds/leds-mlxreg.c -+++ b/drivers/leds/leds-mlxreg.c -@@ -22,6 +22,7 @@ - #define MLXREG_LED_AMBER_SOLID 0x09 /* Solid amber */ - #define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */ - #define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */ -+#define MLXREG_LED_CAPABILITY_CLEAR GENMASK(31, 8) /* Clear mask */ - - /** - * struct mlxreg_led_data - led control data: -@@ -186,7 +187,8 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) - struct mlxreg_core_data *data = led_pdata->data; - struct mlxreg_led_data *led_data; - struct led_classdev *led_cdev; -- enum led_brightness brightness; -+ int brightness; -+ u32 regval; - int i; - int err; - -@@ -196,6 +198,23 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) - if (!led_data) - return -ENOMEM; - -+ if (data->capability) { -+ err = regmap_read(led_pdata->regmap, data->capability, -+ ®val); -+ if (err) { -+ dev_err(&priv->pdev->dev, "Failed to query capability register\n"); -+ return err; -+ } -+ if (!(regval & data->bit)) -+ continue; -+ /* -+ * Field "bit" can contain one capability bit in 0 byte -+ * and offset bit in 1-3 bytes. Clear capability bit and -+ * keep only offset bit. -+ */ -+ data->bit &= MLXREG_LED_CAPABILITY_CLEAR; -+ } -+ - led_cdev = &led_data->led_cdev; - led_data->data_parent = priv; - if (strstr(data->label, "red") || -diff --git a/drivers/net/ethernet/mellanox/mlxsw/Kconfig b/drivers/net/ethernet/mellanox/mlxsw/Kconfig -index 8a291eb..51e485b 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/Kconfig -+++ b/drivers/net/ethernet/mellanox/mlxsw/Kconfig -@@ -28,6 +28,15 @@ config MLXSW_CORE_THERMAL - Say Y here if you want to automatically control fans speed according - ambient temperature reported by ASIC. - -+config MLXSW_CORE_QSFP -+ bool "QSFP support for Mellanox Technologies Switch ASICs" -+ depends on MLXSW_CORE && HWMON -+ depends on !(MLXSW_CORE=y && HWMON=m) -+ default y -+ ---help--- -+ Say Y here if you want to expose sysfs QSFP interface on mlxsw -+ devices. -+ - config MLXSW_PCI - tristate "PCI bus implementation for Mellanox Technologies Switch ASICs" - depends on PCI && HAS_IOMEM && MLXSW_CORE -diff --git a/drivers/net/ethernet/mellanox/mlxsw/Makefile b/drivers/net/ethernet/mellanox/mlxsw/Makefile -index 68fa44a..da75f61 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/Makefile -+++ b/drivers/net/ethernet/mellanox/mlxsw/Makefile -@@ -1,9 +1,10 @@ - # SPDX-License-Identifier: GPL-2.0 - obj-$(CONFIG_MLXSW_CORE) += mlxsw_core.o - mlxsw_core-objs := core.o core_acl_flex_keys.o \ -- core_acl_flex_actions.o -+ core_acl_flex_actions.o core_env.o - mlxsw_core-$(CONFIG_MLXSW_CORE_HWMON) += core_hwmon.o - mlxsw_core-$(CONFIG_MLXSW_CORE_THERMAL) += core_thermal.o -+mlxsw_core-$(CONFIG_MLXSW_CORE_QSFP) += qsfp_sysfs.o - obj-$(CONFIG_MLXSW_PCI) += mlxsw_pci.o - mlxsw_pci-objs := pci.o - obj-$(CONFIG_MLXSW_I2C) += mlxsw_i2c.o -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c -index 2e6df58..c38c1c5 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core.c -@@ -78,6 +78,7 @@ struct mlxsw_core { - struct mlxsw_res res; - struct mlxsw_hwmon *hwmon; - struct mlxsw_thermal *thermal; -+ struct mlxsw_qsfp *qsfp; - struct mlxsw_core_port *ports; - unsigned int max_ports; - bool reload_fail; -@@ -122,6 +123,12 @@ void *mlxsw_core_driver_priv(struct mlxsw_core *mlxsw_core) - } - EXPORT_SYMBOL(mlxsw_core_driver_priv); - -+bool mlxsw_core_res_query_enabled(const struct mlxsw_core *mlxsw_core) -+{ -+ return mlxsw_core->driver->res_query_enabled; -+} -+EXPORT_SYMBOL(mlxsw_core_res_query_enabled); -+ - struct mlxsw_rx_listener_item { - struct list_head list; - struct mlxsw_rx_listener rxl; -@@ -971,9 +978,9 @@ static const struct devlink_ops mlxsw_devlink_ops = { - }; - - int mlxsw_core_bus_device_register(const struct mlxsw_bus_info *mlxsw_bus_info, -- const struct mlxsw_bus *mlxsw_bus, -- void *bus_priv, bool reload, -- struct devlink *devlink) -+ const struct mlxsw_bus *mlxsw_bus, -+ void *bus_priv, bool reload, -+ struct devlink *devlink) - { - const char *device_kind = mlxsw_bus_info->device_kind; - struct mlxsw_core *mlxsw_core; -@@ -1040,6 +1047,18 @@ int mlxsw_core_bus_device_register(const struct mlxsw_bus_info *mlxsw_bus_info, - goto err_devlink_register; - } - -+ if (mlxsw_driver->init) { -+ err = mlxsw_driver->init(mlxsw_core, mlxsw_bus_info); -+ if (err) -+ goto err_driver_init; -+ } -+ -+ if (mlxsw_driver->params_register && !reload) { -+ err = mlxsw_driver->params_register(mlxsw_core); -+ if (err) -+ goto err_register_params; -+ } -+ - err = mlxsw_hwmon_init(mlxsw_core, mlxsw_bus_info, &mlxsw_core->hwmon); - if (err) - goto err_hwmon_init; -@@ -1049,19 +1068,22 @@ int mlxsw_core_bus_device_register(const struct mlxsw_bus_info *mlxsw_bus_info, - if (err) - goto err_thermal_init; - -- if (mlxsw_driver->init) { -- err = mlxsw_driver->init(mlxsw_core, mlxsw_bus_info); -- if (err) -- goto err_driver_init; -- } -+ err = mlxsw_qsfp_init(mlxsw_core, mlxsw_bus_info, -+ &mlxsw_core->qsfp); -+ if (err) -+ goto err_qsfp_init; - - return 0; - --err_driver_init: -+err_qsfp_init: - mlxsw_thermal_fini(mlxsw_core->thermal); - err_thermal_init: - mlxsw_hwmon_fini(mlxsw_core->hwmon); - err_hwmon_init: -+ if (mlxsw_driver->params_unregister && !reload) -+ mlxsw_driver->params_unregister(mlxsw_core); -+err_register_params: -+err_driver_init: - if (!reload) - devlink_unregister(devlink); - err_devlink_register: -@@ -1100,8 +1122,11 @@ void mlxsw_core_bus_device_unregister(struct mlxsw_core *mlxsw_core, - - if (mlxsw_core->driver->fini) - mlxsw_core->driver->fini(mlxsw_core); -+ mlxsw_qsfp_fini(mlxsw_core->qsfp); - mlxsw_thermal_fini(mlxsw_core->thermal); - mlxsw_hwmon_fini(mlxsw_core->hwmon); -+ if (mlxsw_core->driver->params_unregister && !reload) -+ mlxsw_core->driver->params_unregister(mlxsw_core); - if (!reload) - devlink_unregister(devlink); - mlxsw_emad_fini(mlxsw_core); -@@ -1114,6 +1139,8 @@ void mlxsw_core_bus_device_unregister(struct mlxsw_core *mlxsw_core, - return; - - reload_fail_deinit: -+ if (mlxsw_core->driver->params_unregister) -+ mlxsw_core->driver->params_unregister(mlxsw_core); - devlink_unregister(devlink); - devlink_resources_unregister(devlink, NULL); - devlink_free(devlink); -@@ -1871,6 +1898,43 @@ void mlxsw_core_fw_flash_end(struct mlxsw_core *mlxsw_core) - } - EXPORT_SYMBOL(mlxsw_core_fw_flash_end); - -+int mlxsw_core_resources_query(struct mlxsw_core *mlxsw_core, char *mbox, -+ struct mlxsw_res *res) -+{ -+ int index, i; -+ u64 data; -+ u16 id; -+ int err; -+ -+ if (!res) -+ return 0; -+ -+ mlxsw_cmd_mbox_zero(mbox); -+ -+ for (index = 0; index < MLXSW_CMD_QUERY_RESOURCES_MAX_QUERIES; -+ index++) { -+ err = mlxsw_cmd_query_resources(mlxsw_core, mbox, index); -+ if (err) -+ return err; -+ -+ for (i = 0; i < MLXSW_CMD_QUERY_RESOURCES_PER_QUERY; i++) { -+ id = mlxsw_cmd_mbox_query_resource_id_get(mbox, i); -+ data = mlxsw_cmd_mbox_query_resource_data_get(mbox, i); -+ -+ if (id == MLXSW_CMD_QUERY_RESOURCES_TABLE_END_ID) -+ return 0; -+ -+ mlxsw_res_parse(res, id, data); -+ } -+ } -+ -+ /* If after MLXSW_RESOURCES_QUERY_MAX_QUERIES we still didn't get -+ * MLXSW_RESOURCES_TABLE_END_ID, something went bad in the FW. -+ */ -+ return -EIO; -+} -+EXPORT_SYMBOL(mlxsw_core_resources_query); -+ - static int __init mlxsw_core_module_init(void) - { - int err; -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.h b/drivers/net/ethernet/mellanox/mlxsw/core.h -index c4e4971..3fb3a75 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core.h -+++ b/drivers/net/ethernet/mellanox/mlxsw/core.h -@@ -28,6 +28,8 @@ unsigned int mlxsw_core_max_ports(const struct mlxsw_core *mlxsw_core); - - void *mlxsw_core_driver_priv(struct mlxsw_core *mlxsw_core); - -+bool mlxsw_core_res_query_enabled(const struct mlxsw_core *mlxsw_core); -+ - int mlxsw_core_driver_register(struct mlxsw_driver *mlxsw_driver); - void mlxsw_core_driver_unregister(struct mlxsw_driver *mlxsw_driver); - -@@ -182,6 +184,8 @@ int mlxsw_core_port_get_phys_port_name(struct mlxsw_core *mlxsw_core, - int mlxsw_core_schedule_dw(struct delayed_work *dwork, unsigned long delay); - bool mlxsw_core_schedule_work(struct work_struct *work); - void mlxsw_core_flush_owq(void); -+int mlxsw_core_resources_query(struct mlxsw_core *mlxsw_core, char *mbox, -+ struct mlxsw_res *res); - - #define MLXSW_CONFIG_PROFILE_SWID_COUNT 8 - -@@ -282,6 +286,8 @@ struct mlxsw_driver { - const struct mlxsw_config_profile *profile, - u64 *p_single_size, u64 *p_double_size, - u64 *p_linear_size); -+ int (*params_register)(struct mlxsw_core *mlxsw_core); -+ void (*params_unregister)(struct mlxsw_core *mlxsw_core); - u8 txhdr_len; - const struct mlxsw_config_profile *profile; - bool res_query_enabled; -@@ -342,6 +348,7 @@ struct mlxsw_bus_info { - struct mlxsw_fw_rev fw_rev; - u8 vsd[MLXSW_CMD_BOARDINFO_VSD_LEN]; - u8 psid[MLXSW_CMD_BOARDINFO_PSID_LEN]; -+ u8 low_frequency; - }; - - struct mlxsw_hwmon; -@@ -392,4 +399,33 @@ static inline void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) - - #endif - -+enum mlxsw_devlink_param_id { -+ MLXSW_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, -+ MLXSW_DEVLINK_PARAM_ID_ACL_REGION_REHASH_INTERVAL, -+}; -+ -+struct mlxsw_qsfp; -+ -+#ifdef CONFIG_MLXSW_CORE_QSFP -+ -+int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, -+ const struct mlxsw_bus_info *mlxsw_bus_info, -+ struct mlxsw_qsfp **p_qsfp); -+void mlxsw_qsfp_fini(struct mlxsw_qsfp *qsfp); -+ -+#else -+ -+static inline int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, -+ const struct mlxsw_bus_info *mlxsw_bus_info, -+ struct mlxsw_qsfp **p_qsfp) -+{ -+ return 0; -+} -+ -+static inline void mlxsw_qsfp_fini(struct mlxsw_qsfp *qsfp) -+{ -+} -+ -+#endif -+ - #endif -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_env.c b/drivers/net/ethernet/mellanox/mlxsw/core_env.c -new file mode 100644 -index 0000000..03eae1e ---- /dev/null -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_env.c -@@ -0,0 +1,256 @@ -+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 -+/* Copyright (c) 2018 Mellanox Technologies. All rights reserved */ -+ -+#include -+#include -+#include -+ -+#include "core.h" -+#include "core_env.h" -+#include "item.h" -+#include "reg.h" -+ -+static int mlxsw_env_validate_cable_ident(struct mlxsw_core *core, int id, -+ bool *qsfp) -+{ -+ char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; -+ char mcia_pl[MLXSW_REG_MCIA_LEN]; -+ u8 ident; -+ int err; -+ -+ mlxsw_reg_mcia_pack(mcia_pl, id, 0, MLXSW_REG_MCIA_PAGE0_LO_OFF, 0, 1, -+ MLXSW_REG_MCIA_I2C_ADDR_LOW); -+ err = mlxsw_reg_query(core, MLXSW_REG(mcia), mcia_pl); -+ if (err) -+ return err; -+ mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); -+ ident = eeprom_tmp[0]; -+ switch (ident) { -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: -+ *qsfp = false; -+ break; -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP_DD: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS_CMIS: -+ *qsfp = true; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int -+mlxsw_env_query_module_eeprom(struct mlxsw_core *mlxsw_core, int module, -+ u16 offset, u16 size, bool qsfp, void *data, -+ unsigned int *p_read_size) -+{ -+ char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; -+ char mcia_pl[MLXSW_REG_MCIA_LEN]; -+ u16 i2c_addr; -+ u8 page = 0; -+ int status; -+ int err; -+ -+ size = min_t(u16, size, MLXSW_REG_MCIA_EEPROM_SIZE); -+ -+ if (offset < MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH && -+ offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) -+ /* Cross pages read, read until offset 256 in low page */ -+ size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - offset; -+ -+ i2c_addr = MLXSW_REG_MCIA_I2C_ADDR_LOW; -+ if (offset >= MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) { -+ if (qsfp) { -+ page = MLXSW_REG_MCIA_PAGE_GET(offset); -+ offset -= MLXSW_REG_MCIA_EEPROM_UP_PAGE_LENGTH * page; -+ if (offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) -+ size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - -+ offset; -+ } else { -+ i2c_addr = MLXSW_REG_MCIA_I2C_ADDR_HIGH; -+ offset -= MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH; -+ } -+ } -+ -+ mlxsw_reg_mcia_pack(mcia_pl, module, 0, page, offset, size, i2c_addr); -+ -+ err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcia), mcia_pl); -+ if (err) -+ return err; -+ -+ status = mlxsw_reg_mcia_status_get(mcia_pl); -+ if (status) -+ return -EIO; -+ -+ mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); -+ memcpy(data, eeprom_tmp, size); -+ *p_read_size = size; -+ -+ return 0; -+} -+ -+int mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, int module, -+ int off, int *temp) -+{ -+ char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; -+ union { -+ u8 buf[MLXSW_REG_MCIA_TH_ITEM_SIZE]; -+ u16 temp; -+ } temp_thresh; -+ char mcia_pl[MLXSW_REG_MCIA_LEN] = {0}; -+ char mtmp_pl[MLXSW_REG_MTMP_LEN]; -+ unsigned int module_temp; -+ bool qsfp; -+ int err; -+ -+ mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN + module, -+ false, false); -+ err = mlxsw_reg_query(core, MLXSW_REG(mtmp), mtmp_pl); -+ if (err) -+ return err; -+ mlxsw_reg_mtmp_unpack(mtmp_pl, &module_temp, NULL, NULL); -+ if (!module_temp) { -+ *temp = 0; -+ return 0; -+ } -+ -+ /* Read Free Side Device Temperature Thresholds from page 03h -+ * (MSB at lower byte address). -+ * Bytes: -+ * 128-129 - Temp High Alarm (SFP_TEMP_HIGH_ALARM); -+ * 130-131 - Temp Low Alarm (SFP_TEMP_LOW_ALARM); -+ * 132-133 - Temp High Warning (SFP_TEMP_HIGH_WARN); -+ * 134-135 - Temp Low Warning (SFP_TEMP_LOW_WARN); -+ */ -+ -+ /* Validate module identifier value. */ -+ err = mlxsw_env_validate_cable_ident(core, module, &qsfp); -+ if (err) -+ return err; -+ -+ if (qsfp) -+ mlxsw_reg_mcia_pack(mcia_pl, module, 0, -+ MLXSW_REG_MCIA_TH_PAGE_NUM, -+ MLXSW_REG_MCIA_TH_PAGE_OFF + off, -+ MLXSW_REG_MCIA_TH_ITEM_SIZE, -+ MLXSW_REG_MCIA_I2C_ADDR_LOW); -+ else -+ mlxsw_reg_mcia_pack(mcia_pl, module, 0, -+ MLXSW_REG_MCIA_PAGE0_LO, -+ off, MLXSW_REG_MCIA_TH_ITEM_SIZE, -+ MLXSW_REG_MCIA_I2C_ADDR_HIGH); -+ -+ err = mlxsw_reg_query(core, MLXSW_REG(mcia), mcia_pl); -+ if (err) -+ return err; -+ -+ mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); -+ memcpy(temp_thresh.buf, eeprom_tmp, MLXSW_REG_MCIA_TH_ITEM_SIZE); -+ *temp = temp_thresh.temp * 1000; -+ -+ return 0; -+} -+ -+int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module, -+ struct ethtool_modinfo *modinfo) -+{ -+ u8 module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE]; -+ u16 offset = MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE; -+ u8 module_rev_id, module_id, diag_mon; -+ unsigned int read_size; -+ int err; -+ -+ err = mlxsw_env_query_module_eeprom(mlxsw_core, module, 0, offset, -+ false, module_info, &read_size); -+ if (err) -+ return err; -+ -+ if (read_size < offset) -+ return -EIO; -+ -+ module_rev_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID]; -+ module_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID]; -+ -+ switch (module_id) { -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP: -+ modinfo->type = ETH_MODULE_SFF_8436; -+ modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN; -+ break; -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: -+ if (module_id == MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28 || -+ module_rev_id >= -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID_8636) { -+ modinfo->type = ETH_MODULE_SFF_8636; -+ modinfo->eeprom_len = ETH_MODULE_SFF_8636_MAX_LEN; -+ } else { -+ modinfo->type = ETH_MODULE_SFF_8436; -+ modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN; -+ } -+ break; -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: -+ /* Verify if transceiver provides diagnostic monitoring page */ -+ err = mlxsw_env_query_module_eeprom(mlxsw_core, module, -+ SFP_DIAGMON, 1, false, -+ &diag_mon, &read_size); -+ if (err) -+ return err; -+ -+ if (read_size < 1) -+ return -EIO; -+ -+ modinfo->type = ETH_MODULE_SFF_8472; -+ if (diag_mon) -+ modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; -+ else -+ modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN / 2; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+EXPORT_SYMBOL(mlxsw_env_get_module_info); -+ -+int mlxsw_env_get_module_eeprom(struct net_device *netdev, -+ struct mlxsw_core *mlxsw_core, int module, -+ struct ethtool_eeprom *ee, u8 *data) -+{ -+ int offset = ee->offset; -+ unsigned int read_size; -+ bool qsfp; -+ int i = 0; -+ int err; -+ -+ if (!ee->len) -+ return -EINVAL; -+ -+ memset(data, 0, ee->len); -+ -+ /* Validate module identifier type. */ -+ err = mlxsw_env_validate_cable_ident(mlxsw_core, module, &qsfp); -+ if (err) -+ return err; -+ -+ while (i < ee->len) { -+ err = mlxsw_env_query_module_eeprom(mlxsw_core, module, offset, -+ ee->len - i, qsfp, -+ data + i, &read_size); -+ if (err) { -+ netdev_err(netdev, "Eeprom query failed\n"); -+ return err; -+ } -+ -+ i += read_size; -+ offset += read_size; -+ } -+ -+ return 0; -+} -+EXPORT_SYMBOL(mlxsw_env_get_module_eeprom); -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_env.h b/drivers/net/ethernet/mellanox/mlxsw/core_env.h -new file mode 100644 -index 0000000..064d0e7 ---- /dev/null -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_env.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */ -+/* Copyright (c) 2018 Mellanox Technologies. All rights reserved */ -+ -+#ifndef _MLXSW_CORE_ENV_H -+#define _MLXSW_CORE_ENV_H -+ -+int mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, int module, -+ int off, int *temp); -+ -+int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module, -+ struct ethtool_modinfo *modinfo); -+ -+int mlxsw_env_get_module_eeprom(struct net_device *netdev, -+ struct mlxsw_core *mlxsw_core, int module, -+ struct ethtool_eeprom *ee, u8 *data); -+ -+#endif -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c -index e04e816..3fe878d 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c -@@ -7,8 +7,10 @@ - #include - #include - #include -+#include - - #include "core.h" -+#include "core_env.h" - - #define MLXSW_HWMON_TEMP_SENSOR_MAX_COUNT 127 - #define MLXSW_HWMON_ATTR_COUNT (MLXSW_HWMON_TEMP_SENSOR_MAX_COUNT * 4 + \ -@@ -21,6 +23,14 @@ struct mlxsw_hwmon_attr { - char name[32]; - }; - -+static int mlxsw_hwmon_get_attr_index(int index, int count) -+{ -+ if (index >= count) -+ return index % count + MLXSW_REG_MTMP_GBOX_INDEX_MIN; -+ -+ return index; -+} -+ - struct mlxsw_hwmon { - struct mlxsw_core *core; - const struct mlxsw_bus_info *bus_info; -@@ -30,6 +40,8 @@ struct mlxsw_hwmon { - struct attribute *attrs[MLXSW_HWMON_ATTR_COUNT + 1]; - struct mlxsw_hwmon_attr hwmon_attrs[MLXSW_HWMON_ATTR_COUNT]; - unsigned int attrs_count; -+ u8 sensor_count; -+ u8 module_sensor_max; - }; - - static ssize_t mlxsw_hwmon_temp_show(struct device *dev, -@@ -40,18 +52,19 @@ static ssize_t mlxsw_hwmon_temp_show(struct device *dev, - container_of(attr, struct mlxsw_hwmon_attr, dev_attr); - struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; - char mtmp_pl[MLXSW_REG_MTMP_LEN]; -- unsigned int temp; -+ int temp, index; - int err; - -- mlxsw_reg_mtmp_pack(mtmp_pl, mlwsw_hwmon_attr->type_index, -- false, false); -+ index = mlxsw_hwmon_get_attr_index(mlwsw_hwmon_attr->type_index, -+ mlxsw_hwmon->module_sensor_max); -+ mlxsw_reg_mtmp_pack(mtmp_pl, index, false, false); - err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl); - if (err) { - dev_err(mlxsw_hwmon->bus_info->dev, "Failed to query temp sensor\n"); - return err; - } - mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); -- return sprintf(buf, "%u\n", temp); -+ return sprintf(buf, "%d\n", temp); - } - - static ssize_t mlxsw_hwmon_temp_max_show(struct device *dev, -@@ -62,18 +75,19 @@ static ssize_t mlxsw_hwmon_temp_max_show(struct device *dev, - container_of(attr, struct mlxsw_hwmon_attr, dev_attr); - struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; - char mtmp_pl[MLXSW_REG_MTMP_LEN]; -- unsigned int temp_max; -+ int temp_max, index; - int err; - -- mlxsw_reg_mtmp_pack(mtmp_pl, mlwsw_hwmon_attr->type_index, -- false, false); -+ index = mlxsw_hwmon_get_attr_index(mlwsw_hwmon_attr->type_index, -+ mlxsw_hwmon->module_sensor_max); -+ mlxsw_reg_mtmp_pack(mtmp_pl, index, false, false); - err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl); - if (err) { - dev_err(mlxsw_hwmon->bus_info->dev, "Failed to query temp sensor\n"); - return err; - } - mlxsw_reg_mtmp_unpack(mtmp_pl, NULL, &temp_max, NULL); -- return sprintf(buf, "%u\n", temp_max); -+ return sprintf(buf, "%d\n", temp_max); - } - - static ssize_t mlxsw_hwmon_temp_rst_store(struct device *dev, -@@ -85,6 +99,7 @@ static ssize_t mlxsw_hwmon_temp_rst_store(struct device *dev, - struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; - char mtmp_pl[MLXSW_REG_MTMP_LEN]; - unsigned long val; -+ int index; - int err; - - err = kstrtoul(buf, 10, &val); -@@ -93,7 +108,9 @@ static ssize_t mlxsw_hwmon_temp_rst_store(struct device *dev, - if (val != 1) - return -EINVAL; - -- mlxsw_reg_mtmp_pack(mtmp_pl, mlwsw_hwmon_attr->type_index, true, true); -+ index = mlxsw_hwmon_get_attr_index(mlwsw_hwmon_attr->type_index, -+ mlxsw_hwmon->module_sensor_max); -+ mlxsw_reg_mtmp_pack(mtmp_pl, index, true, true); - err = mlxsw_reg_write(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl); - if (err) { - dev_err(mlxsw_hwmon->bus_info->dev, "Failed to reset temp sensor history\n"); -@@ -121,6 +138,27 @@ static ssize_t mlxsw_hwmon_fan_rpm_show(struct device *dev, - return sprintf(buf, "%u\n", mlxsw_reg_mfsm_rpm_get(mfsm_pl)); - } - -+static ssize_t mlxsw_hwmon_fan_fault_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_hwmon_attr *mlwsw_hwmon_attr = -+ container_of(attr, struct mlxsw_hwmon_attr, dev_attr); -+ struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; -+ char fore_pl[MLXSW_REG_FORE_LEN]; -+ bool fault; -+ int err; -+ -+ err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(fore), fore_pl); -+ if (err) { -+ dev_err(mlxsw_hwmon->bus_info->dev, "Failed to query fan\n"); -+ return err; -+ } -+ mlxsw_reg_fore_unpack(fore_pl, mlwsw_hwmon_attr->type_index, &fault); -+ -+ return sprintf(buf, "%u\n", fault); -+} -+ - static ssize_t mlxsw_hwmon_pwm_show(struct device *dev, - struct device_attribute *attr, - char *buf) -@@ -167,12 +205,155 @@ static ssize_t mlxsw_hwmon_pwm_store(struct device *dev, - return len; - } - -+static ssize_t mlxsw_hwmon_module_temp_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_hwmon_attr *mlwsw_hwmon_attr = -+ container_of(attr, struct mlxsw_hwmon_attr, dev_attr); -+ struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; -+ char mtmp_pl[MLXSW_REG_MTMP_LEN]; -+ u8 module; -+ int temp; -+ int err; -+ -+ module = mlwsw_hwmon_attr->type_index - mlxsw_hwmon->sensor_count; -+ mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN + module, -+ false, false); -+ err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl); -+ if (err) -+ return err; -+ mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); -+ -+ return sprintf(buf, "%d\n", temp); -+} -+ -+static ssize_t mlxsw_hwmon_module_temp_fault_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_hwmon_attr *mlwsw_hwmon_attr = -+ container_of(attr, struct mlxsw_hwmon_attr, dev_attr); -+ struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; -+ char mtbr_pl[MLXSW_REG_MTBR_LEN] = {0}; -+ u8 module, fault; -+ u16 temp; -+ int err; -+ -+ module = mlwsw_hwmon_attr->type_index - mlxsw_hwmon->sensor_count; -+ mlxsw_reg_mtbr_pack(mtbr_pl, MLXSW_REG_MTBR_BASE_MODULE_INDEX + module, -+ 1); -+ err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtbr), mtbr_pl); -+ if (err) { -+ dev_err(dev, "Failed to query module temperature sensor\n"); -+ return err; -+ } -+ -+ mlxsw_reg_mtbr_temp_unpack(mtbr_pl, 0, &temp, NULL); -+ -+ /* Update status and temperature cache. */ -+ switch (temp) { -+ case MLXSW_REG_MTBR_BAD_SENS_INFO: -+ /* Untrusted cable is connected. Reading temperature from its -+ * sensor is faulty. -+ */ -+ fault = 1; -+ break; -+ case MLXSW_REG_MTBR_NO_CONN: /* fall-through */ -+ case MLXSW_REG_MTBR_NO_TEMP_SENS: /* fall-through */ -+ case MLXSW_REG_MTBR_INDEX_NA: -+ default: -+ fault = 0; -+ break; -+ } -+ -+ return sprintf(buf, "%u\n", fault); -+} -+ -+static ssize_t -+mlxsw_hwmon_module_temp_critical_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct mlxsw_hwmon_attr *mlwsw_hwmon_attr = -+ container_of(attr, struct mlxsw_hwmon_attr, dev_attr); -+ struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; -+ int temp; -+ u8 module; -+ int err; -+ -+ module = mlwsw_hwmon_attr->type_index - mlxsw_hwmon->sensor_count; -+ err = mlxsw_env_module_temp_thresholds_get(mlxsw_hwmon->core, module, -+ SFP_TEMP_HIGH_WARN, &temp); -+ if (err) { -+ dev_err(dev, "Failed to query module temperature thresholds\n"); -+ return err; -+ } -+ -+ return sprintf(buf, "%u\n", temp); -+} -+ -+static ssize_t -+mlxsw_hwmon_module_temp_emergency_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_hwmon_attr *mlwsw_hwmon_attr = -+ container_of(attr, struct mlxsw_hwmon_attr, dev_attr); -+ struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; -+ u8 module; -+ int temp; -+ int err; -+ -+ module = mlwsw_hwmon_attr->type_index - mlxsw_hwmon->sensor_count; -+ err = mlxsw_env_module_temp_thresholds_get(mlxsw_hwmon->core, module, -+ SFP_TEMP_HIGH_ALARM, &temp); -+ if (err) { -+ dev_err(dev, "Failed to query module temperature thresholds\n"); -+ return err; -+ } -+ -+ return sprintf(buf, "%u\n", temp); -+} -+ -+static ssize_t -+mlxsw_hwmon_module_temp_label_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_hwmon_attr *mlwsw_hwmon_attr = -+ container_of(attr, struct mlxsw_hwmon_attr, dev_attr); -+ -+ return sprintf(buf, "front panel %03u\n", -+ mlwsw_hwmon_attr->type_index); -+} -+ -+static ssize_t -+mlxsw_hwmon_gbox_temp_label_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_hwmon_attr *mlwsw_hwmon_attr = -+ container_of(attr, struct mlxsw_hwmon_attr, dev_attr); -+ struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon; -+ int index = mlwsw_hwmon_attr->type_index - -+ mlxsw_hwmon->module_sensor_max + 1; -+ -+ return sprintf(buf, "gearbox %03u\n", index); -+} -+ - enum mlxsw_hwmon_attr_type { - MLXSW_HWMON_ATTR_TYPE_TEMP, - MLXSW_HWMON_ATTR_TYPE_TEMP_MAX, - MLXSW_HWMON_ATTR_TYPE_TEMP_RST, - MLXSW_HWMON_ATTR_TYPE_FAN_RPM, -+ MLXSW_HWMON_ATTR_TYPE_FAN_FAULT, - MLXSW_HWMON_ATTR_TYPE_PWM, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_FAULT, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_CRIT, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_EMERG, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_LABEL, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_GBOX_LABEL, - }; - - static void mlxsw_hwmon_attr_add(struct mlxsw_hwmon *mlxsw_hwmon, -@@ -209,6 +390,12 @@ static void mlxsw_hwmon_attr_add(struct mlxsw_hwmon *mlxsw_hwmon, - snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), - "fan%u_input", num + 1); - break; -+ case MLXSW_HWMON_ATTR_TYPE_FAN_FAULT: -+ mlxsw_hwmon_attr->dev_attr.show = mlxsw_hwmon_fan_fault_show; -+ mlxsw_hwmon_attr->dev_attr.attr.mode = 0444; -+ snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), -+ "fan%u_fault", num + 1); -+ break; - case MLXSW_HWMON_ATTR_TYPE_PWM: - mlxsw_hwmon_attr->dev_attr.show = mlxsw_hwmon_pwm_show; - mlxsw_hwmon_attr->dev_attr.store = mlxsw_hwmon_pwm_store; -@@ -216,6 +403,47 @@ static void mlxsw_hwmon_attr_add(struct mlxsw_hwmon *mlxsw_hwmon, - snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), - "pwm%u", num + 1); - break; -+ case MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE: -+ mlxsw_hwmon_attr->dev_attr.show = mlxsw_hwmon_module_temp_show; -+ mlxsw_hwmon_attr->dev_attr.attr.mode = 0444; -+ snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), -+ "temp%u_input", num + 1); -+ break; -+ case MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_FAULT: -+ mlxsw_hwmon_attr->dev_attr.show = -+ mlxsw_hwmon_module_temp_fault_show; -+ mlxsw_hwmon_attr->dev_attr.attr.mode = 0444; -+ snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), -+ "temp%u_fault", num + 1); -+ break; -+ case MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_CRIT: -+ mlxsw_hwmon_attr->dev_attr.show = -+ mlxsw_hwmon_module_temp_critical_show; -+ mlxsw_hwmon_attr->dev_attr.attr.mode = 0444; -+ snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), -+ "temp%u_crit", num + 1); -+ break; -+ case MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_EMERG: -+ mlxsw_hwmon_attr->dev_attr.show = -+ mlxsw_hwmon_module_temp_emergency_show; -+ mlxsw_hwmon_attr->dev_attr.attr.mode = 0444; -+ snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), -+ "temp%u_emergency", num + 1); -+ break; -+ case MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_LABEL: -+ mlxsw_hwmon_attr->dev_attr.show = -+ mlxsw_hwmon_module_temp_label_show; -+ mlxsw_hwmon_attr->dev_attr.attr.mode = 0444; -+ snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), -+ "temp%u_label", num + 1); -+ break; -+ case MLXSW_HWMON_ATTR_TYPE_TEMP_GBOX_LABEL: -+ mlxsw_hwmon_attr->dev_attr.show = -+ mlxsw_hwmon_gbox_temp_label_show; -+ mlxsw_hwmon_attr->dev_attr.attr.mode = 0444; -+ snprintf(mlxsw_hwmon_attr->name, sizeof(mlxsw_hwmon_attr->name), -+ "temp%u_label", num + 1); -+ break; - default: - WARN_ON(1); - } -@@ -233,7 +461,6 @@ static int mlxsw_hwmon_temp_init(struct mlxsw_hwmon *mlxsw_hwmon) - { - char mtcap_pl[MLXSW_REG_MTCAP_LEN] = {0}; - char mtmp_pl[MLXSW_REG_MTMP_LEN]; -- u8 sensor_count; - int i; - int err; - -@@ -242,8 +469,8 @@ static int mlxsw_hwmon_temp_init(struct mlxsw_hwmon *mlxsw_hwmon) - dev_err(mlxsw_hwmon->bus_info->dev, "Failed to get number of temp sensors\n"); - return err; - } -- sensor_count = mlxsw_reg_mtcap_sensor_count_get(mtcap_pl); -- for (i = 0; i < sensor_count; i++) { -+ mlxsw_hwmon->sensor_count = mlxsw_reg_mtcap_sensor_count_get(mtcap_pl); -+ for (i = 0; i < mlxsw_hwmon->sensor_count; i++) { - mlxsw_reg_mtmp_pack(mtmp_pl, i, true, true); - err = mlxsw_reg_write(mlxsw_hwmon->core, - MLXSW_REG(mtmp), mtmp_pl); -@@ -280,10 +507,14 @@ static int mlxsw_hwmon_fans_init(struct mlxsw_hwmon *mlxsw_hwmon) - mlxsw_reg_mfcr_unpack(mfcr_pl, &freq, &tacho_active, &pwm_active); - num = 0; - for (type_index = 0; type_index < MLXSW_MFCR_TACHOS_MAX; type_index++) { -- if (tacho_active & BIT(type_index)) -+ if (tacho_active & BIT(type_index)) { - mlxsw_hwmon_attr_add(mlxsw_hwmon, - MLXSW_HWMON_ATTR_TYPE_FAN_RPM, -+ type_index, num); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_FAN_FAULT, - type_index, num++); -+ } - } - num = 0; - for (type_index = 0; type_index < MLXSW_MFCR_PWMS_MAX; type_index++) { -@@ -295,6 +526,100 @@ static int mlxsw_hwmon_fans_init(struct mlxsw_hwmon *mlxsw_hwmon) - return 0; - } - -+static int mlxsw_hwmon_module_init(struct mlxsw_hwmon *mlxsw_hwmon) -+{ -+ char mgpir_pl[MLXSW_REG_MGPIR_LEN]; -+ u8 module_sensor_max; -+ int i, err; -+ -+ if (!mlxsw_core_res_query_enabled(mlxsw_hwmon->core)) -+ return 0; -+ -+ mlxsw_reg_mgpir_pack(mgpir_pl); -+ err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mgpir), mgpir_pl); -+ if (err) -+ return err; -+ -+ mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, -+ &module_sensor_max); -+ -+ /* Add extra attributes for module temperature. Sensor index is -+ * assigned to sensor_count value, while all indexed before -+ * sensor_count are already utilized by the sensors connected through -+ * mtmp register by mlxsw_hwmon_temp_init(). -+ */ -+ mlxsw_hwmon->module_sensor_max = mlxsw_hwmon->sensor_count + -+ module_sensor_max; -+ for (i = mlxsw_hwmon->sensor_count; -+ i < mlxsw_hwmon->module_sensor_max; i++) { -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE, i, i); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_FAULT, -+ i, i); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_CRIT, i, -+ i); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_EMERG, -+ i, i); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MODULE_LABEL, -+ i, i); -+ } -+ -+ return 0; -+} -+ -+static int mlxsw_hwmon_gearbox_init(struct mlxsw_hwmon *mlxsw_hwmon) -+{ -+ enum mlxsw_reg_mgpir_device_type device_type; -+ int index, max_index, sensor_index; -+ char mgpir_pl[MLXSW_REG_MGPIR_LEN]; -+ char mtmp_pl[MLXSW_REG_MTMP_LEN]; -+ u8 gbox_num; -+ int err; -+ -+ mlxsw_reg_mgpir_pack(mgpir_pl); -+ err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mgpir), mgpir_pl); -+ if (err) -+ return err; -+ -+ mlxsw_reg_mgpir_unpack(mgpir_pl, &gbox_num, &device_type, NULL, NULL); -+ if (device_type != MLXSW_REG_MGPIR_DEVICE_TYPE_GEARBOX_DIE || -+ !gbox_num) -+ return 0; -+ -+ index = mlxsw_hwmon->module_sensor_max; -+ max_index = mlxsw_hwmon->module_sensor_max + gbox_num; -+ while (index < max_index) { -+ sensor_index = index % mlxsw_hwmon->module_sensor_max + -+ MLXSW_REG_MTMP_GBOX_INDEX_MIN; -+ mlxsw_reg_mtmp_pack(mtmp_pl, sensor_index, true, true); -+ err = mlxsw_reg_write(mlxsw_hwmon->core, -+ MLXSW_REG(mtmp), mtmp_pl); -+ if (err) { -+ dev_err(mlxsw_hwmon->bus_info->dev, "Failed to setup temp sensor number %d\n", -+ sensor_index); -+ return err; -+ } -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, MLXSW_HWMON_ATTR_TYPE_TEMP, -+ index, index); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_MAX, index, -+ index); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_RST, index, -+ index); -+ mlxsw_hwmon_attr_add(mlxsw_hwmon, -+ MLXSW_HWMON_ATTR_TYPE_TEMP_GBOX_LABEL, -+ index, index); -+ index++; -+ } -+ -+ return 0; -+} -+ - int mlxsw_hwmon_init(struct mlxsw_core *mlxsw_core, - const struct mlxsw_bus_info *mlxsw_bus_info, - struct mlxsw_hwmon **p_hwmon) -@@ -317,6 +642,14 @@ int mlxsw_hwmon_init(struct mlxsw_core *mlxsw_core, - if (err) - goto err_fans_init; - -+ err = mlxsw_hwmon_module_init(mlxsw_hwmon); -+ if (err) -+ goto err_temp_module_init; -+ -+ err = mlxsw_hwmon_gearbox_init(mlxsw_hwmon); -+ if (err) -+ goto err_temp_gearbox_init; -+ - mlxsw_hwmon->groups[0] = &mlxsw_hwmon->group; - mlxsw_hwmon->group.attrs = mlxsw_hwmon->attrs; - -@@ -333,6 +666,8 @@ int mlxsw_hwmon_init(struct mlxsw_core *mlxsw_core, - return 0; - - err_hwmon_register: -+err_temp_gearbox_init: -+err_temp_module_init: - err_fans_init: - err_temp_init: - kfree(mlxsw_hwmon); -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index 6d29dc4..b753729 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -9,17 +9,48 @@ - #include - #include - #include -+#include - - #include "core.h" -+#include "core_env.h" - - #define MLXSW_THERMAL_POLL_INT 1000 /* ms */ --#define MLXSW_THERMAL_MAX_TEMP 110000 /* 110C */ -+#define MLXSW_THERMAL_SLOW_POLL_INT 20000 /* ms */ -+#define MLXSW_THERMAL_ASIC_TEMP_NORM 75000 /* 75C */ -+#define MLXSW_THERMAL_ASIC_TEMP_HIGH 85000 /* 85C */ -+#define MLXSW_THERMAL_ASIC_TEMP_HOT 105000 /* 105C */ -+#define MLXSW_THERMAL_ASIC_TEMP_CRIT 110000 /* 110C */ -+#define MLXSW_THERMAL_HYSTERESIS_TEMP 5000 /* 5C */ -+#define MLXSW_THERMAL_MODULE_TEMP_SHIFT (MLXSW_THERMAL_HYSTERESIS_TEMP * 2) -+#define MLXSW_THERMAL_ZONE_MAX_NAME 16 - #define MLXSW_THERMAL_MAX_STATE 10 - #define MLXSW_THERMAL_MAX_DUTY 255 -+/* Minimum and maximum fan allowed speed in percent: from 20% to 100%. Values -+ * MLXSW_THERMAL_MAX_STATE + x, where x is between 2 and 10 are used for -+ * setting fan speed dynamic minimum. For example, if value is set to 14 (40%) -+ * cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to -+ * introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100. -+ */ -+#define MLXSW_THERMAL_SPEED_MIN (MLXSW_THERMAL_MAX_STATE + 2) -+#define MLXSW_THERMAL_SPEED_MAX (MLXSW_THERMAL_MAX_STATE * 2) -+#define MLXSW_THERMAL_SPEED_MIN_LEVEL 2 /* 20% */ -+ -+/* External cooling devices, allowed for binding to mlxsw thermal zones. */ -+static char * const mlxsw_thermal_external_allowed_cdev[] = { -+ "mlxreg_fan", -+}; -+ -+enum mlxsw_thermal_trips { -+ MLXSW_THERMAL_TEMP_TRIP_NORM, -+ MLXSW_THERMAL_TEMP_TRIP_HIGH, -+ MLXSW_THERMAL_TEMP_TRIP_HOT, -+ MLXSW_THERMAL_TEMP_TRIP_CRIT, -+}; - - struct mlxsw_thermal_trip { - int type; - int temp; -+ int hyst; - int min_state; - int max_state; - }; -@@ -27,32 +58,29 @@ struct mlxsw_thermal_trip { - static const struct mlxsw_thermal_trip default_thermal_trips[] = { - { /* In range - 0-40% PWM */ - .type = THERMAL_TRIP_ACTIVE, -- .temp = 75000, -+ .temp = MLXSW_THERMAL_ASIC_TEMP_NORM, -+ .hyst = MLXSW_THERMAL_HYSTERESIS_TEMP, - .min_state = 0, - .max_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, - }, -- { /* High - 40-100% PWM */ -- .type = THERMAL_TRIP_ACTIVE, -- .temp = 80000, -- .min_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, -- .max_state = MLXSW_THERMAL_MAX_STATE, -- }, - { -- /* Very high - 100% PWM */ -+ /* In range - 40-100% PWM */ - .type = THERMAL_TRIP_ACTIVE, -- .temp = 85000, -- .min_state = MLXSW_THERMAL_MAX_STATE, -+ .temp = MLXSW_THERMAL_ASIC_TEMP_HIGH, -+ .hyst = MLXSW_THERMAL_HYSTERESIS_TEMP, -+ .min_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, - .max_state = MLXSW_THERMAL_MAX_STATE, - }, - { /* Warning */ - .type = THERMAL_TRIP_HOT, -- .temp = 105000, -+ .temp = MLXSW_THERMAL_ASIC_TEMP_HOT, -+ .hyst = MLXSW_THERMAL_HYSTERESIS_TEMP, - .min_state = MLXSW_THERMAL_MAX_STATE, - .max_state = MLXSW_THERMAL_MAX_STATE, - }, - { /* Critical - soft poweroff */ - .type = THERMAL_TRIP_CRITICAL, -- .temp = MLXSW_THERMAL_MAX_TEMP, -+ .temp = MLXSW_THERMAL_ASIC_TEMP_CRIT, - .min_state = MLXSW_THERMAL_MAX_STATE, - .max_state = MLXSW_THERMAL_MAX_STATE, - } -@@ -63,13 +91,32 @@ static const struct mlxsw_thermal_trip default_thermal_trips[] = { - /* Make sure all trips are writable */ - #define MLXSW_THERMAL_TRIP_MASK (BIT(MLXSW_THERMAL_NUM_TRIPS) - 1) - -+struct mlxsw_thermal; -+ -+struct mlxsw_thermal_module { -+ struct mlxsw_thermal *parent; -+ struct thermal_zone_device *tzdev; -+ struct mlxsw_thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; -+ enum thermal_device_mode mode; -+ int module; /* Module or gearbox number */ -+}; -+ - struct mlxsw_thermal { - struct mlxsw_core *core; - const struct mlxsw_bus_info *bus_info; - struct thermal_zone_device *tzdev; -+ int polling_delay; - struct thermal_cooling_device *cdevs[MLXSW_MFCR_PWMS_MAX]; -+ u8 cooling_levels[MLXSW_THERMAL_MAX_STATE + 1]; - struct mlxsw_thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; - enum thermal_device_mode mode; -+ struct mlxsw_thermal_module *tz_module_arr; -+ u8 tz_module_num; -+ struct mlxsw_thermal_module *tz_gearbox_arr; -+ u8 tz_gearbox_num; -+ unsigned int tz_highest_score; -+ struct thermal_zone_device *tz_highest_dev; -+ bool initializing; /* Driver is in initialization stage */ - }; - - static inline u8 mlxsw_state_to_duty(int state) -@@ -93,9 +140,67 @@ static int mlxsw_get_cooling_device_idx(struct mlxsw_thermal *thermal, - if (thermal->cdevs[i] == cdev) - return i; - -+ /* Allow mlxsw thermal zone binding to an external cooling device */ -+ for (i = 0; i < ARRAY_SIZE(mlxsw_thermal_external_allowed_cdev); i++) { -+ if (strnstr(cdev->type, mlxsw_thermal_external_allowed_cdev[i], -+ sizeof(cdev->type))) -+ return 0; -+ } -+ - return -ENODEV; - } - -+static void -+mlxsw_thermal_module_trips_reset(struct mlxsw_thermal_module *tz) -+{ -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = 0; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = 0; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = 0; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = 0; -+} -+ -+static int -+mlxsw_thermal_module_trips_update(struct device *dev, struct mlxsw_core *core, -+ struct mlxsw_thermal_module *tz) -+{ -+ int crit_temp, emerg_temp; -+ int err; -+ -+ err = mlxsw_env_module_temp_thresholds_get(core, tz->module, -+ SFP_TEMP_HIGH_WARN, -+ &crit_temp); -+ if (err) -+ return err; -+ -+ err = mlxsw_env_module_temp_thresholds_get(core, tz->module, -+ SFP_TEMP_HIGH_ALARM, -+ &emerg_temp); -+ if (err) -+ return err; -+ -+ /* According to the system thermal requirements, the thermal zones are -+ * defined with four trip points. The critical and emergency -+ * temperature thresholds, provided by QSFP module are set as "active" -+ * and "hot" trip points, "normal" and "critical" trip points are -+ * derived from "active" and "hot" by subtracting or adding double -+ * hysteresis value. -+ */ -+ if (crit_temp >= MLXSW_THERMAL_MODULE_TEMP_SHIFT) -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = crit_temp - -+ MLXSW_THERMAL_MODULE_TEMP_SHIFT; -+ else -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = crit_temp; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = crit_temp; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = emerg_temp; -+ if (emerg_temp > crit_temp) -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp + -+ MLXSW_THERMAL_MODULE_TEMP_SHIFT; -+ else -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp; -+ -+ return 0; -+} -+ - static int mlxsw_thermal_bind(struct thermal_zone_device *tzdev, - struct thermal_cooling_device *cdev) - { -@@ -162,7 +267,7 @@ static int mlxsw_thermal_set_mode(struct thermal_zone_device *tzdev, - mutex_lock(&tzdev->lock); - - if (mode == THERMAL_DEVICE_ENABLED) -- tzdev->polling_delay = MLXSW_THERMAL_POLL_INT; -+ tzdev->polling_delay = thermal->polling_delay; - else - tzdev->polling_delay = 0; - -@@ -180,9 +285,15 @@ static int mlxsw_thermal_get_temp(struct thermal_zone_device *tzdev, - struct mlxsw_thermal *thermal = tzdev->devdata; - struct device *dev = thermal->bus_info->dev; - char mtmp_pl[MLXSW_REG_MTMP_LEN]; -- unsigned int temp; -+ int temp; - int err; - -+ /* Do not read temperature in initialization stage. */ -+ if (thermal->initializing) { -+ *p_temp = 0; -+ return 0; -+ } -+ - mlxsw_reg_mtmp_pack(mtmp_pl, 0, false, false); - - err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); -@@ -192,7 +303,7 @@ static int mlxsw_thermal_get_temp(struct thermal_zone_device *tzdev, - } - mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); - -- *p_temp = (int) temp; -+ *p_temp = temp; - return 0; - } - -@@ -227,13 +338,35 @@ static int mlxsw_thermal_set_trip_temp(struct thermal_zone_device *tzdev, - struct mlxsw_thermal *thermal = tzdev->devdata; - - if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS || -- temp > MLXSW_THERMAL_MAX_TEMP) -+ temp > MLXSW_THERMAL_ASIC_TEMP_CRIT) - return -EINVAL; - - thermal->trips[trip].temp = temp; - return 0; - } - -+static int mlxsw_thermal_get_trip_hyst(struct thermal_zone_device *tzdev, -+ int trip, int *p_hyst) -+{ -+ struct mlxsw_thermal *thermal = tzdev->devdata; -+ -+ *p_hyst = thermal->trips[trip].hyst; -+ return 0; -+} -+ -+static int mlxsw_thermal_set_trip_hyst(struct thermal_zone_device *tzdev, -+ int trip, int hyst) -+{ -+ struct mlxsw_thermal *thermal = tzdev->devdata; -+ -+ thermal->trips[trip].hyst = hyst; -+ return 0; -+} -+ -+struct thermal_zone_params mlxsw_thermal_params = { -+ .no_hwmon = true, -+}; -+ - static struct thermal_zone_device_ops mlxsw_thermal_ops = { - .bind = mlxsw_thermal_bind, - .unbind = mlxsw_thermal_unbind, -@@ -243,6 +376,242 @@ static struct thermal_zone_device_ops mlxsw_thermal_ops = { - .get_trip_type = mlxsw_thermal_get_trip_type, - .get_trip_temp = mlxsw_thermal_get_trip_temp, - .set_trip_temp = mlxsw_thermal_set_trip_temp, -+ .get_trip_hyst = mlxsw_thermal_get_trip_hyst, -+ .set_trip_hyst = mlxsw_thermal_set_trip_hyst, -+}; -+ -+static int mlxsw_thermal_module_bind(struct thermal_zone_device *tzdev, -+ struct thermal_cooling_device *cdev) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ struct mlxsw_thermal *thermal = tz->parent; -+ int i, j, err; -+ -+ /* If the cooling device is one of ours bind it */ -+ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) -+ return 0; -+ -+ for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { -+ const struct mlxsw_thermal_trip *trip = &tz->trips[i]; -+ -+ err = thermal_zone_bind_cooling_device(tzdev, i, cdev, -+ trip->max_state, -+ trip->min_state, -+ THERMAL_WEIGHT_DEFAULT); -+ if (err < 0) -+ goto err_bind_cooling_device; -+ } -+ return 0; -+ -+err_bind_cooling_device: -+ for (j = i - 1; j >= 0; j--) -+ thermal_zone_unbind_cooling_device(tzdev, j, cdev); -+ return err; -+} -+ -+static int mlxsw_thermal_module_unbind(struct thermal_zone_device *tzdev, -+ struct thermal_cooling_device *cdev) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ struct mlxsw_thermal *thermal = tz->parent; -+ int i; -+ int err; -+ -+ /* If the cooling device is one of ours unbind it */ -+ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) -+ return 0; -+ -+ for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { -+ err = thermal_zone_unbind_cooling_device(tzdev, i, cdev); -+ WARN_ON(err); -+ } -+ return err; -+} -+ -+static int mlxsw_thermal_module_mode_get(struct thermal_zone_device *tzdev, -+ enum thermal_device_mode *mode) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ -+ *mode = tz->mode; -+ -+ return 0; -+} -+ -+static int mlxsw_thermal_module_mode_set(struct thermal_zone_device *tzdev, -+ enum thermal_device_mode mode) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ struct mlxsw_thermal *thermal = tz->parent; -+ -+ mutex_lock(&tzdev->lock); -+ -+ if (mode == THERMAL_DEVICE_ENABLED) -+ tzdev->polling_delay = thermal->polling_delay; -+ else -+ tzdev->polling_delay = 0; -+ -+ mutex_unlock(&tzdev->lock); -+ -+ tz->mode = mode; -+ thermal_zone_device_update(tzdev, THERMAL_EVENT_UNSPECIFIED); -+ -+ return 0; -+} -+ -+static int mlxsw_thermal_module_temp_get(struct thermal_zone_device *tzdev, -+ int *p_temp) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ struct mlxsw_thermal *thermal = tz->parent; -+ struct device *dev = thermal->bus_info->dev; -+ char mtmp_pl[MLXSW_REG_MTMP_LEN]; -+ int temp; -+ int err; -+ -+ /* Do not read temperature in initialization stage. */ -+ if (thermal->initializing) { -+ *p_temp = 0; -+ return 0; -+ } -+ -+ /* Read module temperature. */ -+ mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN + -+ tz->module, false, false); -+ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); -+ if (err) { -+ /* Do not return error - in case of broken module's sensor -+ * it will cause error message flooding. -+ */ -+ temp = 0; -+ *p_temp = (int) temp; -+ return 0; -+ } -+ mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); -+ *p_temp = temp; -+ -+ if (!temp) -+ return 0; -+ -+ /* Update trip points. */ -+ err = mlxsw_thermal_module_trips_update(dev, thermal->core, tz); -+ -+ return 0; -+} -+ -+static int -+mlxsw_thermal_module_trip_type_get(struct thermal_zone_device *tzdev, int trip, -+ enum thermal_trip_type *p_type) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ -+ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) -+ return -EINVAL; -+ -+ *p_type = tz->trips[trip].type; -+ return 0; -+} -+ -+static int -+mlxsw_thermal_module_trip_temp_get(struct thermal_zone_device *tzdev, -+ int trip, int *p_temp) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ -+ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) -+ return -EINVAL; -+ -+ *p_temp = tz->trips[trip].temp; -+ return 0; -+} -+ -+static int -+mlxsw_thermal_module_trip_temp_set(struct thermal_zone_device *tzdev, -+ int trip, int temp) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ -+ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS || -+ temp > tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp) -+ return -EINVAL; -+ -+ tz->trips[trip].temp = temp; -+ return 0; -+} -+ -+static int -+mlxsw_thermal_module_trip_hyst_get(struct thermal_zone_device *tzdev, int trip, -+ int *p_hyst) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ -+ *p_hyst = tz->trips[trip].hyst; -+ return 0; -+} -+ -+static int -+mlxsw_thermal_module_trip_hyst_set(struct thermal_zone_device *tzdev, int trip, -+ int hyst) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ -+ tz->trips[trip].hyst = hyst; -+ return 0; -+} -+ -+static struct thermal_zone_device_ops mlxsw_thermal_module_ops = { -+ .bind = mlxsw_thermal_module_bind, -+ .unbind = mlxsw_thermal_module_unbind, -+ .get_mode = mlxsw_thermal_module_mode_get, -+ .set_mode = mlxsw_thermal_module_mode_set, -+ .get_temp = mlxsw_thermal_module_temp_get, -+ .get_trip_type = mlxsw_thermal_module_trip_type_get, -+ .get_trip_temp = mlxsw_thermal_module_trip_temp_get, -+ .set_trip_temp = mlxsw_thermal_module_trip_temp_set, -+ .get_trip_hyst = mlxsw_thermal_module_trip_hyst_get, -+ .set_trip_hyst = mlxsw_thermal_module_trip_hyst_set, -+}; -+ -+static int mlxsw_thermal_gearbox_temp_get(struct thermal_zone_device *tzdev, -+ int *p_temp) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ struct mlxsw_thermal *thermal = tz->parent; -+ char mtmp_pl[MLXSW_REG_MTMP_LEN]; -+ u16 index; -+ int temp; -+ int err; -+ -+ /* Do not read temperature in initialization stage. */ -+ if (thermal->initializing) { -+ *p_temp = 0; -+ return 0; -+ } -+ -+ index = MLXSW_REG_MTMP_GBOX_INDEX_MIN + tz->module; -+ mlxsw_reg_mtmp_pack(mtmp_pl, index, false, false); -+ -+ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); -+ if (err) -+ return err; -+ -+ mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); -+ -+ *p_temp = temp; -+ return 0; -+} -+ -+static struct thermal_zone_device_ops mlxsw_thermal_gearbox_ops = { -+ .bind = mlxsw_thermal_module_bind, -+ .unbind = mlxsw_thermal_module_unbind, -+ .get_mode = mlxsw_thermal_module_mode_get, -+ .set_mode = mlxsw_thermal_module_mode_set, -+ .get_temp = mlxsw_thermal_gearbox_temp_get, -+ .get_trip_type = mlxsw_thermal_module_trip_type_get, -+ .get_trip_temp = mlxsw_thermal_module_trip_temp_get, -+ .set_trip_temp = mlxsw_thermal_module_trip_temp_set, -+ .get_trip_hyst = mlxsw_thermal_module_trip_hyst_get, -+ .set_trip_hyst = mlxsw_thermal_module_trip_hyst_set, - }; - - static int mlxsw_thermal_get_max_state(struct thermal_cooling_device *cdev, -@@ -285,12 +654,51 @@ static int mlxsw_thermal_set_cur_state(struct thermal_cooling_device *cdev, - struct mlxsw_thermal *thermal = cdev->devdata; - struct device *dev = thermal->bus_info->dev; - char mfsc_pl[MLXSW_REG_MFSC_LEN]; -- int err, idx; -+ unsigned long cur_state, i; -+ int idx; -+ u8 duty; -+ int err; - - idx = mlxsw_get_cooling_device_idx(thermal, cdev); - if (idx < 0) - return idx; - -+ /* Verify if this request is for changing allowed fan dynamical -+ * minimum. If it is - update cooling levels accordingly and update -+ * state, if current state is below the newly requested minimum state. -+ * For example, if current state is 5, and minimal state is to be -+ * changed from 4 to 6, thermal->cooling_levels[0 to 5] will be changed -+ * all from 4 to 6. And state 5 (thermal->cooling_levels[4]) should be -+ * overwritten. -+ */ -+ if (state >= MLXSW_THERMAL_SPEED_MIN && -+ state <= MLXSW_THERMAL_SPEED_MAX) { -+ state -= MLXSW_THERMAL_MAX_STATE; -+ for (i = 0; i <= MLXSW_THERMAL_MAX_STATE; i++) -+ thermal->cooling_levels[i] = max(state, i); -+ -+ mlxsw_reg_mfsc_pack(mfsc_pl, idx, 0); -+ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsc), mfsc_pl); -+ if (err) -+ return err; -+ -+ duty = mlxsw_reg_mfsc_pwm_duty_cycle_get(mfsc_pl); -+ cur_state = mlxsw_duty_to_state(duty); -+ -+ /* If current fan state is lower than requested dynamical -+ * minimum, increase fan speed up to dynamical minimum. -+ */ -+ if (state < cur_state) -+ return 0; -+ -+ state = cur_state; -+ } -+ -+ if (state > MLXSW_THERMAL_MAX_STATE) -+ return -EINVAL; -+ -+ /* Normalize the state to the valid speed range. */ -+ state = thermal->cooling_levels[state]; - mlxsw_reg_mfsc_pack(mfsc_pl, idx, mlxsw_state_to_duty(state)); - err = mlxsw_reg_write(thermal->core, MLXSW_REG(mfsc), mfsc_pl); - if (err) { -@@ -306,6 +714,218 @@ static const struct thermal_cooling_device_ops mlxsw_cooling_ops = { - .set_cur_state = mlxsw_thermal_set_cur_state, - }; - -+static int -+mlxsw_thermal_module_tz_init(struct mlxsw_thermal_module *module_tz) -+{ -+ char tz_name[MLXSW_THERMAL_ZONE_MAX_NAME]; -+ int err; -+ -+ snprintf(tz_name, sizeof(tz_name), "mlxsw-module%d", -+ module_tz->module + 1); -+ module_tz->tzdev = thermal_zone_device_register(tz_name, -+ MLXSW_THERMAL_NUM_TRIPS, -+ MLXSW_THERMAL_TRIP_MASK, -+ module_tz, -+ &mlxsw_thermal_module_ops, -+ &mlxsw_thermal_params, 0, 0); -+ if (IS_ERR(module_tz->tzdev)) { -+ err = PTR_ERR(module_tz->tzdev); -+ return err; -+ } -+ -+ module_tz->mode = THERMAL_DEVICE_DISABLED; -+ return 0; -+} -+ -+static void mlxsw_thermal_module_tz_fini(struct thermal_zone_device *tzdev) -+{ -+ thermal_zone_device_unregister(tzdev); -+} -+ -+static int -+mlxsw_thermal_module_init(struct device *dev, struct mlxsw_core *core, -+ struct mlxsw_thermal *thermal, u8 module) -+{ -+ struct mlxsw_thermal_module *module_tz; -+ -+ module_tz = &thermal->tz_module_arr[module]; -+ /* Skip if parent is already set (case of port split). */ -+ if (module_tz->parent) -+ return 0; -+ module_tz->module = module; -+ module_tz->parent = thermal; -+ memcpy(module_tz->trips, default_thermal_trips, -+ sizeof(thermal->trips)); -+ /* Initialize all trip point. */ -+ mlxsw_thermal_module_trips_reset(module_tz); -+ -+ return 0; -+} -+ -+static void mlxsw_thermal_module_fini(struct mlxsw_thermal_module *module_tz) -+{ -+ if (module_tz && module_tz->tzdev) { -+ mlxsw_thermal_module_tz_fini(module_tz->tzdev); -+ module_tz->tzdev = NULL; -+ module_tz->parent = NULL; -+ } -+} -+ -+static int -+mlxsw_thermal_modules_init(struct device *dev, struct mlxsw_core *core, -+ struct mlxsw_thermal *thermal) -+{ -+ char mgpir_pl[MLXSW_REG_MGPIR_LEN]; -+ struct mlxsw_thermal_module *module_tz; -+ int i, err; -+ -+ if (!mlxsw_core_res_query_enabled(core)) -+ return 0; -+ -+ mlxsw_reg_mgpir_pack(mgpir_pl); -+ err = mlxsw_reg_query(core, MLXSW_REG(mgpir), mgpir_pl); -+ if (err) -+ return err; -+ -+ mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, -+ &thermal->tz_module_num); -+ if (!thermal->tz_module_num) -+ return 0; -+ -+ thermal->tz_module_arr = kcalloc(thermal->tz_module_num, -+ sizeof(*thermal->tz_module_arr), -+ GFP_KERNEL); -+ if (!thermal->tz_module_arr) -+ return -ENOMEM; -+ -+ for (i = 0; i < thermal->tz_module_num; i++) { -+ err = mlxsw_thermal_module_init(dev, core, thermal, i); -+ if (err) -+ goto err_unreg_tz_module_arr; -+ } -+ -+ for (i = 0; i < thermal->tz_module_num; i++) { -+ module_tz = &thermal->tz_module_arr[i]; -+ if (!module_tz->parent) -+ continue; -+ err = mlxsw_thermal_module_tz_init(module_tz); -+ if (err) -+ goto err_unreg_tz_module_arr; -+ } -+ -+ return 0; -+ -+err_unreg_tz_module_arr: -+ for (i = thermal->tz_module_num - 1; i >= 0; i--) -+ mlxsw_thermal_module_fini(&thermal->tz_module_arr[i]); -+ kfree(thermal->tz_module_arr); -+ return err; -+} -+ -+static void -+mlxsw_thermal_modules_fini(struct mlxsw_thermal *thermal) -+{ -+ int i; -+ -+ if (!mlxsw_core_res_query_enabled(thermal->core)) -+ return; -+ -+ for (i = thermal->tz_module_num - 1; i >= 0; i--) -+ mlxsw_thermal_module_fini(&thermal->tz_module_arr[i]); -+ kfree(thermal->tz_module_arr); -+} -+ -+static int -+mlxsw_thermal_gearbox_tz_init(struct mlxsw_thermal_module *gearbox_tz) -+{ -+ char tz_name[MLXSW_THERMAL_ZONE_MAX_NAME]; -+ -+ snprintf(tz_name, sizeof(tz_name), "mlxsw-gearbox%d", -+ gearbox_tz->module + 1); -+ gearbox_tz->tzdev = thermal_zone_device_register(tz_name, -+ MLXSW_THERMAL_NUM_TRIPS, -+ MLXSW_THERMAL_TRIP_MASK, -+ gearbox_tz, -+ &mlxsw_thermal_gearbox_ops, -+ &mlxsw_thermal_params, 0, 0); -+ if (IS_ERR(gearbox_tz->tzdev)) -+ return PTR_ERR(gearbox_tz->tzdev); -+ -+ gearbox_tz->mode = THERMAL_DEVICE_DISABLED; -+ return 0; -+} -+ -+static void -+mlxsw_thermal_gearbox_tz_fini(struct mlxsw_thermal_module *gearbox_tz) -+{ -+ thermal_zone_device_unregister(gearbox_tz->tzdev); -+} -+ -+static int -+mlxsw_thermal_gearboxes_init(struct device *dev, struct mlxsw_core *core, -+ struct mlxsw_thermal *thermal) -+{ -+ enum mlxsw_reg_mgpir_device_type device_type; -+ struct mlxsw_thermal_module *gearbox_tz; -+ char mgpir_pl[MLXSW_REG_MGPIR_LEN]; -+ u8 gbox_num; -+ int i; -+ int err; -+ -+ if (!mlxsw_core_res_query_enabled(core)) -+ return 0; -+ -+ mlxsw_reg_mgpir_pack(mgpir_pl); -+ err = mlxsw_reg_query(core, MLXSW_REG(mgpir), mgpir_pl); -+ if (err) -+ return err; -+ -+ mlxsw_reg_mgpir_unpack(mgpir_pl, &gbox_num, &device_type, NULL, -+ NULL); -+ if (device_type != MLXSW_REG_MGPIR_DEVICE_TYPE_GEARBOX_DIE || -+ !gbox_num) -+ return 0; -+ -+ thermal->tz_gearbox_num = gbox_num; -+ thermal->tz_gearbox_arr = kcalloc(thermal->tz_gearbox_num, -+ sizeof(*thermal->tz_gearbox_arr), -+ GFP_KERNEL); -+ if (!thermal->tz_gearbox_arr) -+ return -ENOMEM; -+ -+ for (i = 0; i < thermal->tz_gearbox_num; i++) { -+ gearbox_tz = &thermal->tz_gearbox_arr[i]; -+ memcpy(gearbox_tz->trips, default_thermal_trips, -+ sizeof(thermal->trips)); -+ gearbox_tz->module = i; -+ gearbox_tz->parent = thermal; -+ err = mlxsw_thermal_gearbox_tz_init(gearbox_tz); -+ if (err) -+ goto err_unreg_tz_gearbox; -+ } -+ -+ return 0; -+ -+err_unreg_tz_gearbox: -+ for (i--; i >= 0; i--) -+ mlxsw_thermal_gearbox_tz_fini(&thermal->tz_gearbox_arr[i]); -+ kfree(thermal->tz_gearbox_arr); -+ return err; -+} -+ -+static void -+mlxsw_thermal_gearboxes_fini(struct mlxsw_thermal *thermal) -+{ -+ int i; -+ -+ if (!mlxsw_core_res_query_enabled(thermal->core)) -+ return; -+ -+ for (i = thermal->tz_gearbox_num - 1; i >= 0; i--) -+ mlxsw_thermal_gearbox_tz_fini(&thermal->tz_gearbox_arr[i]); -+ kfree(thermal->tz_gearbox_arr); -+} -+ - int mlxsw_thermal_init(struct mlxsw_core *core, - const struct mlxsw_bus_info *bus_info, - struct mlxsw_thermal **p_thermal) -@@ -326,6 +946,7 @@ int mlxsw_thermal_init(struct mlxsw_core *core, - thermal->core = core; - thermal->bus_info = bus_info; - memcpy(thermal->trips, default_thermal_trips, sizeof(thermal->trips)); -+ thermal->initializing = true; - - err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfcr), mfcr_pl); - if (err) { -@@ -358,8 +979,9 @@ int mlxsw_thermal_init(struct mlxsw_core *core, - if (pwm_active & BIT(i)) { - struct thermal_cooling_device *cdev; - -- cdev = thermal_cooling_device_register("Fan", thermal, -- &mlxsw_cooling_ops); -+ cdev = thermal_cooling_device_register("mlxsw_fan", -+ thermal, -+ &mlxsw_cooling_ops); - if (IS_ERR(cdev)) { - err = PTR_ERR(cdev); - dev_err(dev, "Failed to register cooling device\n"); -@@ -369,22 +991,48 @@ int mlxsw_thermal_init(struct mlxsw_core *core, - } - } - -+ /* Initialize cooling levels per PWM state. */ -+ for (i = 0; i < MLXSW_THERMAL_MAX_STATE; i++) -+ thermal->cooling_levels[i] = max(MLXSW_THERMAL_SPEED_MIN_LEVEL, -+ i); -+ -+ thermal->polling_delay = bus_info->low_frequency ? -+ MLXSW_THERMAL_SLOW_POLL_INT : -+ MLXSW_THERMAL_POLL_INT; -+ - thermal->tzdev = thermal_zone_device_register("mlxsw", - MLXSW_THERMAL_NUM_TRIPS, - MLXSW_THERMAL_TRIP_MASK, - thermal, - &mlxsw_thermal_ops, -- NULL, 0, -- MLXSW_THERMAL_POLL_INT); -+ &mlxsw_thermal_params, 0, -+ thermal->polling_delay); - if (IS_ERR(thermal->tzdev)) { - err = PTR_ERR(thermal->tzdev); - dev_err(dev, "Failed to register thermal zone\n"); - goto err_unreg_cdevs; - } - -- thermal->mode = THERMAL_DEVICE_ENABLED; -+ err = mlxsw_thermal_modules_init(dev, core, thermal); -+ if (err) -+ goto err_unreg_tzdev; -+ -+ err = mlxsw_thermal_gearboxes_init(dev, core, thermal); -+ if (err) -+ goto err_unreg_modules_tzdev; -+ -+ thermal->mode = THERMAL_DEVICE_DISABLED; -+ thermal->initializing = false; - *p_thermal = thermal; - return 0; -+ -+err_unreg_modules_tzdev: -+ mlxsw_thermal_modules_fini(thermal); -+err_unreg_tzdev: -+ if (thermal->tzdev) { -+ thermal_zone_device_unregister(thermal->tzdev); -+ thermal->tzdev = NULL; -+ } - err_unreg_cdevs: - for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) - if (thermal->cdevs[i]) -@@ -398,6 +1046,8 @@ void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) - { - int i; - -+ mlxsw_thermal_gearboxes_fini(thermal); -+ mlxsw_thermal_modules_fini(thermal); - if (thermal->tzdev) { - thermal_zone_device_unregister(thermal->tzdev); - thermal->tzdev = NULL; -diff --git a/drivers/net/ethernet/mellanox/mlxsw/minimal.c b/drivers/net/ethernet/mellanox/mlxsw/minimal.c -index 5a6c445..504db12 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/minimal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/minimal.c -@@ -1,66 +1,354 @@ - // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 --/* Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved */ -+/* Copyright (c) 2016-2019 Mellanox Technologies. All rights reserved */ - -+#include -+#include -+#include - #include - #include - #include - #include -+#include - #include - - #include "core.h" -+#include "core_env.h" - #include "i2c.h" - --static const char mlxsw_minimal_driver_name[] = "mlxsw_minimal"; -+static const char mlxsw_m_driver_name[] = "mlxsw_minimal"; - --static const struct mlxsw_config_profile mlxsw_minimal_config_profile; -+#define MLXSW_M_FWREV_MINOR 2000 -+#define MLXSW_M_FWREV_SUBMINOR 1886 - --static struct mlxsw_driver mlxsw_minimal_driver = { -- .kind = mlxsw_minimal_driver_name, -- .priv_size = 1, -- .profile = &mlxsw_minimal_config_profile, -+struct mlxsw_m_port; -+ -+struct mlxsw_m { -+ struct mlxsw_m_port **ports; -+ int *module_to_port; -+ struct mlxsw_core *core; -+ const struct mlxsw_bus_info *bus_info; -+ u8 base_mac[ETH_ALEN]; -+ u8 max_ports; -+}; -+ -+struct mlxsw_m_port { -+ struct net_device *dev; -+ struct mlxsw_m *mlxsw_m; -+ u8 local_port; -+ u8 module; -+}; -+ -+static int mlxsw_m_port_dummy_open_stop(struct net_device *dev) -+{ -+ return 0; -+} -+ -+static const struct net_device_ops mlxsw_m_port_netdev_ops = { -+ .ndo_open = mlxsw_m_port_dummy_open_stop, -+ .ndo_stop = mlxsw_m_port_dummy_open_stop, -+}; -+ -+static void mlxsw_m_module_get_drvinfo(struct net_device *dev, -+ struct ethtool_drvinfo *drvinfo) -+{ -+ struct mlxsw_m_port *mlxsw_m_port = netdev_priv(dev); -+ struct mlxsw_m *mlxsw_m = mlxsw_m_port->mlxsw_m; -+ -+ strlcpy(drvinfo->driver, mlxsw_m->bus_info->device_kind, -+ sizeof(drvinfo->driver)); -+ snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version), -+ "%d.%d.%d", -+ mlxsw_m->bus_info->fw_rev.major, -+ mlxsw_m->bus_info->fw_rev.minor, -+ mlxsw_m->bus_info->fw_rev.subminor); -+ strlcpy(drvinfo->bus_info, mlxsw_m->bus_info->device_name, -+ sizeof(drvinfo->bus_info)); -+} -+ -+static int mlxsw_m_get_module_info(struct net_device *netdev, -+ struct ethtool_modinfo *modinfo) -+{ -+ struct mlxsw_m_port *mlxsw_m_port = netdev_priv(netdev); -+ struct mlxsw_core *core = mlxsw_m_port->mlxsw_m->core; -+ -+ return mlxsw_env_get_module_info(core, mlxsw_m_port->module, modinfo); -+} -+ -+static int -+mlxsw_m_get_module_eeprom(struct net_device *netdev, struct ethtool_eeprom *ee, -+ u8 *data) -+{ -+ struct mlxsw_m_port *mlxsw_m_port = netdev_priv(netdev); -+ struct mlxsw_core *core = mlxsw_m_port->mlxsw_m->core; -+ -+ return mlxsw_env_get_module_eeprom(netdev, core, mlxsw_m_port->module, -+ ee, data); -+} -+ -+static const struct ethtool_ops mlxsw_m_port_ethtool_ops = { -+ .get_drvinfo = mlxsw_m_module_get_drvinfo, -+ .get_module_info = mlxsw_m_get_module_info, -+ .get_module_eeprom = mlxsw_m_get_module_eeprom, -+}; -+ -+static int -+mlxsw_m_port_dev_addr_get(struct mlxsw_m_port *mlxsw_m_port) -+{ -+ struct mlxsw_m *mlxsw_m = mlxsw_m_port->mlxsw_m; -+ struct net_device *dev = mlxsw_m_port->dev; -+ char ppad_pl[MLXSW_REG_PPAD_LEN]; -+ int err; -+ -+ mlxsw_reg_ppad_pack(ppad_pl, false, 0); -+ err = mlxsw_reg_query(mlxsw_m->core, MLXSW_REG(ppad), ppad_pl); -+ if (err) -+ return err; -+ mlxsw_reg_ppad_mac_memcpy_from(ppad_pl, dev->dev_addr); -+ /* The last byte value in base mac address is guaranteed -+ * to be such it does not overflow when adding local_port -+ * value. -+ */ -+ dev->dev_addr[ETH_ALEN - 1] = mlxsw_m_port->module + 1; -+ return 0; -+} -+ -+static void mlxsw_m_port_switchdev_init(struct mlxsw_m_port *mlxsw_m_port) -+{ -+} -+ -+static void mlxsw_m_port_switchdev_fini(struct mlxsw_m_port *mlxsw_m_port) -+{ -+} -+ -+static int mlxsw_m_fw_rev_validate(struct mlxsw_m *mlxsw_m) -+{ -+ const struct mlxsw_fw_rev *rev = &mlxsw_m->bus_info->fw_rev; -+ -+ dev_info(mlxsw_m->bus_info->dev, "The firmware version %d.%d.%d\n", -+ rev->major, rev->minor, rev->subminor); -+ /* Validate driver & FW are compatible */ -+ if (rev->minor >= MLXSW_M_FWREV_MINOR && -+ rev->subminor >= MLXSW_M_FWREV_SUBMINOR) -+ return 0; -+ -+ dev_info(mlxsw_m->bus_info->dev, "The firmware version %d.%d.%d is incompatible with the driver (required >= %d.%d.%d)\n", -+ rev->major, rev->minor, rev->subminor, rev->major, -+ MLXSW_M_FWREV_MINOR, MLXSW_M_FWREV_SUBMINOR); -+ -+ return -EINVAL; -+} -+ -+static int -+mlxsw_m_port_create(struct mlxsw_m *mlxsw_m, u8 local_port, u8 module) -+{ -+ struct mlxsw_m_port *mlxsw_m_port; -+ struct net_device *dev; -+ int err; -+ -+ err = mlxsw_core_port_init(mlxsw_m->core, local_port); -+ if (err) { -+ dev_err(mlxsw_m->bus_info->dev, "Port %d: Failed to init core port\n", -+ local_port); -+ return err; -+ } -+ -+ dev = alloc_etherdev(sizeof(struct mlxsw_m_port)); -+ if (!dev) { -+ err = -ENOMEM; -+ goto err_alloc_etherdev; -+ } -+ -+ SET_NETDEV_DEV(dev, mlxsw_m->bus_info->dev); -+ mlxsw_m_port = netdev_priv(dev); -+ mlxsw_m_port->dev = dev; -+ mlxsw_m_port->mlxsw_m = mlxsw_m; -+ mlxsw_m_port->local_port = module; -+ mlxsw_m_port->module = module; -+ -+ dev->netdev_ops = &mlxsw_m_port_netdev_ops; -+ dev->ethtool_ops = &mlxsw_m_port_ethtool_ops; -+ -+ err = mlxsw_m_port_dev_addr_get(mlxsw_m_port); -+ if (err) { -+ dev_err(mlxsw_m->bus_info->dev, "Port %d: Unable to get port mac address\n", -+ mlxsw_m_port->local_port); -+ goto err_dev_addr_get; -+ } -+ -+ netif_carrier_off(dev); -+ mlxsw_m_port_switchdev_init(mlxsw_m_port); -+ mlxsw_m->ports[local_port] = mlxsw_m_port; -+ err = register_netdev(dev); -+ if (err) { -+ dev_err(mlxsw_m->bus_info->dev, "Port %d: Failed to register netdev\n", -+ mlxsw_m_port->local_port); -+ goto err_register_netdev; -+ } -+ -+ return 0; -+ -+err_register_netdev: -+ mlxsw_m->ports[local_port] = NULL; -+ mlxsw_m_port_switchdev_fini(mlxsw_m_port); -+ free_netdev(dev); -+err_dev_addr_get: -+err_alloc_etherdev: -+ mlxsw_core_port_fini(mlxsw_m->core, local_port); -+ return err; -+} -+ -+static void mlxsw_m_port_remove(struct mlxsw_m *mlxsw_m, u8 local_port) -+{ -+ struct mlxsw_m_port *mlxsw_m_port = mlxsw_m->ports[local_port]; -+ -+ mlxsw_core_port_clear(mlxsw_m->core, local_port, mlxsw_m); -+ unregister_netdev(mlxsw_m_port->dev); /* This calls ndo_stop */ -+ mlxsw_m->ports[local_port] = NULL; -+ mlxsw_m_port_switchdev_fini(mlxsw_m_port); -+ free_netdev(mlxsw_m_port->dev); -+ mlxsw_core_port_fini(mlxsw_m->core, local_port); -+} -+ -+static int mlxsw_m_ports_create(struct mlxsw_m *mlxsw_m) -+{ -+ char mgpir_pl[MLXSW_REG_MGPIR_LEN]; -+ int i; -+ int err; -+ -+ mlxsw_reg_mgpir_pack(mgpir_pl); -+ err = mlxsw_reg_query(mlxsw_m->core, MLXSW_REG(mgpir), mgpir_pl); -+ if (err) -+ return err; -+ -+ mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, -+ &mlxsw_m->max_ports); -+ if (!mlxsw_m->max_ports) -+ return 0; -+ -+ mlxsw_m->ports = kcalloc(mlxsw_m->max_ports, sizeof(*mlxsw_m->ports), -+ GFP_KERNEL); -+ if (!mlxsw_m->ports) -+ return -ENOMEM; -+ -+ mlxsw_m->module_to_port = kmalloc_array(mlxsw_m->max_ports, sizeof(int), -+ GFP_KERNEL); -+ if (!mlxsw_m->module_to_port) { -+ err = -ENOMEM; -+ goto err_module_to_port_alloc; -+ } -+ -+ /* Create port objects for each valid entry */ -+ for (i = 0; i < mlxsw_m->max_ports; i++) { -+ mlxsw_m->module_to_port[i] = i; -+ err = mlxsw_m_port_create(mlxsw_m, mlxsw_m->module_to_port[i], -+ i); -+ if (err) -+ goto err_module_to_port_create; -+ } -+ -+ return 0; -+ -+err_module_to_port_create: -+ for (i--; i >= 0; i--) -+ mlxsw_m_port_remove(mlxsw_m, mlxsw_m->module_to_port[i]); -+ kfree(mlxsw_m->module_to_port); -+err_module_to_port_alloc: -+ kfree(mlxsw_m->ports); -+ return err; -+} -+ -+static void mlxsw_m_ports_remove(struct mlxsw_m *mlxsw_m) -+{ -+ int i; -+ -+ for (i = 0; i < mlxsw_m->max_ports; i++) -+ mlxsw_m_port_remove(mlxsw_m, mlxsw_m->module_to_port[i]); -+ -+ kfree(mlxsw_m->module_to_port); -+ kfree(mlxsw_m->ports); -+} -+ -+static int mlxsw_m_init(struct mlxsw_core *mlxsw_core, -+ const struct mlxsw_bus_info *mlxsw_bus_info) -+{ -+ struct mlxsw_m *mlxsw_m = mlxsw_core_driver_priv(mlxsw_core); -+ int err; -+ -+ mlxsw_m->core = mlxsw_core; -+ mlxsw_m->bus_info = mlxsw_bus_info; -+ -+ err = mlxsw_m_fw_rev_validate(mlxsw_m); -+ if (err) -+ return err; -+ -+ err = mlxsw_m_ports_create(mlxsw_m); -+ if (err) { -+ dev_err(mlxsw_m->bus_info->dev, "Failed to create ports\n"); -+ return err; -+ } -+ -+ return 0; -+} -+ -+static void mlxsw_m_fini(struct mlxsw_core *mlxsw_core) -+{ -+ struct mlxsw_m *mlxsw_m = mlxsw_core_driver_priv(mlxsw_core); -+ -+ mlxsw_m_ports_remove(mlxsw_m); -+} -+ -+static const struct mlxsw_config_profile mlxsw_m_config_profile; -+ -+static struct mlxsw_driver mlxsw_m_driver = { -+ .kind = mlxsw_m_driver_name, -+ .priv_size = sizeof(struct mlxsw_m), -+ .init = mlxsw_m_init, -+ .fini = mlxsw_m_fini, -+ .profile = &mlxsw_m_config_profile, -+ .res_query_enabled = true, - }; - --static const struct i2c_device_id mlxsw_minimal_i2c_id[] = { -+static const struct i2c_device_id mlxsw_m_i2c_id[] = { - { "mlxsw_minimal", 0}, - { }, - }; - --static struct i2c_driver mlxsw_minimal_i2c_driver = { -+static struct i2c_driver mlxsw_m_i2c_driver = { - .driver.name = "mlxsw_minimal", - .class = I2C_CLASS_HWMON, -- .id_table = mlxsw_minimal_i2c_id, -+ .id_table = mlxsw_m_i2c_id, - }; - --static int __init mlxsw_minimal_module_init(void) -+static int __init mlxsw_m_module_init(void) - { - int err; - -- err = mlxsw_core_driver_register(&mlxsw_minimal_driver); -+ err = mlxsw_core_driver_register(&mlxsw_m_driver); - if (err) - return err; - -- err = mlxsw_i2c_driver_register(&mlxsw_minimal_i2c_driver); -+ err = mlxsw_i2c_driver_register(&mlxsw_m_i2c_driver); - if (err) - goto err_i2c_driver_register; - - return 0; - - err_i2c_driver_register: -- mlxsw_core_driver_unregister(&mlxsw_minimal_driver); -+ mlxsw_core_driver_unregister(&mlxsw_m_driver); - - return err; - } - --static void __exit mlxsw_minimal_module_exit(void) -+static void __exit mlxsw_m_module_exit(void) - { -- mlxsw_i2c_driver_unregister(&mlxsw_minimal_i2c_driver); -- mlxsw_core_driver_unregister(&mlxsw_minimal_driver); -+ mlxsw_i2c_driver_unregister(&mlxsw_m_i2c_driver); -+ mlxsw_core_driver_unregister(&mlxsw_m_driver); - } - --module_init(mlxsw_minimal_module_init); --module_exit(mlxsw_minimal_module_exit); -+module_init(mlxsw_m_module_init); -+module_exit(mlxsw_m_module_exit); - - MODULE_LICENSE("Dual BSD/GPL"); - MODULE_AUTHOR("Vadim Pasternak "); - MODULE_DESCRIPTION("Mellanox minimal driver"); --MODULE_DEVICE_TABLE(i2c, mlxsw_minimal_i2c_id); -+MODULE_DEVICE_TABLE(i2c, mlxsw_m_i2c_id); -diff --git a/drivers/net/ethernet/mellanox/mlxsw/pci.c b/drivers/net/ethernet/mellanox/mlxsw/pci.c -index a903e97..b40455f 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/pci.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/pci.c -@@ -1039,42 +1039,6 @@ mlxsw_pci_config_profile_swid_config(struct mlxsw_pci *mlxsw_pci, - mlxsw_cmd_mbox_config_profile_swid_config_mask_set(mbox, index, mask); - } - --static int mlxsw_pci_resources_query(struct mlxsw_pci *mlxsw_pci, char *mbox, -- struct mlxsw_res *res) --{ -- int index, i; -- u64 data; -- u16 id; -- int err; -- -- if (!res) -- return 0; -- -- mlxsw_cmd_mbox_zero(mbox); -- -- for (index = 0; index < MLXSW_CMD_QUERY_RESOURCES_MAX_QUERIES; -- index++) { -- err = mlxsw_cmd_query_resources(mlxsw_pci->core, mbox, index); -- if (err) -- return err; -- -- for (i = 0; i < MLXSW_CMD_QUERY_RESOURCES_PER_QUERY; i++) { -- id = mlxsw_cmd_mbox_query_resource_id_get(mbox, i); -- data = mlxsw_cmd_mbox_query_resource_data_get(mbox, i); -- -- if (id == MLXSW_CMD_QUERY_RESOURCES_TABLE_END_ID) -- return 0; -- -- mlxsw_res_parse(res, id, data); -- } -- } -- -- /* If after MLXSW_RESOURCES_QUERY_MAX_QUERIES we still didn't get -- * MLXSW_RESOURCES_TABLE_END_ID, something went bad in the FW. -- */ -- return -EIO; --} -- - static int - mlxsw_pci_profile_get_kvd_sizes(const struct mlxsw_pci *mlxsw_pci, - const struct mlxsw_config_profile *profile, -@@ -1459,7 +1423,7 @@ static int mlxsw_pci_init(void *bus_priv, struct mlxsw_core *mlxsw_core, - if (err) - goto err_boardinfo; - -- err = mlxsw_pci_resources_query(mlxsw_pci, mbox, res); -+ err = mlxsw_core_resources_query(mlxsw_core, mbox, res); - if (err) - goto err_query_resources; - -@@ -1722,7 +1686,6 @@ static int mlxsw_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) - { - const char *driver_name = pdev->driver->name; - struct mlxsw_pci *mlxsw_pci; -- bool called_again = false; - int err; - - mlxsw_pci = kzalloc(sizeof(*mlxsw_pci), GFP_KERNEL); -@@ -1779,18 +1742,10 @@ static int mlxsw_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) - mlxsw_pci->bus_info.dev = &pdev->dev; - mlxsw_pci->id = id; - --again: - err = mlxsw_core_bus_device_register(&mlxsw_pci->bus_info, - &mlxsw_pci_bus, mlxsw_pci, false, - NULL); -- /* -EAGAIN is returned in case the FW was updated. FW needs -- * a reset, so lets try to call mlxsw_core_bus_device_register() -- * again. -- */ -- if (err == -EAGAIN && !called_again) { -- called_again = true; -- goto again; -- } else if (err) { -+ if (err) { - dev_err(&pdev->dev, "cannot register bus device\n"); - goto err_bus_device_register; - } -diff --git a/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c b/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c -new file mode 100644 -index 0000000..49563a7 ---- /dev/null -+++ b/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c -@@ -0,0 +1,454 @@ -+/* -+ * drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c -+ * Copyright (c) 2017 Mellanox Technologies. All rights reserved. -+ * Copyright (c) 2017 Vadim Pasternak -+ * -+ * Redistribution and use in source and binary forms, with or without -+ * modification, are permitted provided that the following conditions are met: -+ * -+ * 1. Redistributions of source code must retain the above copyright -+ * notice, this list of conditions and the following disclaimer. -+ * 2. Redistributions in binary form must reproduce the above copyright -+ * notice, this list of conditions and the following disclaimer in the -+ * documentation and/or other materials provided with the distribution. -+ * 3. Neither the names of the copyright holders nor the names of its -+ * contributors may be used to endorse or promote products derived from -+ * this software without specific prior written permission. -+ * -+ * Alternatively, this software may be distributed under the terms of the -+ * GNU General Public License ("GPL") version 2 as published by the Free -+ * Software Foundation. -+ * -+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -+ * POSSIBILITY OF SUCH DAMAGE. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "core.h" -+ -+#define MLXSW_QSFP_I2C_ADDR 0x50 -+#define MLXSW_QSFP_PAGE_NUM 5 -+#define MLXSW_QSFP_PAGE_SIZE 128 -+#define MLXSW_QSFP_SUB_PAGE_NUM 3 -+#define MLXSW_QSFP_SUB_PAGE_SIZE 48 -+#define MLXSW_QSFP_LAST_SUB_PAGE_SIZE 32 -+#define MLXSW_QSFP_MAX_NUM 128 -+#define MLXSW_QSFP_MIN_REQ_LEN 4 -+#define MLXSW_QSFP_STATUS_VALID_TIME (HZ) -+#define MLXSW_QSFP_MAX_CPLD_NUM 3 -+#define MLXSW_QSFP_MIN_CPLD_NUM 1 -+ -+static const u8 mlxsw_qsfp_page_number[] = { 0xa0, 0x00, 0x01, 0x02, 0x03 }; -+static const u16 mlxsw_qsfp_page_shift[] = { 0x00, 0x80, 0x80, 0x80, 0x80 }; -+ -+/** -+ * Mellanox device Management Cable Info Access Register buffer for reading -+ * QSFP EEPROM info is limited by 48 bytes. In case full page is to be read -+ * (128 bytes), such request will be implemented by three transactions of size -+ * 48, 48, 32. -+ */ -+static const u16 mlxsw_qsfp_sub_page_size[] = { -+ MLXSW_QSFP_SUB_PAGE_SIZE, -+ MLXSW_QSFP_SUB_PAGE_SIZE, -+ MLXSW_QSFP_LAST_SUB_PAGE_SIZE -+}; -+ -+struct mlxsw_qsfp_module { -+ unsigned long last_updated; -+ u8 cache_status; -+}; -+ -+struct mlxsw_qsfp { -+ struct mlxsw_core *core; -+ const struct mlxsw_bus_info *bus_info; -+ struct attribute *attrs[MLXSW_QSFP_MAX_NUM + 1]; -+ struct device_attribute *dev_attrs; -+ struct bin_attribute *eeprom; -+ struct bin_attribute **eeprom_attr_list; -+ struct mlxsw_qsfp_module modules[MLXSW_QSFP_MAX_NUM]; -+ u8 module_ind[MLXSW_QSFP_MAX_NUM]; -+ u8 module_count; -+ struct attribute *cpld_attrs[MLXSW_QSFP_MAX_CPLD_NUM + 1]; -+ struct device_attribute *cpld_dev_attrs; -+}; -+ -+static int mlxsw_qsfp_cpld_num = MLXSW_QSFP_MIN_CPLD_NUM; -+static int mlxsw_qsfp_num = MLXSW_QSFP_MAX_NUM / 2; -+ -+static int -+mlxsw_qsfp_query_module_eeprom(struct mlxsw_qsfp *mlxsw_qsfp, u8 index, -+ loff_t off, size_t count, int page, char *buf) -+{ -+ char eeprom_tmp[MLXSW_QSFP_PAGE_SIZE]; -+ char mcia_pl[MLXSW_REG_MCIA_LEN]; -+ int status; -+ int err; -+ -+ mlxsw_reg_mcia_pack(mcia_pl, index, 0, page, off, count, -+ MLXSW_QSFP_I2C_ADDR); -+ -+ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mcia), mcia_pl); -+ if (err) -+ return err; -+ -+ status = mlxsw_reg_mcia_status_get(mcia_pl); -+ if (status) -+ return -EIO; -+ -+ mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); -+ memcpy(buf, eeprom_tmp, count); -+ -+ return 0; -+} -+ -+static int -+mlxsw_qsfp_get_module_eeprom(struct mlxsw_qsfp *mlxsw_qsfp, u8 index, -+ char *buf, loff_t off, size_t count) -+{ -+ int page_ind, page, page_off, subpage, offset, size, res = 0; -+ int err; -+ -+ if (!count) -+ return -EINVAL; -+ -+ memset(buf, 0, count); -+ size = count; -+ while (res < count) { -+ page_ind = off / MLXSW_QSFP_PAGE_SIZE; -+ page_off = off % MLXSW_QSFP_PAGE_SIZE; -+ page = mlxsw_qsfp_page_number[page_ind]; -+ offset = mlxsw_qsfp_page_shift[page_ind] + page_off; -+ subpage = page_off / MLXSW_QSFP_SUB_PAGE_SIZE; -+ size = min_t(u16, size, mlxsw_qsfp_sub_page_size[subpage]); -+ err = mlxsw_qsfp_query_module_eeprom(mlxsw_qsfp, index, offset, -+ size, page, buf + res); -+ if (err) { -+ dev_err(mlxsw_qsfp->bus_info->dev, "Eeprom query failed\n"); -+ return err; -+ } -+ off += size; -+ res += size; -+ size = count - size; -+ } -+ -+ return res; -+} -+ -+static ssize_t mlxsw_qsfp_bin_read(struct file *filp, struct kobject *kobj, -+ struct bin_attribute *attr, char *buf, -+ loff_t off, size_t count) -+{ -+ struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(container_of(kobj, -+ struct device, kobj)); -+ u8 *module_ind = attr->private; -+ size_t size; -+ -+ size = mlxsw_qsfp->eeprom[*module_ind].size; -+ -+ if (off > size) -+ return -ESPIPE; -+ else if (off == size) -+ return 0; -+ else if ((off + count) > size) -+ count = size - off; -+ -+ return mlxsw_qsfp_get_module_eeprom(mlxsw_qsfp, *module_ind, buf, off, -+ count); -+} -+ -+static ssize_t -+mlxsw_qsfp_status_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(dev); -+ char mcia_pl[MLXSW_REG_MCIA_LEN]; -+ int status; -+ u32 i; -+ int err; -+ -+ for (i = 0; i < mlxsw_qsfp->module_count; i++) { -+ if ((mlxsw_qsfp->dev_attrs + i) == attr) -+ break; -+ } -+ if (i == mlxsw_qsfp->module_count) -+ return -EINVAL; -+ -+ if (time_before(jiffies, mlxsw_qsfp->modules[i].last_updated + -+ MLXSW_QSFP_STATUS_VALID_TIME)) -+ return sprintf(buf, "%u\n", -+ mlxsw_qsfp->modules[i].cache_status); -+ -+ mlxsw_reg_mcia_pack(mcia_pl, i, 0, 0, 0, MLXSW_QSFP_MIN_REQ_LEN, -+ MLXSW_QSFP_I2C_ADDR); -+ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mcia), mcia_pl); -+ if (err) -+ return err; -+ -+ status = mlxsw_reg_mcia_status_get(mcia_pl); -+ mlxsw_qsfp->modules[i].cache_status = !status; -+ mlxsw_qsfp->modules[i].last_updated = jiffies; -+ -+ return sprintf(buf, "%u\n", !status); -+} -+ -+static ssize_t -+mlxsw_qsfp_cpld_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(dev); -+ char msci_pl[MLXSW_REG_MSCI_LEN]; -+ u32 version, i; -+ int err; -+ -+ for (i = 0; i < mlxsw_qsfp_cpld_num; i++) { -+ if ((mlxsw_qsfp->cpld_dev_attrs + i) == attr) -+ break; -+ } -+ if (i == mlxsw_qsfp_cpld_num) -+ return -EINVAL; -+ -+ mlxsw_reg_msci_pack(msci_pl, i); -+ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(msci), msci_pl); -+ if (err) -+ return err; -+ -+ version = mlxsw_reg_msci_version_get(msci_pl); -+ -+ return sprintf(buf, "%u\n", version); -+} -+ -+static int mlxsw_qsfp_dmi_set_cpld_num(const struct dmi_system_id *dmi) -+{ -+ mlxsw_qsfp_cpld_num = MLXSW_QSFP_MAX_CPLD_NUM; -+ -+ return 1; -+}; -+ -+static int mlxsw_qsfp_dmi_set_qsfp_num(const struct dmi_system_id *dmi) -+{ -+ mlxsw_qsfp_num = MLXSW_QSFP_MAX_NUM; -+ -+ return 1; -+}; -+ -+static const struct dmi_system_id mlxsw_qsfp_dmi_table[] = { -+ { -+ .callback = mlxsw_qsfp_dmi_set_cpld_num, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MSN24"), -+ }, -+ }, -+ { -+ .callback = mlxsw_qsfp_dmi_set_cpld_num, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MSN27"), -+ }, -+ }, -+ { -+ .callback = mlxsw_qsfp_dmi_set_qsfp_num, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MSN37"), -+ }, -+ }, -+ { -+ .callback = mlxsw_qsfp_dmi_set_qsfp_num, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MSN38"), -+ }, -+ }, -+ { } -+}; -+MODULE_DEVICE_TABLE(dmi, mlxsw_qsfp_dmi_table); -+ -+static int mlxsw_qsfp_set_module_num(struct mlxsw_qsfp *mlxsw_qsfp) -+{ -+ char pmlp_pl[MLXSW_REG_PMLP_LEN]; -+ u8 width; -+ int i, err; -+ -+ for (i = 1; i <= mlxsw_qsfp_num; i++) { -+ mlxsw_reg_pmlp_pack(pmlp_pl, i); -+ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(pmlp), -+ pmlp_pl); -+ if (err) -+ return err; -+ width = mlxsw_reg_pmlp_width_get(pmlp_pl); -+ if (!width) -+ continue; -+ mlxsw_qsfp->module_count++; -+ } -+ -+ return 0; -+} -+ -+int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, -+ const struct mlxsw_bus_info *mlxsw_bus_info, -+ struct mlxsw_qsfp **p_qsfp) -+{ -+ struct device_attribute *dev_attr, *cpld_dev_attr; -+ char mgpir_pl[MLXSW_REG_MGPIR_LEN]; -+ struct mlxsw_qsfp *mlxsw_qsfp; -+ struct bin_attribute *eeprom; -+ int i, count; -+ int err; -+ -+ if (!strcmp(mlxsw_bus_info->device_kind, "i2c")) -+ return 0; -+ -+ dmi_check_system(mlxsw_qsfp_dmi_table); -+ -+ mlxsw_qsfp = devm_kzalloc(mlxsw_bus_info->dev, sizeof(*mlxsw_qsfp), -+ GFP_KERNEL); -+ if (!mlxsw_qsfp) -+ return -ENOMEM; -+ -+ mlxsw_qsfp->core = mlxsw_core; -+ mlxsw_qsfp->bus_info = mlxsw_bus_info; -+ mlxsw_bus_info->dev->platform_data = mlxsw_qsfp; -+ -+ mlxsw_reg_mgpir_pack(mgpir_pl); -+ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mgpir), mgpir_pl); -+ if (err) { -+ err = mlxsw_qsfp_set_module_num(mlxsw_qsfp); -+ if (err) -+ return err; -+ } else { -+ mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, -+ &mlxsw_qsfp->module_count); -+ if (!mlxsw_qsfp->module_count) -+ return 0; -+ } -+ -+ count = mlxsw_qsfp->module_count + 1; -+ mlxsw_qsfp->eeprom = devm_kzalloc(mlxsw_bus_info->dev, -+ mlxsw_qsfp->module_count * -+ sizeof(*mlxsw_qsfp->eeprom), -+ GFP_KERNEL); -+ if (!mlxsw_qsfp->eeprom) -+ return -ENOMEM; -+ -+ mlxsw_qsfp->eeprom_attr_list = devm_kzalloc(mlxsw_bus_info->dev, -+ count * -+ sizeof(mlxsw_qsfp->eeprom), -+ GFP_KERNEL); -+ if (!mlxsw_qsfp->eeprom_attr_list) -+ return -ENOMEM; -+ -+ mlxsw_qsfp->dev_attrs = devm_kzalloc(mlxsw_bus_info->dev, count * -+ sizeof(*mlxsw_qsfp->dev_attrs), -+ GFP_KERNEL); -+ if (!mlxsw_qsfp->dev_attrs) -+ return -ENOMEM; -+ -+ mlxsw_qsfp->cpld_dev_attrs = devm_kzalloc(mlxsw_bus_info->dev, -+ mlxsw_qsfp_cpld_num * -+ sizeof(*mlxsw_qsfp->cpld_dev_attrs), -+ GFP_KERNEL); -+ if (!mlxsw_qsfp->cpld_dev_attrs) -+ return -ENOMEM; -+ -+ eeprom = mlxsw_qsfp->eeprom; -+ dev_attr = mlxsw_qsfp->dev_attrs; -+ for (i = 0; i < mlxsw_qsfp->module_count; i++, eeprom++, dev_attr++) { -+ dev_attr->show = mlxsw_qsfp_status_show; -+ dev_attr->attr.mode = 0444; -+ dev_attr->attr.name = devm_kasprintf(mlxsw_bus_info->dev, -+ GFP_KERNEL, -+ "qsfp%d_status", i + 1); -+ mlxsw_qsfp->attrs[i] = &dev_attr->attr; -+ sysfs_attr_init(&dev_attr->attr); -+ err = sysfs_create_file(&mlxsw_bus_info->dev->kobj, -+ mlxsw_qsfp->attrs[i]); -+ if (err) -+ goto err_create_file; -+ -+ sysfs_bin_attr_init(eeprom); -+ eeprom->attr.name = devm_kasprintf(mlxsw_bus_info->dev, -+ GFP_KERNEL, "qsfp%d", -+ i + 1); -+ eeprom->attr.mode = 0444; -+ eeprom->read = mlxsw_qsfp_bin_read; -+ eeprom->size = MLXSW_QSFP_PAGE_NUM * MLXSW_QSFP_PAGE_SIZE; -+ mlxsw_qsfp->module_ind[i] = i; -+ eeprom->private = &mlxsw_qsfp->module_ind[i]; -+ mlxsw_qsfp->eeprom_attr_list[i] = eeprom; -+ err = sysfs_create_bin_file(&mlxsw_bus_info->dev->kobj, -+ eeprom); -+ if (err) -+ goto err_create_bin_file; -+ } -+ -+ cpld_dev_attr = mlxsw_qsfp->cpld_dev_attrs; -+ for (i = 0; i < mlxsw_qsfp_cpld_num; i++, cpld_dev_attr++) { -+ cpld_dev_attr->show = mlxsw_qsfp_cpld_show; -+ cpld_dev_attr->attr.mode = 0444; -+ cpld_dev_attr->attr.name = devm_kasprintf(mlxsw_bus_info->dev, -+ GFP_KERNEL, -+ "cpld%d_version", i + 1); -+ mlxsw_qsfp->cpld_attrs[i] = &cpld_dev_attr->attr; -+ sysfs_attr_init(&cpld_dev_attr->attr); -+ err = sysfs_create_file(&mlxsw_bus_info->dev->kobj, -+ mlxsw_qsfp->cpld_attrs[i]); -+ if (err) -+ goto err_create_cpld_file; -+ } -+ -+ *p_qsfp = mlxsw_qsfp; -+ -+ return 0; -+ -+err_create_cpld_file: -+ sysfs_remove_file(&mlxsw_bus_info->dev->kobj, -+ mlxsw_qsfp->cpld_attrs[i--]); -+ i = mlxsw_qsfp->module_count; -+err_create_bin_file: -+ sysfs_remove_file(&mlxsw_bus_info->dev->kobj, -+ mlxsw_qsfp->attrs[i--]); -+err_create_file: -+ while (--i > 0) { -+ sysfs_remove_bin_file(&mlxsw_bus_info->dev->kobj, -+ mlxsw_qsfp->eeprom_attr_list[i]); -+ sysfs_remove_file(&mlxsw_bus_info->dev->kobj, -+ mlxsw_qsfp->attrs[i]); -+ } -+ -+ return err; -+} -+ -+void mlxsw_qsfp_fini(struct mlxsw_qsfp *mlxsw_qsfp) -+{ -+ int i; -+ -+ if (!strcmp(mlxsw_qsfp->bus_info->device_kind, "i2c")) -+ return; -+ -+ for (i = mlxsw_qsfp->module_count - 1; i >= 0; i--) { -+ sysfs_remove_bin_file(&mlxsw_qsfp->bus_info->dev->kobj, -+ mlxsw_qsfp->eeprom_attr_list[i]); -+ sysfs_remove_file(&mlxsw_qsfp->bus_info->dev->kobj, -+ mlxsw_qsfp->attrs[i]); -+ } -+} -+ -+MODULE_LICENSE("Dual BSD/GPL"); -+MODULE_AUTHOR("Vadim Pasternak "); -+MODULE_DESCRIPTION("Mellanox switch QSFP sysfs driver"); -diff --git a/drivers/net/ethernet/mellanox/mlxsw/reg.h b/drivers/net/ethernet/mellanox/mlxsw/reg.h -index c989587..7ca9814 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/reg.h -+++ b/drivers/net/ethernet/mellanox/mlxsw/reg.h -@@ -295,6 +295,7 @@ enum mlxsw_reg_sfd_rec_type { - MLXSW_REG_SFD_REC_TYPE_UNICAST = 0x0, - MLXSW_REG_SFD_REC_TYPE_UNICAST_LAG = 0x1, - MLXSW_REG_SFD_REC_TYPE_MULTICAST = 0x2, -+ MLXSW_REG_SFD_REC_TYPE_UNICAST_TUNNEL = 0xC, - }; - - /* reg_sfd_rec_type -@@ -525,6 +526,61 @@ mlxsw_reg_sfd_mc_pack(char *payload, int rec_index, - mlxsw_reg_sfd_mc_mid_set(payload, rec_index, mid); - } - -+/* reg_sfd_uc_tunnel_uip_msb -+ * When protocol is IPv4, the most significant byte of the underlay IPv4 -+ * destination IP. -+ * When protocol is IPv6, reserved. -+ * Access: RW -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfd, uc_tunnel_uip_msb, MLXSW_REG_SFD_BASE_LEN, 24, -+ 8, MLXSW_REG_SFD_REC_LEN, 0x08, false); -+ -+/* reg_sfd_uc_tunnel_fid -+ * Filtering ID. -+ * Access: Index -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfd, uc_tunnel_fid, MLXSW_REG_SFD_BASE_LEN, 0, 16, -+ MLXSW_REG_SFD_REC_LEN, 0x08, false); -+ -+enum mlxsw_reg_sfd_uc_tunnel_protocol { -+ MLXSW_REG_SFD_UC_TUNNEL_PROTOCOL_IPV4, -+ MLXSW_REG_SFD_UC_TUNNEL_PROTOCOL_IPV6, -+}; -+ -+/* reg_sfd_uc_tunnel_protocol -+ * IP protocol. -+ * Access: RW -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfd, uc_tunnel_protocol, MLXSW_REG_SFD_BASE_LEN, 27, -+ 1, MLXSW_REG_SFD_REC_LEN, 0x0C, false); -+ -+/* reg_sfd_uc_tunnel_uip_lsb -+ * When protocol is IPv4, the least significant bytes of the underlay -+ * IPv4 destination IP. -+ * When protocol is IPv6, pointer to the underlay IPv6 destination IP -+ * which is configured by RIPS. -+ * Access: RW -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfd, uc_tunnel_uip_lsb, MLXSW_REG_SFD_BASE_LEN, 0, -+ 24, MLXSW_REG_SFD_REC_LEN, 0x0C, false); -+ -+static inline void -+mlxsw_reg_sfd_uc_tunnel_pack(char *payload, int rec_index, -+ enum mlxsw_reg_sfd_rec_policy policy, -+ const char *mac, u16 fid, -+ enum mlxsw_reg_sfd_rec_action action, u32 uip, -+ enum mlxsw_reg_sfd_uc_tunnel_protocol proto) -+{ -+ mlxsw_reg_sfd_rec_pack(payload, rec_index, -+ MLXSW_REG_SFD_REC_TYPE_UNICAST_TUNNEL, mac, -+ action); -+ mlxsw_reg_sfd_rec_policy_set(payload, rec_index, policy); -+ mlxsw_reg_sfd_uc_tunnel_uip_msb_set(payload, rec_index, uip >> 24); -+ mlxsw_reg_sfd_uc_tunnel_uip_lsb_set(payload, rec_index, uip); -+ mlxsw_reg_sfd_uc_tunnel_fid_set(payload, rec_index, fid); -+ mlxsw_reg_sfd_uc_tunnel_protocol_set(payload, rec_index, proto); -+} -+ - /* SFN - Switch FDB Notification Register - * ------------------------------------------- - * The switch provides notifications on newly learned FDB entries and -@@ -585,6 +641,10 @@ enum mlxsw_reg_sfn_rec_type { - MLXSW_REG_SFN_REC_TYPE_AGED_OUT_MAC = 0x7, - /* Aged-out MAC address on a LAG port. */ - MLXSW_REG_SFN_REC_TYPE_AGED_OUT_MAC_LAG = 0x8, -+ /* Learned unicast tunnel record. */ -+ MLXSW_REG_SFN_REC_TYPE_LEARNED_UNICAST_TUNNEL = 0xD, -+ /* Aged-out unicast tunnel record. */ -+ MLXSW_REG_SFN_REC_TYPE_AGED_OUT_UNICAST_TUNNEL = 0xE, - }; - - /* reg_sfn_rec_type -@@ -648,6 +708,66 @@ static inline void mlxsw_reg_sfn_mac_lag_unpack(char *payload, int rec_index, - *p_lag_id = mlxsw_reg_sfn_mac_lag_lag_id_get(payload, rec_index); - } - -+/* reg_sfn_uc_tunnel_uip_msb -+ * When protocol is IPv4, the most significant byte of the underlay IPv4 -+ * address of the remote VTEP. -+ * When protocol is IPv6, reserved. -+ * Access: RO -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfn, uc_tunnel_uip_msb, MLXSW_REG_SFN_BASE_LEN, 24, -+ 8, MLXSW_REG_SFN_REC_LEN, 0x08, false); -+ -+enum mlxsw_reg_sfn_uc_tunnel_protocol { -+ MLXSW_REG_SFN_UC_TUNNEL_PROTOCOL_IPV4, -+ MLXSW_REG_SFN_UC_TUNNEL_PROTOCOL_IPV6, -+}; -+ -+/* reg_sfn_uc_tunnel_protocol -+ * IP protocol. -+ * Access: RO -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfn, uc_tunnel_protocol, MLXSW_REG_SFN_BASE_LEN, 27, -+ 1, MLXSW_REG_SFN_REC_LEN, 0x0C, false); -+ -+/* reg_sfn_uc_tunnel_uip_lsb -+ * When protocol is IPv4, the least significant bytes of the underlay -+ * IPv4 address of the remote VTEP. -+ * When protocol is IPv6, ipv6_id to be queried from TNIPSD. -+ * Access: RO -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfn, uc_tunnel_uip_lsb, MLXSW_REG_SFN_BASE_LEN, 0, -+ 24, MLXSW_REG_SFN_REC_LEN, 0x0C, false); -+ -+enum mlxsw_reg_sfn_tunnel_port { -+ MLXSW_REG_SFN_TUNNEL_PORT_NVE, -+ MLXSW_REG_SFN_TUNNEL_PORT_VPLS, -+ MLXSW_REG_SFN_TUNNEL_FLEX_TUNNEL0, -+ MLXSW_REG_SFN_TUNNEL_FLEX_TUNNEL1, -+}; -+ -+/* reg_sfn_uc_tunnel_port -+ * Tunnel port. -+ * Reserved on Spectrum. -+ * Access: RO -+ */ -+MLXSW_ITEM32_INDEXED(reg, sfn, tunnel_port, MLXSW_REG_SFN_BASE_LEN, 0, 4, -+ MLXSW_REG_SFN_REC_LEN, 0x10, false); -+ -+static inline void -+mlxsw_reg_sfn_uc_tunnel_unpack(char *payload, int rec_index, char *mac, -+ u16 *p_fid, u32 *p_uip, -+ enum mlxsw_reg_sfn_uc_tunnel_protocol *p_proto) -+{ -+ u32 uip_msb, uip_lsb; -+ -+ mlxsw_reg_sfn_rec_mac_memcpy_from(payload, rec_index, mac); -+ *p_fid = mlxsw_reg_sfn_mac_fid_get(payload, rec_index); -+ uip_msb = mlxsw_reg_sfn_uc_tunnel_uip_msb_get(payload, rec_index); -+ uip_lsb = mlxsw_reg_sfn_uc_tunnel_uip_lsb_get(payload, rec_index); -+ *p_uip = uip_msb << 24 | uip_lsb; -+ *p_proto = mlxsw_reg_sfn_uc_tunnel_protocol_get(payload, rec_index); -+} -+ - /* SPMS - Switch Port MSTP/RSTP State Register - * ------------------------------------------- - * Configures the spanning tree state of a physical port. -@@ -1069,6 +1189,8 @@ enum mlxsw_reg_sfdf_flush_type { - MLXSW_REG_SFDF_FLUSH_PER_PORT_AND_FID, - MLXSW_REG_SFDF_FLUSH_PER_LAG, - MLXSW_REG_SFDF_FLUSH_PER_LAG_AND_FID, -+ MLXSW_REG_SFDF_FLUSH_PER_NVE, -+ MLXSW_REG_SFDF_FLUSH_PER_NVE_AND_FID, - }; - - /* reg_sfdf_flush_type -@@ -1079,6 +1201,10 @@ enum mlxsw_reg_sfdf_flush_type { - * 3 - All FID dynamic entries pointing to port are flushed. - * 4 - All dynamic entries pointing to LAG are flushed. - * 5 - All FID dynamic entries pointing to LAG are flushed. -+ * 6 - All entries of type "Unicast Tunnel" or "Multicast Tunnel" are -+ * flushed. -+ * 7 - All entries of type "Unicast Tunnel" or "Multicast Tunnel" are -+ * flushed, per FID. - * Access: RW - */ - MLXSW_ITEM32(reg, sfdf, flush_type, 0x04, 28, 4); -@@ -1315,12 +1441,19 @@ MLXSW_ITEM32(reg, slcr, type, 0x00, 0, 4); - */ - MLXSW_ITEM32(reg, slcr, lag_hash, 0x04, 0, 20); - --static inline void mlxsw_reg_slcr_pack(char *payload, u16 lag_hash) -+/* reg_slcr_seed -+ * LAG seed value. The seed is the same for all ports. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, slcr, seed, 0x08, 0, 32); -+ -+static inline void mlxsw_reg_slcr_pack(char *payload, u16 lag_hash, u32 seed) - { - MLXSW_REG_ZERO(slcr, payload); - mlxsw_reg_slcr_pp_set(payload, MLXSW_REG_SLCR_PP_GLOBAL); - mlxsw_reg_slcr_type_set(payload, MLXSW_REG_SLCR_TYPE_CRC); - mlxsw_reg_slcr_lag_hash_set(payload, lag_hash); -+ mlxsw_reg_slcr_seed_set(payload, seed); - } - - /* SLCOR - Switch LAG Collector Register -@@ -2066,6 +2199,14 @@ MLXSW_ITEM32(reg, pagt, size, 0x00, 0, 8); - */ - MLXSW_ITEM32(reg, pagt, acl_group_id, 0x08, 0, 16); - -+/* reg_pagt_multi -+ * Multi-ACL -+ * 0 - This ACL is the last ACL in the multi-ACL -+ * 1 - This ACL is part of a multi-ACL -+ * Access: RW -+ */ -+MLXSW_ITEM32_INDEXED(reg, pagt, multi, 0x30, 31, 1, 0x04, 0x00, false); -+ - /* reg_pagt_acl_id - * ACL identifier - * Access: RW -@@ -2079,12 +2220,13 @@ static inline void mlxsw_reg_pagt_pack(char *payload, u16 acl_group_id) - } - - static inline void mlxsw_reg_pagt_acl_id_pack(char *payload, int index, -- u16 acl_id) -+ u16 acl_id, bool multi) - { - u8 size = mlxsw_reg_pagt_size_get(payload); - - if (index >= size) - mlxsw_reg_pagt_size_set(payload, index + 1); -+ mlxsw_reg_pagt_multi_set(payload, index, multi); - mlxsw_reg_pagt_acl_id_set(payload, index, acl_id); - } - -@@ -2362,6 +2504,43 @@ static inline void mlxsw_reg_pefa_unpack(char *payload, bool *p_a) - *p_a = mlxsw_reg_pefa_a_get(payload); - } - -+/* PEMRBT - Policy-Engine Multicast Router Binding Table Register -+ * -------------------------------------------------------------- -+ * This register is used for binding Multicast router to an ACL group -+ * that serves the MC router. -+ * This register is not supported by SwitchX/-2 and Spectrum. -+ */ -+#define MLXSW_REG_PEMRBT_ID 0x3014 -+#define MLXSW_REG_PEMRBT_LEN 0x14 -+ -+MLXSW_REG_DEFINE(pemrbt, MLXSW_REG_PEMRBT_ID, MLXSW_REG_PEMRBT_LEN); -+ -+enum mlxsw_reg_pemrbt_protocol { -+ MLXSW_REG_PEMRBT_PROTO_IPV4, -+ MLXSW_REG_PEMRBT_PROTO_IPV6, -+}; -+ -+/* reg_pemrbt_protocol -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, pemrbt, protocol, 0x00, 0, 1); -+ -+/* reg_pemrbt_group_id -+ * ACL group identifier. -+ * Range 0..cap_max_acl_groups-1 -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, pemrbt, group_id, 0x10, 0, 16); -+ -+static inline void -+mlxsw_reg_pemrbt_pack(char *payload, enum mlxsw_reg_pemrbt_protocol protocol, -+ u16 group_id) -+{ -+ MLXSW_REG_ZERO(pemrbt, payload); -+ mlxsw_reg_pemrbt_protocol_set(payload, protocol); -+ mlxsw_reg_pemrbt_group_id_set(payload, group_id); -+} -+ - /* PTCE-V2 - Policy-Engine TCAM Entry Register Version 2 - * ----------------------------------------------------- - * This register is used for accessing rules within a TCAM region. -@@ -2573,7 +2752,7 @@ mlxsw_reg_perpt_pack(char *payload, u8 erpt_bank, u8 erpt_index, - mlxsw_reg_perpt_erpt_bank_set(payload, erpt_bank); - mlxsw_reg_perpt_erpt_index_set(payload, erpt_index); - mlxsw_reg_perpt_key_size_set(payload, key_size); -- mlxsw_reg_perpt_bf_bypass_set(payload, true); -+ mlxsw_reg_perpt_bf_bypass_set(payload, false); - mlxsw_reg_perpt_erp_id_set(payload, erp_id); - mlxsw_reg_perpt_erpt_base_bank_set(payload, erpt_base_bank); - mlxsw_reg_perpt_erpt_base_index_set(payload, erpt_base_index); -@@ -2765,8 +2944,9 @@ static inline void mlxsw_reg_ptce3_pack(char *payload, bool valid, - u32 priority, - const char *tcam_region_info, - const char *key, u8 erp_id, -- bool large_exists, u32 lkey_id, -- u32 action_pointer) -+ u16 delta_start, u8 delta_mask, -+ u8 delta_value, bool large_exists, -+ u32 lkey_id, u32 action_pointer) - { - MLXSW_REG_ZERO(ptce3, payload); - mlxsw_reg_ptce3_v_set(payload, valid); -@@ -2775,6 +2955,9 @@ static inline void mlxsw_reg_ptce3_pack(char *payload, bool valid, - mlxsw_reg_ptce3_tcam_region_info_memcpy_to(payload, tcam_region_info); - mlxsw_reg_ptce3_flex2_key_blocks_memcpy_to(payload, key); - mlxsw_reg_ptce3_erp_id_set(payload, erp_id); -+ mlxsw_reg_ptce3_delta_start_set(payload, delta_start); -+ mlxsw_reg_ptce3_delta_mask_set(payload, delta_mask); -+ mlxsw_reg_ptce3_delta_value_set(payload, delta_value); - mlxsw_reg_ptce3_large_exists_set(payload, large_exists); - mlxsw_reg_ptce3_large_entry_key_id_set(payload, lkey_id); - mlxsw_reg_ptce3_action_pointer_set(payload, action_pointer); -@@ -2832,7 +3015,7 @@ static inline void mlxsw_reg_percr_pack(char *payload, u16 region_id) - mlxsw_reg_percr_region_id_set(payload, region_id); - mlxsw_reg_percr_atcam_ignore_prune_set(payload, false); - mlxsw_reg_percr_ctcam_ignore_prune_set(payload, false); -- mlxsw_reg_percr_bf_bypass_set(payload, true); -+ mlxsw_reg_percr_bf_bypass_set(payload, false); - } - - /* PERERP - Policy-Engine Region eRP Register -@@ -2921,6 +3104,72 @@ static inline void mlxsw_reg_pererp_pack(char *payload, u16 region_id, - mlxsw_reg_pererp_master_rp_id_set(payload, master_rp_id); - } - -+/* PEABFE - Policy-Engine Algorithmic Bloom Filter Entries Register -+ * ---------------------------------------------------------------- -+ * This register configures the Bloom filter entries. -+ */ -+#define MLXSW_REG_PEABFE_ID 0x3022 -+#define MLXSW_REG_PEABFE_BASE_LEN 0x10 -+#define MLXSW_REG_PEABFE_BF_REC_LEN 0x4 -+#define MLXSW_REG_PEABFE_BF_REC_MAX_COUNT 256 -+#define MLXSW_REG_PEABFE_LEN (MLXSW_REG_PEABFE_BASE_LEN + \ -+ MLXSW_REG_PEABFE_BF_REC_LEN * \ -+ MLXSW_REG_PEABFE_BF_REC_MAX_COUNT) -+ -+MLXSW_REG_DEFINE(peabfe, MLXSW_REG_PEABFE_ID, MLXSW_REG_PEABFE_LEN); -+ -+/* reg_peabfe_size -+ * Number of BF entries to be updated. -+ * Range 1..256 -+ * Access: Op -+ */ -+MLXSW_ITEM32(reg, peabfe, size, 0x00, 0, 9); -+ -+/* reg_peabfe_bf_entry_state -+ * Bloom filter state -+ * 0 - Clear -+ * 1 - Set -+ * Access: RW -+ */ -+MLXSW_ITEM32_INDEXED(reg, peabfe, bf_entry_state, -+ MLXSW_REG_PEABFE_BASE_LEN, 31, 1, -+ MLXSW_REG_PEABFE_BF_REC_LEN, 0x00, false); -+ -+/* reg_peabfe_bf_entry_bank -+ * Bloom filter bank ID -+ * Range 0..cap_max_erp_table_banks-1 -+ * Access: Index -+ */ -+MLXSW_ITEM32_INDEXED(reg, peabfe, bf_entry_bank, -+ MLXSW_REG_PEABFE_BASE_LEN, 24, 4, -+ MLXSW_REG_PEABFE_BF_REC_LEN, 0x00, false); -+ -+/* reg_peabfe_bf_entry_index -+ * Bloom filter entry index -+ * Range 0..2^cap_max_bf_log-1 -+ * Access: Index -+ */ -+MLXSW_ITEM32_INDEXED(reg, peabfe, bf_entry_index, -+ MLXSW_REG_PEABFE_BASE_LEN, 0, 24, -+ MLXSW_REG_PEABFE_BF_REC_LEN, 0x00, false); -+ -+static inline void mlxsw_reg_peabfe_pack(char *payload) -+{ -+ MLXSW_REG_ZERO(peabfe, payload); -+} -+ -+static inline void mlxsw_reg_peabfe_rec_pack(char *payload, int rec_index, -+ u8 state, u8 bank, u32 bf_index) -+{ -+ u8 num_rec = mlxsw_reg_peabfe_size_get(payload); -+ -+ if (rec_index >= num_rec) -+ mlxsw_reg_peabfe_size_set(payload, rec_index + 1); -+ mlxsw_reg_peabfe_bf_entry_state_set(payload, rec_index, state); -+ mlxsw_reg_peabfe_bf_entry_bank_set(payload, rec_index, bank); -+ mlxsw_reg_peabfe_bf_entry_index_set(payload, rec_index, bf_index); -+} -+ - /* IEDR - Infrastructure Entry Delete Register - * ---------------------------------------------------- - * This register is used for deleting entries from the entry tables. -@@ -4162,8 +4411,11 @@ MLXSW_ITEM32(reg, ppcnt, pnat, 0x00, 14, 2); - - enum mlxsw_reg_ppcnt_grp { - MLXSW_REG_PPCNT_IEEE_8023_CNT = 0x0, -+ MLXSW_REG_PPCNT_RFC_2863_CNT = 0x1, - MLXSW_REG_PPCNT_RFC_2819_CNT = 0x2, -+ MLXSW_REG_PPCNT_RFC_3635_CNT = 0x3, - MLXSW_REG_PPCNT_EXT_CNT = 0x5, -+ MLXSW_REG_PPCNT_DISCARD_CNT = 0x6, - MLXSW_REG_PPCNT_PRIO_CNT = 0x10, - MLXSW_REG_PPCNT_TC_CNT = 0x11, - MLXSW_REG_PPCNT_TC_CONG_TC = 0x13, -@@ -4178,6 +4430,7 @@ enum mlxsw_reg_ppcnt_grp { - * 0x2: RFC 2819 Counters - * 0x3: RFC 3635 Counters - * 0x5: Ethernet Extended Counters -+ * 0x6: Ethernet Discard Counters - * 0x8: Link Level Retransmission Counters - * 0x10: Per Priority Counters - * 0x11: Per Traffic Class Counters -@@ -4321,8 +4574,46 @@ MLXSW_ITEM64(reg, ppcnt, a_pause_mac_ctrl_frames_received, - MLXSW_ITEM64(reg, ppcnt, a_pause_mac_ctrl_frames_transmitted, - MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x90, 0, 64); - -+/* Ethernet RFC 2863 Counter Group */ -+ -+/* reg_ppcnt_if_in_discards -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, if_in_discards, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x10, 0, 64); -+ -+/* reg_ppcnt_if_out_discards -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, if_out_discards, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x38, 0, 64); -+ -+/* reg_ppcnt_if_out_errors -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, if_out_errors, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x40, 0, 64); -+ - /* Ethernet RFC 2819 Counter Group */ - -+/* reg_ppcnt_ether_stats_undersize_pkts -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ether_stats_undersize_pkts, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x30, 0, 64); -+ -+/* reg_ppcnt_ether_stats_oversize_pkts -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ether_stats_oversize_pkts, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x38, 0, 64); -+ -+/* reg_ppcnt_ether_stats_fragments -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ether_stats_fragments, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x40, 0, 64); -+ - /* reg_ppcnt_ether_stats_pkts64octets - * Access: RO - */ -@@ -4383,6 +4674,32 @@ MLXSW_ITEM64(reg, ppcnt, ether_stats_pkts4096to8191octets, - MLXSW_ITEM64(reg, ppcnt, ether_stats_pkts8192to10239octets, - MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0xA0, 0, 64); - -+/* Ethernet RFC 3635 Counter Group */ -+ -+/* reg_ppcnt_dot3stats_fcs_errors -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, dot3stats_fcs_errors, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x08, 0, 64); -+ -+/* reg_ppcnt_dot3stats_symbol_errors -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, dot3stats_symbol_errors, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x60, 0, 64); -+ -+/* reg_ppcnt_dot3control_in_unknown_opcodes -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, dot3control_in_unknown_opcodes, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x68, 0, 64); -+ -+/* reg_ppcnt_dot3in_pause_frames -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, dot3in_pause_frames, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x70, 0, 64); -+ - /* Ethernet Extended Counter Group Counters */ - - /* reg_ppcnt_ecn_marked -@@ -4391,6 +4708,80 @@ MLXSW_ITEM64(reg, ppcnt, ether_stats_pkts8192to10239octets, - MLXSW_ITEM64(reg, ppcnt, ecn_marked, - MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x08, 0, 64); - -+/* Ethernet Discard Counter Group Counters */ -+ -+/* reg_ppcnt_ingress_general -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ingress_general, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x00, 0, 64); -+ -+/* reg_ppcnt_ingress_policy_engine -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ingress_policy_engine, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x08, 0, 64); -+ -+/* reg_ppcnt_ingress_vlan_membership -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ingress_vlan_membership, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x10, 0, 64); -+ -+/* reg_ppcnt_ingress_tag_frame_type -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ingress_tag_frame_type, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x18, 0, 64); -+ -+/* reg_ppcnt_egress_vlan_membership -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, egress_vlan_membership, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x20, 0, 64); -+ -+/* reg_ppcnt_loopback_filter -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, loopback_filter, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x28, 0, 64); -+ -+/* reg_ppcnt_egress_general -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, egress_general, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x30, 0, 64); -+ -+/* reg_ppcnt_egress_hoq -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, egress_hoq, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x40, 0, 64); -+ -+/* reg_ppcnt_egress_policy_engine -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, egress_policy_engine, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x50, 0, 64); -+ -+/* reg_ppcnt_ingress_tx_link_down -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, ingress_tx_link_down, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x58, 0, 64); -+ -+/* reg_ppcnt_egress_stp_filter -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, egress_stp_filter, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x60, 0, 64); -+ -+/* reg_ppcnt_egress_sll -+ * Access: RO -+ */ -+MLXSW_ITEM64(reg, ppcnt, egress_sll, -+ MLXSW_REG_PPCNT_COUNTERS_OFFSET + 0x70, 0, 64); -+ - /* Ethernet Per Priority Group Counters */ - - /* reg_ppcnt_rx_octets -@@ -4793,6 +5184,7 @@ enum mlxsw_reg_htgt_trap_group { - MLXSW_REG_HTGT_TRAP_GROUP_SP_EVENT, - MLXSW_REG_HTGT_TRAP_GROUP_SP_IPV6_MLD, - MLXSW_REG_HTGT_TRAP_GROUP_SP_IPV6_ND, -+ MLXSW_REG_HTGT_TRAP_GROUP_SP_LBERROR, - }; - - /* reg_htgt_trap_group -@@ -5283,6 +5675,8 @@ enum mlxsw_reg_ritr_loopback_protocol { - MLXSW_REG_RITR_LOOPBACK_PROTOCOL_IPIP_IPV4, - /* IPinIP IPv6 underlay Unicast */ - MLXSW_REG_RITR_LOOPBACK_PROTOCOL_IPIP_IPV6, -+ /* IPinIP generic - used for Spectrum-2 underlay RIF */ -+ MLXSW_REG_RITR_LOOPBACK_GENERIC, - }; - - /* reg_ritr_loopback_protocol -@@ -5323,6 +5717,13 @@ MLXSW_ITEM32(reg, ritr, loopback_ipip_options, 0x10, 20, 4); - */ - MLXSW_ITEM32(reg, ritr, loopback_ipip_uvr, 0x10, 0, 16); - -+/* reg_ritr_loopback_ipip_underlay_rif -+ * Underlay ingress router interface. -+ * Reserved for Spectrum. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, ritr, loopback_ipip_underlay_rif, 0x14, 0, 16); -+ - /* reg_ritr_loopback_ipip_usip* - * Encapsulation Underlay source IP. - * Access: RW -@@ -5438,11 +5839,12 @@ static inline void - mlxsw_reg_ritr_loopback_ipip_common_pack(char *payload, - enum mlxsw_reg_ritr_loopback_ipip_type ipip_type, - enum mlxsw_reg_ritr_loopback_ipip_options options, -- u16 uvr_id, u32 gre_key) -+ u16 uvr_id, u16 underlay_rif, u32 gre_key) - { - mlxsw_reg_ritr_loopback_ipip_type_set(payload, ipip_type); - mlxsw_reg_ritr_loopback_ipip_options_set(payload, options); - mlxsw_reg_ritr_loopback_ipip_uvr_set(payload, uvr_id); -+ mlxsw_reg_ritr_loopback_ipip_underlay_rif_set(payload, underlay_rif); - mlxsw_reg_ritr_loopback_ipip_gre_key_set(payload, gre_key); - } - -@@ -5450,12 +5852,12 @@ static inline void - mlxsw_reg_ritr_loopback_ipip4_pack(char *payload, - enum mlxsw_reg_ritr_loopback_ipip_type ipip_type, - enum mlxsw_reg_ritr_loopback_ipip_options options, -- u16 uvr_id, u32 usip, u32 gre_key) -+ u16 uvr_id, u16 underlay_rif, u32 usip, u32 gre_key) - { - mlxsw_reg_ritr_loopback_protocol_set(payload, - MLXSW_REG_RITR_LOOPBACK_PROTOCOL_IPIP_IPV4); - mlxsw_reg_ritr_loopback_ipip_common_pack(payload, ipip_type, options, -- uvr_id, gre_key); -+ uvr_id, underlay_rif, gre_key); - mlxsw_reg_ritr_loopback_ipip_usip4_set(payload, usip); - } - -@@ -6817,6 +7219,13 @@ MLXSW_ITEM32(reg, rtdp, type, 0x00, 28, 4); - */ - MLXSW_ITEM32(reg, rtdp, tunnel_index, 0x00, 0, 24); - -+/* reg_rtdp_egress_router_interface -+ * Underlay egress router interface. -+ * Valid range is from 0 to cap_max_router_interfaces - 1 -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, rtdp, egress_router_interface, 0x40, 0, 16); -+ - /* IPinIP */ - - /* reg_rtdp_ipip_irif -@@ -7466,6 +7875,35 @@ static inline void mlxsw_reg_mfsl_unpack(char *payload, u8 tacho, - *p_tach_max = mlxsw_reg_mfsl_tach_max_get(payload); - } - -+/* FORE - Fan Out of Range Event Register -+ * -------------------------------------- -+ * This register reports the status of the controlled fans compared to the -+ * range defined by the MFSL register. -+ */ -+#define MLXSW_REG_FORE_ID 0x9007 -+#define MLXSW_REG_FORE_LEN 0x0C -+ -+MLXSW_REG_DEFINE(fore, MLXSW_REG_FORE_ID, MLXSW_REG_FORE_LEN); -+ -+/* fan_under_limit -+ * Fan speed is below the low limit defined in MFSL register. Each bit relates -+ * to a single tachometer and indicates the specific tachometer reading is -+ * below the threshold. -+ * Access: RO -+ */ -+MLXSW_ITEM32(reg, fore, fan_under_limit, 0x00, 16, 10); -+ -+static inline void mlxsw_reg_fore_unpack(char *payload, u8 tacho, -+ bool *fault) -+{ -+ u16 limit; -+ -+ if (fault) { -+ limit = mlxsw_reg_fore_fan_under_limit_get(payload); -+ *fault = limit & BIT(tacho); -+ } -+} -+ - /* MTCAP - Management Temperature Capabilities - * ------------------------------------------- - * This register exposes the capabilities of the device and -@@ -7494,16 +7932,21 @@ MLXSW_ITEM32(reg, mtcap, sensor_count, 0x00, 0, 7); - - MLXSW_REG_DEFINE(mtmp, MLXSW_REG_MTMP_ID, MLXSW_REG_MTMP_LEN); - -+#define MLXSW_REG_MTMP_MODULE_INDEX_MIN 64 -+#define MLXSW_REG_MTMP_GBOX_INDEX_MIN 256 - /* reg_mtmp_sensor_index - * Sensors index to access. - * 64-127 of sensor_index are mapped to the SFP+/QSFP modules sequentially - * (module 0 is mapped to sensor_index 64). - * Access: Index - */ --MLXSW_ITEM32(reg, mtmp, sensor_index, 0x00, 0, 7); -+MLXSW_ITEM32(reg, mtmp, sensor_index, 0x00, 0, 12); - - /* Convert to milli degrees Celsius */ --#define MLXSW_REG_MTMP_TEMP_TO_MC(val) (val * 125) -+#define MLXSW_REG_MTMP_TEMP_TO_MC(val) ({ typeof(val) v_ = (val); \ -+ ((v_) >= 0) ? ((v_) * 125) : \ -+ ((s16)((GENMASK(15, 0) + (v_) + 1) \ -+ * 125)); }) - - /* reg_mtmp_temperature - * Temperature reading from the sensor. Reading is in 0.125 Celsius -@@ -7562,7 +8005,7 @@ MLXSW_ITEM32(reg, mtmp, temperature_threshold_lo, 0x10, 0, 16); - */ - MLXSW_ITEM_BUF(reg, mtmp, sensor_name, 0x18, MLXSW_REG_MTMP_SENSOR_NAME_SIZE); - --static inline void mlxsw_reg_mtmp_pack(char *payload, u8 sensor_index, -+static inline void mlxsw_reg_mtmp_pack(char *payload, u16 sensor_index, - bool max_temp_enable, - bool max_temp_reset) - { -@@ -7574,11 +8017,10 @@ static inline void mlxsw_reg_mtmp_pack(char *payload, u8 sensor_index, - MLXSW_REG_MTMP_THRESH_HI); - } - --static inline void mlxsw_reg_mtmp_unpack(char *payload, unsigned int *p_temp, -- unsigned int *p_max_temp, -- char *sensor_name) -+static inline void mlxsw_reg_mtmp_unpack(char *payload, int *p_temp, -+ int *p_max_temp, char *sensor_name) - { -- u16 temp; -+ s16 temp; - - if (p_temp) { - temp = mlxsw_reg_mtmp_temperature_get(payload); -@@ -7592,6 +8034,80 @@ static inline void mlxsw_reg_mtmp_unpack(char *payload, unsigned int *p_temp, - mlxsw_reg_mtmp_sensor_name_memcpy_from(payload, sensor_name); - } - -+/* MTBR - Management Temperature Bulk Register -+ * ------------------------------------------- -+ * This register is used for bulk temperature reading. -+ */ -+#define MLXSW_REG_MTBR_ID 0x900F -+#define MLXSW_REG_MTBR_BASE_LEN 0x10 /* base length, without records */ -+#define MLXSW_REG_MTBR_REC_LEN 0x04 /* record length */ -+#define MLXSW_REG_MTBR_REC_MAX_COUNT 47 /* firmware limitation */ -+#define MLXSW_REG_MTBR_LEN (MLXSW_REG_MTBR_BASE_LEN + \ -+ MLXSW_REG_MTBR_REC_LEN * \ -+ MLXSW_REG_MTBR_REC_MAX_COUNT) -+ -+MLXSW_REG_DEFINE(mtbr, MLXSW_REG_MTBR_ID, MLXSW_REG_MTBR_LEN); -+ -+/* reg_mtbr_base_sensor_index -+ * Base sensors index to access (0 - ASIC sensor, 1-63 - ambient sensors, -+ * 64-127 are mapped to the SFP+/QSFP modules sequentially). -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, mtbr, base_sensor_index, 0x00, 0, 12); -+ -+/* reg_mtbr_num_rec -+ * Request: Number of records to read -+ * Response: Number of records read -+ * See above description for more details. -+ * Range 1..255 -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, mtbr, num_rec, 0x04, 0, 8); -+ -+/* reg_mtbr_rec_max_temp -+ * The highest measured temperature from the sensor. -+ * When the bit mte is cleared, the field max_temperature is reserved. -+ * Access: RO -+ */ -+MLXSW_ITEM32_INDEXED(reg, mtbr, rec_max_temp, MLXSW_REG_MTBR_BASE_LEN, 16, -+ 16, MLXSW_REG_MTBR_REC_LEN, 0x00, false); -+ -+/* reg_mtbr_rec_temp -+ * Temperature reading from the sensor. Reading is in 0..125 Celsius -+ * degrees units. -+ * Access: RO -+ */ -+MLXSW_ITEM32_INDEXED(reg, mtbr, rec_temp, MLXSW_REG_MTBR_BASE_LEN, 0, 16, -+ MLXSW_REG_MTBR_REC_LEN, 0x00, false); -+ -+static inline void mlxsw_reg_mtbr_pack(char *payload, u16 base_sensor_index, -+ u8 num_rec) -+{ -+ MLXSW_REG_ZERO(mtbr, payload); -+ mlxsw_reg_mtbr_base_sensor_index_set(payload, base_sensor_index); -+ mlxsw_reg_mtbr_num_rec_set(payload, num_rec); -+} -+ -+/* Error codes from temperatute reading */ -+enum mlxsw_reg_mtbr_temp_status { -+ MLXSW_REG_MTBR_NO_CONN = 0x8000, -+ MLXSW_REG_MTBR_NO_TEMP_SENS = 0x8001, -+ MLXSW_REG_MTBR_INDEX_NA = 0x8002, -+ MLXSW_REG_MTBR_BAD_SENS_INFO = 0x8003, -+}; -+ -+/* Base index for reading modules temperature */ -+#define MLXSW_REG_MTBR_BASE_MODULE_INDEX 64 -+ -+static inline void mlxsw_reg_mtbr_temp_unpack(char *payload, int rec_ind, -+ u16 *p_temp, u16 *p_max_temp) -+{ -+ if (p_temp) -+ *p_temp = mlxsw_reg_mtbr_rec_temp_get(payload, rec_ind); -+ if (p_max_temp) -+ *p_max_temp = mlxsw_reg_mtbr_rec_max_temp_get(payload, rec_ind); -+} -+ - /* MCIA - Management Cable Info Access - * ----------------------------------- - * MCIA register is used to access the SFP+ and QSFP connector's EPROM. -@@ -7646,13 +8162,48 @@ MLXSW_ITEM32(reg, mcia, device_address, 0x04, 0, 16); - */ - MLXSW_ITEM32(reg, mcia, size, 0x08, 0, 16); - --#define MLXSW_SP_REG_MCIA_EEPROM_SIZE 48 -+#define MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH 256 -+#define MLXSW_REG_MCIA_EEPROM_UP_PAGE_LENGTH 128 -+#define MLXSW_REG_MCIA_EEPROM_SIZE 48 -+#define MLXSW_REG_MCIA_I2C_ADDR_LOW 0x50 -+#define MLXSW_REG_MCIA_I2C_ADDR_HIGH 0x51 -+#define MLXSW_REG_MCIA_PAGE0_LO_OFF 0xa0 -+#define MLXSW_REG_MCIA_TH_ITEM_SIZE 2 -+#define MLXSW_REG_MCIA_TH_PAGE_NUM 3 -+#define MLXSW_REG_MCIA_PAGE0_LO 0 -+#define MLXSW_REG_MCIA_TH_PAGE_OFF 0x80 -+ -+enum mlxsw_reg_mcia_eeprom_module_info_rev_id { -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID_UNSPC = 0x00, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID_8436 = 0x01, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID_8636 = 0x03, -+}; -+ -+enum mlxsw_reg_mcia_eeprom_module_info_id { -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP = 0x03, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP = 0x0C, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS = 0x0D, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28 = 0x11, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD = 0x18, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP_DD = 0x1A, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS_CMIS = 0x1E, -+}; -+ -+enum mlxsw_reg_mcia_eeprom_module_info { -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE, -+}; - - /* reg_mcia_eeprom - * Bytes to read/write. - * Access: RW - */ --MLXSW_ITEM_BUF(reg, mcia, eeprom, 0x10, MLXSW_SP_REG_MCIA_EEPROM_SIZE); -+MLXSW_ITEM_BUF(reg, mcia, eeprom, 0x10, MLXSW_REG_MCIA_EEPROM_SIZE); -+ -+#define MLXSW_REG_MCIA_PAGE_GET(off) (((off) - \ -+ MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) / \ -+ MLXSW_REG_MCIA_EEPROM_UP_PAGE_LENGTH + 1) - - static inline void mlxsw_reg_mcia_pack(char *payload, u8 module, u8 lock, - u8 page_number, u16 device_addr, -@@ -7988,6 +8539,43 @@ static inline void mlxsw_reg_mlcr_pack(char *payload, u8 local_port, - MLXSW_REG_MLCR_DURATION_MAX : 0); - } - -+/* MSCI - Management System CPLD Information Register -+ * --------------------------------------------------- -+ * This register allows querying for the System CPLD(s) information. -+ */ -+#define MLXSW_REG_MSCI_ID 0x902A -+#define MLXSW_REG_MSCI_LEN 0x10 -+ -+static const struct mlxsw_reg_info mlxsw_reg_msci = { -+ .id = MLXSW_REG_MSCI_ID, -+ .len = MLXSW_REG_MSCI_LEN, -+}; -+ -+/* reg_msci_index -+ * Index to access. -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, msci, index, 0x00, 0, 4); -+ -+/* reg_msci_version -+ * CPLD version. -+ * Access: R0 -+ */ -+MLXSW_ITEM32(reg, msci, version, 0x04, 0, 32); -+ -+static inline void -+mlxsw_reg_msci_pack(char *payload, u8 index) -+{ -+ MLXSW_REG_ZERO(msci, payload); -+ mlxsw_reg_msci_index_set(payload, index); -+} -+ -+static inline void -+mlxsw_reg_msci_unpack(char *payload, u16 *p_version) -+{ -+ *p_version = mlxsw_reg_msci_version_get(payload); -+} -+ - /* MCQI - Management Component Query Information - * --------------------------------------------- - * This register allows querying information about firmware components. -@@ -8299,6 +8887,567 @@ static inline void mlxsw_reg_mgpc_pack(char *payload, u32 counter_index, - mlxsw_reg_mgpc_opcode_set(payload, opcode); - } - -+/* MPRS - Monitoring Parsing State Register -+ * ---------------------------------------- -+ * The MPRS register is used for setting up the parsing for hash, -+ * policy-engine and routing. -+ */ -+#define MLXSW_REG_MPRS_ID 0x9083 -+#define MLXSW_REG_MPRS_LEN 0x14 -+ -+MLXSW_REG_DEFINE(mprs, MLXSW_REG_MPRS_ID, MLXSW_REG_MPRS_LEN); -+ -+/* reg_mprs_parsing_depth -+ * Minimum parsing depth. -+ * Need to enlarge parsing depth according to L3, MPLS, tunnels, ACL -+ * rules, traps, hash, etc. Default is 96 bytes. Reserved when SwitchX-2. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, mprs, parsing_depth, 0x00, 0, 16); -+ -+/* reg_mprs_parsing_en -+ * Parsing enable. -+ * Bit 0 - Enable parsing of NVE of types VxLAN, VxLAN-GPE, GENEVE and -+ * NVGRE. Default is enabled. Reserved when SwitchX-2. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, mprs, parsing_en, 0x04, 0, 16); -+ -+/* reg_mprs_vxlan_udp_dport -+ * VxLAN UDP destination port. -+ * Used for identifying VxLAN packets and for dport field in -+ * encapsulation. Default is 4789. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, mprs, vxlan_udp_dport, 0x10, 0, 16); -+ -+static inline void mlxsw_reg_mprs_pack(char *payload, u16 parsing_depth, -+ u16 vxlan_udp_dport) -+{ -+ MLXSW_REG_ZERO(mprs, payload); -+ mlxsw_reg_mprs_parsing_depth_set(payload, parsing_depth); -+ mlxsw_reg_mprs_parsing_en_set(payload, true); -+ mlxsw_reg_mprs_vxlan_udp_dport_set(payload, vxlan_udp_dport); -+} -+ -+/* MGPIR - Management General Peripheral Information Register -+ * ---------------------------------------------------------- -+ * MGPIR register allows software to query the hardware and -+ * firmware general information of peripheral entities. -+ */ -+#define MLXSW_REG_MGPIR_ID 0x9100 -+#define MLXSW_REG_MGPIR_LEN 0xA0 -+ -+MLXSW_REG_DEFINE(mgpir, MLXSW_REG_MGPIR_ID, MLXSW_REG_MGPIR_LEN); -+ -+enum mlxsw_reg_mgpir_device_type { -+ MLXSW_REG_MGPIR_DEVICE_TYPE_NONE, -+ MLXSW_REG_MGPIR_DEVICE_TYPE_GEARBOX_DIE, -+}; -+ -+/* device_type -+ * Access: RO -+ */ -+MLXSW_ITEM32(reg, mgpir, device_type, 0x00, 24, 4); -+ -+/* devices_per_flash -+ * Number of devices of device_type per flash (can be shared by few devices). -+ * Access: RO -+ */ -+MLXSW_ITEM32(reg, mgpir, devices_per_flash, 0x00, 16, 8); -+ -+/* num_of_devices -+ * Number of devices of device_type. -+ * Access: RO -+ */ -+MLXSW_ITEM32(reg, mgpir, num_of_devices, 0x00, 0, 8); -+ -+/* num_of_modules -+ * Number of modules. -+ * Access: RO -+ */ -+MLXSW_ITEM32(reg, mgpir, num_of_modules, 0x04, 0, 8); -+ -+static inline void mlxsw_reg_mgpir_pack(char *payload) -+{ -+ MLXSW_REG_ZERO(mgpir, payload); -+} -+ -+static inline void -+mlxsw_reg_mgpir_unpack(char *payload, u8 *num_of_devices, -+ enum mlxsw_reg_mgpir_device_type *device_type, -+ u8 *devices_per_flash, u8 *num_of_modules) -+{ -+ if (num_of_devices) -+ *num_of_devices = mlxsw_reg_mgpir_num_of_devices_get(payload); -+ if (device_type) -+ *device_type = mlxsw_reg_mgpir_device_type_get(payload); -+ if (devices_per_flash) -+ *devices_per_flash = -+ mlxsw_reg_mgpir_devices_per_flash_get(payload); -+ if (num_of_modules) -+ *num_of_modules = mlxsw_reg_mgpir_num_of_modules_get(payload); -+} -+ -+/* TNGCR - Tunneling NVE General Configuration Register -+ * ---------------------------------------------------- -+ * The TNGCR register is used for setting up the NVE Tunneling configuration. -+ */ -+#define MLXSW_REG_TNGCR_ID 0xA001 -+#define MLXSW_REG_TNGCR_LEN 0x44 -+ -+MLXSW_REG_DEFINE(tngcr, MLXSW_REG_TNGCR_ID, MLXSW_REG_TNGCR_LEN); -+ -+enum mlxsw_reg_tngcr_type { -+ MLXSW_REG_TNGCR_TYPE_VXLAN, -+ MLXSW_REG_TNGCR_TYPE_VXLAN_GPE, -+ MLXSW_REG_TNGCR_TYPE_GENEVE, -+ MLXSW_REG_TNGCR_TYPE_NVGRE, -+}; -+ -+/* reg_tngcr_type -+ * Tunnel type for encapsulation and decapsulation. The types are mutually -+ * exclusive. -+ * Note: For Spectrum the NVE parsing must be enabled in MPRS. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, type, 0x00, 0, 4); -+ -+/* reg_tngcr_nve_valid -+ * The VTEP is valid. Allows adding FDB entries for tunnel encapsulation. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_valid, 0x04, 31, 1); -+ -+/* reg_tngcr_nve_ttl_uc -+ * The TTL for NVE tunnel encapsulation underlay unicast packets. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_ttl_uc, 0x04, 0, 8); -+ -+/* reg_tngcr_nve_ttl_mc -+ * The TTL for NVE tunnel encapsulation underlay multicast packets. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_ttl_mc, 0x08, 0, 8); -+ -+enum { -+ /* Do not copy flow label. Calculate flow label using nve_flh. */ -+ MLXSW_REG_TNGCR_FL_NO_COPY, -+ /* Copy flow label from inner packet if packet is IPv6 and -+ * encapsulation is by IPv6. Otherwise, calculate flow label using -+ * nve_flh. -+ */ -+ MLXSW_REG_TNGCR_FL_COPY, -+}; -+ -+/* reg_tngcr_nve_flc -+ * For NVE tunnel encapsulation: Flow label copy from inner packet. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_flc, 0x0C, 25, 1); -+ -+enum { -+ /* Flow label is static. In Spectrum this means '0'. Spectrum-2 -+ * uses {nve_fl_prefix, nve_fl_suffix}. -+ */ -+ MLXSW_REG_TNGCR_FL_NO_HASH, -+ /* 8 LSBs of the flow label are calculated from ECMP hash of the -+ * inner packet. 12 MSBs are configured by nve_fl_prefix. -+ */ -+ MLXSW_REG_TNGCR_FL_HASH, -+}; -+ -+/* reg_tngcr_nve_flh -+ * NVE flow label hash. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_flh, 0x0C, 24, 1); -+ -+/* reg_tngcr_nve_fl_prefix -+ * NVE flow label prefix. Constant 12 MSBs of the flow label. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_fl_prefix, 0x0C, 8, 12); -+ -+/* reg_tngcr_nve_fl_suffix -+ * NVE flow label suffix. Constant 8 LSBs of the flow label. -+ * Reserved when nve_flh=1 and for Spectrum. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_fl_suffix, 0x0C, 0, 8); -+ -+enum { -+ /* Source UDP port is fixed (default '0') */ -+ MLXSW_REG_TNGCR_UDP_SPORT_NO_HASH, -+ /* Source UDP port is calculated based on hash */ -+ MLXSW_REG_TNGCR_UDP_SPORT_HASH, -+}; -+ -+/* reg_tngcr_nve_udp_sport_type -+ * NVE UDP source port type. -+ * Spectrum uses LAG hash (SLCRv2). Spectrum-2 uses ECMP hash (RECRv2). -+ * When the source UDP port is calculated based on hash, then the 8 LSBs -+ * are calculated from hash the 8 MSBs are configured by -+ * nve_udp_sport_prefix. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_udp_sport_type, 0x10, 24, 1); -+ -+/* reg_tngcr_nve_udp_sport_prefix -+ * NVE UDP source port prefix. Constant 8 MSBs of the UDP source port. -+ * Reserved when NVE type is NVGRE. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_udp_sport_prefix, 0x10, 8, 8); -+ -+/* reg_tngcr_nve_group_size_mc -+ * The amount of sequential linked lists of MC entries. The first linked -+ * list is configured by SFD.underlay_mc_ptr. -+ * Valid values: 1, 2, 4, 8, 16, 32, 64 -+ * The linked list are configured by TNUMT. -+ * The hash is set by LAG hash. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_group_size_mc, 0x18, 0, 8); -+ -+/* reg_tngcr_nve_group_size_flood -+ * The amount of sequential linked lists of flooding entries. The first -+ * linked list is configured by SFMR.nve_tunnel_flood_ptr -+ * Valid values: 1, 2, 4, 8, 16, 32, 64 -+ * The linked list are configured by TNUMT. -+ * The hash is set by LAG hash. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, nve_group_size_flood, 0x1C, 0, 8); -+ -+/* reg_tngcr_learn_enable -+ * During decapsulation, whether to learn from NVE port. -+ * Reserved when Spectrum-2. See TNPC. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, learn_enable, 0x20, 31, 1); -+ -+/* reg_tngcr_underlay_virtual_router -+ * Underlay virtual router. -+ * Reserved when Spectrum-2. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, underlay_virtual_router, 0x20, 0, 16); -+ -+/* reg_tngcr_underlay_rif -+ * Underlay ingress router interface. RIF type should be loopback generic. -+ * Reserved when Spectrum. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, underlay_rif, 0x24, 0, 16); -+ -+/* reg_tngcr_usipv4 -+ * Underlay source IPv4 address of the NVE. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tngcr, usipv4, 0x28, 0, 32); -+ -+/* reg_tngcr_usipv6 -+ * Underlay source IPv6 address of the NVE. For Spectrum, must not be -+ * modified under traffic of NVE tunneling encapsulation. -+ * Access: RW -+ */ -+MLXSW_ITEM_BUF(reg, tngcr, usipv6, 0x30, 16); -+ -+static inline void mlxsw_reg_tngcr_pack(char *payload, -+ enum mlxsw_reg_tngcr_type type, -+ bool valid, u8 ttl) -+{ -+ MLXSW_REG_ZERO(tngcr, payload); -+ mlxsw_reg_tngcr_type_set(payload, type); -+ mlxsw_reg_tngcr_nve_valid_set(payload, valid); -+ mlxsw_reg_tngcr_nve_ttl_uc_set(payload, ttl); -+ mlxsw_reg_tngcr_nve_ttl_mc_set(payload, ttl); -+ mlxsw_reg_tngcr_nve_flc_set(payload, MLXSW_REG_TNGCR_FL_NO_COPY); -+ mlxsw_reg_tngcr_nve_flh_set(payload, 0); -+ mlxsw_reg_tngcr_nve_udp_sport_type_set(payload, -+ MLXSW_REG_TNGCR_UDP_SPORT_HASH); -+ mlxsw_reg_tngcr_nve_udp_sport_prefix_set(payload, 0); -+ mlxsw_reg_tngcr_nve_group_size_mc_set(payload, 1); -+ mlxsw_reg_tngcr_nve_group_size_flood_set(payload, 1); -+} -+ -+/* TNUMT - Tunneling NVE Underlay Multicast Table Register -+ * ------------------------------------------------------- -+ * The TNUMT register is for building the underlay MC table. It is used -+ * for MC, flooding and BC traffic into the NVE tunnel. -+ */ -+#define MLXSW_REG_TNUMT_ID 0xA003 -+#define MLXSW_REG_TNUMT_LEN 0x20 -+ -+MLXSW_REG_DEFINE(tnumt, MLXSW_REG_TNUMT_ID, MLXSW_REG_TNUMT_LEN); -+ -+enum mlxsw_reg_tnumt_record_type { -+ MLXSW_REG_TNUMT_RECORD_TYPE_IPV4, -+ MLXSW_REG_TNUMT_RECORD_TYPE_IPV6, -+ MLXSW_REG_TNUMT_RECORD_TYPE_LABEL, -+}; -+ -+/* reg_tnumt_record_type -+ * Record type. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnumt, record_type, 0x00, 28, 4); -+ -+enum mlxsw_reg_tnumt_tunnel_port { -+ MLXSW_REG_TNUMT_TUNNEL_PORT_NVE, -+ MLXSW_REG_TNUMT_TUNNEL_PORT_VPLS, -+ MLXSW_REG_TNUMT_TUNNEL_FLEX_TUNNEL0, -+ MLXSW_REG_TNUMT_TUNNEL_FLEX_TUNNEL1, -+}; -+ -+/* reg_tnumt_tunnel_port -+ * Tunnel port. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnumt, tunnel_port, 0x00, 24, 4); -+ -+/* reg_tnumt_underlay_mc_ptr -+ * Index to the underlay multicast table. -+ * For Spectrum the index is to the KVD linear. -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, tnumt, underlay_mc_ptr, 0x00, 0, 24); -+ -+/* reg_tnumt_vnext -+ * The next_underlay_mc_ptr is valid. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnumt, vnext, 0x04, 31, 1); -+ -+/* reg_tnumt_next_underlay_mc_ptr -+ * The next index to the underlay multicast table. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnumt, next_underlay_mc_ptr, 0x04, 0, 24); -+ -+/* reg_tnumt_record_size -+ * Number of IP addresses in the record. -+ * Range is 1..cap_max_nve_mc_entries_ipv{4,6} -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnumt, record_size, 0x08, 0, 3); -+ -+/* reg_tnumt_udip -+ * The underlay IPv4 addresses. udip[i] is reserved if i >= size -+ * Access: RW -+ */ -+MLXSW_ITEM32_INDEXED(reg, tnumt, udip, 0x0C, 0, 32, 0x04, 0x00, false); -+ -+/* reg_tnumt_udip_ptr -+ * The pointer to the underlay IPv6 addresses. udip_ptr[i] is reserved if -+ * i >= size. The IPv6 addresses are configured by RIPS. -+ * Access: RW -+ */ -+MLXSW_ITEM32_INDEXED(reg, tnumt, udip_ptr, 0x0C, 0, 24, 0x04, 0x00, false); -+ -+static inline void mlxsw_reg_tnumt_pack(char *payload, -+ enum mlxsw_reg_tnumt_record_type type, -+ enum mlxsw_reg_tnumt_tunnel_port tport, -+ u32 underlay_mc_ptr, bool vnext, -+ u32 next_underlay_mc_ptr, -+ u8 record_size) -+{ -+ MLXSW_REG_ZERO(tnumt, payload); -+ mlxsw_reg_tnumt_record_type_set(payload, type); -+ mlxsw_reg_tnumt_tunnel_port_set(payload, tport); -+ mlxsw_reg_tnumt_underlay_mc_ptr_set(payload, underlay_mc_ptr); -+ mlxsw_reg_tnumt_vnext_set(payload, vnext); -+ mlxsw_reg_tnumt_next_underlay_mc_ptr_set(payload, next_underlay_mc_ptr); -+ mlxsw_reg_tnumt_record_size_set(payload, record_size); -+} -+ -+/* TNQCR - Tunneling NVE QoS Configuration Register -+ * ------------------------------------------------ -+ * The TNQCR register configures how QoS is set in encapsulation into the -+ * underlay network. -+ */ -+#define MLXSW_REG_TNQCR_ID 0xA010 -+#define MLXSW_REG_TNQCR_LEN 0x0C -+ -+MLXSW_REG_DEFINE(tnqcr, MLXSW_REG_TNQCR_ID, MLXSW_REG_TNQCR_LEN); -+ -+/* reg_tnqcr_enc_set_dscp -+ * For encapsulation: How to set DSCP field: -+ * 0 - Copy the DSCP from the overlay (inner) IP header to the underlay -+ * (outer) IP header. If there is no IP header, use TNQDR.dscp -+ * 1 - Set the DSCP field as TNQDR.dscp -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnqcr, enc_set_dscp, 0x04, 28, 1); -+ -+static inline void mlxsw_reg_tnqcr_pack(char *payload) -+{ -+ MLXSW_REG_ZERO(tnqcr, payload); -+ mlxsw_reg_tnqcr_enc_set_dscp_set(payload, 0); -+} -+ -+/* TNQDR - Tunneling NVE QoS Default Register -+ * ------------------------------------------ -+ * The TNQDR register configures the default QoS settings for NVE -+ * encapsulation. -+ */ -+#define MLXSW_REG_TNQDR_ID 0xA011 -+#define MLXSW_REG_TNQDR_LEN 0x08 -+ -+MLXSW_REG_DEFINE(tnqdr, MLXSW_REG_TNQDR_ID, MLXSW_REG_TNQDR_LEN); -+ -+/* reg_tnqdr_local_port -+ * Local port number (receive port). CPU port is supported. -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, tnqdr, local_port, 0x00, 16, 8); -+ -+/* reg_tnqdr_dscp -+ * For encapsulation, the default DSCP. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnqdr, dscp, 0x04, 0, 6); -+ -+static inline void mlxsw_reg_tnqdr_pack(char *payload, u8 local_port) -+{ -+ MLXSW_REG_ZERO(tnqdr, payload); -+ mlxsw_reg_tnqdr_local_port_set(payload, local_port); -+ mlxsw_reg_tnqdr_dscp_set(payload, 0); -+} -+ -+/* TNEEM - Tunneling NVE Encapsulation ECN Mapping Register -+ * -------------------------------------------------------- -+ * The TNEEM register maps ECN of the IP header at the ingress to the -+ * encapsulation to the ECN of the underlay network. -+ */ -+#define MLXSW_REG_TNEEM_ID 0xA012 -+#define MLXSW_REG_TNEEM_LEN 0x0C -+ -+MLXSW_REG_DEFINE(tneem, MLXSW_REG_TNEEM_ID, MLXSW_REG_TNEEM_LEN); -+ -+/* reg_tneem_overlay_ecn -+ * ECN of the IP header in the overlay network. -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, tneem, overlay_ecn, 0x04, 24, 2); -+ -+/* reg_tneem_underlay_ecn -+ * ECN of the IP header in the underlay network. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tneem, underlay_ecn, 0x04, 16, 2); -+ -+static inline void mlxsw_reg_tneem_pack(char *payload, u8 overlay_ecn, -+ u8 underlay_ecn) -+{ -+ MLXSW_REG_ZERO(tneem, payload); -+ mlxsw_reg_tneem_overlay_ecn_set(payload, overlay_ecn); -+ mlxsw_reg_tneem_underlay_ecn_set(payload, underlay_ecn); -+} -+ -+/* TNDEM - Tunneling NVE Decapsulation ECN Mapping Register -+ * -------------------------------------------------------- -+ * The TNDEM register configures the actions that are done in the -+ * decapsulation. -+ */ -+#define MLXSW_REG_TNDEM_ID 0xA013 -+#define MLXSW_REG_TNDEM_LEN 0x0C -+ -+MLXSW_REG_DEFINE(tndem, MLXSW_REG_TNDEM_ID, MLXSW_REG_TNDEM_LEN); -+ -+/* reg_tndem_underlay_ecn -+ * ECN field of the IP header in the underlay network. -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, tndem, underlay_ecn, 0x04, 24, 2); -+ -+/* reg_tndem_overlay_ecn -+ * ECN field of the IP header in the overlay network. -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, tndem, overlay_ecn, 0x04, 16, 2); -+ -+/* reg_tndem_eip_ecn -+ * Egress IP ECN. ECN field of the IP header of the packet which goes out -+ * from the decapsulation. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tndem, eip_ecn, 0x04, 8, 2); -+ -+/* reg_tndem_trap_en -+ * Trap enable: -+ * 0 - No trap due to decap ECN -+ * 1 - Trap enable with trap_id -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tndem, trap_en, 0x08, 28, 4); -+ -+/* reg_tndem_trap_id -+ * Trap ID. Either DECAP_ECN0 or DECAP_ECN1. -+ * Reserved when trap_en is '0'. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tndem, trap_id, 0x08, 0, 9); -+ -+static inline void mlxsw_reg_tndem_pack(char *payload, u8 underlay_ecn, -+ u8 overlay_ecn, u8 ecn, bool trap_en, -+ u16 trap_id) -+{ -+ MLXSW_REG_ZERO(tndem, payload); -+ mlxsw_reg_tndem_underlay_ecn_set(payload, underlay_ecn); -+ mlxsw_reg_tndem_overlay_ecn_set(payload, overlay_ecn); -+ mlxsw_reg_tndem_eip_ecn_set(payload, ecn); -+ mlxsw_reg_tndem_trap_en_set(payload, trap_en); -+ mlxsw_reg_tndem_trap_id_set(payload, trap_id); -+} -+ -+/* TNPC - Tunnel Port Configuration Register -+ * ----------------------------------------- -+ * The TNPC register is used for tunnel port configuration. -+ * Reserved when Spectrum. -+ */ -+#define MLXSW_REG_TNPC_ID 0xA020 -+#define MLXSW_REG_TNPC_LEN 0x18 -+ -+MLXSW_REG_DEFINE(tnpc, MLXSW_REG_TNPC_ID, MLXSW_REG_TNPC_LEN); -+ -+enum mlxsw_reg_tnpc_tunnel_port { -+ MLXSW_REG_TNPC_TUNNEL_PORT_NVE, -+ MLXSW_REG_TNPC_TUNNEL_PORT_VPLS, -+ MLXSW_REG_TNPC_TUNNEL_FLEX_TUNNEL0, -+ MLXSW_REG_TNPC_TUNNEL_FLEX_TUNNEL1, -+}; -+ -+/* reg_tnpc_tunnel_port -+ * Tunnel port. -+ * Access: Index -+ */ -+MLXSW_ITEM32(reg, tnpc, tunnel_port, 0x00, 0, 4); -+ -+/* reg_tnpc_learn_enable_v6 -+ * During IPv6 underlay decapsulation, whether to learn from tunnel port. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnpc, learn_enable_v6, 0x04, 1, 1); -+ -+/* reg_tnpc_learn_enable_v4 -+ * During IPv4 underlay decapsulation, whether to learn from tunnel port. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, tnpc, learn_enable_v4, 0x04, 0, 1); -+ -+static inline void mlxsw_reg_tnpc_pack(char *payload, -+ enum mlxsw_reg_tnpc_tunnel_port tport, -+ bool learn_enable) -+{ -+ MLXSW_REG_ZERO(tnpc, payload); -+ mlxsw_reg_tnpc_tunnel_port_set(payload, tport); -+ mlxsw_reg_tnpc_learn_enable_v4_set(payload, learn_enable); -+ mlxsw_reg_tnpc_learn_enable_v6_set(payload, learn_enable); -+} -+ - /* TIGCR - Tunneling IPinIP General Configuration Register - * ------------------------------------------------------- - * The TIGCR register is used for setting up the IPinIP Tunnel configuration. -@@ -8356,8 +9505,15 @@ MLXSW_ITEM32(reg, sbpr, dir, 0x00, 24, 2); - */ - MLXSW_ITEM32(reg, sbpr, pool, 0x00, 0, 4); - -+/* reg_sbpr_infi_size -+ * Size is infinite. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, sbpr, infi_size, 0x04, 31, 1); -+ - /* reg_sbpr_size - * Pool size in buffer cells. -+ * Reserved when infi_size = 1. - * Access: RW - */ - MLXSW_ITEM32(reg, sbpr, size, 0x04, 0, 24); -@@ -8375,13 +9531,15 @@ MLXSW_ITEM32(reg, sbpr, mode, 0x08, 0, 4); - - static inline void mlxsw_reg_sbpr_pack(char *payload, u8 pool, - enum mlxsw_reg_sbxx_dir dir, -- enum mlxsw_reg_sbpr_mode mode, u32 size) -+ enum mlxsw_reg_sbpr_mode mode, u32 size, -+ bool infi_size) - { - MLXSW_REG_ZERO(sbpr, payload); - mlxsw_reg_sbpr_pool_set(payload, pool); - mlxsw_reg_sbpr_dir_set(payload, dir); - mlxsw_reg_sbpr_mode_set(payload, mode); - mlxsw_reg_sbpr_size_set(payload, size); -+ mlxsw_reg_sbpr_infi_size_set(payload, infi_size); - } - - /* SBCM - Shared Buffer Class Management Register -@@ -8429,6 +9587,12 @@ MLXSW_ITEM32(reg, sbcm, min_buff, 0x18, 0, 24); - #define MLXSW_REG_SBXX_DYN_MAX_BUFF_MIN 1 - #define MLXSW_REG_SBXX_DYN_MAX_BUFF_MAX 14 - -+/* reg_sbcm_infi_max -+ * Max buffer is infinite. -+ * Access: RW -+ */ -+MLXSW_ITEM32(reg, sbcm, infi_max, 0x1C, 31, 1); -+ - /* reg_sbcm_max_buff - * When the pool associated to the port-pg/tclass is configured to - * static, Maximum buffer size for the limiter configured in cells. -@@ -8438,6 +9602,7 @@ MLXSW_ITEM32(reg, sbcm, min_buff, 0x18, 0, 24); - * 0: 0 - * i: (1/128)*2^(i-1), for i=1..14 - * 0xFF: Infinity -+ * Reserved when infi_max = 1. - * Access: RW - */ - MLXSW_ITEM32(reg, sbcm, max_buff, 0x1C, 0, 24); -@@ -8450,7 +9615,8 @@ MLXSW_ITEM32(reg, sbcm, pool, 0x24, 0, 4); - - static inline void mlxsw_reg_sbcm_pack(char *payload, u8 local_port, u8 pg_buff, - enum mlxsw_reg_sbxx_dir dir, -- u32 min_buff, u32 max_buff, u8 pool) -+ u32 min_buff, u32 max_buff, -+ bool infi_max, u8 pool) - { - MLXSW_REG_ZERO(sbcm, payload); - mlxsw_reg_sbcm_local_port_set(payload, local_port); -@@ -8458,6 +9624,7 @@ static inline void mlxsw_reg_sbcm_pack(char *payload, u8 local_port, u8 pg_buff, - mlxsw_reg_sbcm_dir_set(payload, dir); - mlxsw_reg_sbcm_min_buff_set(payload, min_buff); - mlxsw_reg_sbcm_max_buff_set(payload, max_buff); -+ mlxsw_reg_sbcm_infi_max_set(payload, infi_max); - mlxsw_reg_sbcm_pool_set(payload, pool); - } - -@@ -8768,8 +9935,10 @@ static const struct mlxsw_reg_info *mlxsw_reg_infos[] = { - MLXSW_REG(ppbs), - MLXSW_REG(prcr), - MLXSW_REG(pefa), -+ MLXSW_REG(pemrbt), - MLXSW_REG(ptce2), - MLXSW_REG(perpt), -+ MLXSW_REG(peabfe), - MLXSW_REG(perar), - MLXSW_REG(ptce3), - MLXSW_REG(percr), -@@ -8818,18 +9987,30 @@ static const struct mlxsw_reg_info *mlxsw_reg_infos[] = { - MLXSW_REG(mfsc), - MLXSW_REG(mfsm), - MLXSW_REG(mfsl), -+ MLXSW_REG(fore), - MLXSW_REG(mtcap), - MLXSW_REG(mtmp), -+ MLXSW_REG(mtbr), - MLXSW_REG(mcia), - MLXSW_REG(mpat), - MLXSW_REG(mpar), - MLXSW_REG(mrsr), - MLXSW_REG(mlcr), -+ MLXSW_REG(msci), - MLXSW_REG(mpsc), - MLXSW_REG(mcqi), - MLXSW_REG(mcc), - MLXSW_REG(mcda), - MLXSW_REG(mgpc), -+ MLXSW_REG(mprs), -+ MLXSW_REG(mgpir), -+ MLXSW_REG(tngcr), -+ MLXSW_REG(tnumt), -+ MLXSW_REG(tnqcr), -+ MLXSW_REG(tnqdr), -+ MLXSW_REG(tneem), -+ MLXSW_REG(tndem), -+ MLXSW_REG(tnpc), - MLXSW_REG(tigcr), - MLXSW_REG(sbpr), - MLXSW_REG(sbcm), -diff --git a/drivers/net/ethernet/mellanox/mlxsw/resources.h b/drivers/net/ethernet/mellanox/mlxsw/resources.h -index 79a31de..b8b3a01 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/resources.h -+++ b/drivers/net/ethernet/mellanox/mlxsw/resources.h -@@ -41,11 +41,14 @@ enum mlxsw_res_id { - MLXSW_RES_ID_ACL_ERPT_ENTRIES_4KB, - MLXSW_RES_ID_ACL_ERPT_ENTRIES_8KB, - MLXSW_RES_ID_ACL_ERPT_ENTRIES_12KB, -+ MLXSW_RES_ID_ACL_MAX_BF_LOG, - MLXSW_RES_ID_MAX_CPU_POLICERS, - MLXSW_RES_ID_MAX_VRS, - MLXSW_RES_ID_MAX_RIFS, - MLXSW_RES_ID_MC_ERIF_LIST_ENTRIES, - MLXSW_RES_ID_MAX_LPM_TREES, -+ MLXSW_RES_ID_MAX_NVE_MC_ENTRIES_IPV4, -+ MLXSW_RES_ID_MAX_NVE_MC_ENTRIES_IPV6, - - /* Internal resources. - * Determined by the SW, not queried from the HW. -@@ -91,11 +94,14 @@ static u16 mlxsw_res_ids[] = { - [MLXSW_RES_ID_ACL_ERPT_ENTRIES_4KB] = 0x2951, - [MLXSW_RES_ID_ACL_ERPT_ENTRIES_8KB] = 0x2952, - [MLXSW_RES_ID_ACL_ERPT_ENTRIES_12KB] = 0x2953, -+ [MLXSW_RES_ID_ACL_MAX_BF_LOG] = 0x2960, - [MLXSW_RES_ID_MAX_CPU_POLICERS] = 0x2A13, - [MLXSW_RES_ID_MAX_VRS] = 0x2C01, - [MLXSW_RES_ID_MAX_RIFS] = 0x2C02, - [MLXSW_RES_ID_MC_ERIF_LIST_ENTRIES] = 0x2C10, - [MLXSW_RES_ID_MAX_LPM_TREES] = 0x2C30, -+ [MLXSW_RES_ID_MAX_NVE_MC_ENTRIES_IPV4] = 0x2E02, -+ [MLXSW_RES_ID_MAX_NVE_MC_ENTRIES_IPV6] = 0x2E03, - }; - - struct mlxsw_res { -diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c -index d52c821..77be37a 100644 ---- a/drivers/platform/mellanox/mlxreg-hotplug.c -+++ b/drivers/platform/mellanox/mlxreg-hotplug.c -@@ -496,18 +496,53 @@ static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) - { - struct mlxreg_core_hotplug_platform_data *pdata; - struct mlxreg_core_item *item; -- int i, ret; -+ struct mlxreg_core_data *data; -+ u32 regval; -+ int i, j, ret; - - pdata = dev_get_platdata(&priv->pdev->dev); - item = pdata->items; - - for (i = 0; i < pdata->counter; i++, item++) { -+ if (item->capability) { -+ /* -+ * Read group capability register to get actual number -+ * of interrupt capable components and set group mask -+ * accordingly. -+ */ -+ ret = regmap_read(priv->regmap, item->capability, -+ ®val); -+ if (ret) -+ goto out; -+ -+ item->mask = GENMASK((regval & item->mask) - 1, 0); -+ } -+ - /* Clear group presense event. */ - ret = regmap_write(priv->regmap, item->reg + - MLXREG_HOTPLUG_EVENT_OFF, 0); - if (ret) - goto out; - -+ /* -+ * Verify if hardware configuration requires to disable -+ * interrupt capability for some of components. -+ */ -+ data = item->data; -+ for (j = 0; j < item->count; j++, data++) { -+ /* Verify if the attribute has capability register. */ -+ if (data->capability) { -+ /* Read capability register. */ -+ ret = regmap_read(priv->regmap, -+ data->capability, ®val); -+ if (ret) -+ goto out; -+ -+ if (!(regval & data->bit)) -+ item->mask &= ~BIT(j); -+ } -+ } -+ - /* Set group initial status as mask and unmask group event. */ - if (item->inversed) { - item->cache = item->mask; -@@ -621,11 +656,8 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev) - priv->irq = pdata->irq; - } else { - priv->irq = platform_get_irq(pdev, 0); -- if (priv->irq < 0) { -- dev_err(&pdev->dev, "Failed to get platform irq: %d\n", -- priv->irq); -+ if (priv->irq < 0) - return priv->irq; -- } - } - - priv->regmap = pdata->regmap; -diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c -index 69e28c1..c27548f 100644 ---- a/drivers/platform/x86/mlx-platform.c -+++ b/drivers/platform/x86/mlx-platform.c -@@ -1,34 +1,9 @@ -+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 - /* -- * Copyright (c) 2016 Mellanox Technologies. All rights reserved. -- * Copyright (c) 2016 Vadim Pasternak -+ * Mellanox platform driver - * -- * Redistribution and use in source and binary forms, with or without -- * modification, are permitted provided that the following conditions are met: -- * -- * 1. Redistributions of source code must retain the above copyright -- * notice, this list of conditions and the following disclaimer. -- * 2. Redistributions in binary form must reproduce the above copyright -- * notice, this list of conditions and the following disclaimer in the -- * documentation and/or other materials provided with the distribution. -- * 3. Neither the names of the copyright holders nor the names of its -- * contributors may be used to endorse or promote products derived from -- * this software without specific prior written permission. -- * -- * Alternatively, this software may be distributed under the terms of the -- * GNU General Public License ("GPL") version 2 as published by the Free -- * Software Foundation. -- * -- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -- * POSSIBILITY OF SUCH DAMAGE. -+ * Copyright (C) 2016-2018 Mellanox Technologies -+ * Copyright (C) 2016-2018 Vadim Pasternak - */ - - #include -@@ -49,12 +24,19 @@ - #define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 - #define MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET 0x00 - #define MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET 0x01 -+#define MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET 0x02 -+#define MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET 0x03 - #define MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET 0x1d -+#define MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET 0x1e -+#define MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET 0x1f - #define MLXPLAT_CPLD_LPC_REG_LED1_OFFSET 0x20 - #define MLXPLAT_CPLD_LPC_REG_LED2_OFFSET 0x21 - #define MLXPLAT_CPLD_LPC_REG_LED3_OFFSET 0x22 - #define MLXPLAT_CPLD_LPC_REG_LED4_OFFSET 0x23 - #define MLXPLAT_CPLD_LPC_REG_LED5_OFFSET 0x24 -+#define MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION 0x2a -+#define MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET 0x2b -+#define MLXPLAT_CPLD_LPC_REG_GP0_OFFSET 0x2e - #define MLXPLAT_CPLD_LPC_REG_GP1_OFFSET 0x30 - #define MLXPLAT_CPLD_LPC_REG_WP1_OFFSET 0x31 - #define MLXPLAT_CPLD_LPC_REG_GP2_OFFSET 0x32 -@@ -64,6 +46,10 @@ - #define MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET 0x3b - #define MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET 0x40 - #define MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET 0x41 -+#define MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET 0x42 -+#define MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET 0x43 -+#define MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET 0x44 -+#define MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET 0x45 - #define MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET 0x50 - #define MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET 0x51 - #define MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET 0x52 -@@ -76,6 +62,17 @@ - #define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88 - #define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89 - #define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a -+#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET 0xc7 -+#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET 0xc8 -+#define MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET 0xc9 -+#define MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET 0xcb -+#define MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET 0xcd -+#define MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET 0xce -+#define MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET 0xcf -+#define MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET 0xd1 -+#define MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET 0xd2 -+#define MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET 0xd3 -+#define MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET 0xe2 - #define MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET 0xe3 - #define MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET 0xe4 - #define MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET 0xe5 -@@ -89,9 +86,17 @@ - #define MLXPLAT_CPLD_LPC_REG_TACHO10_OFFSET 0xee - #define MLXPLAT_CPLD_LPC_REG_TACHO11_OFFSET 0xef - #define MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET 0xf0 -+#define MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET 0xf5 -+#define MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET 0xf6 -+#define MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET 0xf7 -+#define MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET 0xf8 -+#define MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET 0xf9 -+#define MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET 0xfb -+#define MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET 0xfc - #define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 - #define MLXPLAT_CPLD_LPC_I2C_CH1_OFF 0xdb - #define MLXPLAT_CPLD_LPC_I2C_CH2_OFF 0xda -+#define MLXPLAT_CPLD_LPC_I2C_CH3_OFF 0xdc - - #define MLXPLAT_CPLD_LPC_PIO_OFFSET 0x10000UL - #define MLXPLAT_CPLD_LPC_REG1 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ -@@ -100,6 +105,9 @@ - #define MLXPLAT_CPLD_LPC_REG2 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ - MLXPLAT_CPLD_LPC_I2C_CH2_OFF) | \ - MLXPLAT_CPLD_LPC_PIO_OFFSET) -+#define MLXPLAT_CPLD_LPC_REG3 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ -+ MLXPLAT_CPLD_LPC_I2C_CH3_OFF) | \ -+ MLXPLAT_CPLD_LPC_PIO_OFFSET) - - /* Masks for aggregation, psu, pwr and fan event in CPLD related registers. */ - #define MLXPLAT_CPLD_AGGR_ASIC_MASK_DEF 0x04 -@@ -111,20 +119,34 @@ - MLXPLAT_CPLD_AGGR_FAN_MASK_DEF) - #define MLXPLAT_CPLD_AGGR_ASIC_MASK_NG 0x01 - #define MLXPLAT_CPLD_AGGR_MASK_NG_DEF 0x04 -+#define MLXPLAT_CPLD_AGGR_MASK_COMEX BIT(0) - #define MLXPLAT_CPLD_LOW_AGGR_MASK_LOW 0xc1 -+#define MLXPLAT_CPLD_LOW_AGGR_MASK_I2C BIT(6) - #define MLXPLAT_CPLD_PSU_MASK GENMASK(1, 0) - #define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0) -+#define MLXPLAT_CPLD_PSU_EXT_MASK GENMASK(3, 0) -+#define MLXPLAT_CPLD_PWR_EXT_MASK GENMASK(3, 0) - #define MLXPLAT_CPLD_FAN_MASK GENMASK(3, 0) - #define MLXPLAT_CPLD_ASIC_MASK GENMASK(1, 0) - #define MLXPLAT_CPLD_FAN_NG_MASK GENMASK(5, 0) - #define MLXPLAT_CPLD_LED_LO_NIBBLE_MASK GENMASK(7, 4) - #define MLXPLAT_CPLD_LED_HI_NIBBLE_MASK GENMASK(3, 0) -+#define MLXPLAT_CPLD_VOLTREG_UPD_MASK GENMASK(5, 4) -+#define MLXPLAT_CPLD_I2C_CAP_BIT 0x04 -+#define MLXPLAT_CPLD_I2C_CAP_MASK GENMASK(5, MLXPLAT_CPLD_I2C_CAP_BIT) -+ -+/* Masks for aggregation for comex carriers */ -+#define MLXPLAT_CPLD_AGGR_MASK_CARRIER BIT(1) -+#define MLXPLAT_CPLD_AGGR_MASK_CARR_DEF (MLXPLAT_CPLD_AGGR_ASIC_MASK_DEF | \ -+ MLXPLAT_CPLD_AGGR_MASK_CARRIER) -+#define MLXPLAT_CPLD_LOW_AGGRCX_MASK 0xc1 - - /* Default I2C parent bus number */ - #define MLXPLAT_CPLD_PHYS_ADAPTER_DEF_NR 1 - - /* Maximum number of possible physical buses equipped on system */ - #define MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM 16 -+#define MLXPLAT_CPLD_MAX_PHYS_EXT_ADAPTER_NUM 24 - - /* Number of channels in group */ - #define MLXPLAT_CPLD_GRP_CHNL_NUM 8 -@@ -132,19 +154,33 @@ - /* Start channel numbers */ - #define MLXPLAT_CPLD_CH1 2 - #define MLXPLAT_CPLD_CH2 10 -+#define MLXPLAT_CPLD_CH3 18 - - /* Number of LPC attached MUX platform devices */ --#define MLXPLAT_CPLD_LPC_MUX_DEVS 2 -+#define MLXPLAT_CPLD_LPC_MUX_DEVS 3 - - /* Hotplug devices adapter numbers */ - #define MLXPLAT_CPLD_NR_NONE -1 - #define MLXPLAT_CPLD_PSU_DEFAULT_NR 10 - #define MLXPLAT_CPLD_PSU_MSNXXXX_NR 4 -+#define MLXPLAT_CPLD_PSU_MSNXXXX_NR2 3 - #define MLXPLAT_CPLD_FAN1_DEFAULT_NR 11 - #define MLXPLAT_CPLD_FAN2_DEFAULT_NR 12 - #define MLXPLAT_CPLD_FAN3_DEFAULT_NR 13 - #define MLXPLAT_CPLD_FAN4_DEFAULT_NR 14 - -+/* Masks and default values for watchdogs */ -+#define MLXPLAT_CPLD_WD1_CLEAR_MASK GENMASK(7, 1) -+#define MLXPLAT_CPLD_WD2_CLEAR_MASK (GENMASK(7, 0) & ~BIT(1)) -+ -+#define MLXPLAT_CPLD_WD_TYPE1_TO_MASK GENMASK(7, 4) -+#define MLXPLAT_CPLD_WD_TYPE2_TO_MASK 0 -+#define MLXPLAT_CPLD_WD_RESET_ACT_MASK GENMASK(7, 1) -+#define MLXPLAT_CPLD_WD_FAN_ACT_MASK (GENMASK(7, 0) & ~BIT(4)) -+#define MLXPLAT_CPLD_WD_COUNT_ACT_MASK (GENMASK(7, 0) & ~BIT(7)) -+#define MLXPLAT_CPLD_WD_DFLT_TIMEOUT 30 -+#define MLXPLAT_CPLD_WD_MAX_DEVS 2 -+ - /* mlxplat_priv - platform private data - * @pdev_i2c - i2c controller platform device - * @pdev_mux - array of mux platform devices -@@ -152,6 +188,8 @@ - * @pdev_led - led platform devices - * @pdev_io_regs - register access platform devices - * @pdev_fan - FAN platform devices -+ * @pdev_wd - array of watchdog platform devices -+ * @regmap: device register map - */ - struct mlxplat_priv { - struct platform_device *pdev_i2c; -@@ -160,6 +198,8 @@ struct mlxplat_priv { - struct platform_device *pdev_led; - struct platform_device *pdev_io_regs; - struct platform_device *pdev_fan; -+ struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS]; -+ void *regmap; - }; - - /* Regions for LPC I2C controller and LPC base register space */ -@@ -173,6 +213,30 @@ static const struct resource mlxplat_lpc_resources[] = { - IORESOURCE_IO), - }; - -+/* Platform i2c next generation systems data */ -+static struct mlxreg_core_data mlxplat_mlxcpld_i2c_ng_items_data[] = { -+ { -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, -+ .mask = MLXPLAT_CPLD_I2C_CAP_MASK, -+ .bit = MLXPLAT_CPLD_I2C_CAP_BIT, -+ }, -+}; -+ -+static struct mlxreg_core_item mlxplat_mlxcpld_i2c_ng_items[] = { -+ { -+ .data = mlxplat_mlxcpld_i2c_ng_items_data, -+ }, -+}; -+ -+/* Platform next generation systems i2c data */ -+static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_i2c_ng_data = { -+ .items = mlxplat_mlxcpld_i2c_ng_items, -+ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, -+ .mask = MLXPLAT_CPLD_AGGR_MASK_COMEX, -+ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET, -+ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_I2C, -+}; -+ - /* Platform default channels */ - static const int mlxplat_default_channels[][MLXPLAT_CPLD_GRP_CHNL_NUM] = { - { -@@ -191,7 +255,33 @@ static const int mlxplat_default_channels[][MLXPLAT_CPLD_GRP_CHNL_NUM] = { - static const int mlxplat_msn21xx_channels[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; - - /* Platform mux data */ --static struct i2c_mux_reg_platform_data mlxplat_mux_data[] = { -+static struct i2c_mux_reg_platform_data mlxplat_default_mux_data[] = { -+ { -+ .parent = 1, -+ .base_nr = MLXPLAT_CPLD_CH1, -+ .write_only = 1, -+ .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG1, -+ .reg_size = 1, -+ .idle_in_use = 1, -+ }, -+ { -+ .parent = 1, -+ .base_nr = MLXPLAT_CPLD_CH2, -+ .write_only = 1, -+ .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG2, -+ .reg_size = 1, -+ .idle_in_use = 1, -+ }, -+ -+}; -+ -+/* Platform mux configuration variables */ -+static int mlxplat_max_adap_num; -+static int mlxplat_mux_num; -+static struct i2c_mux_reg_platform_data *mlxplat_mux_data; -+ -+/* Platform extended mux data */ -+static struct i2c_mux_reg_platform_data mlxplat_extended_mux_data[] = { - { - .parent = 1, - .base_nr = MLXPLAT_CPLD_CH1, -@@ -204,6 +294,14 @@ static struct i2c_mux_reg_platform_data mlxplat_mux_data[] = { - .parent = 1, - .base_nr = MLXPLAT_CPLD_CH2, - .write_only = 1, -+ .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG3, -+ .reg_size = 1, -+ .idle_in_use = 1, -+ }, -+ { -+ .parent = 1, -+ .base_nr = MLXPLAT_CPLD_CH3, -+ .write_only = 1, - .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG2, - .reg_size = 1, - .idle_in_use = 1, -@@ -254,6 +352,22 @@ static struct i2c_board_info mlxplat_mlxcpld_fan[] = { - }, - }; - -+/* Platform hotplug comex carrier system family data */ -+static struct mlxreg_core_data mlxplat_mlxcpld_comex_psu_items_data[] = { -+ { -+ .label = "psu1", -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = BIT(0), -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, -+ }, -+ { -+ .label = "psu2", -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = BIT(1), -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, -+ }, -+}; -+ - /* Platform hotplug default data */ - static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { - { -@@ -368,6 +482,45 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = { - }, - }; - -+static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = { -+ { -+ .data = mlxplat_mlxcpld_comex_psu_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER, -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = MLXPLAT_CPLD_PSU_MASK, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_psu), -+ .inversed = 1, -+ .health = false, -+ }, -+ { -+ .data = mlxplat_mlxcpld_default_pwr_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER, -+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, -+ .mask = MLXPLAT_CPLD_PWR_MASK, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), -+ .inversed = 0, -+ .health = false, -+ }, -+ { -+ .data = mlxplat_mlxcpld_default_fan_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER, -+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, -+ .mask = MLXPLAT_CPLD_FAN_MASK, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_fan), -+ .inversed = 1, -+ .health = false, -+ }, -+ { -+ .data = mlxplat_mlxcpld_default_asic_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_ASIC_MASK_DEF, -+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, -+ .mask = MLXPLAT_CPLD_ASIC_MASK, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data), -+ .inversed = 0, -+ .health = true, -+ }, -+}; -+ - static - struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_data = { - .items = mlxplat_mlxcpld_default_items, -@@ -378,6 +531,16 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_data = { - .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, - }; - -+static -+struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_comex_data = { -+ .items = mlxplat_mlxcpld_comex_items, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_comex_items), -+ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, -+ .mask = MLXPLAT_CPLD_AGGR_MASK_CARR_DEF, -+ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET, -+ .mask_low = MLXPLAT_CPLD_LOW_AGGRCX_MASK, -+}; -+ - static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_pwr_items_data[] = { - { - .label = "pwr1", -@@ -606,36 +769,48 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_fan_items_data[] = { - .label = "fan1", - .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, - .mask = BIT(0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(0), - .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "fan2", - .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, - .mask = BIT(1), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(1), - .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "fan3", - .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, - .mask = BIT(2), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(2), - .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "fan4", - .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, - .mask = BIT(3), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(3), - .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "fan5", - .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, - .mask = BIT(4), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(4), - .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "fan6", - .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, - .mask = BIT(5), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(5), - .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - }; -@@ -684,7 +859,117 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_ng_data = { - .items = mlxplat_mlxcpld_default_ng_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), - .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, -- .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, -+ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, -+ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, -+ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, -+}; -+ -+/* Platform hotplug extended system family data */ -+static struct mlxreg_core_data mlxplat_mlxcpld_ext_psu_items_data[] = { -+ { -+ .label = "psu1", -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = BIT(0), -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, -+ }, -+ { -+ .label = "psu2", -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = BIT(1), -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, -+ }, -+ { -+ .label = "psu3", -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = BIT(2), -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, -+ }, -+ { -+ .label = "psu4", -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = BIT(3), -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, -+ }, -+}; -+ -+static struct mlxreg_core_data mlxplat_mlxcpld_ext_pwr_items_data[] = { -+ { -+ .label = "pwr1", -+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, -+ .mask = BIT(0), -+ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], -+ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, -+ }, -+ { -+ .label = "pwr2", -+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, -+ .mask = BIT(1), -+ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], -+ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, -+ }, -+ { -+ .label = "pwr3", -+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, -+ .mask = BIT(2), -+ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], -+ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR2, -+ }, -+ { -+ .label = "pwr4", -+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, -+ .mask = BIT(3), -+ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], -+ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR2, -+ }, -+}; -+ -+static struct mlxreg_core_item mlxplat_mlxcpld_ext_items[] = { -+ { -+ .data = mlxplat_mlxcpld_ext_psu_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, -+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, -+ .mask = MLXPLAT_CPLD_PSU_EXT_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_psu_items_data), -+ .inversed = 1, -+ .health = false, -+ }, -+ { -+ .data = mlxplat_mlxcpld_ext_pwr_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, -+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, -+ .mask = MLXPLAT_CPLD_PWR_EXT_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_pwr_items_data), -+ .inversed = 0, -+ .health = false, -+ }, -+ { -+ .data = mlxplat_mlxcpld_default_ng_fan_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, -+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, -+ .mask = MLXPLAT_CPLD_FAN_NG_MASK, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data), -+ .inversed = 1, -+ .health = false, -+ }, -+ { -+ .data = mlxplat_mlxcpld_default_asic_items_data, -+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, -+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, -+ .mask = MLXPLAT_CPLD_ASIC_MASK, -+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data), -+ .inversed = 0, -+ .health = true, -+ }, -+}; -+ -+static -+struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { -+ .items = mlxplat_mlxcpld_ext_items, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_ext_items), -+ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, -+ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, - .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, - .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, - }; -@@ -838,61 +1123,90 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_led_data[] = { - .label = "fan1:green", - .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, - .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(0), - }, - { - .label = "fan1:orange", - .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, - .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(0), - }, - { - .label = "fan2:green", - .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, - .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(1), - }, - { - .label = "fan2:orange", - .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, - .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(1), - }, - { - .label = "fan3:green", - .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, - .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(2), - }, - { - .label = "fan3:orange", - .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, - .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(2), - }, - { - .label = "fan4:green", - .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, - .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(3), - }, - { - .label = "fan4:orange", - .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, - .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(3), - }, - { - .label = "fan5:green", - .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, - .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(4), - }, - { - .label = "fan5:orange", - .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, - .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(4), - }, - { - .label = "fan6:green", - .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, - .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(5), - }, - { - .label = "fan6:orange", - .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, - .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, -+ .bit = BIT(5), -+ }, -+ { -+ .label = "uid:blue", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, - }, - }; - -@@ -901,64 +1215,138 @@ static struct mlxreg_core_platform_data mlxplat_default_ng_led_data = { - .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_led_data), - }; - --/* Platform register access default */ --static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { -+/* Platform led for Comex based 100GbE systems */ -+static struct mlxreg_core_data mlxplat_mlxcpld_comex_100G_led_data[] = { - { -- .label = "cpld1_version", -- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, -- .bit = GENMASK(7, 0), -- .mode = 0444, -+ .label = "status:green", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, - }, - { -- .label = "cpld2_version", -- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, -- .bit = GENMASK(7, 0), -- .mode = 0444, -+ .label = "status:red", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK - }, - { -- .label = "reset_long_pb", -- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -- .mask = GENMASK(7, 0) & ~BIT(0), -- .mode = 0444, -+ .label = "psu:green", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, - }, - { -- .label = "reset_short_pb", -- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -- .mask = GENMASK(7, 0) & ~BIT(1), -- .mode = 0444, -+ .label = "psu:red", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, - }, - { -- .label = "reset_aux_pwr_or_ref", -- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -- .mask = GENMASK(7, 0) & ~BIT(2), -- .mode = 0444, -+ .label = "fan1:green", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, - }, - { -- .label = "reset_main_pwr_fail", -- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -- .mask = GENMASK(7, 0) & ~BIT(3), -- .mode = 0444, -+ .label = "fan1:red", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, - }, - { -- .label = "reset_sw_reset", -- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -- .mask = GENMASK(7, 0) & ~BIT(4), -- .mode = 0444, -+ .label = "fan2:green", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, - }, - { -- .label = "reset_fw_reset", -- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -- .mask = GENMASK(7, 0) & ~BIT(5), -- .mode = 0444, -+ .label = "fan2:red", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, - }, - { -- .label = "reset_hotswap_or_wd", -- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -- .mask = GENMASK(7, 0) & ~BIT(6), -- .mode = 0444, -+ .label = "fan3:green", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, - }, - { -- .label = "reset_asic_thermal", -+ .label = "fan3:red", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ }, -+ { -+ .label = "fan4:green", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ }, -+ { -+ .label = "fan4:red", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, -+ }, -+ { -+ .label = "uid:blue", -+ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, -+ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, -+ }, -+}; -+ -+static struct mlxreg_core_platform_data mlxplat_comex_100G_led_data = { -+ .data = mlxplat_mlxcpld_comex_100G_led_data, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_comex_100G_led_data), -+}; -+ -+/* Platform register access default */ -+static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { -+ { -+ .label = "cpld1_version", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld2_version", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_long_pb", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_short_pb", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_aux_pwr_or_ref", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_main_pwr_fail", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_sw_reset", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_fw_reset", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_hotswap_or_wd", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_asic_thermal", - .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, - .mask = GENMASK(7, 0) & ~BIT(7), - .mode = 0444, -@@ -1064,6 +1452,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = { - .mode = 0444, - }, - { -+ .label = "reset_sff_wd", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, -+ }, -+ { - .label = "psu1_on", - .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, - .mask = GENMASK(7, 0) & ~BIT(0), -@@ -1088,6 +1482,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = { - .mode = 0200, - }, - { -+ .label = "select_iio", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0644, -+ }, -+ { - .label = "asic_health", - .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, - .mask = MLXPLAT_CPLD_ASIC_MASK, -@@ -1101,6 +1501,221 @@ static struct mlxreg_core_platform_data mlxplat_msn21xx_regs_io_data = { - .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_regs_io_data), - }; - -+/* Platform register access for next generation systems families data */ -+static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { -+ { -+ .label = "cpld1_version", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld2_version", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld3_version", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld4_version", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_long_pb", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_short_pb", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_aux_pwr_or_ref", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_from_comex", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_from_asic", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_swb_wd", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_asic_thermal", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_comex_pwr_fail", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_platform", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_soc", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_comex_wd", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_voltmon_upgrade_fail", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_system", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_sw_pwr_off", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_comex_thermal", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_reload_bios", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, -+ }, -+ { -+ .label = "reset_ac_pwr_fail", -+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, -+ }, -+ { -+ .label = "psu1_on", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0200, -+ }, -+ { -+ .label = "psu2_on", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0200, -+ }, -+ { -+ .label = "pwr_cycle", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), -+ .mode = 0200, -+ }, -+ { -+ .label = "pwr_down", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0200, -+ }, -+ { -+ .label = "jtag_enable", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0644, -+ }, -+ { -+ .label = "asic_health", -+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, -+ .mask = MLXPLAT_CPLD_ASIC_MASK, -+ .bit = 1, -+ .mode = 0444, -+ }, -+ { -+ .label = "fan_dir", -+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "voltreg_update_status", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET, -+ .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK, -+ .bit = 5, -+ .mode = 0444, -+ }, -+ { -+ .label = "vpd_wp", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0644, -+ }, -+ { -+ .label = "pcie_asic_reset_dis", -+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0644, -+ }, -+ { -+ .label = "config1", -+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "config2", -+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "ufm_version", -+ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+}; -+ -+static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = { -+ .data = mlxplat_mlxcpld_default_ng_regs_io_data, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data), -+}; -+ - /* Platform FAN default */ - static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_data[] = { - { -@@ -1111,61 +1726,89 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_data[] = { - .label = "tacho1", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(0), - }, - { - .label = "tacho2", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(1), - }, - { - .label = "tacho3", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO3_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(2), - }, - { - .label = "tacho4", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO4_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(3), - }, - { - .label = "tacho5", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO5_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(4), - }, - { - .label = "tacho6", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO6_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(5), - }, - { - .label = "tacho7", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO7_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(6), - }, - { - .label = "tacho8", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO8_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, -+ .bit = BIT(7), - }, - { - .label = "tacho9", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO9_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET, -+ .bit = BIT(0), - }, - { - .label = "tacho10", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO10_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET, -+ .bit = BIT(1), - }, - { - .label = "tacho11", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO11_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET, -+ .bit = BIT(2), - }, - { - .label = "tacho12", - .reg = MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET, - .mask = GENMASK(7, 0), -+ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET, -+ .bit = BIT(3), -+ }, -+ { -+ .label = "conf", -+ .capability = MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET, - }, - }; - -@@ -1174,6 +1817,148 @@ static struct mlxreg_core_platform_data mlxplat_default_fan_data = { - .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_data), - }; - -+/* Watchdog type1: hardware implementation version1 -+ * (MSN2700, MSN2410, MSN2740, MSN2100 and MSN2140 systems). -+ */ -+static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type1[] = { -+ { -+ .label = "action", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, -+ .bit = 0, -+ }, -+ { -+ .label = "timeout", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK, -+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, -+ }, -+ { -+ .label = "ping", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD1_CLEAR_MASK, -+ .bit = 0, -+ }, -+ { -+ .label = "reset", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .bit = 6, -+ }, -+}; -+ -+static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type1[] = { -+ { -+ .label = "action", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, -+ .bit = 4, -+ }, -+ { -+ .label = "timeout", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK, -+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, -+ }, -+ { -+ .label = "ping", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD1_CLEAR_MASK, -+ .bit = 1, -+ }, -+}; -+ -+static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type1[] = { -+ { -+ .data = mlxplat_mlxcpld_wd_main_regs_type1, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type1), -+ .version = MLX_WDT_TYPE1, -+ .identity = "mlx-wdt-main", -+ }, -+ { -+ .data = mlxplat_mlxcpld_wd_aux_regs_type1, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type1), -+ .version = MLX_WDT_TYPE1, -+ .identity = "mlx-wdt-aux", -+ }, -+}; -+ -+/* Watchdog type2: hardware implementation version 2 -+ * (all systems except (MSN2700, MSN2410, MSN2740, MSN2100 and MSN2140). -+ */ -+static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type2[] = { -+ { -+ .label = "action", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, -+ .bit = 0, -+ }, -+ { -+ .label = "timeout", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, -+ }, -+ { -+ .label = "timeleft", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ }, -+ { -+ .label = "ping", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, -+ .bit = 0, -+ }, -+ { -+ .label = "reset", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .bit = 6, -+ }, -+}; -+ -+static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type2[] = { -+ { -+ .label = "action", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, -+ .bit = 4, -+ }, -+ { -+ .label = "timeout", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, -+ }, -+ { -+ .label = "timeleft", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ }, -+ { -+ .label = "ping", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, -+ .bit = 4, -+ }, -+}; -+ -+static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type2[] = { -+ { -+ .data = mlxplat_mlxcpld_wd_main_regs_type2, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type2), -+ .version = MLX_WDT_TYPE2, -+ .identity = "mlx-wdt-main", -+ }, -+ { -+ .data = mlxplat_mlxcpld_wd_aux_regs_type2, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type2), -+ .version = MLX_WDT_TYPE2, -+ .identity = "mlx-wdt-aux", -+ }, -+}; -+ - static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) - { - switch (reg) { -@@ -1182,12 +1967,15 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_LED3_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED4_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: - case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WP1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: -@@ -1196,6 +1984,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: - return true; -@@ -1208,12 +2004,19 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) - switch (reg) { - case MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED2_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED3_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED4_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION: -+ case MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: - case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WP1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: -@@ -1222,6 +2025,10 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: -@@ -1234,6 +2041,16 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: - case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET: -@@ -1248,6 +2065,14 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_TACHO11_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET: - return true; - } - return false; -@@ -1258,18 +2083,29 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) - switch (reg) { - case MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED2_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED3_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED4_OFFSET: - case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION: -+ case MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: - case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET: - case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: -@@ -1282,6 +2118,10 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: - case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET: -@@ -1296,6 +2136,14 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_TACHO11_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET: - return true; - } - return false; -@@ -1305,6 +2153,25 @@ static const struct reg_default mlxplat_mlxcpld_regmap_default[] = { - { MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, 0x00 }, - { MLXPLAT_CPLD_LPC_REG_WP2_OFFSET, 0x00 }, - { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, -+ { MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 }, -+}; -+ -+static const struct reg_default mlxplat_mlxcpld_regmap_ng[] = { -+ { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, -+ { MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 }, -+}; -+ -+static const struct reg_default mlxplat_mlxcpld_regmap_comex_default[] = { -+ { MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET, -+ MLXPLAT_CPLD_LOW_AGGRCX_MASK }, -+ { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, -+}; -+ -+static const struct reg_default mlxplat_mlxcpld_regmap_ng400[] = { -+ { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, -+ { MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, 0x00 }, -+ { MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, 0x00 }, -+ { MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, 0x00 }, - }; - - struct mlxplat_mlxcpld_regmap_context { -@@ -1345,21 +2212,70 @@ static const struct regmap_config mlxplat_mlxcpld_regmap_config = { - .reg_write = mlxplat_mlxcpld_reg_write, - }; - -+static const struct regmap_config mlxplat_mlxcpld_regmap_config_ng = { -+ .reg_bits = 8, -+ .val_bits = 8, -+ .max_register = 255, -+ .cache_type = REGCACHE_FLAT, -+ .writeable_reg = mlxplat_mlxcpld_writeable_reg, -+ .readable_reg = mlxplat_mlxcpld_readable_reg, -+ .volatile_reg = mlxplat_mlxcpld_volatile_reg, -+ .reg_defaults = mlxplat_mlxcpld_regmap_ng, -+ .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_ng), -+ .reg_read = mlxplat_mlxcpld_reg_read, -+ .reg_write = mlxplat_mlxcpld_reg_write, -+}; -+ -+static const struct regmap_config mlxplat_mlxcpld_regmap_config_comex = { -+ .reg_bits = 8, -+ .val_bits = 8, -+ .max_register = 255, -+ .cache_type = REGCACHE_FLAT, -+ .writeable_reg = mlxplat_mlxcpld_writeable_reg, -+ .readable_reg = mlxplat_mlxcpld_readable_reg, -+ .volatile_reg = mlxplat_mlxcpld_volatile_reg, -+ .reg_defaults = mlxplat_mlxcpld_regmap_comex_default, -+ .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_comex_default), -+ .reg_read = mlxplat_mlxcpld_reg_read, -+ .reg_write = mlxplat_mlxcpld_reg_write, -+}; -+ -+static const struct regmap_config mlxplat_mlxcpld_regmap_config_ng400 = { -+ .reg_bits = 8, -+ .val_bits = 8, -+ .max_register = 255, -+ .cache_type = REGCACHE_FLAT, -+ .writeable_reg = mlxplat_mlxcpld_writeable_reg, -+ .readable_reg = mlxplat_mlxcpld_readable_reg, -+ .volatile_reg = mlxplat_mlxcpld_volatile_reg, -+ .reg_defaults = mlxplat_mlxcpld_regmap_ng400, -+ .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_ng400), -+ .reg_read = mlxplat_mlxcpld_reg_read, -+ .reg_write = mlxplat_mlxcpld_reg_write, -+}; -+ - static struct resource mlxplat_mlxcpld_resources[] = { - [0] = DEFINE_RES_IRQ_NAMED(17, "mlxreg-hotplug"), - }; - - static struct platform_device *mlxplat_dev; -+static struct mlxreg_core_hotplug_platform_data *mlxplat_i2c; - static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug; - static struct mlxreg_core_platform_data *mlxplat_led; - static struct mlxreg_core_platform_data *mlxplat_regs_io; - static struct mlxreg_core_platform_data *mlxplat_fan; -+static struct mlxreg_core_platform_data -+ *mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS]; -+static const struct regmap_config *mlxplat_regmap_config; - - static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) - { - int i; - -- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { -+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; -+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); -+ mlxplat_mux_data = mlxplat_default_mux_data; -+ for (i = 0; i < mlxplat_mux_num; i++) { - mlxplat_mux_data[i].values = mlxplat_default_channels[i]; - mlxplat_mux_data[i].n_values = - ARRAY_SIZE(mlxplat_default_channels[i]); -@@ -1369,15 +2285,19 @@ static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) - mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; - mlxplat_led = &mlxplat_default_led_data; - mlxplat_regs_io = &mlxplat_default_regs_io_data; -+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; - - return 1; --}; -+} - - static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi) - { - int i; - -- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { -+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; -+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); -+ mlxplat_mux_data = mlxplat_default_mux_data; -+ for (i = 0; i < mlxplat_mux_num; i++) { - mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; - mlxplat_mux_data[i].n_values = - ARRAY_SIZE(mlxplat_msn21xx_channels); -@@ -1387,15 +2307,19 @@ static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi) - mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; - mlxplat_led = &mlxplat_msn21xx_led_data; - mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data; -+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; - - return 1; --}; -+} - - static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi) - { - int i; - -- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { -+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; -+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); -+ mlxplat_mux_data = mlxplat_default_mux_data; -+ for (i = 0; i < mlxplat_mux_num; i++) { - mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; - mlxplat_mux_data[i].n_values = - ARRAY_SIZE(mlxplat_msn21xx_channels); -@@ -1405,15 +2329,19 @@ static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi) - mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; - mlxplat_led = &mlxplat_default_led_data; - mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data; -+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; - - return 1; --}; -+} - - static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi) - { - int i; - -- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { -+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; -+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); -+ mlxplat_mux_data = mlxplat_default_mux_data; -+ for (i = 0; i < mlxplat_mux_num; i++) { - mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; - mlxplat_mux_data[i].n_values = - ARRAY_SIZE(mlxplat_msn21xx_channels); -@@ -1423,15 +2351,19 @@ static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi) - mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; - mlxplat_led = &mlxplat_msn21xx_led_data; - mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data; -+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; - - return 1; --}; -+} - - static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) - { - int i; - -- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { -+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; -+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); -+ mlxplat_mux_data = mlxplat_default_mux_data; -+ for (i = 0; i < mlxplat_mux_num; i++) { - mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; - mlxplat_mux_data[i].n_values = - ARRAY_SIZE(mlxplat_msn21xx_channels); -@@ -1440,13 +2372,116 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) - mlxplat_hotplug->deferred_nr = - mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; - mlxplat_led = &mlxplat_default_ng_led_data; -+ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; - mlxplat_fan = &mlxplat_default_fan_data; -+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) -+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; -+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; -+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng; - - return 1; --}; -+} -+ -+static int __init mlxplat_dmi_comex_matched(const struct dmi_system_id *dmi) -+{ -+ int i; -+ -+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_EXT_ADAPTER_NUM; -+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_extended_mux_data); -+ mlxplat_mux_data = mlxplat_extended_mux_data; -+ for (i = 0; i < mlxplat_mux_num; i++) { -+ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; -+ mlxplat_mux_data[i].n_values = -+ ARRAY_SIZE(mlxplat_msn21xx_channels); -+ } -+ mlxplat_hotplug = &mlxplat_mlxcpld_comex_data; -+ mlxplat_hotplug->deferred_nr = MLXPLAT_CPLD_MAX_PHYS_EXT_ADAPTER_NUM; -+ mlxplat_led = &mlxplat_comex_100G_led_data; -+ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; -+ mlxplat_fan = &mlxplat_default_fan_data; -+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) -+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; -+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_comex; -+ -+ return 1; -+} -+ -+static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi) -+{ -+ int i; -+ -+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; -+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); -+ mlxplat_mux_data = mlxplat_default_mux_data; -+ for (i = 0; i < mlxplat_mux_num; i++) { -+ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; -+ mlxplat_mux_data[i].n_values = -+ ARRAY_SIZE(mlxplat_msn21xx_channels); -+ } -+ mlxplat_hotplug = &mlxplat_mlxcpld_ext_data; -+ mlxplat_hotplug->deferred_nr = -+ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; -+ mlxplat_led = &mlxplat_default_ng_led_data; -+ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; -+ mlxplat_fan = &mlxplat_default_fan_data; -+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) -+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; -+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; -+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; -+ -+ return 1; -+} - - static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { - { -+ .callback = mlxplat_dmi_default_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"), -+ }, -+ }, -+ { -+ .callback = mlxplat_dmi_msn21xx_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"), -+ }, -+ }, -+ { -+ .callback = mlxplat_dmi_msn274x_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"), -+ }, -+ }, -+ { -+ .callback = mlxplat_dmi_msn201x_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"), -+ }, -+ }, -+ { -+ .callback = mlxplat_dmi_qmb7xx_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"), -+ }, -+ }, -+ { -+ .callback = mlxplat_dmi_qmb7xx_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0007"), -+ }, -+ }, -+ { -+ .callback = mlxplat_dmi_comex_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0009"), -+ }, -+ }, -+ { -+ .callback = mlxplat_dmi_ng400_matched, -+ .matches = { -+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"), -+ }, -+ }, -+ { - .callback = mlxplat_dmi_msn274x_matched, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -@@ -1499,51 +2534,28 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -- DMI_MATCH(DMI_PRODUCT_NAME, "QMB7"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MQM87"), - }, - }, - { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -- DMI_MATCH(DMI_PRODUCT_NAME, "SN37"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MSN37"), - }, - }, - { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -- DMI_MATCH(DMI_PRODUCT_NAME, "SN34"), -- }, -- }, -- { -- .callback = mlxplat_dmi_default_matched, -- .matches = { -- DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"), -- }, -- }, -- { -- .callback = mlxplat_dmi_msn21xx_matched, -- .matches = { -- DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"), -- }, -- }, -- { -- .callback = mlxplat_dmi_msn274x_matched, -- .matches = { -- DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"), -- }, -- }, -- { -- .callback = mlxplat_dmi_msn201x_matched, -- .matches = { -- DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MSN34"), - }, - }, - { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { -- DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"), -+ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "MSN38"), - }, - }, - { } -@@ -1559,7 +2571,7 @@ static int mlxplat_mlxcpld_verify_bus_topology(int *nr) - /* Scan adapters from expected id to verify it is free. */ - *nr = MLXPLAT_CPLD_PHYS_ADAPTER_DEF_NR; - for (i = MLXPLAT_CPLD_PHYS_ADAPTER_DEF_NR; i < -- MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; i++) { -+ mlxplat_max_adap_num; i++) { - search_adap = i2c_get_adapter(i); - if (search_adap) { - i2c_put_adapter(search_adap); -@@ -1573,12 +2585,12 @@ static int mlxplat_mlxcpld_verify_bus_topology(int *nr) - } - - /* Return with error if free id for adapter is not found. */ -- if (i == MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM) -+ if (i == mlxplat_max_adap_num) - return -ENODEV; - - /* Shift adapter ids, since expected parent adapter is not free. */ - *nr = i; -- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { -+ for (i = 0; i < mlxplat_mux_num; i++) { - shift = *nr - mlxplat_mux_data[i].parent; - mlxplat_mux_data[i].parent = *nr; - mlxplat_mux_data[i].base_nr += shift; -@@ -1612,19 +2624,42 @@ static int __init mlxplat_init(void) - } - platform_set_drvdata(mlxplat_dev, priv); - -+ mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev, -+ mlxplat_lpc_resources[1].start, 1); -+ if (!mlxplat_mlxcpld_regmap_ctx.base) { -+ err = -ENOMEM; -+ goto fail_alloc; -+ } -+ -+ if (!mlxplat_regmap_config) -+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config; -+ -+ priv->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL, -+ &mlxplat_mlxcpld_regmap_ctx, -+ mlxplat_regmap_config); -+ if (IS_ERR(priv->regmap)) { -+ err = PTR_ERR(priv->regmap); -+ goto fail_alloc; -+ } -+ - err = mlxplat_mlxcpld_verify_bus_topology(&nr); - if (nr < 0) - goto fail_alloc; - -- nr = (nr == MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM) ? -1 : nr; -- priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", nr, -- NULL, 0); -+ nr = (nr == mlxplat_max_adap_num) ? -1 : nr; -+ if (mlxplat_i2c) -+ mlxplat_i2c->regmap = priv->regmap; -+ priv->pdev_i2c = platform_device_register_resndata( -+ &mlxplat_dev->dev, "i2c_mlxcpld", -+ nr, mlxplat_mlxcpld_resources, -+ ARRAY_SIZE(mlxplat_mlxcpld_resources), -+ mlxplat_i2c, sizeof(*mlxplat_i2c)); - if (IS_ERR(priv->pdev_i2c)) { - err = PTR_ERR(priv->pdev_i2c); - goto fail_alloc; - } - -- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { -+ for (i = 0; i < mlxplat_mux_num; i++) { - priv->pdev_mux[i] = platform_device_register_resndata( - &priv->pdev_i2c->dev, - "i2c-mux-reg", i, NULL, -@@ -1636,21 +2671,8 @@ static int __init mlxplat_init(void) - } - } - -- mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev, -- mlxplat_lpc_resources[1].start, 1); -- if (!mlxplat_mlxcpld_regmap_ctx.base) { -- err = -ENOMEM; -- goto fail_platform_mux_register; -- } -- -- mlxplat_hotplug->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL, -- &mlxplat_mlxcpld_regmap_ctx, -- &mlxplat_mlxcpld_regmap_config); -- if (IS_ERR(mlxplat_hotplug->regmap)) { -- err = PTR_ERR(mlxplat_hotplug->regmap); -- goto fail_platform_mux_register; -- } -- -+ /* Add hotplug driver */ -+ mlxplat_hotplug->regmap = priv->regmap; - priv->pdev_hotplug = platform_device_register_resndata( - &mlxplat_dev->dev, "mlxreg-hotplug", - PLATFORM_DEVID_NONE, -@@ -1663,16 +2685,16 @@ static int __init mlxplat_init(void) - } - - /* Set default registers. */ -- for (j = 0; j < mlxplat_mlxcpld_regmap_config.num_reg_defaults; j++) { -- err = regmap_write(mlxplat_hotplug->regmap, -- mlxplat_mlxcpld_regmap_default[j].reg, -- mlxplat_mlxcpld_regmap_default[j].def); -+ for (j = 0; j < mlxplat_regmap_config->num_reg_defaults; j++) { -+ err = regmap_write(priv->regmap, -+ mlxplat_regmap_config->reg_defaults[j].reg, -+ mlxplat_regmap_config->reg_defaults[j].def); - if (err) - goto fail_platform_mux_register; - } - - /* Add LED driver. */ -- mlxplat_led->regmap = mlxplat_hotplug->regmap; -+ mlxplat_led->regmap = priv->regmap; - priv->pdev_led = platform_device_register_resndata( - &mlxplat_dev->dev, "leds-mlxreg", - PLATFORM_DEVID_NONE, NULL, 0, -@@ -1684,7 +2706,7 @@ static int __init mlxplat_init(void) - - /* Add registers io access driver. */ - if (mlxplat_regs_io) { -- mlxplat_regs_io->regmap = mlxplat_hotplug->regmap; -+ mlxplat_regs_io->regmap = priv->regmap; - priv->pdev_io_regs = platform_device_register_resndata( - &mlxplat_dev->dev, "mlxreg-io", - PLATFORM_DEVID_NONE, NULL, 0, -@@ -1698,7 +2720,7 @@ static int __init mlxplat_init(void) - - /* Add FAN driver. */ - if (mlxplat_fan) { -- mlxplat_fan->regmap = mlxplat_hotplug->regmap; -+ mlxplat_fan->regmap = priv->regmap; - priv->pdev_fan = platform_device_register_resndata( - &mlxplat_dev->dev, "mlxreg-fan", - PLATFORM_DEVID_NONE, NULL, 0, -@@ -1710,15 +2732,33 @@ static int __init mlxplat_init(void) - } - } - -+ /* Add WD drivers. */ -+ for (j = 0; j < MLXPLAT_CPLD_WD_MAX_DEVS; j++) { -+ if (mlxplat_wd_data[j]) { -+ mlxplat_wd_data[j]->regmap = priv->regmap; -+ priv->pdev_wd[j] = platform_device_register_resndata( -+ &mlxplat_dev->dev, "mlx-wdt", -+ j, NULL, 0, -+ mlxplat_wd_data[j], -+ sizeof(*mlxplat_wd_data[j])); -+ if (IS_ERR(priv->pdev_wd[j])) { -+ err = PTR_ERR(priv->pdev_wd[j]); -+ goto fail_platform_wd_register; -+ } -+ } -+ } -+ - /* Sync registers with hardware. */ -- regcache_mark_dirty(mlxplat_hotplug->regmap); -- err = regcache_sync(mlxplat_hotplug->regmap); -+ regcache_mark_dirty(priv->regmap); -+ err = regcache_sync(priv->regmap); - if (err) -- goto fail_platform_fan_register; -+ goto fail_platform_wd_register; - - return 0; - --fail_platform_fan_register: -+fail_platform_wd_register: -+ while (--j >= 0) -+ platform_device_unregister(priv->pdev_wd[j]); - if (mlxplat_fan) - platform_device_unregister(priv->pdev_fan); - fail_platform_io_regs_register: -@@ -1744,6 +2784,8 @@ static void __exit mlxplat_exit(void) - struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); - int i; - -+ for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0 ; i--) -+ platform_device_unregister(priv->pdev_wd[i]); - if (priv->pdev_fan) - platform_device_unregister(priv->pdev_fan); - if (priv->pdev_io_regs) -@@ -1751,7 +2793,7 @@ static void __exit mlxplat_exit(void) - platform_device_unregister(priv->pdev_led); - platform_device_unregister(priv->pdev_hotplug); - -- for (i = ARRAY_SIZE(mlxplat_mux_data) - 1; i >= 0 ; i--) -+ for (i = mlxplat_mux_num - 1; i >= 0 ; i--) - platform_device_unregister(priv->pdev_mux[i]); - - platform_device_unregister(priv->pdev_i2c); -diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig -index 709d4de..da31a4b 100644 ---- a/drivers/watchdog/Kconfig -+++ b/drivers/watchdog/Kconfig -@@ -241,6 +241,22 @@ config RAVE_SP_WATCHDOG - help - Support for the watchdog on RAVE SP device. - -+config MLX_WDT -+ tristate "Mellanox Watchdog" -+ depends on MELLANOX_PLATFORM -+ select WATCHDOG_CORE -+ select REGMAP -+ help -+ This is the driver for the hardware watchdog on Mellanox systems. -+ If you are going to use it, say Y here, otherwise N. -+ This driver can be used together with the watchdog daemon. -+ It can also watch your kernel to make sure it doesn't freeze, -+ and if it does, it reboots your system after a certain amount of -+ time. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called mlx-wdt. -+ - # ALPHA Architecture - - # ARM Architecture -diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile -index bf92e7b..42709be 100644 ---- a/drivers/watchdog/Makefile -+++ b/drivers/watchdog/Makefile -@@ -139,6 +139,7 @@ obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o - obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o - obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o - obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o -+obj-$(CONFIG_MLX_WDT) += mlx_wdt.o - - # M68K Architecture - obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o -diff --git a/drivers/watchdog/mlx_wdt.c b/drivers/watchdog/mlx_wdt.c -new file mode 100644 -index 0000000..74b0622 ---- /dev/null -+++ b/drivers/watchdog/mlx_wdt.c -@@ -0,0 +1,289 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Mellanox watchdog driver -+ * -+ * Copyright (C) 2019 Mellanox Technologies -+ * Copyright (C) 2019 Michael Shych -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define MLXREG_WDT_CLOCK_SCALE 1000 -+#define MLXREG_WDT_MAX_TIMEOUT_TYPE1 32 -+#define MLXREG_WDT_MAX_TIMEOUT_TYPE2 255 -+#define MLXREG_WDT_MIN_TIMEOUT 1 -+#define MLXREG_WDT_OPTIONS_BASE (WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | \ -+ WDIOF_SETTIMEOUT) -+ -+/** -+ * struct mlxreg_wdt - wd private data: -+ * -+ * @wdd: watchdog device; -+ * @device: basic device; -+ * @pdata: data received from platform driver; -+ * @regmap: register map of parent device; -+ * @timeout: defined timeout in sec.; -+ * @action_idx: index for direct access to action register; -+ * @timeout_idx:index for direct access to TO register; -+ * @tleft_idx: index for direct access to time left register; -+ * @ping_idx: index for direct access to ping register; -+ * @reset_idx: index for direct access to reset cause register; -+ * @wd_type: watchdog HW type; -+ */ -+struct mlxreg_wdt { -+ struct watchdog_device wdd; -+ struct mlxreg_core_platform_data *pdata; -+ void *regmap; -+ int action_idx; -+ int timeout_idx; -+ int tleft_idx; -+ int ping_idx; -+ int reset_idx; -+ enum mlxreg_wdt_type wdt_type; -+}; -+ -+static void mlxreg_wdt_check_card_reset(struct mlxreg_wdt *wdt) -+{ -+ struct mlxreg_core_data *reg_data; -+ u32 regval; -+ int rc; -+ -+ if (wdt->reset_idx == -EINVAL) -+ return; -+ -+ if (!(wdt->wdd.info->options & WDIOF_CARDRESET)) -+ return; -+ -+ reg_data = &wdt->pdata->data[wdt->reset_idx]; -+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val); -+ if (!rc) { -+ if (regval & ~reg_data->mask) { -+ wdt->wdd.bootstatus = WDIOF_CARDRESET; -+ dev_info(wdt->wdd.parent, -+ "watchdog previously reset the CPU\n"); -+ } -+ } -+} -+ -+static int mlxreg_wdt_start(struct watchdog_device *wdd) -+{ -+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); -+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx]; -+ -+ return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask, -+ BIT(reg_data->bit)); -+} -+ -+static int mlxreg_wdt_stop(struct watchdog_device *wdd) -+{ -+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); -+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx]; -+ -+ return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask, -+ ~BIT(reg_data->bit)); -+} -+ -+static int mlxreg_wdt_ping(struct watchdog_device *wdd) -+{ -+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); -+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->ping_idx]; -+ -+ return regmap_update_bits_base(wdt->regmap, reg_data->reg, -+ ~reg_data->mask, BIT(reg_data->bit), -+ NULL, false, true); -+} -+ -+static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd, -+ unsigned int timeout) -+{ -+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); -+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx]; -+ u32 regval, set_time, hw_timeout; -+ int rc; -+ -+ if (wdt->wdt_type == MLX_WDT_TYPE1) { -+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val); -+ if (rc) -+ return rc; -+ -+ hw_timeout = order_base_2(timeout * MLXREG_WDT_CLOCK_SCALE); -+ regval = (regval & reg_data->mask) | hw_timeout; -+ /* Rowndown to actual closest number of sec. */ -+ set_time = BIT(hw_timeout) / MLXREG_WDT_CLOCK_SCALE; -+ } else { -+ set_time = timeout; -+ regval = timeout; -+ } -+ -+ wdd->timeout = set_time; -+ rc = regmap_write(wdt->regmap, reg_data->reg, regval); -+ -+ if (!rc) { -+ /* -+ * Restart watchdog with new timeout period -+ * if watchdog is already started. -+ */ -+ if (watchdog_active(wdd)) { -+ rc = mlxreg_wdt_stop(wdd); -+ if (!rc) -+ rc = mlxreg_wdt_start(wdd); -+ } -+ } -+ -+ return rc; -+} -+ -+static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd) -+{ -+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); -+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->tleft_idx]; -+ u32 regval; -+ int rc; -+ -+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val); -+ /* Return 0 timeleft in case of failure register read. */ -+ return rc == 0 ? regval : 0; -+} -+ -+static const struct watchdog_ops mlxreg_wdt_ops_type1 = { -+ .start = mlxreg_wdt_start, -+ .stop = mlxreg_wdt_stop, -+ .ping = mlxreg_wdt_ping, -+ .set_timeout = mlxreg_wdt_set_timeout, -+ .owner = THIS_MODULE, -+}; -+ -+static const struct watchdog_ops mlxreg_wdt_ops_type2 = { -+ .start = mlxreg_wdt_start, -+ .stop = mlxreg_wdt_stop, -+ .ping = mlxreg_wdt_ping, -+ .set_timeout = mlxreg_wdt_set_timeout, -+ .get_timeleft = mlxreg_wdt_get_timeleft, -+ .owner = THIS_MODULE, -+}; -+ -+static const struct watchdog_info mlxreg_wdt_main_info = { -+ .options = MLXREG_WDT_OPTIONS_BASE -+ | WDIOF_CARDRESET, -+ .identity = "mlx-wdt-main", -+}; -+ -+static const struct watchdog_info mlxreg_wdt_aux_info = { -+ .options = MLXREG_WDT_OPTIONS_BASE -+ | WDIOF_ALARMONLY, -+ .identity = "mlx-wdt-aux", -+}; -+ -+static void mlxreg_wdt_config(struct mlxreg_wdt *wdt, -+ struct mlxreg_core_platform_data *pdata) -+{ -+ struct mlxreg_core_data *data = pdata->data; -+ int i; -+ -+ wdt->reset_idx = -EINVAL; -+ for (i = 0; i < pdata->counter; i++, data++) { -+ if (strnstr(data->label, "action", sizeof(data->label))) -+ wdt->action_idx = i; -+ else if (strnstr(data->label, "timeout", sizeof(data->label))) -+ wdt->timeout_idx = i; -+ else if (strnstr(data->label, "timeleft", sizeof(data->label))) -+ wdt->tleft_idx = i; -+ else if (strnstr(data->label, "ping", sizeof(data->label))) -+ wdt->ping_idx = i; -+ else if (strnstr(data->label, "reset", sizeof(data->label))) -+ wdt->reset_idx = i; -+ } -+ -+ wdt->pdata = pdata; -+ if (strnstr(pdata->identity, mlxreg_wdt_main_info.identity, -+ sizeof(mlxreg_wdt_main_info.identity))) -+ wdt->wdd.info = &mlxreg_wdt_main_info; -+ else -+ wdt->wdd.info = &mlxreg_wdt_aux_info; -+ -+ wdt->wdt_type = pdata->version; -+ if (wdt->wdt_type == MLX_WDT_TYPE2) { -+ wdt->wdd.ops = &mlxreg_wdt_ops_type2; -+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2; -+ } else { -+ wdt->wdd.ops = &mlxreg_wdt_ops_type1; -+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1; -+ } -+ wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT; -+} -+ -+static int mlxreg_wdt_init_timeout(struct mlxreg_wdt *wdt, -+ struct mlxreg_core_platform_data *pdata) -+{ -+ u32 timeout; -+ -+ timeout = pdata->data[wdt->timeout_idx].health_cntr; -+ return mlxreg_wdt_set_timeout(&wdt->wdd, timeout); -+} -+ -+static int mlxreg_wdt_probe(struct platform_device *pdev) -+{ -+ struct mlxreg_core_platform_data *pdata; -+ struct mlxreg_wdt *wdt; -+ int rc; -+ -+ pdata = dev_get_platdata(&pdev->dev); -+ if (!pdata) { -+ dev_err(&pdev->dev, "Failed to get platform data.\n"); -+ return -EINVAL; -+ } -+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); -+ if (!wdt) -+ return -ENOMEM; -+ -+ wdt->wdd.parent = &pdev->dev; -+ wdt->regmap = pdata->regmap; -+ mlxreg_wdt_config(wdt, pdata); -+ -+ if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOWAYOUT)) -+ watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT); -+ watchdog_stop_on_reboot(&wdt->wdd); -+ watchdog_set_drvdata(&wdt->wdd, wdt); -+ rc = mlxreg_wdt_init_timeout(wdt, pdata); -+ if (rc) -+ goto register_error; -+ -+ if ((pdata->features & MLXREG_CORE_WD_FEATURE_START_AT_BOOT)) { -+ rc = mlxreg_wdt_start(&wdt->wdd); -+ if (rc) -+ goto register_error; -+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); -+ } -+ mlxreg_wdt_check_card_reset(wdt); -+ rc = devm_watchdog_register_device(&pdev->dev, &wdt->wdd); -+ -+register_error: -+ if (rc) -+ dev_err(&pdev->dev, -+ "Cannot register watchdog device (err=%d)\n", rc); -+ return rc; -+} -+ -+static struct platform_driver mlxreg_wdt_driver = { -+ .probe = mlxreg_wdt_probe, -+ .driver = { -+ .name = "mlx-wdt", -+ }, -+}; -+ -+module_platform_driver(mlxreg_wdt_driver); -+ -+MODULE_AUTHOR("Michael Shych "); -+MODULE_DESCRIPTION("Mellanox watchdog driver"); -+MODULE_LICENSE("GPL"); -+MODULE_ALIAS("platform:mlx-wdt"); -diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h -index 19f5cb6..b8da8ae 100644 ---- a/include/linux/platform_data/mlxreg.h -+++ b/include/linux/platform_data/mlxreg.h -@@ -35,6 +35,19 @@ - #define __LINUX_PLATFORM_DATA_MLXREG_H - - #define MLXREG_CORE_LABEL_MAX_SIZE 32 -+#define MLXREG_CORE_WD_FEATURE_NOWAYOUT BIT(0) -+#define MLXREG_CORE_WD_FEATURE_START_AT_BOOT BIT(1) -+ -+/** -+ * enum mlxreg_wdt_type - type of HW watchdog -+ * -+ * TYPE1 HW watchdog implementation exist in old systems. -+ * All new systems have TYPE2 HW watchdog. -+ */ -+enum mlxreg_wdt_type { -+ MLX_WDT_TYPE1, -+ MLX_WDT_TYPE2, -+}; - - /** - * struct mlxreg_hotplug_device - I2C device data: -@@ -61,6 +74,7 @@ struct mlxreg_hotplug_device { - * @reg: attribute register; - * @mask: attribute access mask; - * @bit: attribute effective bit; -+ * @capability: attribute capability register; - * @mode: access mode; - * @np - pointer to node platform associated with attribute; - * @hpdev - hotplug device data; -@@ -72,6 +86,7 @@ struct mlxreg_core_data { - u32 reg; - u32 mask; - u32 bit; -+ u32 capability; - umode_t mode; - struct device_node *np; - struct mlxreg_hotplug_device hpdev; -@@ -86,6 +101,7 @@ struct mlxreg_core_data { - * @aggr_mask: group aggregation mask; - * @reg: group interrupt status register; - * @mask: group interrupt mask; -+ * @capability: group capability register; - * @cache: last status value for elements fro the same group; - * @count: number of available elements in the group; - * @ind: element's index inside the group; -@@ -97,6 +113,7 @@ struct mlxreg_core_item { - u32 aggr_mask; - u32 reg; - u32 mask; -+ u32 capability; - u32 cache; - u8 count; - u8 ind; -@@ -107,14 +124,20 @@ struct mlxreg_core_item { - /** - * struct mlxreg_core_platform_data - platform data: - * -- * @led_data: led private data; -+ * @data: instance private data; - * @regmap: register map of parent device; -- * @counter: number of led instances; -+ * @counter: number of instances; -+ * @features: supported features of device; -+ * @version: implementation version; -+ * @identity: device identity name; - */ - struct mlxreg_core_platform_data { - struct mlxreg_core_data *data; - void *regmap; - int counter; -+ u32 features; -+ u32 version; -+ char identity[MLXREG_CORE_LABEL_MAX_SIZE]; - }; - - /** -diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h -index dc69391..08bb155 100644 ---- a/include/uapi/linux/ethtool.h -+++ b/include/uapi/linux/ethtool.h -@@ -1691,6 +1691,9 @@ static inline int ethtool_validate_duplex(__u8 duplex) - #define ETH_MODULE_SFF_8436 0x4 - #define ETH_MODULE_SFF_8436_LEN 256 - -+#define ETH_MODULE_SFF_8636_MAX_LEN 640 -+#define ETH_MODULE_SFF_8436_MAX_LEN 640 -+ - /* Reset flags */ - /* The reset() operation must clear the flags for the components which - * were actually reset. On successful return, the flags indicate the --- -2.11.0 - diff --git a/patch/0001-hwmon-lm75-add-support-for-PCT2075.patch b/patch/0001-hwmon-lm75-add-support-for-PCT2075.patch deleted file mode 100644 index b409f9e15..000000000 --- a/patch/0001-hwmon-lm75-add-support-for-PCT2075.patch +++ /dev/null @@ -1,115 +0,0 @@ -From 90b3a0b833131831bf259e3506aaf9f29317aae9 Mon Sep 17 00:00:00 2001 -From: Daniel Mack -Date: Thu, 11 Jul 2019 14:45:04 +0200 -Subject: [PATCH 1/6] hwmon: (lm75) add support for PCT2075 - -The NXP PCT2075 is largely compatible with other chips already supported -by the LM75 driver. It uses an 11-bit resolution and defaults to 100 ms -sampling period. The datasheet is here: - - https://www.nxp.com/docs/en/data-sheet/PCT2075.pdf - -Signed-off-by: Daniel Mack -Link: https://lore.kernel.org/r/20190711124504.7580-2-daniel@zonque.org -[groeck: Documentation update] -Signed-off-by: Guenter Roeck ---- - Documentation/hwmon/lm75 | 35 +++++++++++++++++++++++++---------- - drivers/hwmon/lm75.c | 10 ++++++++++ - 2 files changed, 35 insertions(+), 10 deletions(-) - -diff --git a/Documentation/hwmon/lm75 b/Documentation/hwmon/lm75 -index ac95edfcd907..ef7c2afa604e 100644 ---- a/Documentation/hwmon/lm75 -+++ b/Documentation/hwmon/lm75 -@@ -46,20 +46,35 @@ Supported chips: - Prefixes: 'tmp100', 'tmp101', 'tmp105', 'tmp112', 'tmp175', 'tmp75', 'tmp75c', 'tmp275' - Addresses scanned: none - Datasheet: Publicly available at the Texas Instruments website -- http://www.ti.com/product/tmp100 -- http://www.ti.com/product/tmp101 -- http://www.ti.com/product/tmp105 -- http://www.ti.com/product/tmp112 -- http://www.ti.com/product/tmp75 -- http://www.ti.com/product/tmp75c -- http://www.ti.com/product/tmp175 -- http://www.ti.com/product/tmp275 -- * NXP LM75B -- Prefix: 'lm75b' -+ -+ http://www.ti.com/product/tmp100 -+ -+ http://www.ti.com/product/tmp101 -+ -+ http://www.ti.com/product/tmp105 -+ -+ http://www.ti.com/product/tmp112 -+ -+ http://www.ti.com/product/tmp75 -+ -+ http://www.ti.com/product/tmp75b -+ -+ http://www.ti.com/product/tmp75c -+ -+ http://www.ti.com/product/tmp175 -+ -+ http://www.ti.com/product/tmp275 -+ -+ * NXP LM75B, PCT2075 -+ -+ Prefix: 'lm75b', 'pct2075' -+ - Addresses scanned: none - Datasheet: Publicly available at the NXP website - http://www.nxp.com/documents/data_sheet/LM75B.pdf - -+ http://www.nxp.com/docs/en/data-sheet/PCT2075.pdf -+ - Author: Frodo Looijaard - - Description -diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c -index 7f28912c9abc..9a04af3d7449 100644 ---- a/drivers/hwmon/lm75.c -+++ b/drivers/hwmon/lm75.c -@@ -48,6 +48,7 @@ enum lm75_type { /* keep sorted in alphabetical order */ - max6625, - max6626, - mcp980x, -+ pct2075, - stds75, - tcn75, - tmp100, -@@ -343,6 +344,10 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) - data->resolution = 9; - data->sample_time = MSEC_PER_SEC / 8; - break; -+ case pct2075: -+ data->resolution = 11; -+ data->sample_time = MSEC_PER_SEC / 10; -+ break; - case mcp980x: - data->resolution_limits = 9; - /* fall through */ -@@ -416,6 +421,7 @@ static const struct i2c_device_id lm75_ids[] = { - { "max6625", max6625, }, - { "max6626", max6626, }, - { "mcp980x", mcp980x, }, -+ { "pct2075", pct2075, }, - { "stds75", stds75, }, - { "tcn75", tcn75, }, - { "tmp100", tmp100, }, -@@ -475,6 +481,10 @@ static const struct of_device_id lm75_of_match[] = { - .compatible = "maxim,mcp980x", - .data = (void *)mcp980x - }, -+ { -+ .compatible = "nxp,pct2075", -+ .data = (void *)pct2075 -+ }, - { - .compatible = "st,stds75", - .data = (void *)stds75 --- -2.17.1 - diff --git a/patch/0003-mlxsw-minimal-Fix-validation-for-FW-minor-version.patch b/patch/0003-mlxsw-minimal-Fix-validation-for-FW-minor-version.patch deleted file mode 100644 index 2ce47907e..000000000 --- a/patch/0003-mlxsw-minimal-Fix-validation-for-FW-minor-version.patch +++ /dev/null @@ -1,31 +0,0 @@ -From d45701f5f15e77f3961717d7f076163d3c2124e2 Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [v4.19 backport 2/2] mlxsw: minimal: Fix validation for FW minor - version - -Fix validation for FW minor version in order to prevent driver -initialization in case FW minor version is older than expected. - -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/minimal.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/minimal.c b/drivers/net/ethernet/mellanox/mlxsw/minimal.c -index 504db124e..8cc969758 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/minimal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/minimal.c -@@ -127,8 +127,9 @@ static int mlxsw_m_fw_rev_validate(struct mlxsw_m *mlxsw_m) - dev_info(mlxsw_m->bus_info->dev, "The firmware version %d.%d.%d\n", - rev->major, rev->minor, rev->subminor); - /* Validate driver & FW are compatible */ -- if (rev->minor >= MLXSW_M_FWREV_MINOR && -- rev->subminor >= MLXSW_M_FWREV_SUBMINOR) -+ if ((rev->minor > MLXSW_M_FWREV_MINOR) || -+ (rev->minor == MLXSW_M_FWREV_MINOR && -+ rev->subminor >= MLXSW_M_FWREV_SUBMINOR)) - return 0; - - dev_info(mlxsw_m->bus_info->dev, "The firmware version %d.%d.%d is incompatible with the driver (required >= %d.%d.%d)\n", diff --git a/patch/0004-mlxsw-core-Add-the-hottest-thermal-zone-detection.patch b/patch/0004-mlxsw-core-Add-the-hottest-thermal-zone-detection.patch deleted file mode 100644 index c2c4a3238..000000000 --- a/patch/0004-mlxsw-core-Add-the-hottest-thermal-zone-detection.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 835fc5247dc6c3991fcc1bfbbb5cfa05db613cbc Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [PATCH 5.3 backport 3/3] mlxsw: core: Add the hottest thermal zone - detection - -When multiple sensors are mapped to the same cooling device, the -cooling device should be set according the worst sensor from the -sensors associated with this cooling device. - -Provide the hottest thermal zone detection and enforce cooling device -to follow the temperature trends of the hottest zone only. -Prevent competition for the cooling device control from others zones, -by "stable trend" indication. A cooling device will not perform any -actions associated with a zone with a "stable trend". - -When other thermal zone is detected as a hottest, a cooling device is -to be switched to following temperature trends of new hottest zone. - -Thermal zone score is represented by 32 bits unsigned integer and -calculated according to the next formula: -For T < TZ, where t from {normal trip = 0, high trip = 1, hot -trip = 2, critical = 3}: -TZ score = (T + (TZ - T) / 2) / (TZ - T) * 256 ** j; -Highest thermal zone score s is set as MAX(TZscore); -Following this formula, if TZ is in trip point higher than TZ, -the higher score is to be always assigned to TZ. - -For two thermal zones located at the same kind of trip point, the higher -score will be assigned to the zone which is closer to the next trip -point. Thus, the highest score will always be assigned objectively to -the hottest thermal zone. - -All the thermal zones initially are to be configured with mode -"enabled" with the "step_wise" governor. - -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 55 ++++++++++++++++++++ - 1 file changed, 55 insertions(+) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index b75372998..e4343bf11 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -23,6 +23,7 @@ - #define MLXSW_THERMAL_HYSTERESIS_TEMP 5000 /* 5C */ - #define MLXSW_THERMAL_MODULE_TEMP_SHIFT (MLXSW_THERMAL_HYSTERESIS_TEMP * 2) - #define MLXSW_THERMAL_ZONE_MAX_NAME 16 -+#define MLXSW_THERMAL_TEMP_SCORE_MAX GENMASK(31, 0) - #define MLXSW_THERMAL_MAX_STATE 10 - #define MLXSW_THERMAL_MAX_DUTY 255 - /* Minimum and maximum fan allowed speed in percent: from 20% to 100%. Values -@@ -201,6 +202,34 @@ mlxsw_thermal_module_trips_update(struct device *dev, struct mlxsw_core *core, - return 0; - } - -+static void mlxsw_thermal_tz_score_update(struct mlxsw_thermal *thermal, -+ struct thermal_zone_device *tzdev, -+ struct mlxsw_thermal_trip *trips, -+ int temp) -+{ -+ struct mlxsw_thermal_trip *trip = trips; -+ unsigned int score, delta, i, shift = 1; -+ -+ /* Calculate thermal zone score, if temperature is above the critical -+ * threshold score is set to MLXSW_THERMAL_TEMP_SCORE_MAX. -+ */ -+ score = MLXSW_THERMAL_TEMP_SCORE_MAX; -+ for (i = MLXSW_THERMAL_TEMP_TRIP_NORM; i < MLXSW_THERMAL_NUM_TRIPS; -+ i++, trip++) { -+ if (temp < trip->temp) { -+ delta = DIV_ROUND_CLOSEST(temp, trip->temp - temp); -+ score = delta * shift; -+ break; -+ } -+ shift *= 256; -+ } -+ -+ if (score > thermal->tz_highest_score) { -+ thermal->tz_highest_score = score; -+ thermal->tz_highest_dev = tzdev; -+ } -+} -+ - static int mlxsw_thermal_bind(struct thermal_zone_device *tzdev, - struct thermal_cooling_device *cdev) - { -@@ -302,6 +331,9 @@ static int mlxsw_thermal_get_temp(struct thermal_zone_device *tzdev, - return err; - } - mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); -+ if (temp > 0) -+ mlxsw_thermal_tz_score_update(thermal, tzdev, thermal->trips, -+ temp); - - *p_temp = temp; - return 0; -@@ -363,6 +395,22 @@ static int mlxsw_thermal_set_trip_hyst(struct thermal_zone_device *tzdev, - return 0; - } - -+static int mlxsw_thermal_trend_get(struct thermal_zone_device *tzdev, -+ int trip, enum thermal_trend *trend) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ struct mlxsw_thermal *thermal = tz->parent; -+ -+ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) -+ return -EINVAL; -+ -+ if (tzdev == thermal->tz_highest_dev) -+ return 1; -+ -+ *trend = THERMAL_TREND_STABLE; -+ return 0; -+} -+ - struct thermal_zone_params mlxsw_thermal_params = { - .no_hwmon = true, - }; -@@ -378,6 +426,7 @@ static struct thermal_zone_device_ops mlxsw_thermal_ops = { - .set_trip_temp = mlxsw_thermal_set_trip_temp, - .get_trip_hyst = mlxsw_thermal_get_trip_hyst, - .set_trip_hyst = mlxsw_thermal_set_trip_hyst, -+ .get_trend = mlxsw_thermal_trend_get, - }; - - static int mlxsw_thermal_module_bind(struct thermal_zone_device *tzdev, -@@ -495,6 +544,8 @@ static int mlxsw_thermal_module_temp_get(struct thermal_zone_device *tzdev, - - /* Update trip points. */ - err = mlxsw_thermal_module_trips_update(dev, thermal->core, tz); -+ if (!err && temp > 0) -+ mlxsw_thermal_tz_score_update(thermal, tzdev, tz->trips, temp); - - return 0; - } -@@ -570,6 +621,7 @@ static struct thermal_zone_device_ops mlxsw_thermal_module_ops = { - .set_trip_temp = mlxsw_thermal_module_trip_temp_set, - .get_trip_hyst = mlxsw_thermal_module_trip_hyst_get, - .set_trip_hyst = mlxsw_thermal_module_trip_hyst_set, -+ .get_trend = mlxsw_thermal_trend_get, - }; - - static int mlxsw_thermal_gearbox_temp_get(struct thermal_zone_device *tzdev, -@@ -596,6 +648,8 @@ static int mlxsw_thermal_gearbox_temp_get(struct thermal_zone_device *tzdev, - return err; - - mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); -+ if (temp > 0) -+ mlxsw_thermal_tz_score_update(thermal, tzdev, tz->trips, temp); - - *p_temp = temp; - return 0; -@@ -612,6 +666,7 @@ static struct thermal_zone_device_ops mlxsw_thermal_gearbox_ops = { - .set_trip_temp = mlxsw_thermal_module_trip_temp_set, - .get_trip_hyst = mlxsw_thermal_module_trip_hyst_get, - .set_trip_hyst = mlxsw_thermal_module_trip_hyst_set, -+ .get_trend = mlxsw_thermal_trend_get, - }; - - static int mlxsw_thermal_get_max_state(struct thermal_cooling_device *cdev, diff --git a/patch/0005-hwmon-pmbus-core-Add-support-for-vid-mode-detection-.patch b/patch/0005-hwmon-pmbus-core-Add-support-for-vid-mode-detection-.patch deleted file mode 100644 index afe957b0c..000000000 --- a/patch/0005-hwmon-pmbus-core-Add-support-for-vid-mode-detection-.patch +++ /dev/null @@ -1,384 +0,0 @@ -From 7b4746d925e3173141005f414a239224bd765488 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Mon, 29 Jun 2020 19:05:59 +0300 -Subject: [backport 04/12] hwmon: (pmbus/core) Add support for vid mode - detection per page bases - -Add support for VID protocol detection per page bases, instead of -detecting it based on PMBU_VOUT readout from page 0. -The reason that some devices allows to configure different VID modes -per page within the same device. - -Extend "vrm_version" with the type for Intel IMVP9 and AMD 6.25mV VID -modes. -Add calculation for those types. - -Add support for devices XDPE12254, XDPE12284. -All these device support two pages. -The below lists of VOUT_MODE command readout with their related VID -protocols, Digital to Analog Converter steps, supported by these -devices: -VR12.0 mode, 5-mV DAC - 0x01; -VR12.5 mode, 10-mV DAC - 0x02; -IMVP9 mode, 5-mV DAC - 0x03; -AMD mode 6.25mV - 0x10. ---- - drivers/hwmon/pmbus/Kconfig | 9 +++ - drivers/hwmon/pmbus/Makefile | 1 + - drivers/hwmon/pmbus/max20751.c | 2 +- - drivers/hwmon/pmbus/pmbus.c | 5 +- - drivers/hwmon/pmbus/pmbus.h | 4 +- - drivers/hwmon/pmbus/pmbus_core.c | 10 ++- - drivers/hwmon/pmbus/tps53679.c | 44 +++++----- - drivers/hwmon/pmbus/xdpe12284.c | 171 +++++++++++++++++++++++++++++++++++++++ - 8 files changed, 219 insertions(+), 27 deletions(-) - create mode 100644 drivers/hwmon/pmbus/xdpe12284.c - -diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig -index a82018a..14c0572 100644 ---- a/drivers/hwmon/pmbus/Kconfig -+++ b/drivers/hwmon/pmbus/Kconfig -@@ -186,6 +186,15 @@ config SENSORS_UCD9200 - This driver can also be built as a module. If so, the module will - be called ucd9200. - -+config SENSORS_XDPE122 -+ tristate "Infineon XDPE122 family" -+ help -+ If you say yes here you get hardware monitoring support for Infineon -+ XDPE12254, XDPE12284, device. -+ -+ This driver can also be built as a module. If so, the module will -+ be called xdpe12284. -+ - config SENSORS_ZL6100 - tristate "Intersil ZL6100 and compatibles" - default n -diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile -index ea0e395..8c8edb5 100644 ---- a/drivers/hwmon/pmbus/Makefile -+++ b/drivers/hwmon/pmbus/Makefile -@@ -20,4 +20,5 @@ obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o - obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o - obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o - obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o -+obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o - obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o -diff --git a/drivers/hwmon/pmbus/max20751.c b/drivers/hwmon/pmbus/max20751.c -index ab74aea..394c662 100644 ---- a/drivers/hwmon/pmbus/max20751.c -+++ b/drivers/hwmon/pmbus/max20751.c -@@ -25,7 +25,7 @@ static struct pmbus_driver_info max20751_info = { - .pages = 1, - .format[PSC_VOLTAGE_IN] = linear, - .format[PSC_VOLTAGE_OUT] = vid, -- .vrm_version = vr12, -+ .vrm_version[0] = vr12, - .format[PSC_TEMPERATURE] = linear, - .format[PSC_CURRENT_OUT] = linear, - .format[PSC_POWER] = linear, -diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c -index 7688dab..4db0400 100644 ---- a/drivers/hwmon/pmbus/pmbus.c -+++ b/drivers/hwmon/pmbus/pmbus.c -@@ -123,7 +123,7 @@ static int pmbus_identify(struct i2c_client *client, - } - - if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) { -- int vout_mode; -+ int vout_mode, i; - - vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); - if (vout_mode >= 0 && vout_mode != 0xff) { -@@ -132,7 +132,8 @@ static int pmbus_identify(struct i2c_client *client, - break; - case 1: - info->format[PSC_VOLTAGE_OUT] = vid; -- info->vrm_version = vr11; -+ for (i = 0; i < info->pages; i++) -+ info->vrm_version[i] = vr11; - break; - case 2: - info->format[PSC_VOLTAGE_OUT] = direct; -diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h -index 1d24397..5481ff9 100644 ---- a/drivers/hwmon/pmbus/pmbus.h -+++ b/drivers/hwmon/pmbus/pmbus.h -@@ -375,12 +375,12 @@ enum pmbus_sensor_classes { - #define PMBUS_PAGE_VIRTUAL BIT(31) - - enum pmbus_data_format { linear = 0, direct, vid }; --enum vrm_version { vr11 = 0, vr12, vr13 }; -+enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv }; - - struct pmbus_driver_info { - int pages; /* Total number of pages */ - enum pmbus_data_format format[PSC_NUM_CLASSES]; -- enum vrm_version vrm_version; -+ enum vrm_version vrm_version[PMBUS_PAGES]; /* vrm version per page */ - /* - * Support one set of coefficients for each sensor type - * Used for chips providing data in direct mode. -diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c -index cd24b37..1d7f950 100644 ---- a/drivers/hwmon/pmbus/pmbus_core.c -+++ b/drivers/hwmon/pmbus/pmbus_core.c -@@ -709,7 +709,7 @@ static long pmbus_reg2data_vid(struct pmbus_data *data, - long val = sensor->data; - long rv = 0; - -- switch (data->info->vrm_version) { -+ switch (data->info->vrm_version[sensor->page]) { - case vr11: - if (val >= 0x02 && val <= 0xb2) - rv = DIV_ROUND_CLOSEST(160000 - (val - 2) * 625, 100); -@@ -722,6 +722,14 @@ static long pmbus_reg2data_vid(struct pmbus_data *data, - if (val >= 0x01) - rv = 500 + (val - 1) * 10; - break; -+ case imvp9: -+ if (val >= 0x01) -+ rv = 200 + (val - 1) * 10; -+ break; -+ case amd625mv: -+ if (val >= 0x0 && val <= 0xd8) -+ rv = DIV_ROUND_CLOSEST(155000 - val * 625, 100); -+ break; - } - return rv; - } -diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c -index 2bc352c..0a30101 100644 ---- a/drivers/hwmon/pmbus/tps53679.c -+++ b/drivers/hwmon/pmbus/tps53679.c -@@ -33,27 +33,29 @@ static int tps53679_identify(struct i2c_client *client, - struct pmbus_driver_info *info) - { - u8 vout_params; -- int ret; -- -- /* Read the register with VOUT scaling value.*/ -- ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); -- if (ret < 0) -- return ret; -- -- vout_params = ret & GENMASK(4, 0); -- -- switch (vout_params) { -- case TPS53679_PROT_VR13_10MV: -- case TPS53679_PROT_VR12_5_10MV: -- info->vrm_version = vr13; -- break; -- case TPS53679_PROT_VR13_5MV: -- case TPS53679_PROT_VR12_5MV: -- case TPS53679_PROT_IMVP8_5MV: -- info->vrm_version = vr12; -- break; -- default: -- return -EINVAL; -+ int i, ret; -+ -+ for (i = 0; i < TPS53679_PAGE_NUM; i++) { -+ /* Read the register with VOUT scaling value.*/ -+ ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE); -+ if (ret < 0) -+ return ret; -+ -+ vout_params = ret & GENMASK(4, 0); -+ -+ switch (vout_params) { -+ case TPS53679_PROT_VR13_10MV: -+ case TPS53679_PROT_VR12_5_10MV: -+ info->vrm_version[i] = vr13; -+ break; -+ case TPS53679_PROT_VR13_5MV: -+ case TPS53679_PROT_VR12_5MV: -+ case TPS53679_PROT_IMVP8_5MV: -+ info->vrm_version[i] = vr12; -+ break; -+ default: -+ return -EINVAL; -+ } - } - - return 0; -diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c -new file mode 100644 -index 0000000..660556b ---- /dev/null -+++ b/drivers/hwmon/pmbus/xdpe12284.c -@@ -0,0 +1,171 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers -+ * -+ * Copyright (c) 2020 Mellanox Technologies. All rights reserved. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include "pmbus.h" -+ -+#define XDPE122_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ -+#define XDPE122_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */ -+#define XDPE122_PROT_IMVP9_10MV 0x03 /* IMVP9 mode, 10-mV DAC */ -+#define XDPE122_AMD_625MV 0x10 /* AMD mode 6.25mV */ -+#define XDPE122_PAGE_NUM 2 -+ -+static int xdpe122_read_word_data(struct i2c_client *client, int page, int reg) -+{ -+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); -+ long val; -+ s16 exponent; -+ s32 mantissa; -+ int ret; -+ -+ switch (reg) { -+ case PMBUS_VOUT_OV_FAULT_LIMIT: -+ case PMBUS_VOUT_UV_FAULT_LIMIT: -+ ret = pmbus_read_word_data(client, page, reg); -+ if (ret < 0) -+ return ret; -+ -+ /* Convert register value to LINEAR11 data. */ -+ exponent = ((s16)ret) >> 11; -+ mantissa = ((s16)((ret & GENMASK(10, 0)) << 5)) >> 5; -+ val = mantissa * 1000L; -+ if (exponent >= 0) -+ val <<= exponent; -+ else -+ val >>= -exponent; -+ -+ /* Convert data to VID register. */ -+ switch (info->vrm_version[page]) { -+ case vr13: -+ if (val >= 500) -+ return 1 + DIV_ROUND_CLOSEST(val - 500, 10); -+ return 0; -+ case vr12: -+ if (val >= 250) -+ return 1 + DIV_ROUND_CLOSEST(val - 250, 5); -+ return 0; -+ case imvp9: -+ if (val >= 200) -+ return 1 + DIV_ROUND_CLOSEST(val - 200, 10); -+ return 0; -+ case amd625mv: -+ if (val >= 200 && val <= 1550) -+ return DIV_ROUND_CLOSEST((1550 - val) * 100, -+ 625); -+ return 0; -+ default: -+ return -EINVAL; -+ } -+ default: -+ return -ENODATA; -+ } -+ -+ return 0; -+} -+ -+static int xdpe122_identify(struct i2c_client *client, -+ struct pmbus_driver_info *info) -+{ -+ u8 vout_params; -+ int i, ret; -+ -+ for (i = 0; i < XDPE122_PAGE_NUM; i++) { -+ /* Read the register with VOUT scaling value.*/ -+ ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE); -+ if (ret < 0) -+ return ret; -+ -+ vout_params = ret & GENMASK(4, 0); -+ -+ switch (vout_params) { -+ case XDPE122_PROT_VR12_5_10MV: -+ info->vrm_version[i] = vr13; -+ break; -+ case XDPE122_PROT_VR12_5MV: -+ info->vrm_version[i] = vr12; -+ break; -+ case XDPE122_PROT_IMVP9_10MV: -+ info->vrm_version[i] = imvp9; -+ break; -+ case XDPE122_AMD_625MV: -+ info->vrm_version[i] = amd625mv; -+ break; -+ default: -+ return -EINVAL; -+ } -+ } -+ -+ return 0; -+} -+ -+static struct pmbus_driver_info xdpe122_info = { -+ .pages = XDPE122_PAGE_NUM, -+ .format[PSC_VOLTAGE_IN] = linear, -+ .format[PSC_VOLTAGE_OUT] = vid, -+ .format[PSC_TEMPERATURE] = linear, -+ .format[PSC_CURRENT_IN] = linear, -+ .format[PSC_CURRENT_OUT] = linear, -+ .format[PSC_POWER] = linear, -+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | -+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | -+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | -+ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, -+ .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | -+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | -+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | -+ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, -+ .identify = xdpe122_identify, -+ .read_word_data = xdpe122_read_word_data, -+}; -+ -+static int xdpe122_probe(struct i2c_client *client, -+ const struct i2c_device_id *id) -+{ -+ struct pmbus_driver_info *info; -+ -+ info = devm_kmemdup(&client->dev, &xdpe122_info, sizeof(*info), -+ GFP_KERNEL); -+ if (!info) -+ return -ENOMEM; -+ -+ return pmbus_do_probe(client, id, info); -+} -+ -+static const struct i2c_device_id xdpe122_id[] = { -+ {"xdpe12254", 0}, -+ {"xdpe12284", 0}, -+ {} -+}; -+ -+MODULE_DEVICE_TABLE(i2c, xdpe122_id); -+ -+static const struct of_device_id __maybe_unused xdpe122_of_match[] = { -+ {.compatible = "infineon,xdpe12254"}, -+ {.compatible = "infineon,xdpe12284"}, -+ {} -+}; -+MODULE_DEVICE_TABLE(of, xdpe122_of_match); -+ -+static struct i2c_driver xdpe122_driver = { -+ .driver = { -+ .name = "xdpe12284", -+ .of_match_table = of_match_ptr(xdpe122_of_match), -+ }, -+ .probe = xdpe122_probe, -+ .remove = pmbus_do_remove, -+ .id_table = xdpe122_id, -+}; -+ -+module_i2c_driver(xdpe122_driver); -+ -+MODULE_AUTHOR("Vadim Pasternak "); -+MODULE_DESCRIPTION("PMBus driver for Infineon XDPE122 family"); -+MODULE_LICENSE("GPL"); --- -2.11.0 - diff --git a/patch/0006-platform-mellanox-mlxreg-hotplug-Use-capability-r.patch b/patch/0006-platform-mellanox-mlxreg-hotplug-Use-capability-r.patch deleted file mode 100644 index 3fddfde2a..000000000 --- a/patch/0006-platform-mellanox-mlxreg-hotplug-Use-capability-r.patch +++ /dev/null @@ -1,71 +0,0 @@ -From af50fedeeaa767a0c7c3a9cf399fe23314adc609 Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [platform-next v1 1/2] platform/mellanox: mlxreg-hotplug: Use - capability register for attribute creation - -Create the 'sysfs' attributes according to configuration provided -through the capability register, which purpose is to indicate the -actual number of the components within the particular group. -Such components could be, for example the FAN or power supply units. -The motivation is to avoid adding a new code in the future in order to -distinct between the systems types supported different number of the -components like power supplies, FANs, ASICs, line cards. - -Signed-off-by: Vadim Pasternak ---- - drivers/platform/mellanox/mlxreg-hotplug.c | 23 +++++++++++++++++++---- - 1 file changed, 19 insertions(+), 4 deletions(-) - -diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c -index 77be37a1f..c21754821 100644 ---- a/drivers/platform/mellanox/mlxreg-hotplug.c -+++ b/drivers/platform/mellanox/mlxreg-hotplug.c -@@ -196,17 +196,29 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) - struct mlxreg_core_hotplug_platform_data *pdata; - struct mlxreg_core_item *item; - struct mlxreg_core_data *data; -- int num_attrs = 0, id = 0, i, j; -+ u32 regval; -+ int num_attrs = 0, id = 0, i, j, k, ret; - - pdata = dev_get_platdata(&priv->pdev->dev); - item = pdata->items; - - /* Go over all kinds of items - psu, pwr, fan. */ - for (i = 0; i < pdata->counter; i++, item++) { -- num_attrs += item->count; - data = item->data; - /* Go over all units within the item. */ -- for (j = 0; j < item->count; j++, data++, id++) { -+ for (j = 0, k = 0; j < item->count; j++, data++) { -+ if (data->capability) { -+ /* -+ * Read capability register and skip non -+ * relevant attributes. -+ */ -+ ret = regmap_read(priv->regmap, -+ data->capability, ®val); -+ if (ret) -+ return ret; -+ if (!(regval & data->bit)) -+ continue; -+ } - PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; - PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, - GFP_KERNEL, -@@ -224,9 +236,12 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) - PRIV_DEV_ATTR(id).dev_attr.show = - mlxreg_hotplug_attr_show; - PRIV_DEV_ATTR(id).nr = i; -- PRIV_DEV_ATTR(id).index = j; -+ PRIV_DEV_ATTR(id).index = k; - sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); -+ id++; -+ k++; - } -+ num_attrs += k; - } - - priv->group.attrs = devm_kcalloc(&priv->pdev->dev, diff --git a/patch/0007-mlxsw-core-thermal-Set-default-thermal-trips-at-init.patch b/patch/0007-mlxsw-core-thermal-Set-default-thermal-trips-at-init.patch deleted file mode 100644 index 8be9d97ee..000000000 --- a/patch/0007-mlxsw-core-thermal-Set-default-thermal-trips-at-init.patch +++ /dev/null @@ -1,47 +0,0 @@ -From d1bfc3dfb848ea1b402e2299b1e3b974aad9be60 Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [backport 1/2] mlxsw: core: thermal: Set default thermal trips at - initialization - -Set default thermal trip temperatures during thermal zone -initialization. Otherwise polling time for thermal control -could stay zero and such thermal zones will not be triggered by -thermal algorithm. - -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 12 ++++++++---- - 1 file changed, 8 insertions(+), 4 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index e4343bf11..77578f5bb 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -20,6 +20,10 @@ - #define MLXSW_THERMAL_ASIC_TEMP_HIGH 85000 /* 85C */ - #define MLXSW_THERMAL_ASIC_TEMP_HOT 105000 /* 105C */ - #define MLXSW_THERMAL_ASIC_TEMP_CRIT 110000 /* 110C */ -+#define MLXSW_THERMAL_MODULE_TEMP_NORM 60000 /* 60C */ -+#define MLXSW_THERMAL_MODULE_TEMP_HIGH 70000 /* 70C */ -+#define MLXSW_THERMAL_MODULE_TEMP_HOT 80000 /* 80C */ -+#define MLXSW_THERMAL_MODULE_TEMP_CRIT 90000 /* 90C */ - #define MLXSW_THERMAL_HYSTERESIS_TEMP 5000 /* 5C */ - #define MLXSW_THERMAL_MODULE_TEMP_SHIFT (MLXSW_THERMAL_HYSTERESIS_TEMP * 2) - #define MLXSW_THERMAL_ZONE_MAX_NAME 16 -@@ -154,10 +158,10 @@ static int mlxsw_get_cooling_device_idx(struct mlxsw_thermal *thermal, - static void - mlxsw_thermal_module_trips_reset(struct mlxsw_thermal_module *tz) - { -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = 0; -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = 0; -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = 0; -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = 0; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = MLXSW_THERMAL_MODULE_TEMP_NORM; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = MLXSW_THERMAL_MODULE_TEMP_HIGH; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = MLXSW_THERMAL_MODULE_TEMP_HOT; -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = MLXSW_THERMAL_MODULE_TEMP_CRIT; - } - - static int diff --git a/patch/0008-mlxsw-qsfp_sysfs-Remove-obsolete-code-for-QSFP-EEPRO.patch b/patch/0008-mlxsw-qsfp_sysfs-Remove-obsolete-code-for-QSFP-EEPRO.patch deleted file mode 100644 index fa819c5d5..000000000 --- a/patch/0008-mlxsw-qsfp_sysfs-Remove-obsolete-code-for-QSFP-EEPRO.patch +++ /dev/null @@ -1,409 +0,0 @@ -From 148312b80f5285b25140a72cf3d4671e3486dcd2 Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [backport 2/2] mlxsw: qsfp_sysfs: Remove obsolete code for QSFP - EEPROM reading - -Remove QSFP EEPROM attribures from 'sysfs'. -This code is obsoleted. - -Make de-init order symmetrical with init. - . -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core.c | 4 - drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c | 299 +--------------------- - 2 files changed, 13 insertions(+), 290 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c -index c38c1c565..b67d61077 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core.c -@@ -1120,11 +1120,11 @@ void mlxsw_core_bus_device_unregister(struct mlxsw_core *mlxsw_core, - return; - } - -- if (mlxsw_core->driver->fini) -- mlxsw_core->driver->fini(mlxsw_core); - mlxsw_qsfp_fini(mlxsw_core->qsfp); - mlxsw_thermal_fini(mlxsw_core->thermal); - mlxsw_hwmon_fini(mlxsw_core->hwmon); -+ if (mlxsw_core->driver->fini) -+ mlxsw_core->driver->fini(mlxsw_core); - if (mlxsw_core->driver->params_unregister && !reload) - mlxsw_core->driver->params_unregister(mlxsw_core); - if (!reload) -diff --git a/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c b/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c -index 49563a703..931dbd58e 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c -@@ -41,171 +41,18 @@ - - #include "core.h" - --#define MLXSW_QSFP_I2C_ADDR 0x50 --#define MLXSW_QSFP_PAGE_NUM 5 --#define MLXSW_QSFP_PAGE_SIZE 128 --#define MLXSW_QSFP_SUB_PAGE_NUM 3 --#define MLXSW_QSFP_SUB_PAGE_SIZE 48 --#define MLXSW_QSFP_LAST_SUB_PAGE_SIZE 32 --#define MLXSW_QSFP_MAX_NUM 128 --#define MLXSW_QSFP_MIN_REQ_LEN 4 --#define MLXSW_QSFP_STATUS_VALID_TIME (HZ) --#define MLXSW_QSFP_MAX_CPLD_NUM 3 --#define MLXSW_QSFP_MIN_CPLD_NUM 1 -+#define MLXSW_QSFP_MAX_CPLD_NUM 3 -+#define MLXSW_QSFP_MIN_CPLD_NUM 1 - --static const u8 mlxsw_qsfp_page_number[] = { 0xa0, 0x00, 0x01, 0x02, 0x03 }; --static const u16 mlxsw_qsfp_page_shift[] = { 0x00, 0x80, 0x80, 0x80, 0x80 }; -- --/** -- * Mellanox device Management Cable Info Access Register buffer for reading -- * QSFP EEPROM info is limited by 48 bytes. In case full page is to be read -- * (128 bytes), such request will be implemented by three transactions of size -- * 48, 48, 32. -- */ --static const u16 mlxsw_qsfp_sub_page_size[] = { -- MLXSW_QSFP_SUB_PAGE_SIZE, -- MLXSW_QSFP_SUB_PAGE_SIZE, -- MLXSW_QSFP_LAST_SUB_PAGE_SIZE --}; -- --struct mlxsw_qsfp_module { -- unsigned long last_updated; -- u8 cache_status; --}; -+static int mlxsw_qsfp_cpld_num = MLXSW_QSFP_MIN_CPLD_NUM; - - struct mlxsw_qsfp { - struct mlxsw_core *core; - const struct mlxsw_bus_info *bus_info; -- struct attribute *attrs[MLXSW_QSFP_MAX_NUM + 1]; -- struct device_attribute *dev_attrs; -- struct bin_attribute *eeprom; -- struct bin_attribute **eeprom_attr_list; -- struct mlxsw_qsfp_module modules[MLXSW_QSFP_MAX_NUM]; -- u8 module_ind[MLXSW_QSFP_MAX_NUM]; -- u8 module_count; - struct attribute *cpld_attrs[MLXSW_QSFP_MAX_CPLD_NUM + 1]; - struct device_attribute *cpld_dev_attrs; - }; - --static int mlxsw_qsfp_cpld_num = MLXSW_QSFP_MIN_CPLD_NUM; --static int mlxsw_qsfp_num = MLXSW_QSFP_MAX_NUM / 2; -- --static int --mlxsw_qsfp_query_module_eeprom(struct mlxsw_qsfp *mlxsw_qsfp, u8 index, -- loff_t off, size_t count, int page, char *buf) --{ -- char eeprom_tmp[MLXSW_QSFP_PAGE_SIZE]; -- char mcia_pl[MLXSW_REG_MCIA_LEN]; -- int status; -- int err; -- -- mlxsw_reg_mcia_pack(mcia_pl, index, 0, page, off, count, -- MLXSW_QSFP_I2C_ADDR); -- -- err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mcia), mcia_pl); -- if (err) -- return err; -- -- status = mlxsw_reg_mcia_status_get(mcia_pl); -- if (status) -- return -EIO; -- -- mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); -- memcpy(buf, eeprom_tmp, count); -- -- return 0; --} -- --static int --mlxsw_qsfp_get_module_eeprom(struct mlxsw_qsfp *mlxsw_qsfp, u8 index, -- char *buf, loff_t off, size_t count) --{ -- int page_ind, page, page_off, subpage, offset, size, res = 0; -- int err; -- -- if (!count) -- return -EINVAL; -- -- memset(buf, 0, count); -- size = count; -- while (res < count) { -- page_ind = off / MLXSW_QSFP_PAGE_SIZE; -- page_off = off % MLXSW_QSFP_PAGE_SIZE; -- page = mlxsw_qsfp_page_number[page_ind]; -- offset = mlxsw_qsfp_page_shift[page_ind] + page_off; -- subpage = page_off / MLXSW_QSFP_SUB_PAGE_SIZE; -- size = min_t(u16, size, mlxsw_qsfp_sub_page_size[subpage]); -- err = mlxsw_qsfp_query_module_eeprom(mlxsw_qsfp, index, offset, -- size, page, buf + res); -- if (err) { -- dev_err(mlxsw_qsfp->bus_info->dev, "Eeprom query failed\n"); -- return err; -- } -- off += size; -- res += size; -- size = count - size; -- } -- -- return res; --} -- --static ssize_t mlxsw_qsfp_bin_read(struct file *filp, struct kobject *kobj, -- struct bin_attribute *attr, char *buf, -- loff_t off, size_t count) --{ -- struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(container_of(kobj, -- struct device, kobj)); -- u8 *module_ind = attr->private; -- size_t size; -- -- size = mlxsw_qsfp->eeprom[*module_ind].size; -- -- if (off > size) -- return -ESPIPE; -- else if (off == size) -- return 0; -- else if ((off + count) > size) -- count = size - off; -- -- return mlxsw_qsfp_get_module_eeprom(mlxsw_qsfp, *module_ind, buf, off, -- count); --} -- --static ssize_t --mlxsw_qsfp_status_show(struct device *dev, struct device_attribute *attr, -- char *buf) --{ -- struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(dev); -- char mcia_pl[MLXSW_REG_MCIA_LEN]; -- int status; -- u32 i; -- int err; -- -- for (i = 0; i < mlxsw_qsfp->module_count; i++) { -- if ((mlxsw_qsfp->dev_attrs + i) == attr) -- break; -- } -- if (i == mlxsw_qsfp->module_count) -- return -EINVAL; -- -- if (time_before(jiffies, mlxsw_qsfp->modules[i].last_updated + -- MLXSW_QSFP_STATUS_VALID_TIME)) -- return sprintf(buf, "%u\n", -- mlxsw_qsfp->modules[i].cache_status); -- -- mlxsw_reg_mcia_pack(mcia_pl, i, 0, 0, 0, MLXSW_QSFP_MIN_REQ_LEN, -- MLXSW_QSFP_I2C_ADDR); -- err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mcia), mcia_pl); -- if (err) -- return err; -- -- status = mlxsw_reg_mcia_status_get(mcia_pl); -- mlxsw_qsfp->modules[i].cache_status = !status; -- mlxsw_qsfp->modules[i].last_updated = jiffies; -- -- return sprintf(buf, "%u\n", !status); --} -- - static ssize_t - mlxsw_qsfp_cpld_show(struct device *dev, struct device_attribute *attr, - char *buf) -@@ -239,13 +86,6 @@ static int mlxsw_qsfp_dmi_set_cpld_num(const struct dmi_system_id *dmi) - return 1; - }; - --static int mlxsw_qsfp_dmi_set_qsfp_num(const struct dmi_system_id *dmi) --{ -- mlxsw_qsfp_num = MLXSW_QSFP_MAX_NUM; -- -- return 1; --}; -- - static const struct dmi_system_id mlxsw_qsfp_dmi_table[] = { - { - .callback = mlxsw_qsfp_dmi_set_cpld_num, -@@ -261,55 +101,18 @@ static const struct dmi_system_id mlxsw_qsfp_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "MSN27"), - }, - }, -- { -- .callback = mlxsw_qsfp_dmi_set_qsfp_num, -- .matches = { -- DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -- DMI_MATCH(DMI_PRODUCT_NAME, "MSN37"), -- }, -- }, -- { -- .callback = mlxsw_qsfp_dmi_set_qsfp_num, -- .matches = { -- DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), -- DMI_MATCH(DMI_PRODUCT_NAME, "MSN38"), -- }, -- }, - { } - }; - MODULE_DEVICE_TABLE(dmi, mlxsw_qsfp_dmi_table); - --static int mlxsw_qsfp_set_module_num(struct mlxsw_qsfp *mlxsw_qsfp) --{ -- char pmlp_pl[MLXSW_REG_PMLP_LEN]; -- u8 width; -- int i, err; -- -- for (i = 1; i <= mlxsw_qsfp_num; i++) { -- mlxsw_reg_pmlp_pack(pmlp_pl, i); -- err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(pmlp), -- pmlp_pl); -- if (err) -- return err; -- width = mlxsw_reg_pmlp_width_get(pmlp_pl); -- if (!width) -- continue; -- mlxsw_qsfp->module_count++; -- } -- -- return 0; --} - - int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, - const struct mlxsw_bus_info *mlxsw_bus_info, - struct mlxsw_qsfp **p_qsfp) - { -- struct device_attribute *dev_attr, *cpld_dev_attr; -- char mgpir_pl[MLXSW_REG_MGPIR_LEN]; -+ struct device_attribute *cpld_dev_attr; - struct mlxsw_qsfp *mlxsw_qsfp; -- struct bin_attribute *eeprom; -- int i, count; -- int err; -+ int i, err; - - if (!strcmp(mlxsw_bus_info->device_kind, "i2c")) - return 0; -@@ -324,41 +127,6 @@ int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, - mlxsw_qsfp->core = mlxsw_core; - mlxsw_qsfp->bus_info = mlxsw_bus_info; - mlxsw_bus_info->dev->platform_data = mlxsw_qsfp; -- -- mlxsw_reg_mgpir_pack(mgpir_pl); -- err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mgpir), mgpir_pl); -- if (err) { -- err = mlxsw_qsfp_set_module_num(mlxsw_qsfp); -- if (err) -- return err; -- } else { -- mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, -- &mlxsw_qsfp->module_count); -- if (!mlxsw_qsfp->module_count) -- return 0; -- } -- -- count = mlxsw_qsfp->module_count + 1; -- mlxsw_qsfp->eeprom = devm_kzalloc(mlxsw_bus_info->dev, -- mlxsw_qsfp->module_count * -- sizeof(*mlxsw_qsfp->eeprom), -- GFP_KERNEL); -- if (!mlxsw_qsfp->eeprom) -- return -ENOMEM; -- -- mlxsw_qsfp->eeprom_attr_list = devm_kzalloc(mlxsw_bus_info->dev, -- count * -- sizeof(mlxsw_qsfp->eeprom), -- GFP_KERNEL); -- if (!mlxsw_qsfp->eeprom_attr_list) -- return -ENOMEM; -- -- mlxsw_qsfp->dev_attrs = devm_kzalloc(mlxsw_bus_info->dev, count * -- sizeof(*mlxsw_qsfp->dev_attrs), -- GFP_KERNEL); -- if (!mlxsw_qsfp->dev_attrs) -- return -ENOMEM; -- - mlxsw_qsfp->cpld_dev_attrs = devm_kzalloc(mlxsw_bus_info->dev, - mlxsw_qsfp_cpld_num * - sizeof(*mlxsw_qsfp->cpld_dev_attrs), -@@ -366,37 +134,6 @@ int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, - if (!mlxsw_qsfp->cpld_dev_attrs) - return -ENOMEM; - -- eeprom = mlxsw_qsfp->eeprom; -- dev_attr = mlxsw_qsfp->dev_attrs; -- for (i = 0; i < mlxsw_qsfp->module_count; i++, eeprom++, dev_attr++) { -- dev_attr->show = mlxsw_qsfp_status_show; -- dev_attr->attr.mode = 0444; -- dev_attr->attr.name = devm_kasprintf(mlxsw_bus_info->dev, -- GFP_KERNEL, -- "qsfp%d_status", i + 1); -- mlxsw_qsfp->attrs[i] = &dev_attr->attr; -- sysfs_attr_init(&dev_attr->attr); -- err = sysfs_create_file(&mlxsw_bus_info->dev->kobj, -- mlxsw_qsfp->attrs[i]); -- if (err) -- goto err_create_file; -- -- sysfs_bin_attr_init(eeprom); -- eeprom->attr.name = devm_kasprintf(mlxsw_bus_info->dev, -- GFP_KERNEL, "qsfp%d", -- i + 1); -- eeprom->attr.mode = 0444; -- eeprom->read = mlxsw_qsfp_bin_read; -- eeprom->size = MLXSW_QSFP_PAGE_NUM * MLXSW_QSFP_PAGE_SIZE; -- mlxsw_qsfp->module_ind[i] = i; -- eeprom->private = &mlxsw_qsfp->module_ind[i]; -- mlxsw_qsfp->eeprom_attr_list[i] = eeprom; -- err = sysfs_create_bin_file(&mlxsw_bus_info->dev->kobj, -- eeprom); -- if (err) -- goto err_create_bin_file; -- } -- - cpld_dev_attr = mlxsw_qsfp->cpld_dev_attrs; - for (i = 0; i < mlxsw_qsfp_cpld_num; i++, cpld_dev_attr++) { - cpld_dev_attr->show = mlxsw_qsfp_cpld_show; -@@ -417,19 +154,9 @@ int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, - return 0; - - err_create_cpld_file: -- sysfs_remove_file(&mlxsw_bus_info->dev->kobj, -- mlxsw_qsfp->cpld_attrs[i--]); -- i = mlxsw_qsfp->module_count; --err_create_bin_file: -- sysfs_remove_file(&mlxsw_bus_info->dev->kobj, -- mlxsw_qsfp->attrs[i--]); --err_create_file: -- while (--i > 0) { -- sysfs_remove_bin_file(&mlxsw_bus_info->dev->kobj, -- mlxsw_qsfp->eeprom_attr_list[i]); -+ while (--i > 0) - sysfs_remove_file(&mlxsw_bus_info->dev->kobj, -- mlxsw_qsfp->attrs[i]); -- } -+ mlxsw_qsfp->cpld_attrs[i]); - - return err; - } -@@ -438,15 +165,11 @@ void mlxsw_qsfp_fini(struct mlxsw_qsfp *mlxsw_qsfp) - { - int i; - -- if (!strcmp(mlxsw_qsfp->bus_info->device_kind, "i2c")) -- return; -- -- for (i = mlxsw_qsfp->module_count - 1; i >= 0; i--) { -- sysfs_remove_bin_file(&mlxsw_qsfp->bus_info->dev->kobj, -- mlxsw_qsfp->eeprom_attr_list[i]); -+ for (i = 0; i < mlxsw_qsfp_cpld_num; i++) - sysfs_remove_file(&mlxsw_qsfp->bus_info->dev->kobj, -- mlxsw_qsfp->attrs[i]); -- } -+ mlxsw_qsfp->cpld_attrs[i]); -+ devm_kfree(mlxsw_qsfp->bus_info->dev, mlxsw_qsfp->cpld_dev_attrs); -+ devm_kfree(mlxsw_qsfp->bus_info->dev, mlxsw_qsfp); - } - - MODULE_LICENSE("Dual BSD/GPL"); diff --git a/patch/0009-platform-mellanox-mlxreg-io-Add-support-for-compl.patch b/patch/0009-platform-mellanox-mlxreg-io-Add-support-for-compl.patch deleted file mode 100644 index 019deccea..000000000 --- a/patch/0009-platform-mellanox-mlxreg-io-Add-support-for-compl.patch +++ /dev/null @@ -1,400 +0,0 @@ -From f0b3ddaafa62a2d36ce88bf369dfaf8b8efb7c6b Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [backport] platform/mellanox: mlxreg-io: Add support for complex - attributes - -Add support for attributes composed from few registers. -Such attributes could occupy from 2 to 4 sequential registers. -This is not the case for double word size register space, for word size -register space complex attribute can occupy up to two register, for -byte size - up to four. These attributes can carry, for example, CPLD -or FPGA versioning, power consuming info, etcetera. - -Signed-off-by: Vadim Pasternak ---- - drivers/platform/mellanox/mlxreg-hotplug.c | 31 ++++--- - drivers/platform/mellanox/mlxreg-io.c | 54 +++++++++--- - drivers/platform/x86/mlx-platform.c | 128 ++++++++++++++++++++++++++++ - include/linux/platform_data/mlxreg.h | 2 - 4 files changed, 189 insertions(+), 26 deletions(-) - -diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c -index c21754821..832fcf282 100644 ---- a/drivers/platform/mellanox/mlxreg-hotplug.c -+++ b/drivers/platform/mellanox/mlxreg-hotplug.c -@@ -204,9 +204,26 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) - - /* Go over all kinds of items - psu, pwr, fan. */ - for (i = 0; i < pdata->counter; i++, item++) { -+ if (item->capability) { -+ /* -+ * Read group capability register to get actual number -+ * of interrupt capable components and set group mask -+ * accordingly. -+ */ -+ ret = regmap_read(priv->regmap, item->capability, -+ ®val); -+ if (ret) -+ return ret; -+ -+ item->mask = GENMASK((regval & item->mask) - 1, 0); -+ } -+ - data = item->data; - /* Go over all units within the item. */ - for (j = 0, k = 0; j < item->count; j++, data++) { -+ /* Skip if bit in mask is not set. */ -+ if (!(item->mask & BIT(j))) -+ continue; - if (data->capability) { - /* - * Read capability register and skip non -@@ -519,20 +536,6 @@ static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) - item = pdata->items; - - for (i = 0; i < pdata->counter; i++, item++) { -- if (item->capability) { -- /* -- * Read group capability register to get actual number -- * of interrupt capable components and set group mask -- * accordingly. -- */ -- ret = regmap_read(priv->regmap, item->capability, -- ®val); -- if (ret) -- goto out; -- -- item->mask = GENMASK((regval & item->mask) - 1, 0); -- } -- - /* Clear group presense event. */ - ret = regmap_write(priv->regmap, item->reg + - MLXREG_HOTPLUG_EVENT_OFF, 0); -diff --git a/drivers/platform/mellanox/mlxreg-io.c b/drivers/platform/mellanox/mlxreg-io.c -index acfaf64ff..cc7b78301 100644 ---- a/drivers/platform/mellanox/mlxreg-io.c -+++ b/drivers/platform/mellanox/mlxreg-io.c -@@ -30,6 +30,8 @@ - * @mlxreg_io_dev_attr: sysfs sensor device attribute array; - * @group: sysfs attribute group; - * @groups: list of sysfs attribute group for hwmon registration; -+ * @regsize: size of a register value; -+ * @regmax: max register value; - */ - struct mlxreg_io_priv_data { - struct platform_device *pdev; -@@ -39,27 +41,31 @@ struct mlxreg_io_priv_data { - struct sensor_device_attribute mlxreg_io_dev_attr[MLXREG_IO_ATT_NUM]; - struct attribute_group group; - const struct attribute_group *groups[2]; -+ int regsize; -+ int regmax; - }; - - static int - mlxreg_io_get_reg(void *regmap, struct mlxreg_core_data *data, u32 in_val, -- bool rw_flag, u32 *regval) -+ bool rw_flag, int regsize, int regmax, u32 *regval) - { -- int ret; -+ int i, val, ret; - - ret = regmap_read(regmap, data->reg, regval); - if (ret) - goto access_error; - - /* -- * There are three kinds of attributes: single bit, full register's -- * bits and bit sequence. For the first kind field mask indicates which -- * bits are not related and field bit is set zero. For the second kind -- * field mask is set to zero and field bit is set with all bits one. -- * No special handling for such kind of attributes - pass value as is. -- * For the third kind, field mask indicates which bits are related and -- * field bit is set to the first bit number (from 1 to 32) is the bit -- * sequence. -+ * There are four kinds of attributes: single bit, full register's -+ * bits, bit sequence, bits in few registers For the first kind field -+ * mask indicates which bits are not related and field bit is set zero. -+ * For the second kind field mask is set to zero and field bit is set -+ * with all bits one. No special handling for such kind of attributes - -+ * pass value as is. For the third kind, field mask indicates which -+ * bits are related and field bit is set to the first bit number (from -+ * 1 to 32) is the bit sequence. For the fourth mask - the number of -+ * registers which should be written for attribute are set according -+ * to 'data->bit' field. - */ - if (!data->bit) { - /* Single bit. */ -@@ -83,6 +89,22 @@ mlxreg_io_get_reg(void *regmap, struct mlxreg_core_data *data, u32 in_val, - /* Clear relevant bits and set them to new value. */ - *regval = (*regval & ~data->mask) | in_val; - } -+ } else { -+ /* -+ * Some attributes could occupied few registers in case regmap -+ * bit size is 8 or 16. Maximum register for such case is -+ * respectively 0xff or 0xffff. Not relevant for the case, when -+ * regmap bit size is 32 and maximum registers 0xffffffff. -+ * Compose attribute from 'regnum' registers. -+ */ -+ for (i = 1; i < data->regnum; i++) { -+ ret = regmap_read(regmap, data->reg + i, &val); -+ if (ret) -+ goto access_error; -+ -+ *regval |= rol32(val, regsize * i); -+ } -+ *regval = le32_to_cpu(*regval & regmax); - } - - access_error: -@@ -99,7 +121,8 @@ mlxreg_io_attr_show(struct device *dev, struct device_attribute *attr, - u32 regval = 0; - int ret; - -- ret = mlxreg_io_get_reg(priv->pdata->regmap, data, 0, true, ®val); -+ ret = mlxreg_io_get_reg(priv->pdata->regmap, data, 0, true, -+ priv->regsize, priv->regmax, ®val); - if (ret) - goto access_error; - -@@ -128,7 +151,7 @@ mlxreg_io_attr_store(struct device *dev, struct device_attribute *attr, - return ret; - - ret = mlxreg_io_get_reg(priv->pdata->regmap, data, input_val, false, -- ®val); -+ priv->regsize, priv->regmax, ®val); - if (ret) - goto access_error; - -@@ -207,6 +230,13 @@ static int mlxreg_io_probe(struct platform_device *pdev) - } - - priv->pdev = pdev; -+ priv->regsize = regmap_get_val_bytes(priv->pdata->regmap); -+ if (priv->regsize < 0) -+ return priv->regsize; -+ -+ priv->regmax = regmap_get_max_register(priv->pdata->regmap); -+ if (priv->regmax < 0) -+ return priv->regmax; - - err = mlxreg_io_attr_init(priv); - if (err) { -diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c -index f5d70d67f..7fac80615 100644 ---- a/drivers/platform/x86/mlx-platform.c -+++ b/drivers/platform/x86/mlx-platform.c -@@ -26,6 +26,10 @@ - #define MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET 0x01 - #define MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET 0x02 - #define MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET 0x03 -+#define MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET 0x04 -+#define MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET 0x06 -+#define MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET 0x08 -+#define MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET 0x0a - #define MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET 0x1d - #define MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET 0x1e - #define MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET 0x1f -@@ -72,6 +76,10 @@ - #define MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET 0xd1 - #define MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET 0xd2 - #define MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET 0xd3 -+#define MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET 0xde -+#define MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET 0xdf -+#define MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET 0xe0 -+#define MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET 0xe1 - #define MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET 0xe2 - #define MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET 0xe3 - #define MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET 0xe4 -@@ -1303,6 +1311,32 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { - .bit = GENMASK(7, 0), - .mode = 0444, - }, -+ { -+ .label = "cpld1_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld2_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld1_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld2_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, - { - .label = "reset_long_pb", - .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -@@ -1409,6 +1443,32 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = { - .bit = GENMASK(7, 0), - .mode = 0444, - }, -+ { -+ .label = "cpld1_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld2_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld1_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld2_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, - { - .label = "reset_long_pb", - .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -@@ -1527,6 +1587,58 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { - .bit = GENMASK(7, 0), - .mode = 0444, - }, -+ { -+ .label = "cpld1_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld2_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld3_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld4_pn", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET, -+ .bit = GENMASK(15, 0), -+ .mode = 0444, -+ .regnum = 2, -+ }, -+ { -+ .label = "cpld1_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld2_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld3_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "cpld4_version_min", -+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, - { - .label = "reset_long_pb", - .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -@@ -2006,6 +2118,10 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET: -@@ -2051,6 +2167,10 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET: -@@ -2085,6 +2205,10 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET: -@@ -2122,6 +2246,10 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET: -diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h -index b8da8aef2..a2adc3ad4 100644 ---- a/include/linux/platform_data/mlxreg.h -+++ b/include/linux/platform_data/mlxreg.h -@@ -80,6 +80,7 @@ struct mlxreg_hotplug_device { - * @hpdev - hotplug device data; - * @health_cntr: dynamic device health indication counter; - * @attached: true if device has been attached after good health indication; -+ * @regnum: number of registers occupied by multi-register attribute; - */ - struct mlxreg_core_data { - char label[MLXREG_CORE_LABEL_MAX_SIZE]; -@@ -92,6 +93,7 @@ struct mlxreg_core_data { - struct mlxreg_hotplug_device hpdev; - u8 health_cntr; - bool attached; -+ u8 regnum; - }; - - /** diff --git a/patch/0010-platform-mellanox-mlxreg-hotplug-Add-environmental-d.patch b/patch/0010-platform-mellanox-mlxreg-hotplug-Add-environmental-d.patch deleted file mode 100644 index 36db054ab..000000000 --- a/patch/0010-platform-mellanox-mlxreg-hotplug-Add-environmental-d.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 11d848d48f9a2eb7b0466c1dddf07684c4691012 Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [backport] platform/mellanox: mlxreg-hotplug: Add environmental data - to uevent - -Send "udev" event with environmental data in order to allow handling -"ENV{}" variables in "udev" rules. - -Signed-off-by: Vadim Pasternak ---- - drivers/platform/mellanox/mlxreg-hotplug.c | 28 ++++++++++++++++++++++++++-- - 1 file changed, 26 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c -index 832fcf282..3e99ad9fe 100644 ---- a/drivers/platform/mellanox/mlxreg-hotplug.c -+++ b/drivers/platform/mellanox/mlxreg-hotplug.c -@@ -32,6 +32,7 @@ - */ - - #include -+#include - #include - #include - #include -@@ -97,13 +98,36 @@ struct mlxreg_hotplug_priv_data { - u8 not_asserted; - }; - -+/* Environment variables array for udev. */ -+static char *mlxreg_hotplug_udev_envp[] = { NULL, NULL }; -+ -+static int -+mlxreg_hotplug_udev_event_send(struct kobject *kobj, -+ struct mlxreg_core_data *data, bool action) -+{ -+ char event_str[MLXREG_CORE_LABEL_MAX_SIZE + 2]; -+ char label[MLXREG_CORE_LABEL_MAX_SIZE] = { 0 }; -+ int i; -+ -+ mlxreg_hotplug_udev_envp[0] = event_str; -+ for (i = 0; data->label[i]; i++) -+ label[i] = toupper(data->label[i]); -+ -+ if (action) -+ snprintf(event_str, MLXREG_CORE_LABEL_MAX_SIZE, "%s=1", label); -+ else -+ snprintf(event_str, MLXREG_CORE_LABEL_MAX_SIZE, "%s=0", label); -+ -+ return kobject_uevent_env(kobj, KOBJ_CHANGE, mlxreg_hotplug_udev_envp); -+} -+ - static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv, - struct mlxreg_core_data *data) - { - struct mlxreg_core_hotplug_platform_data *pdata; - - /* Notify user by sending hwmon uevent. */ -- kobject_uevent(&priv->hwmon->kobj, KOBJ_CHANGE); -+ mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, true); - - /* - * Return if adapter number is negative. It could be in case hotplug -@@ -141,7 +165,7 @@ mlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data *priv, - struct mlxreg_core_data *data) - { - /* Notify user by sending hwmon uevent. */ -- kobject_uevent(&priv->hwmon->kobj, KOBJ_CHANGE); -+ mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, false); - - if (data->hpdev.client) { - i2c_unregister_device(data->hpdev.client); diff --git a/patch/0011-mlxsw-core-thermal-Separate-temperature-trend-read-c.patch b/patch/0011-mlxsw-core-thermal-Separate-temperature-trend-read-c.patch deleted file mode 100644 index e941d3526..000000000 --- a/patch/0011-mlxsw-core-thermal-Separate-temperature-trend-read-c.patch +++ /dev/null @@ -1,78 +0,0 @@ -From b3fb34f9f6215e0677de88e07f2139b56ba7508f Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [backport 4.19 2/3] mlxsw: core: thermal: Separate temperature trend - read callback - -Separate temperature trend reading callback between chip thermal zone -and others types of thermal zones (modules and gearboxes). -Currently all these types use same get_trend() callback, which is wrong for -the chip thermal zone, which is defined as "parent zone". -When get_trend() callback is invoked, it obtains "parent zone" from the -relevant thermal device data, which is correct for modules and -gearboxes thermal devices, but not for chip thermal device, which is -a parent. -Keep using the existing get_trend() callback for modules and gearboxes, -add new get_trend() callback for chip. - -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 23 +++++++++++++++++--- - 1 file changed, 19 insertions(+), 4 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index 77578f5bb..775343a25 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -402,8 +402,7 @@ static int mlxsw_thermal_set_trip_hyst(struct thermal_zone_device *tzdev, - static int mlxsw_thermal_trend_get(struct thermal_zone_device *tzdev, - int trip, enum thermal_trend *trend) - { -- struct mlxsw_thermal_module *tz = tzdev->devdata; -- struct mlxsw_thermal *thermal = tz->parent; -+ struct mlxsw_thermal *thermal = tzdev->devdata; - - if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) - return -EINVAL; -@@ -614,6 +613,22 @@ mlxsw_thermal_module_trip_hyst_set(struct thermal_zone_device *tzdev, int trip, - return 0; - } - -+static int mlxsw_thermal_module_trend_get(struct thermal_zone_device *tzdev, -+ int trip, enum thermal_trend *trend) -+{ -+ struct mlxsw_thermal_module *tz = tzdev->devdata; -+ struct mlxsw_thermal *thermal = tz->parent; -+ -+ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) -+ return -EINVAL; -+ -+ if (tzdev == thermal->tz_highest_dev) -+ return 1; -+ -+ *trend = THERMAL_TREND_STABLE; -+ return 0; -+} -+ - static struct thermal_zone_device_ops mlxsw_thermal_module_ops = { - .bind = mlxsw_thermal_module_bind, - .unbind = mlxsw_thermal_module_unbind, -@@ -625,7 +640,7 @@ static struct thermal_zone_device_ops mlxsw_thermal_module_ops = { - .set_trip_temp = mlxsw_thermal_module_trip_temp_set, - .get_trip_hyst = mlxsw_thermal_module_trip_hyst_get, - .set_trip_hyst = mlxsw_thermal_module_trip_hyst_set, -- .get_trend = mlxsw_thermal_trend_get, -+ .get_trend = mlxsw_thermal_module_trend_get, - }; - - static int mlxsw_thermal_gearbox_temp_get(struct thermal_zone_device *tzdev, -@@ -670,7 +685,7 @@ static struct thermal_zone_device_ops mlxsw_thermal_gearbox_ops = { - .set_trip_temp = mlxsw_thermal_module_trip_temp_set, - .get_trip_hyst = mlxsw_thermal_module_trip_hyst_get, - .set_trip_hyst = mlxsw_thermal_module_trip_hyst_set, -- .get_trend = mlxsw_thermal_trend_get, -+ .get_trend = mlxsw_thermal_module_trend_get, - }; - - static int mlxsw_thermal_get_max_state(struct thermal_cooling_device *cdev, diff --git a/patch/0012-Add-support-for-new-transceivers-types-QSFP-DD-and-Q.patch b/patch/0012-Add-support-for-new-transceivers-types-QSFP-DD-and-Q.patch deleted file mode 100644 index a80cd2372..000000000 --- a/patch/0012-Add-support-for-new-transceivers-types-QSFP-DD-and-Q.patch +++ /dev/null @@ -1,221 +0,0 @@ -From abc748c3e12290443c4ec95aed11b59bf4c948f3 Mon Sep 17 00:00:00 2001 - -From: Vadim Pasternak - -Subject: [backport] Add support for new transceivers types: QSFP-DD and QSFP+ - with Common Management Interface Specification (CMIS). - -The Quad Small Form Factor Pluggable Double Density (QSFP-DD) hardware -specification defines a form factor that supports up to 400 Gbps in -aggregate over an 8x50-Gbps electrical interface. -The Common Management Interface Specification (CMIS) supports multiple -interface settings and speed negotiation controls. -The QSFP-DD supports both optical and copper interfaces. - -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core_env.c | 71 ++++++++++++++++++------ - drivers/net/ethernet/mellanox/mlxsw/reg.h | 2 + - 2 files changed, 54 insertions(+), 19 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_env.c b/drivers/net/ethernet/mellanox/mlxsw/core_env.c -index 03eae1ed2..80c8432dd 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_env.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_env.c -@@ -10,8 +10,14 @@ - #include "item.h" - #include "reg.h" - -+#define MLXSW_REG_MCIA_EEPROM_FLAT_MEMORY BIT(7) -+#define MLXSW_ENV_CMIS_PAGE_OFF 0x0d -+#define MLXSW_ENV_PAGE_MAP(page) (((page) < \ -+ MLXSW_REG_MCIA_TH_PAGE_NUM) ? (page) : (page) + \ -+ MLXSW_ENV_CMIS_PAGE_OFF) -+ - static int mlxsw_env_validate_cable_ident(struct mlxsw_core *core, int id, -- bool *qsfp) -+ bool *qsfp, bool *cmis) - { - char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; - char mcia_pl[MLXSW_REG_MCIA_LEN]; -@@ -25,17 +31,21 @@ static int mlxsw_env_validate_cable_ident(struct mlxsw_core *core, int id, - return err; - mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); - ident = eeprom_tmp[0]; -+ *cmis = false; - switch (ident) { -- case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP_DD: - *qsfp = false; - break; - case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP: /* fall-through */ - case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS: /* fall-through */ -- case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: -+ *qsfp = true; -+ break; - case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD: /* fall-through */ -- case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP_DD: /* fall-through */ - case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS_CMIS: - *qsfp = true; -+ *cmis = true; - break; - default: - return -EINVAL; -@@ -46,8 +56,8 @@ static int mlxsw_env_validate_cable_ident(struct mlxsw_core *core, int id, - - static int - mlxsw_env_query_module_eeprom(struct mlxsw_core *mlxsw_core, int module, -- u16 offset, u16 size, bool qsfp, void *data, -- unsigned int *p_read_size) -+ u16 offset, u16 size, bool qsfp, bool cmis, -+ void *data, unsigned int *p_read_size) - { - char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; - char mcia_pl[MLXSW_REG_MCIA_LEN]; -@@ -77,6 +87,8 @@ mlxsw_env_query_module_eeprom(struct mlxsw_core *mlxsw_core, int module, - } - } - -+ if (cmis) -+ page = MLXSW_ENV_PAGE_MAP(page); - mlxsw_reg_mcia_pack(mcia_pl, module, 0, page, offset, size, i2c_addr); - - err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcia), mcia_pl); -@@ -105,7 +117,8 @@ int mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, int module, - char mcia_pl[MLXSW_REG_MCIA_LEN] = {0}; - char mtmp_pl[MLXSW_REG_MTMP_LEN]; - unsigned int module_temp; -- bool qsfp; -+ bool qsfp, cmis; -+ int page; - int err; - - mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN + module, -@@ -129,21 +142,25 @@ int mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, int module, - */ - - /* Validate module identifier value. */ -- err = mlxsw_env_validate_cable_ident(core, module, &qsfp); -+ err = mlxsw_env_validate_cable_ident(core, module, &qsfp, &cmis); - if (err) - return err; - -- if (qsfp) -- mlxsw_reg_mcia_pack(mcia_pl, module, 0, -- MLXSW_REG_MCIA_TH_PAGE_NUM, -+ if (qsfp) { -+ if (cmis) -+ page = MLXSW_REG_MCIA_TH_PAGE_CMIS_NUM; -+ else -+ page = MLXSW_REG_MCIA_TH_PAGE_NUM; -+ mlxsw_reg_mcia_pack(mcia_pl, module, 0, page, - MLXSW_REG_MCIA_TH_PAGE_OFF + off, - MLXSW_REG_MCIA_TH_ITEM_SIZE, - MLXSW_REG_MCIA_I2C_ADDR_LOW); -- else -+ } else { - mlxsw_reg_mcia_pack(mcia_pl, module, 0, - MLXSW_REG_MCIA_PAGE0_LO, - off, MLXSW_REG_MCIA_TH_ITEM_SIZE, - MLXSW_REG_MCIA_I2C_ADDR_HIGH); -+ } - - err = mlxsw_reg_query(core, MLXSW_REG(mcia), mcia_pl); - if (err) -@@ -163,10 +180,12 @@ int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module, - u16 offset = MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE; - u8 module_rev_id, module_id, diag_mon; - unsigned int read_size; -+ bool unused = false; - int err; - - err = mlxsw_env_query_module_eeprom(mlxsw_core, module, 0, offset, -- false, module_info, &read_size); -+ unused, unused, module_info, -+ &read_size); - if (err) - return err; - -@@ -193,11 +212,13 @@ int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module, - modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN; - } - break; -- case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP_DD: - /* Verify if transceiver provides diagnostic monitoring page */ - err = mlxsw_env_query_module_eeprom(mlxsw_core, module, -- SFP_DIAGMON, 1, false, -- &diag_mon, &read_size); -+ SFP_DIAGMON, 1, unused, -+ unused, &diag_mon, -+ &read_size); - if (err) - return err; - -@@ -210,6 +231,18 @@ int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module, - else - modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN / 2; - break; -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD: /* fall-through */ -+ case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS_CMIS: -+ modinfo->type = ETH_MODULE_SFF_8636; -+ /* Verify if module's EEPROM is a flat memory. In case of flat -+ * memory only page 00h 0-255 bytes can be read. -+ */ -+ if (module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_TYPE_ID] & -+ MLXSW_REG_MCIA_EEPROM_FLAT_MEMORY) -+ modinfo->eeprom_len = ETH_MODULE_SFF_8636_LEN; -+ else -+ modinfo->eeprom_len = ETH_MODULE_SFF_8636_MAX_LEN; -+ break; - default: - return -EINVAL; - } -@@ -224,7 +257,7 @@ int mlxsw_env_get_module_eeprom(struct net_device *netdev, - { - int offset = ee->offset; - unsigned int read_size; -- bool qsfp; -+ bool qsfp, cmis; - int i = 0; - int err; - -@@ -234,13 +267,13 @@ int mlxsw_env_get_module_eeprom(struct net_device *netdev, - memset(data, 0, ee->len); - - /* Validate module identifier type. */ -- err = mlxsw_env_validate_cable_ident(mlxsw_core, module, &qsfp); -+ err = mlxsw_env_validate_cable_ident(mlxsw_core, module, &qsfp, &cmis); - if (err) - return err; - - while (i < ee->len) { - err = mlxsw_env_query_module_eeprom(mlxsw_core, module, offset, -- ee->len - i, qsfp, -+ ee->len - i, qsfp, cmis, - data + i, &read_size); - if (err) { - netdev_err(netdev, "Eeprom query failed\n"); -diff --git a/drivers/net/ethernet/mellanox/mlxsw/reg.h b/drivers/net/ethernet/mellanox/mlxsw/reg.h -index 7ca981464..a5952a03f 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/reg.h -+++ b/drivers/net/ethernet/mellanox/mlxsw/reg.h -@@ -8170,6 +8170,7 @@ MLXSW_ITEM32(reg, mcia, size, 0x08, 0, 16); - #define MLXSW_REG_MCIA_PAGE0_LO_OFF 0xa0 - #define MLXSW_REG_MCIA_TH_ITEM_SIZE 2 - #define MLXSW_REG_MCIA_TH_PAGE_NUM 3 -+#define MLXSW_REG_MCIA_TH_PAGE_CMIS_NUM 2 - #define MLXSW_REG_MCIA_PAGE0_LO 0 - #define MLXSW_REG_MCIA_TH_PAGE_OFF 0x80 - -@@ -8192,6 +8193,7 @@ enum mlxsw_reg_mcia_eeprom_module_info_id { - enum mlxsw_reg_mcia_eeprom_module_info { - MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID, - MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID, -+ MLXSW_REG_MCIA_EEPROM_MODULE_INFO_TYPE_ID, - MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE, - }; - diff --git a/patch/0013-watchdog-mlx-wdt-support-new-watchdog-type-with-long.patch b/patch/0013-watchdog-mlx-wdt-support-new-watchdog-type-with-long.patch deleted file mode 100644 index 8d7a175fc..000000000 --- a/patch/0013-watchdog-mlx-wdt-support-new-watchdog-type-with-long.patch +++ /dev/null @@ -1,372 +0,0 @@ -From cbe97c15b2688abef8745977e85e13dbf1bfc3e8 Mon Sep 17 00:00:00 2001 - -From: Michael Shych - -Subject: [new watchdog type 1/1] watchdog: mlx-wdt: support new watchdog type - with longer timeout period platform_data/mlxreg: support new watchdog type - with longer timeout period platform/x86: mlx-platform: support new watchdog - type with longer timeout - -New programmable logic device can have watchdog type 3 implementation. -It's same as Type 2 with extended maximum timeout period. -Maximum timeout is up-to 65535 sec. -Type 3 HW watchdog implementation can exist on all Mellanox systems. -It is differentiated by WD capability bit. - -Signed-off-by: Michael Shych ---- - drivers/platform/x86/mlx-platform.c | 106 ++++++++++++++++++++++++++++++++++ - drivers/watchdog/mlx_wdt.c | 86 ++++++++++++++++++++++------ - include/linux/platform_data/mlxreg.h | 5 +- - 3 files changed, 179 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c -index 7fac80615..97f60b045 100644 ---- a/drivers/platform/x86/mlx-platform.c -+++ b/drivers/platform/x86/mlx-platform.c -@@ -186,7 +186,9 @@ - #define MLXPLAT_CPLD_WD_RESET_ACT_MASK GENMASK(7, 1) - #define MLXPLAT_CPLD_WD_FAN_ACT_MASK (GENMASK(7, 0) & ~BIT(4)) - #define MLXPLAT_CPLD_WD_COUNT_ACT_MASK (GENMASK(7, 0) & ~BIT(7)) -+#define MLXPLAT_CPLD_WD_CPBLTY_MASK (GENMASK(7, 0) & ~BIT(6)) - #define MLXPLAT_CPLD_WD_DFLT_TIMEOUT 30 -+#define MLXPLAT_CPLD_WD3_DFLT_TIMEOUT 600 - #define MLXPLAT_CPLD_WD_MAX_DEVS 2 - - /* mlxplat_priv - platform private data -@@ -2071,6 +2073,84 @@ static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type2[] = { - }, - }; - -+/* Watchdog type3: hardware implementation version 3 -+ * Can be on all systems. It's differentiated by WD capability bit. -+ * Old systems (MSN2700, MSN2410, MSN2740, MSN2100 and MSN2140) -+ * still have only one main watchdog. -+ */ -+static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type3[] = { -+ { -+ .label = "action", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, -+ .bit = 0, -+ }, -+ { -+ .label = "timeout", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ .health_cntr = MLXPLAT_CPLD_WD3_DFLT_TIMEOUT, -+ }, -+ { -+ .label = "timeleft", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ }, -+ { -+ .label = "ping", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, -+ .bit = 0, -+ }, -+ { -+ .label = "reset", -+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .bit = 6, -+ }, -+}; -+ -+static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type3[] = { -+ { -+ .label = "action", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, -+ .bit = 4, -+ }, -+ { -+ .label = "timeout", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ .health_cntr = MLXPLAT_CPLD_WD3_DFLT_TIMEOUT, -+ }, -+ { -+ .label = "timeleft", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, -+ }, -+ { -+ .label = "ping", -+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, -+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, -+ .bit = 4, -+ }, -+}; -+ -+static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type3[] = { -+ { -+ .data = mlxplat_mlxcpld_wd_main_regs_type3, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type3), -+ .version = MLX_WDT_TYPE3, -+ .identity = "mlx-wdt-main", -+ }, -+ { -+ .data = mlxplat_mlxcpld_wd_aux_regs_type3, -+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type3), -+ .version = MLX_WDT_TYPE3, -+ .identity = "mlx-wdt-aux", -+ }, -+}; -+ - static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) - { - switch (reg) { -@@ -2101,8 +2181,10 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) - case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: -+ case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: - case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: -@@ -2622,6 +2704,27 @@ static int mlxplat_mlxcpld_verify_bus_topology(int *nr) - return 0; - } - -+static int mlxplat_mlxcpld_check_wd_capability(void *regmap) -+{ -+ u32 regval; -+ int i, rc; -+ -+ rc = regmap_read(regmap, MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, -+ ®val); -+ if (rc) -+ return rc; -+ -+ if (!(regval & ~MLXPLAT_CPLD_WD_CPBLTY_MASK)) { -+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type3); i++) { -+ if (mlxplat_wd_data[i]) -+ mlxplat_wd_data[i] = -+ &mlxplat_mlxcpld_wd_set_type3[i]; -+ } -+ } -+ -+ return 0; -+} -+ - static int __init mlxplat_init(void) - { - struct mlxplat_priv *priv; -@@ -2754,6 +2857,9 @@ static int __init mlxplat_init(void) - } - - /* Add WD drivers. */ -+ err = mlxplat_mlxcpld_check_wd_capability(priv->regmap); -+ if (err) -+ goto fail_platform_wd_register; - for (j = 0; j < MLXPLAT_CPLD_WD_MAX_DEVS; j++) { - if (mlxplat_wd_data[j]) { - mlxplat_wd_data[j]->regmap = priv->regmap; -diff --git a/drivers/watchdog/mlx_wdt.c b/drivers/watchdog/mlx_wdt.c -index 74b0622a1..e45701b82 100644 ---- a/drivers/watchdog/mlx_wdt.c -+++ b/drivers/watchdog/mlx_wdt.c -@@ -21,6 +21,7 @@ - #define MLXREG_WDT_CLOCK_SCALE 1000 - #define MLXREG_WDT_MAX_TIMEOUT_TYPE1 32 - #define MLXREG_WDT_MAX_TIMEOUT_TYPE2 255 -+#define MLXREG_WDT_MAX_TIMEOUT_TYPE3 65535 - #define MLXREG_WDT_MIN_TIMEOUT 1 - #define MLXREG_WDT_OPTIONS_BASE (WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | \ - WDIOF_SETTIMEOUT) -@@ -49,6 +50,7 @@ struct mlxreg_wdt { - int tleft_idx; - int ping_idx; - int reset_idx; -+ int regmap_val_sz; - enum mlxreg_wdt_type wdt_type; - }; - -@@ -111,7 +113,8 @@ static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd, - u32 regval, set_time, hw_timeout; - int rc; - -- if (wdt->wdt_type == MLX_WDT_TYPE1) { -+ switch (wdt->wdt_type) { -+ case MLX_WDT_TYPE1: - rc = regmap_read(wdt->regmap, reg_data->reg, ®val); - if (rc) - return rc; -@@ -120,14 +123,32 @@ static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd, - regval = (regval & reg_data->mask) | hw_timeout; - /* Rowndown to actual closest number of sec. */ - set_time = BIT(hw_timeout) / MLXREG_WDT_CLOCK_SCALE; -- } else { -+ rc = regmap_write(wdt->regmap, reg_data->reg, regval); -+ break; -+ case MLX_WDT_TYPE2: -+ set_time = timeout; -+ rc = regmap_write(wdt->regmap, reg_data->reg, timeout); -+ break; -+ case MLX_WDT_TYPE3: -+ /* WD_TYPE3 has 2B set time register */ - set_time = timeout; -- regval = timeout; -+ if (wdt->regmap_val_sz == 1) { -+ regval = timeout & 0xff; -+ rc = regmap_write(wdt->regmap, reg_data->reg, regval); -+ if (!rc) { -+ regval = (timeout & 0xff00) >> 8; -+ rc = regmap_write(wdt->regmap, -+ reg_data->reg + 1, regval); -+ } -+ } else { -+ rc = regmap_write(wdt->regmap, reg_data->reg, timeout); -+ } -+ break; -+ default: -+ return -EINVAL; - } - - wdd->timeout = set_time; -- rc = regmap_write(wdt->regmap, reg_data->reg, regval); -- - if (!rc) { - /* - * Restart watchdog with new timeout period -@@ -147,10 +168,25 @@ static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd) - { - struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); - struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->tleft_idx]; -- u32 regval; -+ u32 regval, msb, lsb; - int rc; - -+ if (wdt->wdt_type == MLX_WDT_TYPE2) { - rc = regmap_read(wdt->regmap, reg_data->reg, ®val); -+ } else { -+ /* WD_TYPE3 has 2 byte timeleft register */ -+ if (wdt->regmap_val_sz == 1) { -+ rc = regmap_read(wdt->regmap, reg_data->reg, &lsb); -+ if (!rc) { -+ rc = regmap_read(wdt->regmap, -+ reg_data->reg + 1, &msb); -+ regval = (msb & 0xff) << 8 | (lsb & 0xff); -+ } -+ } else { -+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val); -+ } -+ } -+ - /* Return 0 timeleft in case of failure register read. */ - return rc == 0 ? regval : 0; - } -@@ -212,13 +248,23 @@ static void mlxreg_wdt_config(struct mlxreg_wdt *wdt, - wdt->wdd.info = &mlxreg_wdt_aux_info; - - wdt->wdt_type = pdata->version; -- if (wdt->wdt_type == MLX_WDT_TYPE2) { -- wdt->wdd.ops = &mlxreg_wdt_ops_type2; -- wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2; -- } else { -+ switch (wdt->wdt_type) { -+ case MLX_WDT_TYPE1: - wdt->wdd.ops = &mlxreg_wdt_ops_type1; - wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1; -+ break; -+ case MLX_WDT_TYPE2: -+ wdt->wdd.ops = &mlxreg_wdt_ops_type2; -+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2; -+ break; -+ case MLX_WDT_TYPE3: -+ wdt->wdd.ops = &mlxreg_wdt_ops_type2; -+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE3; -+ break; -+ default: -+ break; - } -+ - wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT; - } - -@@ -233,26 +279,33 @@ static int mlxreg_wdt_init_timeout(struct mlxreg_wdt *wdt, - - static int mlxreg_wdt_probe(struct platform_device *pdev) - { -+ struct device *dev = &pdev->dev; - struct mlxreg_core_platform_data *pdata; - struct mlxreg_wdt *wdt; - int rc; - -- pdata = dev_get_platdata(&pdev->dev); -+ pdata = dev_get_platdata(dev); - if (!pdata) { -- dev_err(&pdev->dev, "Failed to get platform data.\n"); -+ dev_err(dev, "Failed to get platform data.\n"); - return -EINVAL; - } -- wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); -+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); - if (!wdt) - return -ENOMEM; - -- wdt->wdd.parent = &pdev->dev; -+ wdt->wdd.parent = dev; - wdt->regmap = pdata->regmap; -+ rc = regmap_get_val_bytes(wdt->regmap); -+ if (rc < 0) -+ return -EINVAL; -+ -+ wdt->regmap_val_sz = rc; - mlxreg_wdt_config(wdt, pdata); - - if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOWAYOUT)) - watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT); - watchdog_stop_on_reboot(&wdt->wdd); -+ watchdog_stop_on_unregister(&wdt->wdd); - watchdog_set_drvdata(&wdt->wdd, wdt); - rc = mlxreg_wdt_init_timeout(wdt, pdata); - if (rc) -@@ -265,12 +318,11 @@ static int mlxreg_wdt_probe(struct platform_device *pdev) - set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); - } - mlxreg_wdt_check_card_reset(wdt); -- rc = devm_watchdog_register_device(&pdev->dev, &wdt->wdd); -+ rc = devm_watchdog_register_device(dev, &wdt->wdd); - - register_error: - if (rc) -- dev_err(&pdev->dev, -- "Cannot register watchdog device (err=%d)\n", rc); -+ dev_err(dev, "Cannot register watchdog device (err=%d)\n", rc); - return rc; - } - -diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h -index a2adc3ad4..03d768d73 100644 ---- a/include/linux/platform_data/mlxreg.h -+++ b/include/linux/platform_data/mlxreg.h -@@ -43,10 +43,13 @@ - * - * TYPE1 HW watchdog implementation exist in old systems. - * All new systems have TYPE2 HW watchdog. -+ * TYPE3 HW watchdog can exist on all systems with new CPLD. -+ * TYPE3 is selected by WD capability bit. - */ - enum mlxreg_wdt_type { - MLX_WDT_TYPE1, - MLX_WDT_TYPE2, -+ MLX_WDT_TYPE3, - }; - - /** -@@ -91,7 +94,7 @@ struct mlxreg_core_data { - umode_t mode; - struct device_node *np; - struct mlxreg_hotplug_device hpdev; -- u8 health_cntr; -+ u32 health_cntr; - bool attached; - u8 regnum; - }; diff --git a/patch/0014-mlxsw-i2c-Fix-comment-misspelling.patch b/patch/0014-mlxsw-i2c-Fix-comment-misspelling.patch deleted file mode 100755 index 4da71492f..000000000 --- a/patch/0014-mlxsw-i2c-Fix-comment-misspelling.patch +++ /dev/null @@ -1,31 +0,0 @@ -From bb549427f1fcf0ac05d6d72546847b1d98eaf771 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Sun, 3 Mar 2019 09:12:12 +0000 -Subject: [PATCH backport v4.19 1/8] mlxsw: i2c: Fix comment misspelling - -Fix comment for mlxsw_i2c_write_cmd(). - -Signed-off-by: Vadim Pasternak -Acked-by: Jiri Pirko -Signed-off-by: Ido Schimmel -Signed-off-by: David S. Miller ---- - drivers/net/ethernet/mellanox/mlxsw/i2c.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -index 798bd5aca384..cdeee35b829b 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -@@ -167,7 +167,7 @@ static int mlxsw_i2c_wait_go_bit(struct i2c_client *client, - return err > 0 ? 0 : err; - } - --/* Routine posts a command to ASIC though mail box. */ -+/* Routine posts a command to ASIC through mail box. */ - static int mlxsw_i2c_write_cmd(struct i2c_client *client, - struct mlxsw_i2c *mlxsw_i2c, - int immediate) --- -2.11.0 - diff --git a/patch/0015-mlxsw-i2c-Modify-input-parameter-name-in-initializat.patch b/patch/0015-mlxsw-i2c-Modify-input-parameter-name-in-initializat.patch deleted file mode 100755 index 8f6ebb9bd..000000000 --- a/patch/0015-mlxsw-i2c-Modify-input-parameter-name-in-initializat.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 1d4de996de20444fdb4b2c1ab63bf7fa981dc997 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Sun, 3 Mar 2019 09:12:14 +0000 -Subject: [PATCH backport v4.19 2/8] mlxsw: i2c: Modify input parameter name in - initialization API - -Change input parameter name "resource" to "res" in mlxsw_i2c_init() in -order to align it with mlxsw_pci_init(). - -Signed-off-by: Vadim Pasternak -Acked-by: Jiri Pirko -Signed-off-by: Ido Schimmel -Signed-off-by: David S. Miller ---- - drivers/net/ethernet/mellanox/mlxsw/i2c.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -index cdeee35b829b..0a53c3bff388 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -@@ -414,7 +414,7 @@ static int mlxsw_i2c_skb_transmit(void *bus_priv, struct sk_buff *skb, - static int - mlxsw_i2c_init(void *bus_priv, struct mlxsw_core *mlxsw_core, - const struct mlxsw_config_profile *profile, -- struct mlxsw_res *resources) -+ struct mlxsw_res *res) - { - struct mlxsw_i2c *mlxsw_i2c = bus_priv; - --- -2.11.0 - diff --git a/patch/0016-mlxsw-i2c-Extend-input-parameters-list-of-command-AP.patch b/patch/0016-mlxsw-i2c-Extend-input-parameters-list-of-command-AP.patch deleted file mode 100755 index 0ae87c213..000000000 --- a/patch/0016-mlxsw-i2c-Extend-input-parameters-list-of-command-AP.patch +++ /dev/null @@ -1,199 +0,0 @@ -From 00c82f8ba6cce3434f5f83611ab149cf02d3f801 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Sun, 3 Mar 2019 09:12:15 +0000 -Subject: [PATCH backport v4.19 3/8] mlxsw: i2c: Extend input parameters list - of command API - -Extend input parameters list of command API in mlxsw_i2c_cmd() in order -to support initialization commands. Up until now, only access commands -were supported by I2C driver. - -Signed-off-by: Vadim Pasternak -Acked-by: Jiri Pirko -Signed-off-by: Ido Schimmel -Signed-off-by: David S. Miller ---- - drivers/net/ethernet/mellanox/mlxsw/i2c.c | 120 +++++++++++++++++++++++++----- - 1 file changed, 101 insertions(+), 19 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -index 0a53c3bff388..3f95692be816 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -@@ -20,8 +20,10 @@ - #define MLXSW_I2C_CIR2_OFF_STATUS (MLXSW_I2C_CIR2_BASE + \ - MLXSW_I2C_CIR_STATUS_OFF) - #define MLXSW_I2C_OPMOD_SHIFT 12 -+#define MLXSW_I2C_EVENT_BIT_SHIFT 22 - #define MLXSW_I2C_GO_BIT_SHIFT 23 - #define MLXSW_I2C_CIR_CTRL_STATUS_SHIFT 24 -+#define MLXSW_I2C_EVENT_BIT BIT(MLXSW_I2C_EVENT_BIT_SHIFT) - #define MLXSW_I2C_GO_BIT BIT(MLXSW_I2C_GO_BIT_SHIFT) - #define MLXSW_I2C_GO_OPMODE BIT(MLXSW_I2C_OPMOD_SHIFT) - #define MLXSW_I2C_SET_IMM_CMD (MLXSW_I2C_GO_OPMODE | \ -@@ -33,6 +35,9 @@ - #define MLXSW_I2C_TLV_HDR_SIZE 0x10 - #define MLXSW_I2C_ADDR_WIDTH 4 - #define MLXSW_I2C_PUSH_CMD_SIZE (MLXSW_I2C_ADDR_WIDTH + 4) -+#define MLXSW_I2C_SET_EVENT_CMD (MLXSW_I2C_EVENT_BIT) -+#define MLXSW_I2C_PUSH_EVENT_CMD (MLXSW_I2C_GO_BIT | \ -+ MLXSW_I2C_SET_EVENT_CMD) - #define MLXSW_I2C_READ_SEMA_SIZE 4 - #define MLXSW_I2C_PREP_SIZE (MLXSW_I2C_ADDR_WIDTH + 28) - #define MLXSW_I2C_MBOX_SIZE 20 -@@ -44,6 +49,7 @@ - #define MLXSW_I2C_BLK_MAX 32 - #define MLXSW_I2C_RETRY 5 - #define MLXSW_I2C_TIMEOUT_MSECS 5000 -+#define MLXSW_I2C_MAX_DATA_SIZE 256 - - /** - * struct mlxsw_i2c - device private data: -@@ -213,6 +219,66 @@ static int mlxsw_i2c_write_cmd(struct i2c_client *client, - return 0; - } - -+/* Routine posts initialization command to ASIC through mail box. */ -+static int -+mlxsw_i2c_write_init_cmd(struct i2c_client *client, -+ struct mlxsw_i2c *mlxsw_i2c, u16 opcode, u32 in_mod) -+{ -+ __be32 push_cmd_buf[MLXSW_I2C_PUSH_CMD_SIZE / 4] = { -+ 0, cpu_to_be32(MLXSW_I2C_PUSH_EVENT_CMD) -+ }; -+ __be32 prep_cmd_buf[MLXSW_I2C_PREP_SIZE / 4] = { -+ 0, 0, 0, 0, 0, 0, -+ cpu_to_be32(client->adapter->nr & 0xffff), -+ cpu_to_be32(MLXSW_I2C_SET_EVENT_CMD) -+ }; -+ struct i2c_msg push_cmd = -+ MLXSW_I2C_WRITE_MSG(client, push_cmd_buf, -+ MLXSW_I2C_PUSH_CMD_SIZE); -+ struct i2c_msg prep_cmd = -+ MLXSW_I2C_WRITE_MSG(client, prep_cmd_buf, MLXSW_I2C_PREP_SIZE); -+ u8 status; -+ int err; -+ -+ push_cmd_buf[1] = cpu_to_be32(MLXSW_I2C_PUSH_EVENT_CMD | opcode); -+ prep_cmd_buf[3] = cpu_to_be32(in_mod); -+ prep_cmd_buf[7] = cpu_to_be32(MLXSW_I2C_GO_BIT | opcode); -+ mlxsw_i2c_set_slave_addr((u8 *)prep_cmd_buf, -+ MLXSW_I2C_CIR2_BASE); -+ mlxsw_i2c_set_slave_addr((u8 *)push_cmd_buf, -+ MLXSW_I2C_CIR2_OFF_STATUS); -+ -+ /* Prepare Command Interface Register for transaction */ -+ err = i2c_transfer(client->adapter, &prep_cmd, 1); -+ if (err < 0) -+ return err; -+ else if (err != 1) -+ return -EIO; -+ -+ /* Write out Command Interface Register GO bit to push transaction */ -+ err = i2c_transfer(client->adapter, &push_cmd, 1); -+ if (err < 0) -+ return err; -+ else if (err != 1) -+ return -EIO; -+ -+ /* Wait until go bit is cleared. */ -+ err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, &status); -+ if (err) { -+ dev_err(&client->dev, "HW semaphore is not released"); -+ return err; -+ } -+ -+ /* Validate transaction completion status. */ -+ if (status) { -+ dev_err(&client->dev, "Bad transaction completion status %x\n", -+ status); -+ return -EIO; -+ } -+ -+ return 0; -+} -+ - /* Routine obtains mail box offsets from ASIC register space. */ - static int mlxsw_i2c_get_mbox(struct i2c_client *client, - struct mlxsw_i2c *mlxsw_i2c) -@@ -310,8 +376,8 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, - - /* Routine executes I2C command. */ - static int --mlxsw_i2c_cmd(struct device *dev, size_t in_mbox_size, u8 *in_mbox, -- size_t out_mbox_size, u8 *out_mbox, u8 *status) -+mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, -+ u8 *in_mbox, size_t out_mbox_size, u8 *out_mbox, u8 *status) - { - struct i2c_client *client = to_i2c_client(dev); - struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); -@@ -326,24 +392,40 @@ mlxsw_i2c_cmd(struct device *dev, size_t in_mbox_size, u8 *in_mbox, - - WARN_ON(in_mbox_size % sizeof(u32) || out_mbox_size % sizeof(u32)); - -- reg_size = mlxsw_i2c_get_reg_size(in_mbox); -- num = reg_size / MLXSW_I2C_BLK_MAX; -- if (reg_size % MLXSW_I2C_BLK_MAX) -- num++; -+ if (in_mbox) { -+ reg_size = mlxsw_i2c_get_reg_size(in_mbox); -+ num = reg_size / MLXSW_I2C_BLK_MAX; -+ if (reg_size % MLXSW_I2C_BLK_MAX) -+ num++; - -- if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { -- dev_err(&client->dev, "Could not acquire lock"); -- return -EINVAL; -- } -+ if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { -+ dev_err(&client->dev, "Could not acquire lock"); -+ return -EINVAL; -+ } - -- err = mlxsw_i2c_write(dev, reg_size, in_mbox, num, status); -- if (err) -- goto cmd_fail; -+ err = mlxsw_i2c_write(dev, reg_size, in_mbox, num, status); -+ if (err) -+ goto cmd_fail; -+ -+ /* No out mailbox is case of write transaction. */ -+ if (!out_mbox) { -+ mutex_unlock(&mlxsw_i2c->cmd.lock); -+ return 0; -+ } -+ } else { -+ /* No input mailbox is case of initialization query command. */ -+ reg_size = MLXSW_I2C_MAX_DATA_SIZE; -+ num = reg_size / MLXSW_I2C_BLK_MAX; - -- /* No out mailbox is case of write transaction. */ -- if (!out_mbox) { -- mutex_unlock(&mlxsw_i2c->cmd.lock); -- return 0; -+ if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { -+ dev_err(&client->dev, "Could not acquire lock"); -+ return -EINVAL; -+ } -+ -+ err = mlxsw_i2c_write_init_cmd(client, mlxsw_i2c, opcode, -+ in_mod); -+ if (err) -+ goto cmd_fail; - } - - /* Send read transaction to get output mailbox content. */ -@@ -395,8 +477,8 @@ static int mlxsw_i2c_cmd_exec(void *bus_priv, u16 opcode, u8 opcode_mod, - { - struct mlxsw_i2c *mlxsw_i2c = bus_priv; - -- return mlxsw_i2c_cmd(mlxsw_i2c->dev, in_mbox_size, in_mbox, -- out_mbox_size, out_mbox, status); -+ return mlxsw_i2c_cmd(mlxsw_i2c->dev, opcode, in_mod, in_mbox_size, -+ in_mbox, out_mbox_size, out_mbox, status); - } - - static bool mlxsw_i2c_skb_transmit_busy(void *bus_priv, --- -2.11.0 - diff --git a/patch/0017-mlxsw-i2c-Extend-initialization-by-querying-resource.patch b/patch/0017-mlxsw-i2c-Extend-initialization-by-querying-resource.patch deleted file mode 100755 index 9a5953f40..000000000 --- a/patch/0017-mlxsw-i2c-Extend-initialization-by-querying-resource.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 31f7829248a15f3b9faed7e3bac186b4dbb223cd Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Sun, 3 Mar 2019 09:12:16 +0000 -Subject: [PATCH backport v4.19 4/8] mlxsw: i2c: Extend initialization by - querying resources data - -Extend initialization flow by query requests for chip resources data in -order to obtain chip's specific capabilities, like the number of ports. - -Signed-off-by: Vadim Pasternak -Acked-by: Jiri Pirko -Signed-off-by: Ido Schimmel -Signed-off-by: David S. Miller ---- - drivers/net/ethernet/mellanox/mlxsw/i2c.c | 12 +++++++++++- - 1 file changed, 11 insertions(+), 1 deletion(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -index 3f95692be816..d46a655e3001 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -@@ -14,6 +14,7 @@ - #include "cmd.h" - #include "core.h" - #include "i2c.h" -+#include "resources.h" - - #define MLXSW_I2C_CIR2_BASE 0x72000 - #define MLXSW_I2C_CIR_STATUS_OFF 0x18 -@@ -499,10 +500,19 @@ mlxsw_i2c_init(void *bus_priv, struct mlxsw_core *mlxsw_core, - struct mlxsw_res *res) - { - struct mlxsw_i2c *mlxsw_i2c = bus_priv; -+ char *mbox; -+ int err; - - mlxsw_i2c->core = mlxsw_core; - -- return 0; -+ mbox = mlxsw_cmd_mbox_alloc(); -+ if (!mbox) -+ return -ENOMEM; -+ -+ err = mlxsw_core_resources_query(mlxsw_core, mbox, res); -+ -+ mlxsw_cmd_mbox_free(mbox); -+ return err; - } - - static void mlxsw_i2c_fini(void *bus_priv) --- -2.11.0 - diff --git a/patch/0018-mlxsw-i2c-Extend-initialization-with-querying-firmware.patch b/patch/0018-mlxsw-i2c-Extend-initialization-with-querying-firmware.patch deleted file mode 100755 index ee29e1745..000000000 --- a/patch/0018-mlxsw-i2c-Extend-initialization-with-querying-firmware.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 4b70b08c3bd12b6819bf438b9ac5c646167f8803 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Wed, 29 May 2019 11:47:15 +0300 -Subject: [PATCH backport v4.19 5/8] mlxsw: i2c: Extend initialization with - querying firmware info - -Extend initialization flow with query request for firmware info in -order to obtain firmware version info. -This info is to be provided to minimal driver to support ethtool -get_drvinfo() interface. - -Signed-off-by: Vadim Pasternak -Acked-by: Jiri Pirko -Signed-off-by: Ido Schimmel -Signed-off-by: David S. Miller ---- - drivers/net/ethernet/mellanox/mlxsw/i2c.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -index d46a655e3001..472352977527 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -@@ -509,8 +509,20 @@ mlxsw_i2c_init(void *bus_priv, struct mlxsw_core *mlxsw_core, - if (!mbox) - return -ENOMEM; - -+ err = mlxsw_cmd_query_fw(mlxsw_core, mbox); -+ if (err) -+ goto mbox_put; -+ -+ mlxsw_i2c->bus_info.fw_rev.major = -+ mlxsw_cmd_mbox_query_fw_fw_rev_major_get(mbox); -+ mlxsw_i2c->bus_info.fw_rev.minor = -+ mlxsw_cmd_mbox_query_fw_fw_rev_minor_get(mbox); -+ mlxsw_i2c->bus_info.fw_rev.subminor = -+ mlxsw_cmd_mbox_query_fw_fw_rev_subminor_get(mbox); -+ - err = mlxsw_core_resources_query(mlxsw_core, mbox, res); - -+mbox_put: - mlxsw_cmd_mbox_free(mbox); - return err; - } --- -2.11.0 - diff --git a/patch/0019-mlxsw-i2c-Allow-flexible-setting-of-I2C-transactions.patch b/patch/0019-mlxsw-i2c-Allow-flexible-setting-of-I2C-transactions.patch deleted file mode 100755 index 20709f4bf..000000000 --- a/patch/0019-mlxsw-i2c-Allow-flexible-setting-of-I2C-transactions.patch +++ /dev/null @@ -1,198 +0,0 @@ -From c391e6804e21e80535dae3eb79bc9463552e2ab2 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Wed, 29 May 2019 11:47:16 +0300 -Subject: [PATCH backport v4.19 6/8] mlxsw: i2c: Allow flexible setting of I2C - transactions size - -Current implementation uses fixed size of I2C data transaction buffer. -Allow to set size of I2C transactions according to I2C physical adapter -capability. For that purpose adapter read and write size is obtained -from the I2C physical adapter and buffer size is set according to the -minimum of these two values. If adapter does not provide such info, -default buffer size is to be used. -It allows to improve performance of I2C access to silicon when long -size transactions are used. - -Signed-off-by: Vadim Pasternak -Acked-by: Jiri Pirko -Signed-off-by: Ido Schimmel -Signed-off-by: David S. Miller ---- - drivers/net/ethernet/mellanox/mlxsw/i2c.c | 64 ++++++++++++++++++++++--------- - 1 file changed, 46 insertions(+), 18 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -index 472352977527..ac4f381f0f76 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -@@ -43,11 +43,10 @@ - #define MLXSW_I2C_PREP_SIZE (MLXSW_I2C_ADDR_WIDTH + 28) - #define MLXSW_I2C_MBOX_SIZE 20 - #define MLXSW_I2C_MBOX_OUT_PARAM_OFF 12 --#define MLXSW_I2C_MAX_BUFF_SIZE 32 - #define MLXSW_I2C_MBOX_OFFSET_BITS 20 - #define MLXSW_I2C_MBOX_SIZE_BITS 12 - #define MLXSW_I2C_ADDR_BUF_SIZE 4 --#define MLXSW_I2C_BLK_MAX 32 -+#define MLXSW_I2C_BLK_DEF 32 - #define MLXSW_I2C_RETRY 5 - #define MLXSW_I2C_TIMEOUT_MSECS 5000 - #define MLXSW_I2C_MAX_DATA_SIZE 256 -@@ -62,6 +61,7 @@ - * @dev: I2C device; - * @core: switch core pointer; - * @bus_info: bus info block; -+ * @block_size: maximum block size allowed to pass to under layer; - */ - struct mlxsw_i2c { - struct { -@@ -74,6 +74,7 @@ struct mlxsw_i2c { - struct device *dev; - struct mlxsw_core *core; - struct mlxsw_bus_info bus_info; -+ u16 block_size; - }; - - #define MLXSW_I2C_READ_MSG(_client, _addr_buf, _buf, _len) { \ -@@ -315,20 +316,26 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, - struct i2c_client *client = to_i2c_client(dev); - struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); - unsigned long timeout = msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS); -- u8 tran_buf[MLXSW_I2C_MAX_BUFF_SIZE + MLXSW_I2C_ADDR_BUF_SIZE]; - int off = mlxsw_i2c->cmd.mb_off_in, chunk_size, i, j; - unsigned long end; -+ u8 *tran_buf; - struct i2c_msg write_tran = -- MLXSW_I2C_WRITE_MSG(client, tran_buf, MLXSW_I2C_PUSH_CMD_SIZE); -+ MLXSW_I2C_WRITE_MSG(client, NULL, MLXSW_I2C_PUSH_CMD_SIZE); - int err; - -+ tran_buf = kmalloc(mlxsw_i2c->block_size + MLXSW_I2C_ADDR_BUF_SIZE, -+ GFP_KERNEL); -+ if (!tran_buf) -+ return -ENOMEM; -+ -+ write_tran.buf = tran_buf; - for (i = 0; i < num; i++) { -- chunk_size = (in_mbox_size > MLXSW_I2C_BLK_MAX) ? -- MLXSW_I2C_BLK_MAX : in_mbox_size; -+ chunk_size = (in_mbox_size > mlxsw_i2c->block_size) ? -+ mlxsw_i2c->block_size : in_mbox_size; - write_tran.len = MLXSW_I2C_ADDR_WIDTH + chunk_size; - mlxsw_i2c_set_slave_addr(tran_buf, off); - memcpy(&tran_buf[MLXSW_I2C_ADDR_BUF_SIZE], in_mbox + -- MLXSW_I2C_BLK_MAX * i, chunk_size); -+ mlxsw_i2c->block_size * i, chunk_size); - - j = 0; - end = jiffies + timeout; -@@ -342,9 +349,10 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, - (j++ < MLXSW_I2C_RETRY)); - - if (err != 1) { -- if (!err) -+ if (!err) { - err = -EIO; -- return err; -+ goto mlxsw_i2c_write_exit; -+ } - } - - off += chunk_size; -@@ -355,24 +363,27 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, - err = mlxsw_i2c_write_cmd(client, mlxsw_i2c, 0); - if (err) { - dev_err(&client->dev, "Could not start transaction"); -- return -EIO; -+ err = -EIO; -+ goto mlxsw_i2c_write_exit; - } - - /* Wait until go bit is cleared. */ - err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, p_status); - if (err) { - dev_err(&client->dev, "HW semaphore is not released"); -- return err; -+ goto mlxsw_i2c_write_exit; - } - - /* Validate transaction completion status. */ - if (*p_status) { - dev_err(&client->dev, "Bad transaction completion status %x\n", - *p_status); -- return -EIO; -+ err = -EIO; - } - -- return 0; -+mlxsw_i2c_write_exit: -+ kfree(tran_buf); -+ return err; - } - - /* Routine executes I2C command. */ -@@ -395,8 +406,8 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, - - if (in_mbox) { - reg_size = mlxsw_i2c_get_reg_size(in_mbox); -- num = reg_size / MLXSW_I2C_BLK_MAX; -- if (reg_size % MLXSW_I2C_BLK_MAX) -+ num = reg_size / mlxsw_i2c->block_size; -+ if (reg_size % mlxsw_i2c->block_size) - num++; - - if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { -@@ -416,7 +427,7 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, - } else { - /* No input mailbox is case of initialization query command. */ - reg_size = MLXSW_I2C_MAX_DATA_SIZE; -- num = reg_size / MLXSW_I2C_BLK_MAX; -+ num = reg_size / mlxsw_i2c->block_size; - - if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { - dev_err(&client->dev, "Could not acquire lock"); -@@ -432,8 +443,8 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, - /* Send read transaction to get output mailbox content. */ - read_tran[1].buf = out_mbox; - for (i = 0; i < num; i++) { -- chunk_size = (reg_size > MLXSW_I2C_BLK_MAX) ? -- MLXSW_I2C_BLK_MAX : reg_size; -+ chunk_size = (reg_size > mlxsw_i2c->block_size) ? -+ mlxsw_i2c->block_size : reg_size; - read_tran[1].len = chunk_size; - mlxsw_i2c_set_slave_addr(tran_buf, off); - -@@ -546,6 +557,7 @@ static const struct mlxsw_bus mlxsw_i2c_bus = { - static int mlxsw_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) - { -+ const struct i2c_adapter_quirks *quirks = client->adapter->quirks; - struct mlxsw_i2c *mlxsw_i2c; - u8 status; - int err; -@@ -554,6 +566,22 @@ static int mlxsw_i2c_probe(struct i2c_client *client, - if (!mlxsw_i2c) - return -ENOMEM; - -+ if (quirks) { -+ if ((quirks->max_read_len && -+ quirks->max_read_len < MLXSW_I2C_BLK_DEF) || -+ (quirks->max_write_len && -+ quirks->max_write_len < MLXSW_I2C_BLK_DEF)) { -+ dev_err(&client->dev, "Insufficient transaction buffer length\n"); -+ return -EOPNOTSUPP; -+ } -+ -+ mlxsw_i2c->block_size = max_t(u16, MLXSW_I2C_BLK_DEF, -+ min_t(u16, quirks->max_read_len, -+ quirks->max_write_len)); -+ } else { -+ mlxsw_i2c->block_size = MLXSW_I2C_BLK_DEF; -+ } -+ - i2c_set_clientdata(client, mlxsw_i2c); - mutex_init(&mlxsw_i2c->cmd.lock); - --- -2.11.0 - diff --git a/patch/0020-mlxsw-core-Set-different-thermal-polling-time-based.patch b/patch/0020-mlxsw-core-Set-different-thermal-polling-time-based.patch deleted file mode 100755 index 02bbceaf2..000000000 --- a/patch/0020-mlxsw-core-Set-different-thermal-polling-time-based.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 59a688a401d122af904acbe658502dcbbac813d5 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Wed, 13 Feb 2019 11:28:48 +0000 -Subject: [PATCH backport v4.19 7/8] mlxsw: core: Set different thermal polling - time based on bus frequency capability - -Add low frequency bus capability in order to allow core functionality -separation based on bus type. Driver could run over PCIe, which is -considered as high frequency bus or I2C, which is considered as low -frequency bus. In the last case time setting, for example, for thermal -polling interval, should be increased. - -Use different thermal monitoring based on bus type. For I2C bus time is -set to 20 seconds, while for PCIe 1 second polling interval is used. - -Signed-off-by: Vadim Pasternak -Reviewed-by: Jiri Pirko -Signed-off-by: Ido Schimmel -Signed-off-by: David S. Miller ---- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 6 +++--- - drivers/net/ethernet/mellanox/mlxsw/i2c.c | 1 + - 2 files changed, 4 insertions(+), 3 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index 775343a2568e..d77c4c82b23a 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -807,7 +807,7 @@ mlxsw_thermal_module_tz_init(struct mlxsw_thermal_module *module_tz) - return err; - } - -- module_tz->mode = THERMAL_DEVICE_DISABLED; -+ module_tz->mode = THERMAL_DEVICE_ENABLED; - return 0; - } - -@@ -925,7 +925,7 @@ mlxsw_thermal_gearbox_tz_init(struct mlxsw_thermal_module *gearbox_tz) - if (IS_ERR(gearbox_tz->tzdev)) - return PTR_ERR(gearbox_tz->tzdev); - -- gearbox_tz->mode = THERMAL_DEVICE_DISABLED; -+ gearbox_tz->mode = THERMAL_DEVICE_ENABLED; - return 0; - } - -@@ -1095,7 +1095,7 @@ int mlxsw_thermal_init(struct mlxsw_core *core, - if (err) - goto err_unreg_modules_tzdev; - -- thermal->mode = THERMAL_DEVICE_DISABLED; -+ thermal->mode = THERMAL_DEVICE_ENABLED; - thermal->initializing = false; - *p_thermal = thermal; - return 0; -diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -index ac4f381f0f76..95f408d0e103 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c -@@ -635,6 +635,7 @@ static int mlxsw_i2c_probe(struct i2c_client *client, - mlxsw_i2c->bus_info.device_kind = id->name; - mlxsw_i2c->bus_info.device_name = client->name; - mlxsw_i2c->bus_info.dev = &client->dev; -+ mlxsw_i2c->bus_info.low_frequency = true; - mlxsw_i2c->dev = &client->dev; - - err = mlxsw_core_bus_device_register(&mlxsw_i2c->bus_info, --- -2.11.0 - diff --git a/patch/0021-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch b/patch/0021-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch deleted file mode 100644 index 2e6f560a9..000000000 --- a/patch/0021-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch +++ /dev/null @@ -1,64 +0,0 @@ -From e480c548db43d81659874c4fa89eeda9f4096e5c Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Wed, 3 Jun 2020 10:59:20 +0300 -Subject: [backport bugfix] platform/x86: mlx-platform: Remove PSU EEPROM - configuration - -Remove PSU EEPROM configuration for systems class equipped with -Mellanox chip Spectrume-2. Till now all the systems from this class -used few types of power units, all equipped with EEPROM device with -address space two bytes. Thus, all these devices have been handled by -EEPROM driver "24c32". -There is a new requirement is to support power unit replacement by "off -the shelf" device, matching electrical required parameters. Such device -could be equpped with different EEPROM type, which could be one byte -address space adressing or even could be not equipped with EEPROM. -In such case "24c32" will not work. - -Fixes: 1bd42d94ccab ("platform/x86: mlx-platform: Add support for new 200G IB and Ethernet systems") -Signed-off-by: Vadim Pasternak ---- - drivers/platform/x86/mlx-platform.c | 15 ++------------- - 1 file changed, 2 insertions(+), 13 deletions(-) - -diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c -index 208f4a476..d6c87fb88 100644 ---- a/drivers/platform/x86/mlx-platform.c -+++ b/drivers/platform/x86/mlx-platform.c -@@ -329,15 +329,6 @@ static struct i2c_board_info mlxplat_mlxcpld_psu[] = { - }, - }; - --static struct i2c_board_info mlxplat_mlxcpld_ng_psu[] = { -- { -- I2C_BOARD_INFO("24c32", 0x51), -- }, -- { -- I2C_BOARD_INFO("24c32", 0x50), -- }, --}; -- - static struct i2c_board_info mlxplat_mlxcpld_pwr[] = { - { - I2C_BOARD_INFO("dps460", 0x59), -@@ -762,15 +753,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_psu_items_data[] = { - .label = "psu1", - .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, - .mask = BIT(0), -- .hpdev.brdinfo = &mlxplat_mlxcpld_ng_psu[0], -- .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "psu2", - .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, - .mask = BIT(1), -- .hpdev.brdinfo = &mlxplat_mlxcpld_ng_psu[1], -- .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - }; - --- -2.11.0 - diff --git a/patch/0022-mlxsw-core-Increase-critical-threshold-for-ASIC-ther.patch b/patch/0022-mlxsw-core-Increase-critical-threshold-for-ASIC-ther.patch deleted file mode 100644 index dab910ead..000000000 --- a/patch/0022-mlxsw-core-Increase-critical-threshold-for-ASIC-ther.patch +++ /dev/null @@ -1,42 +0,0 @@ -From f79a25e99568d19ed2cd39de4650ced66de4ab5d Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Thu, 31 Dec 2020 19:27:02 +0200 -Subject: [PATCH mlxsw/net-next 1/1] mlxsw: core: Increase critical threshold - for ASIC thermal zone - -Increase critical threshold for ASIC thermal zone from 110C to 140C -according to the system hardware requirements. All the supported ASICs -(SX, Spectrum1, Spectune2, Spectrum3) could be still operational with -ASIC temperature below 140C. - -According to the system requirements software thermal protection is the -second level of protection, while the first level of protection should -be performed by firmware. So firmware could decide to perform system -thermal shutdown in case the temperature is below 140C. So firmware can -decide to perform system thermal shutdown in case the temperature is -below 140C. In case firmware did not perform it and ASIC temperature -reached 140C, the second level of thermal protection will be performed -by software. - -Fixes: 41e760841d26 ("mlxsw: core: Replace thermal temperature trips with defines") -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index 141e3655e211..d575aa469517 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -19,7 +19,7 @@ - #define MLXSW_THERMAL_ASIC_TEMP_NORM 75000 /* 75C */ - #define MLXSW_THERMAL_ASIC_TEMP_HIGH 85000 /* 85C */ - #define MLXSW_THERMAL_ASIC_TEMP_HOT 105000 /* 105C */ --#define MLXSW_THERMAL_ASIC_TEMP_CRIT 110000 /* 110C */ -+#define MLXSW_THERMAL_ASIC_TEMP_CRIT 140000 /* 140C */ - #define MLXSW_THERMAL_MODULE_TEMP_NORM 60000 /* 60C */ - #define MLXSW_THERMAL_MODULE_TEMP_HIGH 70000 /* 70C */ - #define MLXSW_THERMAL_MODULE_TEMP_HOT 80000 /* 80C */ --- -2.11.0 - diff --git a/patch/0023-mlxsw-core-Add-validation-of-transceiver-temperature.patch b/patch/0023-mlxsw-core-Add-validation-of-transceiver-temperature.patch deleted file mode 100644 index 406aa22ed..000000000 --- a/patch/0023-mlxsw-core-Add-validation-of-transceiver-temperature.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 8845c46138eca323a3fe9cd1332190f092f35888 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Thu, 7 Jan 2021 12:56:21 +0200 -Subject: [PATCH mlxsw/backport 2/2] mlxsw: core: Add validation of transceiver - temperature thresholds - -Validate thresholds to avoid a single failure due to some transceiver -unreliability. Ignore the last readouts in case warning temperature is -above alarm temperature, since it can cause unexpected thermal -shutdown. Stay with the previous values and refresh threshold within -the next iteration. - -This is the rare scenario, but somehow once it has been observed at a -customer site. - -Fixes: 6a79507cfe94 ("mlxsw: core: Extend thermal module with per QSFP module thermal zones") -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 11 +++++++---- - 1 file changed, 7 insertions(+), 4 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index 54d0e8b8d..477c3ed53 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -183,6 +183,12 @@ mlxsw_thermal_module_trips_update(struct device *dev, struct mlxsw_core *core, - if (err) - return err; - -+ if (crit_temp > emerg_temp) { -+ dev_warn(dev, "%s : Critical threshold %d is above emergency threshold %d\n", -+ tz->tzdev->type, crit_temp, emerg_temp); -+ return 0; -+ } -+ - /* According to the system thermal requirements, the thermal zones are - * defined with four trip points. The critical and emergency - * temperature thresholds, provided by QSFP module are set as "active" -@@ -197,11 +203,8 @@ mlxsw_thermal_module_trips_update(struct device *dev, struct mlxsw_core *core, - tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = crit_temp; - tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = crit_temp; - tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = emerg_temp; -- if (emerg_temp > crit_temp) -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp + -+ tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp + - MLXSW_THERMAL_MODULE_TEMP_SHIFT; -- else -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp; - - return 0; - } --- -2.11.0 - diff --git a/patch/0024-mlxsw-core-Fix-memory-leak-on-module-removal.patch b/patch/0024-mlxsw-core-Fix-memory-leak-on-module-removal.patch deleted file mode 100644 index 2ee512fb1..000000000 --- a/patch/0024-mlxsw-core-Fix-memory-leak-on-module-removal.patch +++ /dev/null @@ -1,56 +0,0 @@ -From adc80b6cfedff6dad8b93d46a5ea2775fd5af9ec Mon Sep 17 00:00:00 2001 -From: Ido Schimmel -Date: Sat, 24 Oct 2020 16:37:32 +0300 -Subject: [PATCH] mlxsw: core: Fix memory leak on module removal - -Free the devlink instance during the teardown sequence in the non-reload -case to avoid the following memory leak. - -unreferenced object 0xffff888232895000 (size 2048): - comm "modprobe", pid 1073, jiffies 4295568857 (age 164.871s) - hex dump (first 32 bytes): - 00 01 00 00 00 00 ad de 22 01 00 00 00 00 ad de ........"....... - 10 50 89 32 82 88 ff ff 10 50 89 32 82 88 ff ff .P.2.....P.2.... - backtrace: - [<00000000c704e9a6>] __kmalloc+0x13a/0x2a0 - [<00000000ee30129d>] devlink_alloc+0xff/0x760 - [<0000000092ab3e5d>] 0xffffffffa042e5b0 - [<000000004f3f8a31>] 0xffffffffa042f6ad - [<0000000092800b4b>] 0xffffffffa0491df3 - [<00000000c4843903>] local_pci_probe+0xcb/0x170 - [<000000006993ded7>] pci_device_probe+0x2c2/0x4e0 - [<00000000a8e0de75>] really_probe+0x2c5/0xf90 - [<00000000d42ba75d>] driver_probe_device+0x1eb/0x340 - [<00000000bcc95e05>] device_driver_attach+0x294/0x300 - [<000000000e2bc177>] __driver_attach+0x167/0x2f0 - [<000000007d44cd6e>] bus_for_each_dev+0x148/0x1f0 - [<000000003cd5a91e>] driver_attach+0x45/0x60 - [<000000000041ce51>] bus_add_driver+0x3b8/0x720 - [<00000000f5215476>] driver_register+0x230/0x4e0 - [<00000000d79356f5>] __pci_register_driver+0x190/0x200 - -Fixes: a22712a96291 ("mlxsw: core: Fix devlink unregister flow") -Signed-off-by: Ido Schimmel -Reported-by: Vadim Pasternak -Tested-by: Oleksandr Shamray -Signed-off-by: Jakub Kicinski ---- - drivers/net/ethernet/mellanox/mlxsw/core.c | 2 ++ - 1 files changed, 2 insertions(+), 0 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c -index b67d610..e2faedc 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core.c -@@ -1135,6 +1135,8 @@ void mlxsw_core_bus_device_unregister(struct mlxsw_core *mlxsw_core, - if (!reload) - devlink_resources_unregister(devlink, NULL); - mlxsw_core->bus->fini(mlxsw_core->bus_priv); -+ if (!reload) -+ devlink_free(devlink); - - return; - --- -1.7.1 - diff --git a/patch/0025-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch b/patch/0025-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch deleted file mode 100644 index 1ec4f1f80..000000000 --- a/patch/0025-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 2bf5046bdb649908df8bcc0a012c56eee931a9af Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Wed, 25 Nov 2020 12:10:55 +0200 -Subject: [PATCH] platform/x86: mlx-platform: Remove PSU EEPROM from default platform configuration - -Remove PSU EEPROM configuration for systems class equipped with -Mellanox chip Spectrum and Celeron CPU - system types MSN2700, MSN2100. -Till now all the systems from this class used few types of power units, -all equipped with EEPROM device with address space two bytes. Thus, all -these devices have been handled by EEPROM driver "24c02". - -There is a new requirement is to support power unit replacement by "off -the shelf" device, matching electrical required parameters. Such device -can be equipped with different EEPROM type, which could be one byte -address space addressing or even could be not equipped with EEPROM. -In such case "24c02" will not work. - -Fixes: c6acad68e ("platform/mellanox: mlxreg-hotplug: Modify to use a regmap interface") -Fixes: ba814fdd0 ("platform/x86: mlx-platform: Use defines for bus assignment") -Signed-off-by: Vadim Pasternak -Link: https://lore.kernel.org/r/20201125101056.174708-2-vadimp@nvidia.com -Signed-off-by: Hans de Goede ---- - drivers/platform/x86/mlx-platform.c | 6 ++---- - 1 files changed, 2 insertions(+), 4 deletions(-) - -diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c -index d6c87fb..6400f1d 100644 ---- a/drivers/platform/x86/mlx-platform.c -+++ b/drivers/platform/x86/mlx-platform.c -@@ -375,15 +375,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { - .label = "psu1", - .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, - .mask = BIT(0), -- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0], -- .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "psu2", - .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, - .mask = BIT(1), -- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1], -- .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - }; - --- -1.7.1 - diff --git a/patch/0026-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch b/patch/0026-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch deleted file mode 100644 index 8c763be3f..000000000 --- a/patch/0026-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 912b341585e302ee44fc5a2733f7bcf505e2c86f Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Wed, 25 Nov 2020 12:10:56 +0200 -Subject: [PATCH] platform/x86: mlx-platform: Remove PSU EEPROM from MSN274x platform configuration - -Remove PSU EEPROM configuration for systems class equipped with -Mellanox chip Spectrum and ATOM CPU - system types MSN274x. Till now -all the systems from this class used few types of power units, all -equipped with EEPROM device with address space two bytes. Thus, all -these devices have been handled by EEPROM driver "24c02". - -There is a new requirement is to support power unit replacement by "off -the shelf" device, matching electrical required parameters. Such device -can be equipped with different EEPROM type, which could be one byte -address space addressing or even could be not equipped with EEPROM. -In such case "24c02" will not work. - -Fixes: ef08e14a3 ("platform/x86: mlx-platform: Add support for new msn274x system type") -Signed-off-by: Vadim Pasternak -Link: https://lore.kernel.org/r/20201125101056.174708-3-vadimp@nvidia.com -Signed-off-by: Hans de Goede ---- - drivers/platform/x86/mlx-platform.c | 6 ++---- - 1 files changed, 2 insertions(+), 4 deletions(-) - -diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c -index 6400f1d..c1d1a78 100644 ---- a/drivers/platform/x86/mlx-platform.c -+++ b/drivers/platform/x86/mlx-platform.c -@@ -593,15 +593,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn274x_psu_items_data[] = { - .label = "psu1", - .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, - .mask = BIT(0), -- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0], -- .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - { - .label = "psu2", - .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, - .mask = BIT(1), -- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1], -- .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, -+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, - }, - }; - --- -1.7.1 - diff --git a/patch/0027-mlxsw-core-Remove-critical-trip-point-from-thermal-z.patch b/patch/0027-mlxsw-core-Remove-critical-trip-point-from-thermal-z.patch deleted file mode 100644 index f56f8d570..000000000 --- a/patch/0027-mlxsw-core-Remove-critical-trip-point-from-thermal-z.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 97a18fe05a59a76513ea4e11560760d0450e6f6c Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Sun, 10 Jan 2021 19:27:52 +0200 -Subject: [PATCH net-next 1/1] mlxsw: core: Remove critical trip point from - thermal zones - -Disable software thermal protection by removing critical trip points -for the all thermal zones. - -According to the system requirements software should never perform -system thermal protection, since all the systems implement two levels -of thermal protection: the first one is performed by firmware, the -second, in case firmware was not able to perform protection, by -hardware, while the temperature threshold for hardware protection is -higher than for firmware. - -In both cases, when critical temperature is reached, system will be -shutdown. - -Signed-off-by: Vadim Pasternak ---- - drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 19 ++----------------- - 1 file changed, 2 insertions(+), 17 deletions(-) - -diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -index 477c3ed53..9afac8a04 100644 ---- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c -@@ -19,11 +19,9 @@ - #define MLXSW_THERMAL_ASIC_TEMP_NORM 75000 /* 75C */ - #define MLXSW_THERMAL_ASIC_TEMP_HIGH 85000 /* 85C */ - #define MLXSW_THERMAL_ASIC_TEMP_HOT 105000 /* 105C */ --#define MLXSW_THERMAL_ASIC_TEMP_CRIT 140000 /* 140C */ - #define MLXSW_THERMAL_MODULE_TEMP_NORM 60000 /* 60C */ - #define MLXSW_THERMAL_MODULE_TEMP_HIGH 70000 /* 70C */ - #define MLXSW_THERMAL_MODULE_TEMP_HOT 80000 /* 80C */ --#define MLXSW_THERMAL_MODULE_TEMP_CRIT 90000 /* 90C */ - #define MLXSW_THERMAL_HYSTERESIS_TEMP 5000 /* 5C */ - #define MLXSW_THERMAL_MODULE_TEMP_SHIFT (MLXSW_THERMAL_HYSTERESIS_TEMP * 2) - #define MLXSW_THERMAL_ZONE_MAX_NAME 16 -@@ -49,7 +47,6 @@ enum mlxsw_thermal_trips { - MLXSW_THERMAL_TEMP_TRIP_NORM, - MLXSW_THERMAL_TEMP_TRIP_HIGH, - MLXSW_THERMAL_TEMP_TRIP_HOT, -- MLXSW_THERMAL_TEMP_TRIP_CRIT, - }; - - struct mlxsw_thermal_trip { -@@ -79,16 +76,9 @@ static const struct mlxsw_thermal_trip default_thermal_trips[] = { - { /* Warning */ - .type = THERMAL_TRIP_HOT, - .temp = MLXSW_THERMAL_ASIC_TEMP_HOT, -- .hyst = MLXSW_THERMAL_HYSTERESIS_TEMP, - .min_state = MLXSW_THERMAL_MAX_STATE, - .max_state = MLXSW_THERMAL_MAX_STATE, - }, -- { /* Critical - soft poweroff */ -- .type = THERMAL_TRIP_CRITICAL, -- .temp = MLXSW_THERMAL_ASIC_TEMP_CRIT, -- .min_state = MLXSW_THERMAL_MAX_STATE, -- .max_state = MLXSW_THERMAL_MAX_STATE, -- } - }; - - #define MLXSW_THERMAL_NUM_TRIPS ARRAY_SIZE(default_thermal_trips) -@@ -161,7 +151,6 @@ mlxsw_thermal_module_trips_reset(struct mlxsw_thermal_module *tz) - tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = MLXSW_THERMAL_MODULE_TEMP_NORM; - tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = MLXSW_THERMAL_MODULE_TEMP_HIGH; - tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = MLXSW_THERMAL_MODULE_TEMP_HOT; -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = MLXSW_THERMAL_MODULE_TEMP_CRIT; - } - - static int -@@ -203,8 +192,6 @@ mlxsw_thermal_module_trips_update(struct device *dev, struct mlxsw_core *core, - tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = crit_temp; - tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = crit_temp; - tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = emerg_temp; -- tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp + -- MLXSW_THERMAL_MODULE_TEMP_SHIFT; - - return 0; - } -@@ -376,8 +363,7 @@ static int mlxsw_thermal_set_trip_temp(struct thermal_zone_device *tzdev, - { - struct mlxsw_thermal *thermal = tzdev->devdata; - -- if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS || -- temp > MLXSW_THERMAL_ASIC_TEMP_CRIT) -+ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) - return -EINVAL; - - thermal->trips[trip].temp = temp; -@@ -588,8 +574,7 @@ mlxsw_thermal_module_trip_temp_set(struct thermal_zone_device *tzdev, - { - struct mlxsw_thermal_module *tz = tzdev->devdata; - -- if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS || -- temp > tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp) -+ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) - return -EINVAL; - - tz->trips[trip].temp = temp; --- -2.11.0 - diff --git a/patch/0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch b/patch/0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch deleted file mode 100644 index 55b93426b..000000000 --- a/patch/0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 163b00cde7cf2206e248789d2780121ad5e6a70b Mon Sep 17 00:00:00 2001 -From: Wei Wang -Date: Tue, 12 Nov 2019 12:42:23 -0800 -Subject: [PATCH] thermal: Fix deadlock in thermal thermal_zone_device_check - -1851799e1d29 ("thermal: Fix use-after-free when unregistering thermal zone -device") changed cancel_delayed_work to cancel_delayed_work_sync to avoid -a use-after-free issue. However, cancel_delayed_work_sync could be called -insides the WQ causing deadlock. - -[54109.642398] c0 1162 kworker/u17:1 D 0 11030 2 0x00000000 -[54109.642437] c0 1162 Workqueue: thermal_passive_wq thermal_zone_device_check -[54109.642447] c0 1162 Call trace: -[54109.642456] c0 1162 __switch_to+0x138/0x158 -[54109.642467] c0 1162 __schedule+0xba4/0x1434 -[54109.642480] c0 1162 schedule_timeout+0xa0/0xb28 -[54109.642492] c0 1162 wait_for_common+0x138/0x2e8 -[54109.642511] c0 1162 flush_work+0x348/0x40c -[54109.642522] c0 1162 __cancel_work_timer+0x180/0x218 -[54109.642544] c0 1162 handle_thermal_trip+0x2c4/0x5a4 -[54109.642553] c0 1162 thermal_zone_device_update+0x1b4/0x25c -[54109.642563] c0 1162 thermal_zone_device_check+0x18/0x24 -[54109.642574] c0 1162 process_one_work+0x3cc/0x69c -[54109.642583] c0 1162 worker_thread+0x49c/0x7c0 -[54109.642593] c0 1162 kthread+0x17c/0x1b0 -[54109.642602] c0 1162 ret_from_fork+0x10/0x18 -[54109.643051] c0 1162 kworker/u17:2 D 0 16245 2 0x00000000 -[54109.643067] c0 1162 Workqueue: thermal_passive_wq thermal_zone_device_check -[54109.643077] c0 1162 Call trace: -[54109.643085] c0 1162 __switch_to+0x138/0x158 -[54109.643095] c0 1162 __schedule+0xba4/0x1434 -[54109.643104] c0 1162 schedule_timeout+0xa0/0xb28 -[54109.643114] c0 1162 wait_for_common+0x138/0x2e8 -[54109.643122] c0 1162 flush_work+0x348/0x40c -[54109.643131] c0 1162 __cancel_work_timer+0x180/0x218 -[54109.643141] c0 1162 handle_thermal_trip+0x2c4/0x5a4 -[54109.643150] c0 1162 thermal_zone_device_update+0x1b4/0x25c -[54109.643159] c0 1162 thermal_zone_device_check+0x18/0x24 -[54109.643167] c0 1162 process_one_work+0x3cc/0x69c -[54109.643177] c0 1162 worker_thread+0x49c/0x7c0 -[54109.643186] c0 1162 kthread+0x17c/0x1b0 -[54109.643195] c0 1162 ret_from_fork+0x10/0x18 -[54109.644500] c0 1162 cat D 0 7766 1 0x00000001 -[54109.644515] c0 1162 Call trace: -[54109.644524] c0 1162 __switch_to+0x138/0x158 -[54109.644536] c0 1162 __schedule+0xba4/0x1434 -[54109.644546] c0 1162 schedule_preempt_disabled+0x80/0xb0 -[54109.644555] c0 1162 __mutex_lock+0x3a8/0x7f0 -[54109.644563] c0 1162 __mutex_lock_slowpath+0x14/0x20 -[54109.644575] c0 1162 thermal_zone_get_temp+0x84/0x360 -[54109.644586] c0 1162 temp_show+0x30/0x78 -[54109.644609] c0 1162 dev_attr_show+0x5c/0xf0 -[54109.644628] c0 1162 sysfs_kf_seq_show+0xcc/0x1a4 -[54109.644636] c0 1162 kernfs_seq_show+0x48/0x88 -[54109.644656] c0 1162 seq_read+0x1f4/0x73c -[54109.644664] c0 1162 kernfs_fop_read+0x84/0x318 -[54109.644683] c0 1162 __vfs_read+0x50/0x1bc -[54109.644692] c0 1162 vfs_read+0xa4/0x140 -[54109.644701] c0 1162 SyS_read+0xbc/0x144 -[54109.644708] c0 1162 el0_svc_naked+0x34/0x38 -[54109.845800] c0 1162 D 720.000s 1->7766->7766 cat [panic] - -Fixes: 1851799e1d29 ("thermal: Fix use-after-free when unregistering thermal zone device") -Cc: stable@vger.kernel.org -Signed-off-by: Wei Wang -Signed-off-by: Zhang Rui ---- - drivers/thermal/thermal_core.c | 2 ++-- - 1 file changed, 1 insertions(+), 1 deletions(-) - -diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c -index 7b0ffc1c0..f5a84db55 100644 ---- a/drivers/thermal/thermal_core.c -+++ b/drivers/thermal/thermal_core.c -@@ -296,7 +296,7 @@ static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, - mod_delayed_work(system_freezable_wq, &tz->poll_queue, - msecs_to_jiffies(delay)); - else -- cancel_delayed_work(&tz->poll_queue); -+ cancel_delayed_work_sync(&tz->poll_queue); - } - - static void monitor_thermal_zone(struct thermal_zone_device *tz) diff --git a/patch/0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch b/patch/0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch deleted file mode 100644 index 2ee1b2e05..000000000 --- a/patch/0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch +++ /dev/null @@ -1,874 +0,0 @@ -From 4218cfb886d2de82677a3fe1aeb44858c38f81bd Mon Sep 17 00:00:00 2001 -From: Oleksandr Shamray -Date: Thu, 12 Nov 2020 16:16:50 +0200 -Subject: [PATCH 1/4] hwmon: (pmbus) Add support for MPS Multi-phase mp2975 - controller - -Add support for mp295 device from Monolithic Power Systems, Inc. (MPS) -vendor. This is a dual-loop, digital, multi-phase controller. -This device: -- Supports two power rail. -- Provides 8 pulse-width modulations (PWMs), and can be configured up - to 8-phase operation for rail 1 and up to 4-phase operation for rail - 2. -- Supports two pages 0 and 1 for telemetry and also pages 2 and 3 for - configuration. -- Can configured VOUT readout in direct or VID format and allows - setting of different formats on rails 1 and 2. For VID the following - protocols are available: VR13 mode with 5-mV DAC; VR13 mode with - 10-mV DAC, IMVP9 mode with 5-mV DAC. - -Signed-off-by: Vadim Pasternak ---- - drivers/hwmon/pmbus/Kconfig | 10 + - drivers/hwmon/pmbus/Makefile | 1 + - drivers/hwmon/pmbus/mp2975.c | 804 +++++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 815 insertions(+) - create mode 100644 drivers/hwmon/pmbus/mp2975.c - -diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig -index 14c0572..fbf326e 100644 ---- a/drivers/hwmon/pmbus/Kconfig -+++ b/drivers/hwmon/pmbus/Kconfig -@@ -145,6 +145,16 @@ config SENSORS_MAX8688 - This driver can also be built as a module. If so, the module will - be called max8688. - -+config SENSORS_MP2975 -+ tristate "MPS MP2975" -+ default n -+ help -+ If you say yes here you get hardware monitoring support for MPS -+ MP2975 Dual Loop Digital Multi-Phase Controller. -+ -+ This driver can also be built as a module. If so, the module will -+ be called mp2975. -+ - config SENSORS_TPS40422 - tristate "TI TPS40422" - default n -diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile -index edc7c315c..3f83d8582 100644 ---- a/drivers/hwmon/pmbus/Makefile -+++ b/drivers/hwmon/pmbus/Makefile -@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o - obj-$(CONFIG_SENSORS_MAX34440) += max34440.o - obj-$(CONFIG_SENSORS_DNI_DPS460) += dni_dps460.o - obj-$(CONFIG_SENSORS_MAX8688) += max8688.o -+obj-$(CONFIG_SENSORS_MP2975) += mp2975.o - obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o - obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o - obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o -diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c -new file mode 100644 -index 0000000..a0c2bcf ---- /dev/null -+++ b/drivers/hwmon/pmbus/mp2975.c -@@ -0,0 +1,804 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers -+ * -+ * Copyright (c) 2020 Nvidia Technologies. All rights reserved. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include "pmbus.h" -+ -+/* Vendor specific registers. */ -+#define MP2975_MFR_APS_HYS_R2 0x0d -+#define MP2975_MFR_SLOPE_TRIM3 0x1d -+#define MP2975_MFR_VR_MULTI_CONFIG_R1 0x0d -+#define MP2975_MFR_VR_MULTI_CONFIG_R2 0x1d -+#define MP2975_MFR_APS_DECAY_ADV 0x56 -+#define MP2975_MFR_DC_LOOP_CTRL 0x59 -+#define MP2975_MFR_OCP_UCP_PHASE_SET 0x65 -+#define MP2975_MFR_VR_CONFIG1 0x68 -+#define MP2975_MFR_READ_CS1_2 0x82 -+#define MP2975_MFR_READ_CS3_4 0x83 -+#define MP2975_MFR_READ_CS5_6 0x84 -+#define MP2975_MFR_READ_CS7_8 0x85 -+#define MP2975_MFR_READ_CS9_10 0x86 -+#define MP2975_MFR_READ_CS11_12 0x87 -+#define MP2975_MFR_READ_IOUT_PK 0x90 -+#define MP2975_MFR_READ_POUT_PK 0x91 -+#define MP2975_MFR_READ_VREF_R1 0xa1 -+#define MP2975_MFR_READ_VREF_R2 0xa3 -+#define MP2975_MFR_OVP_TH_SET 0xe5 -+#define MP2975_MFR_UVP_SET 0xe6 -+ -+#define MP2975_VOUT_FORMAT BIT(15) -+#define MP2975_VID_STEP_SEL_R1 BIT(4) -+#define MP2975_IMVP9_EN_R1 BIT(13) -+#define MP2975_VID_STEP_SEL_R2 BIT(3) -+#define MP2975_IMVP9_EN_R2 BIT(12) -+#define MP2975_PRT_THRES_DIV_OV_EN BIT(14) -+#define MP2975_DRMOS_KCS GENMASK(13, 12) -+#define MP2975_PROT_DEV_OV_OFF 10 -+#define MP2975_PROT_DEV_OV_ON 5 -+#define MP2975_SENSE_AMPL BIT(11) -+#define MP2975_SENSE_AMPL_UNIT 1 -+#define MP2975_SENSE_AMPL_HALF 2 -+#define MP2975_VIN_UV_LIMIT_UNIT 8 -+ -+#define MP2975_PSC_VOLTAGE_OUT 0x40 -+#define MP2975_MAX_PHASE_RAIL1 8 -+#define MP2975_MAX_PHASE_RAIL2 4 -+#define MP2975_PAGE_NUM 2 -+ -+#define MP2975_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \ -+ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT) -+ -+struct mp2975_data { -+ struct pmbus_driver_info info; -+ int vout_scale; -+ int vid_step[MP2975_PAGE_NUM]; -+ int vref[MP2975_PAGE_NUM]; -+ int vref_off[MP2975_PAGE_NUM]; -+ int vout_max[MP2975_PAGE_NUM]; -+ int vout_ov_fixed[MP2975_PAGE_NUM]; -+ int vout_format[MP2975_PAGE_NUM]; -+ int curr_sense_gain[MP2975_PAGE_NUM]; -+}; -+ -+#define to_mp2975_data(x) container_of(x, struct mp2975_data, info) -+ -+static int mp2975_read_byte_data(struct i2c_client *client, int page, int reg) -+{ -+ switch (reg) { -+ case PMBUS_VOUT_MODE: -+ /* -+ * Enforce VOUT direct format, since device allows to set the -+ * different formats for the different rails. Conversion from -+ * VID to direct provided by driver internally, in case it is -+ * necessary. -+ */ -+ return MP2975_PSC_VOLTAGE_OUT; -+ default: -+ return -ENODATA; -+ } -+} -+ -+static int -+mp2975_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg, -+ u16 mask) -+{ -+ int ret = pmbus_read_word_data(client, page, reg); -+ -+ return (ret > 0) ? ret & mask : ret; -+} -+ -+static int -+mp2975_vid2direct(int vrf, int val) -+{ -+ switch (vrf) { -+ case vr12: -+ if (val >= 0x01) -+ return 250 + (val - 1) * 5; -+ break; -+ case vr13: -+ if (val >= 0x01) -+ return 500 + (val - 1) * 10; -+ break; -+ case imvp9: -+ if (val >= 0x01) -+ return 200 + (val - 1) * 10; -+ break; -+ default: -+ return -EINVAL; -+ } -+ return 0; -+} -+ -+static int -+mp2975_read_phase(struct i2c_client *client, struct mp2975_data *data, -+ int page, int phase, u8 reg) -+{ -+ u16 mask; -+ int shift = 0, ret; -+ -+ if ((phase + 1) % MP2975_PAGE_NUM) { -+ mask = GENMASK(7, 0); -+ } else { -+ mask = GENMASK(15, 8); -+ shift = 8; -+ } -+ -+ ret = mp2975_read_word_helper(client, page, phase, reg, mask); -+ if (ret < 0) -+ return ret; -+ -+ ret >>= shift; -+ -+ /* -+ * Output value is calculated as: (READ_CSx / 80 – 1.23) / (Kcs * Rcs) -+ * where: -+ * - Kcs is the DrMOS current sense gain of power stage, which is -+ * obtained from the register MP2975_MFR_VR_CONFIG1, bits 13-12 with -+ * the following selection of DrMOS (data->curr_sense_gain[page]): -+ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. -+ * - Rcs is the internal phase current sense resistor which is constant -+ * value 1kΩ. -+ */ -+ return DIV_ROUND_CLOSEST(DIV_ROUND_CLOSEST(ret * 100 - 9840, 100) * -+ 100, data->curr_sense_gain[page]); -+} -+ -+static int -+mp2975_read_phases(struct i2c_client *client, struct mp2975_data *data, -+ int page, int phase) -+{ -+ int ret; -+ -+ if (page) { -+ switch (phase) { -+ case 0 ... 1: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS7_8); -+ break; -+ case 2 ... 3: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS9_10); -+ break; -+ case 4 ... 5: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS11_12); -+ break; -+ default: -+ return -ENODATA; -+ } -+ } else { -+ switch (phase) { -+ case 0 ... 1: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS1_2); -+ break; -+ case 2 ... 3: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS3_4); -+ break; -+ case 4 ... 5: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS5_6); -+ break; -+ case 6 ... 7: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS7_8); -+ break; -+ case 8 ... 9: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS9_10); -+ break; -+ case 10 ... 11: -+ ret = mp2975_read_phase(client, data, page, phase, -+ MP2975_MFR_READ_CS11_12); -+ break; -+ default: -+ return -ENODATA; -+ } -+ } -+ return ret; -+} -+ -+static int mp2975_read_word_data(struct i2c_client *client, int page, -+ int reg) -+{ -+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); -+ struct mp2975_data *data = to_mp2975_data(info); -+ int phase = 255; -+ int ret; -+ -+ switch (reg) { -+ case PMBUS_OT_FAULT_LIMIT: -+ ret = mp2975_read_word_helper(client, page, phase, reg, -+ GENMASK(7, 0)); -+ break; -+ case PMBUS_VIN_OV_FAULT_LIMIT: -+ ret = mp2975_read_word_helper(client, page, phase, reg, -+ GENMASK(7, 0)); -+ if (ret < 0) -+ return ret; -+ -+ ret = DIV_ROUND_CLOSEST(ret, MP2975_VIN_UV_LIMIT_UNIT); -+ break; -+ case PMBUS_VOUT_OV_FAULT_LIMIT: -+ /* -+ * Register provides two values for over-voltage protection -+ * threshold for fixed (ovp2) and tracking (ovp1) modes. The -+ * minimum of these two values is provided as over-voltage -+ * fault alarm. -+ */ -+ ret = mp2975_read_word_helper(client, page, phase, -+ MP2975_MFR_OVP_TH_SET, -+ GENMASK(2, 0)); -+ if (ret < 0) -+ return ret; -+ -+ ret = min_t(int, data->vout_max[page] + 50 * (ret + 1), -+ data->vout_ov_fixed[page]); -+ break; -+ case PMBUS_VOUT_UV_FAULT_LIMIT: -+ ret = mp2975_read_word_helper(client, page, phase, -+ MP2975_MFR_UVP_SET, -+ GENMASK(2, 0)); -+ if (ret < 0) -+ return ret; -+ -+ ret = DIV_ROUND_CLOSEST(data->vref[page] * 10 - 50 * -+ (ret + 1) * data->vout_scale, 10); -+ break; -+ case PMBUS_READ_VOUT: -+ ret = mp2975_read_word_helper(client, page, phase, reg, -+ GENMASK(11, 0)); -+ if (ret < 0) -+ return ret; -+ -+ /* -+ * READ_VOUT can be provided in VID or direct format. The -+ * format type is specified by bit 15 of the register -+ * MP2975_MFR_DC_LOOP_CTRL. The driver enforces VOUT direct -+ * format, since device allows to set the different formats for -+ * the different rails and also all VOUT limits registers are -+ * provided in a direct format. In case format is VID - convert -+ * to direct. -+ */ -+ if (data->vout_format[page] == vid) -+ ret = mp2975_vid2direct(info->vrm_version[page], ret); -+ break; -+ case PMBUS_VIRT_READ_POUT_MAX: -+ ret = mp2975_read_word_helper(client, page, phase, -+ MP2975_MFR_READ_POUT_PK, -+ GENMASK(12, 0)); -+ if (ret < 0) -+ return ret; -+ -+ ret = DIV_ROUND_CLOSEST(ret, 4); -+ break; -+ case PMBUS_VIRT_READ_IOUT_MAX: -+ ret = mp2975_read_word_helper(client, page, phase, -+ MP2975_MFR_READ_IOUT_PK, -+ GENMASK(12, 0)); -+ if (ret < 0) -+ return ret; -+ -+ ret = DIV_ROUND_CLOSEST(ret, 4); -+ break; -+ case PMBUS_READ_IOUT: -+ ret = mp2975_read_phases(client, data, page, phase); -+ if (ret < 0) -+ return ret; -+ -+ break; -+ case PMBUS_UT_WARN_LIMIT: -+ case PMBUS_UT_FAULT_LIMIT: -+ case PMBUS_VIN_UV_WARN_LIMIT: -+ case PMBUS_VIN_UV_FAULT_LIMIT: -+ case PMBUS_VOUT_UV_WARN_LIMIT: -+ case PMBUS_VOUT_OV_WARN_LIMIT: -+ case PMBUS_VIN_OV_WARN_LIMIT: -+ case PMBUS_IIN_OC_FAULT_LIMIT: -+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: -+ case PMBUS_IIN_OC_WARN_LIMIT: -+ case PMBUS_IOUT_OC_WARN_LIMIT: -+ case PMBUS_IOUT_OC_FAULT_LIMIT: -+ case PMBUS_IOUT_UC_FAULT_LIMIT: -+ case PMBUS_POUT_OP_FAULT_LIMIT: -+ case PMBUS_POUT_OP_WARN_LIMIT: -+ case PMBUS_PIN_OP_WARN_LIMIT: -+ return -ENXIO; -+ default: -+ return -ENODATA; -+ } -+ -+ return ret; -+} -+ -+static int mp2975_identify_multiphase_rail2(struct i2c_client *client) -+{ -+ int ret; -+ -+ /* -+ * Identify multiphase for rail 2 - could be from 0 to 4. -+ * In case phase number is zero – only page zero is supported -+ */ -+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); -+ if (ret < 0) -+ return ret; -+ -+ /* Identify multiphase for rail 2 - could be from 0 to 4. */ -+ ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R2); -+ if (ret < 0) -+ return ret; -+ -+ ret &= GENMASK(2, 0); -+ return (ret >= 4) ? 4 : ret; -+} -+#if 0 -+static void mp2975_set_phase_rail1(struct pmbus_driver_info *info) -+{ -+ int i; -+ -+ for (i = 0 ; i < info->phases[0]; i++) -+ info->pfunc[i] = PMBUS_HAVE_IOUT; -+} -+ -+static void mp2975_set_phase_rail2(struct pmbus_driver_info *info) -+{ -+ int max_rail, i; -+ -+ /* Set phases for rail 2 from upper to lower. */ -+ max_rail = info->phases[1] % (MP2975_MAX_PHASE_RAIL2 - 1); -+ for (i = 1 ; i <= max_rail; i++) -+ info->pfunc[MP2975_MAX_PHASE_RAIL1 - i] = PMBUS_HAVE_IOUT; -+} -+ -+static int mp2975_set_multiphase_rail2(struct pmbus_driver_info *info) -+{ -+ switch (info->phases[1]) { -+ case 1 ... 7: -+ mp2975_set_phase_rail2(info); -+ return 0; -+ default: -+ return -EINVAL; -+ } -+} -+ -+static int -+mp2975_identify_multiphase(struct i2c_client *client, struct mp2975_data *data, -+ struct pmbus_driver_info *info) -+{ -+ int ret; -+ -+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); -+ if (ret < 0) -+ return ret; -+ -+ /* Identify multiphase for rail 1 - could be from 1 to 8. */ -+ ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R1); -+ if (ret > 0) -+ info->phases[0] = ret & GENMASK(3, 0); -+ else -+ return (ret) ? ret : -EINVAL; -+ -+ /* -+ * The device provides a total of 8 PWM pins, and can be configured -+ * to different phase count applications for rail 1 and rail 2. -+ * Rail 1 can be set to 8 phases, while rail 2 can only be set to 4 -+ * phases at most. When rail 1’s phase count is configured as 0, rail -+ * 1 operates with 1-phase DCM. When rail 2 phase count is configured -+ * as 0, rail 2 is disabled. -+ */ -+ switch (info->phases[0]) { -+ case 1 ... 4: -+ mp2975_set_phase_rail1(info); -+ return mp2975_set_multiphase_rail2(info); -+ case 5: -+ mp2975_set_phase_rail1(info); -+ switch (info->phases[1]) { -+ case 1 ... 3: -+ return mp2975_set_multiphase_rail2(info); -+ default: -+ return 0; -+ } -+ case 6: -+ mp2975_set_phase_rail1(info); -+ switch (info->phases[1]) { -+ case 1 ... 2: -+ return mp2975_set_multiphase_rail2(info); -+ default: -+ return 0; -+ } -+ case 7: -+ mp2975_set_phase_rail1(info); -+ switch (info->phases[1]) { -+ case 1: -+ return mp2975_set_multiphase_rail2(info); -+ default: -+ return 0; -+ } -+ case 8: -+ mp2975_set_phase_rail1(info); -+ return 0; -+ default: -+ return -EINVAL; -+ } -+} -+#endif -+static int -+mp2975_identify_vid(struct i2c_client *client, struct mp2975_data *data, -+ struct pmbus_driver_info *info, u32 reg, int page, -+ u32 imvp_bit, u32 vr_bit) -+{ -+ int ret; -+ -+ /* Identify VID mode and step selection. */ -+ ret = i2c_smbus_read_word_data(client, reg); -+ if (ret < 0) -+ return ret; -+ -+ if (ret & imvp_bit) { -+ info->vrm_version[page] = imvp9; -+ data->vid_step[page] = MP2975_PROT_DEV_OV_OFF; -+ } else if (ret & vr_bit) { -+ info->vrm_version[page] = vr12; -+ data->vid_step[page] = MP2975_PROT_DEV_OV_ON; -+ } else { -+ info->vrm_version[page] = vr13; -+ data->vid_step[page] = MP2975_PROT_DEV_OV_OFF; -+ } -+ -+ return 0; -+} -+ -+static int -+mp2975_identify_rails_vid(struct i2c_client *client, struct mp2975_data *data, -+ struct pmbus_driver_info *info) -+{ -+ int ret; -+ -+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); -+ if (ret < 0) -+ return ret; -+ -+ /* Identify VID mode for rail 1. */ -+ ret = mp2975_identify_vid(client, data, info, -+ MP2975_MFR_VR_MULTI_CONFIG_R1, 0, -+ MP2975_IMVP9_EN_R1, MP2975_VID_STEP_SEL_R1); -+ if (ret < 0) -+ return ret; -+ -+ /* Identify VID mode for rail 2, if connected. */ -+ if (info->pages == MP2975_PAGE_NUM) -+ ret = mp2975_identify_vid(client, data, info, -+ MP2975_MFR_VR_MULTI_CONFIG_R2, 1, -+ MP2975_IMVP9_EN_R2, -+ MP2975_VID_STEP_SEL_R2); -+ return ret; -+} -+ -+static int -+mp2975_current_sense_gain_get(struct i2c_client *client, -+ struct mp2975_data *data) -+{ -+ int i, ret; -+ -+ /* -+ * Obtain DrMOS current sense gain of power stage from the register -+ * MP2975_MFR_VR_CONFIG1, bits 13-12. The value is selected as below: -+ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other -+ * values are invalid. -+ */ -+ for (i = 0 ; i < data->info.pages; i++) { -+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); -+ if (ret < 0) -+ return ret; -+ ret = i2c_smbus_read_word_data(client, -+ MP2975_MFR_VR_CONFIG1); -+ if (ret < 0) -+ return ret; -+ -+ switch ((ret & MP2975_DRMOS_KCS) >> 12) { -+ case 0: -+ data->curr_sense_gain[i] = 50; -+ break; -+ case 1: -+ data->curr_sense_gain[i] = 85; -+ break; -+ case 2: -+ data->curr_sense_gain[i] = 97; -+ break; -+ default: -+ data->curr_sense_gain[i] = 100; -+ break; -+ } -+ } -+ -+ return 0; -+} -+ -+static int -+mp2975_vref_get(struct i2c_client *client, struct mp2975_data *data, -+ struct pmbus_driver_info *info) -+{ -+ int ret; -+ -+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 3); -+ if (ret < 0) -+ return ret; -+ -+ /* Get voltage reference value for rail 1. */ -+ ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R1); -+ if (ret < 0) -+ return ret; -+ -+ data->vref[0] = ret * data->vid_step[0]; -+ -+ /* Get voltage reference value for rail 2, if connected. */ -+ if (data->info.pages == MP2975_PAGE_NUM) { -+ ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R2); -+ if (ret < 0) -+ return ret; -+ -+ data->vref[1] = ret * data->vid_step[1]; -+ } -+ return 0; -+} -+ -+static int -+mp2975_vref_offset_get(struct i2c_client *client, struct mp2975_data *data, -+ int page) -+{ -+ int ret; -+ -+ ret = i2c_smbus_read_word_data(client, MP2975_MFR_OVP_TH_SET); -+ if (ret < 0) -+ return ret; -+ -+ switch ((ret & GENMASK(5, 3)) >> 3) { -+ case 1: -+ data->vref_off[page] = 140; -+ break; -+ case 2: -+ data->vref_off[page] = 220; -+ break; -+ case 4: -+ data->vref_off[page] = 400; -+ break; -+ default: -+ return -EINVAL; -+ } -+ return 0; -+} -+ -+static int -+mp2975_vout_max_get(struct i2c_client *client, struct mp2975_data *data, -+ struct pmbus_driver_info *info, int page) -+{ -+ int ret; -+ -+ /* Get maximum reference voltage of VID-DAC in VID format. */ -+ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_MAX); -+ if (ret < 0) -+ return ret; -+ -+ data->vout_max[page] = mp2975_vid2direct(info->vrm_version[page], ret & -+ GENMASK(8, 0)); -+ return 0; -+} -+ -+static int -+mp2975_identify_vout_format(struct i2c_client *client, -+ struct mp2975_data *data, int page) -+{ -+ int ret; -+ -+ ret = i2c_smbus_read_word_data(client, MP2975_MFR_DC_LOOP_CTRL); -+ if (ret < 0) -+ return ret; -+ -+ if (ret & MP2975_VOUT_FORMAT) -+ data->vout_format[page] = vid; -+ else -+ data->vout_format[page] = direct; -+ return 0; -+} -+ -+static int -+mp2975_vout_ov_scale_get(struct i2c_client *client, struct mp2975_data *data, -+ struct pmbus_driver_info *info) -+{ -+ int thres_dev, sense_ampl, ret; -+ -+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); -+ if (ret < 0) -+ return ret; -+ -+ /* -+ * Get divider for over- and under-voltage protection thresholds -+ * configuration from the Advanced Options of Auto Phase Shedding and -+ * decay register. -+ */ -+ ret = i2c_smbus_read_word_data(client, MP2975_MFR_APS_DECAY_ADV); -+ if (ret < 0) -+ return ret; -+ thres_dev = ret & MP2975_PRT_THRES_DIV_OV_EN ? MP2975_PROT_DEV_OV_ON : -+ MP2975_PROT_DEV_OV_OFF; -+ -+ /* Select the gain of remote sense amplifier. */ -+ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP); -+ if (ret < 0) -+ return ret; -+ sense_ampl = ret & MP2975_SENSE_AMPL ? MP2975_SENSE_AMPL_HALF : -+ MP2975_SENSE_AMPL_UNIT; -+ -+ data->vout_scale = sense_ampl * thres_dev; -+ -+ return 0; -+} -+ -+static int -+mp2975_vout_per_rail_config_get(struct i2c_client *client, -+ struct mp2975_data *data, -+ struct pmbus_driver_info *info) -+{ -+ int i, ret; -+ -+ for (i = 0; i < data->info.pages; i++) { -+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); -+ if (ret < 0) -+ return ret; -+ -+ /* Obtain voltage reference offsets. */ -+ ret = mp2975_vref_offset_get(client, data, i); -+ if (ret < 0) -+ return ret; -+ -+ /* Obtain maximum voltage values. */ -+ ret = mp2975_vout_max_get(client, data, info, i); -+ if (ret < 0) -+ return ret; -+ -+ /* -+ * Get VOUT format for READ_VOUT command : VID or direct. -+ * Pages on same device can be configured with different -+ * formats. -+ */ -+ ret = mp2975_identify_vout_format(client, data, i); -+ if (ret < 0) -+ return ret; -+ -+ /* -+ * Set over-voltage fixed value. Thresholds are provided as -+ * fixed value, and tracking value. The minimum of them are -+ * exposed as over-voltage critical threshold. -+ */ -+ data->vout_ov_fixed[i] = data->vref[i] + -+ DIV_ROUND_CLOSEST(data->vref_off[i] * -+ data->vout_scale, -+ 10); -+ } -+ -+ return 0; -+} -+ -+static struct pmbus_driver_info mp2975_info = { -+ .pages = 1, -+ .format[PSC_VOLTAGE_IN] = linear, -+ .format[PSC_VOLTAGE_OUT] = direct, -+ .format[PSC_TEMPERATURE] = direct, -+ .format[PSC_CURRENT_IN] = linear, -+ .format[PSC_CURRENT_OUT] = direct, -+ .format[PSC_POWER] = direct, -+ .m[PSC_TEMPERATURE] = 1, -+ .m[PSC_VOLTAGE_OUT] = 1, -+ .R[PSC_VOLTAGE_OUT] = 3, -+ .m[PSC_CURRENT_OUT] = 1, -+ .m[PSC_POWER] = 1, -+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | -+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | -+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | -+ PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, -+ .read_byte_data = mp2975_read_byte_data, -+ .read_word_data = mp2975_read_word_data, -+}; -+ -+static int mp2975_probe(struct i2c_client *client, -+ const struct i2c_device_id *id) -+{ -+ struct pmbus_driver_info *info; -+ struct mp2975_data *data; -+ int ret; -+ -+ data = devm_kzalloc(&client->dev, sizeof(struct mp2975_data), -+ GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ memcpy(&data->info, &mp2975_info, sizeof(*info)); -+ info = &data->info; -+ -+ /* Identify multiphase configuration for rail 2. */ -+ ret = mp2975_identify_multiphase_rail2(client); -+ if (ret < 0) -+ return ret; -+ -+ if (ret) -+ data->info.pages = MP2975_PAGE_NUM; -+ -+ if (ret) { -+ /* Two rails are connected. */ -+ data->info.pages = MP2975_PAGE_NUM; -+#if 0 -+ data->info.phases[1] = ret; -+#endif -+ data->info.func[1] = MP2975_RAIL2_FUNC; -+ } -+#if 0 -+ /* Identify multiphase configuration. */ -+ ret = mp2975_identify_multiphase(client, data, info); -+ if (ret) -+ return ret; -+#endif -+ /* Identify VID setting per rail. */ -+ ret = mp2975_identify_rails_vid(client, data, info); -+ if (ret < 0) -+ return ret; -+ -+ /* Obtain current sense gain of power stage. */ -+ ret = mp2975_current_sense_gain_get(client, data); -+ if (ret) -+ return ret; -+ -+ /* Obtain voltage reference values. */ -+ ret = mp2975_vref_get(client, data, info); -+ if (ret) -+ return ret; -+ -+ /* Obtain vout over-voltage scales. */ -+ ret = mp2975_vout_ov_scale_get(client, data, info); -+ if (ret < 0) -+ return ret; -+ -+ /* Obtain offsets, maximum and format for vout. */ -+ ret = mp2975_vout_per_rail_config_get(client, data, info); -+ if (ret) -+ return ret; -+ -+ return pmbus_do_probe(client, id, info); -+} -+ -+static const struct i2c_device_id mp2975_id[] = { -+ {"mp2975", 0}, -+ {} -+}; -+ -+MODULE_DEVICE_TABLE(i2c, mp2975_id); -+ -+static const struct of_device_id __maybe_unused mp2975_of_match[] = { -+ {.compatible = "mps,mp2975"}, -+ {} -+}; -+MODULE_DEVICE_TABLE(of, mp2975_of_match); -+ -+static struct i2c_driver mp2975_driver = { -+ .driver = { -+ .name = "mp2975", -+ .of_match_table = of_match_ptr(mp2975_of_match), -+ }, -+ .probe = mp2975_probe, -+ .remove = pmbus_do_remove, -+ .id_table = mp2975_id, -+}; -+ -+module_i2c_driver(mp2975_driver); -+ -+MODULE_AUTHOR("Vadim Pasternak "); -+MODULE_DESCRIPTION("PMBus driver for MPS MP2975 device"); -+MODULE_LICENSE("GPL"); --- -2.8.4 - diff --git a/patch/0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch b/patch/0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch deleted file mode 100644 index b96b95fb3..000000000 --- a/patch/0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch +++ /dev/null @@ -1,39 +0,0 @@ -From b33335cbc89357b415865e7c55a4103bbd47f629 Mon Sep 17 00:00:00 2001 -From: Charles Keepax -Date: Wed, 20 Mar 2019 14:58:17 +0000 -Subject: [PATCH 1/2] hwmon: Add convience macro to define simple static - sensors - -cherry-picked from upstream c43a113ca2c807c3e66a5de0ec57d69803b8bc10 - -It takes a fair amount of boiler plate code to add new sensors, add a -macro that can be used to specify simple static sensors. - -Signed-off-by: Charles Keepax -Signed-off-by: Guenter Roeck ---- - include/linux/hwmon.h | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h -index 9493d4a..faa67641 100644 ---- a/include/linux/hwmon.h -+++ b/include/linux/hwmon.h -@@ -363,6 +363,14 @@ struct hwmon_channel_info { - const u32 *config; - }; - -+#define HWMON_CHANNEL_INFO(stype, ...) \ -+ (&(struct hwmon_channel_info) { \ -+ .type = hwmon_##stype, \ -+ .config = (u32 []) { \ -+ __VA_ARGS__, 0 \ -+ } \ -+ }) -+ - /** - * Chip configuration - * @ops: Pointer to hwmon operations. --- -2.8.4 - diff --git a/patch/0031-backport-nvme-Add-hardware-monitoring-support.patch b/patch/0031-backport-nvme-Add-hardware-monitoring-support.patch deleted file mode 100644 index 39084814b..000000000 --- a/patch/0031-backport-nvme-Add-hardware-monitoring-support.patch +++ /dev/null @@ -1,314 +0,0 @@ -From 430612584b9a4904783d767d32cf65c809743796 Mon Sep 17 00:00:00 2001 -From: Guenter Roeck -Date: Wed, 6 Nov 2019 06:35:18 -0800 -Subject: [PATCH 2/2] backport: nvme: Add hardware monitoring support -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -backport from upstream commit 400b6a7b13a3fd71cff087139ce45dd1e5fff444 - -nvme devices report temperature information in the controller information -(for limits) and in the smart log. Currently, the only means to retrieve -this information is the nvme command line interface, which requires -super-user privileges. - -At the same time, it would be desirable to be able to use NVMe temperature -information for thermal control. - -This patch adds support to read NVMe temperatures from the kernel using the -hwmon API and adds temperature zones for NVMe drives. The thermal subsystem -can use this information to set thermal policies, and userspace can access -it using libsensors and/or the "sensors" command. - -Example output from the "sensors" command: - -nvme0-pci-0100 -Adapter: PCI adapter -Composite: +39.0°C (high = +85.0°C, crit = +85.0°C) -Sensor 1: +39.0°C -Sensor 2: +41.0°C - -Reviewed-by: Christoph Hellwig -Signed-off-by: Guenter Roeck -Signed-off-by: Keith Busch ---- - drivers/nvme/host/Kconfig | 10 +++ - drivers/nvme/host/Makefile | 1 + - drivers/nvme/host/core.c | 6 ++ - drivers/nvme/host/hwmon.c | 181 +++++++++++++++++++++++++++++++++++++++++++++ - drivers/nvme/host/nvme.h | 8 ++ - 5 files changed, 206 insertions(+) - create mode 100644 drivers/nvme/host/hwmon.c - -diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig -index 88a8b59..72ddd22 100644 ---- a/drivers/nvme/host/Kconfig -+++ b/drivers/nvme/host/Kconfig -@@ -22,6 +22,16 @@ config NVME_MULTIPATH - /dev/nvmeXnY device will show up for each NVMe namespaces, - even if it is accessible through multiple controllers. - -+config NVME_HWMON -+ bool "NVMe hardware monitoring" -+ depends on (NVME_CORE=y && HWMON=y) || (NVME_CORE=m && HWMON) -+ help -+ This provides support for NVMe hardware monitoring. If enabled, -+ a hardware monitoring device will be created for each NVMe drive -+ in the system. -+ -+ If unsure, say N. -+ - config NVME_FABRICS - tristate - -diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile -index aea459c..525ff99 100644 ---- a/drivers/nvme/host/Makefile -+++ b/drivers/nvme/host/Makefile -@@ -13,6 +13,7 @@ nvme-core-$(CONFIG_TRACING) += trace.o - nvme-core-$(CONFIG_NVME_MULTIPATH) += multipath.o - nvme-core-$(CONFIG_NVM) += lightnvm.o - nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS) += fault_inject.o -+nvme-core-$(CONFIG_NVME_HWMON) += hwmon.o - - nvme-y += pci.o - -diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c -index e26d119..bb37aae 100644 ---- a/drivers/nvme/host/core.c -+++ b/drivers/nvme/host/core.c -@@ -2432,6 +2432,9 @@ int nvme_init_identify(struct nvme_ctrl *ctrl) - ctrl->oacs = le16_to_cpu(id->oacs); - ctrl->oncs = le16_to_cpup(&id->oncs); - ctrl->oaes = le32_to_cpu(id->oaes); -+ ctrl->wctemp = le16_to_cpu(id->wctemp); -+ ctrl->cctemp = le16_to_cpu(id->cctemp); -+ - atomic_set(&ctrl->abort_limit, id->acl + 1); - ctrl->vwc = id->vwc; - ctrl->cntlid = le16_to_cpup(&id->cntlid); -@@ -2528,6 +2531,9 @@ int nvme_init_identify(struct nvme_ctrl *ctrl) - if (ret < 0) - return ret; - -+ if (!ctrl->identified) -+ nvme_hwmon_init(ctrl); -+ - ctrl->identified = true; - - return 0; -diff --git a/drivers/nvme/host/hwmon.c b/drivers/nvme/host/hwmon.c -new file mode 100644 -index 0000000..5480cbb ---- /dev/null -+++ b/drivers/nvme/host/hwmon.c -@@ -0,0 +1,181 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * NVM Express hardware monitoring support -+ * Copyright (c) 2019, Guenter Roeck -+ */ -+ -+#include -+#include -+ -+#include "nvme.h" -+ -+struct nvme_hwmon_data { -+ struct nvme_ctrl *ctrl; -+ struct nvme_smart_log log; -+ struct mutex read_lock; -+}; -+ -+static int nvme_hwmon_get_smart_log(struct nvme_hwmon_data *data) -+{ -+ int ret; -+ -+ ret = nvme_get_log(data->ctrl, NVME_NSID_ALL, NVME_LOG_SMART, 0, -+ &data->log, sizeof(data->log), 0); -+ -+ return ret <= 0 ? ret : -EIO; -+} -+ -+static int nvme_hwmon_read(struct device *dev, enum hwmon_sensor_types type, -+ u32 attr, int channel, long *val) -+{ -+ struct nvme_hwmon_data *data = dev_get_drvdata(dev); -+ struct nvme_smart_log *log = &data->log; -+ int temp; -+ int err; -+ -+ /* -+ * First handle attributes which don't require us to read -+ * the smart log. -+ */ -+ switch (attr) { -+ case hwmon_temp_max: -+ *val = (data->ctrl->wctemp - 273) * 1000; -+ return 0; -+ case hwmon_temp_crit: -+ *val = (data->ctrl->cctemp - 273) * 1000; -+ return 0; -+ default: -+ break; -+ } -+ -+ mutex_lock(&data->read_lock); -+ err = nvme_hwmon_get_smart_log(data); -+ if (err) -+ goto unlock; -+ -+ switch (attr) { -+ case hwmon_temp_input: -+ if (!channel) -+ temp = get_unaligned_le16(log->temperature); -+ else -+ temp = le16_to_cpu(log->temp_sensor[channel - 1]); -+ *val = (temp - 273) * 1000; -+ break; -+ case hwmon_temp_alarm: -+ *val = !!(log->critical_warning & NVME_SMART_CRIT_TEMPERATURE); -+ break; -+ default: -+ err = -EOPNOTSUPP; -+ break; -+ } -+unlock: -+ mutex_unlock(&data->read_lock); -+ return err; -+} -+ -+static const char * const nvme_hwmon_sensor_names[] = { -+ "Composite", -+ "Sensor 1", -+ "Sensor 2", -+ "Sensor 3", -+ "Sensor 4", -+ "Sensor 5", -+ "Sensor 6", -+ "Sensor 7", -+ "Sensor 8", -+}; -+ -+static int nvme_hwmon_read_string(struct device *dev, -+ enum hwmon_sensor_types type, u32 attr, -+ int channel, const char **str) -+{ -+ *str = nvme_hwmon_sensor_names[channel]; -+ return 0; -+} -+ -+static umode_t nvme_hwmon_is_visible(const void *_data, -+ enum hwmon_sensor_types type, -+ u32 attr, int channel) -+{ -+ const struct nvme_hwmon_data *data = _data; -+ -+ switch (attr) { -+ case hwmon_temp_crit: -+ if (!channel && data->ctrl->cctemp) -+ return 0444; -+ break; -+ case hwmon_temp_max: -+ if (!channel && data->ctrl->wctemp) -+ return 0444; -+ break; -+ case hwmon_temp_alarm: -+ if (!channel) -+ return 0444; -+ break; -+ case hwmon_temp_input: -+ case hwmon_temp_label: -+ if (!channel || data->log.temp_sensor[channel - 1]) -+ return 0444; -+ break; -+ default: -+ break; -+ } -+ return 0; -+} -+ -+static const struct hwmon_channel_info *nvme_hwmon_info[] = { -+ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), -+ HWMON_CHANNEL_INFO(temp, -+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | -+ HWMON_T_LABEL | HWMON_T_ALARM, -+ 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 nvme_hwmon_ops = { -+ .is_visible = nvme_hwmon_is_visible, -+ .read = nvme_hwmon_read, -+ .read_string = nvme_hwmon_read_string, -+}; -+ -+static const struct hwmon_chip_info nvme_hwmon_chip_info = { -+ .ops = &nvme_hwmon_ops, -+ .info = nvme_hwmon_info, -+}; -+ -+void nvme_hwmon_init(struct nvme_ctrl *ctrl) -+{ -+ struct device *dev = ctrl->dev; -+ struct nvme_hwmon_data *data; -+ struct device *hwmon; -+ int err; -+ -+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return; -+ -+ data->ctrl = ctrl; -+ mutex_init(&data->read_lock); -+ -+ err = nvme_hwmon_get_smart_log(data); -+ if (err) { -+ dev_warn(dev, "Failed to read smart log (error %d)\n", err); -+ devm_kfree(dev, data); -+ return; -+ } -+ -+ hwmon = devm_hwmon_device_register_with_info(dev, "nvme", data, -+ &nvme_hwmon_chip_info, -+ NULL); -+ if (IS_ERR(hwmon)) { -+ dev_warn(dev, "Failed to instantiate hwmon device\n"); -+ devm_kfree(dev, data); -+ } -+} -diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h -index d5e29b5..029bc8a 100644 ---- a/drivers/nvme/host/nvme.h -+++ b/drivers/nvme/host/nvme.h -@@ -192,6 +192,8 @@ struct nvme_ctrl { - u16 kas; - u8 npss; - u8 apsta; -+ u16 wctemp; -+ u16 cctemp; - u32 oaes; - u32 aen_result; - unsigned int shutdown_timeout; -@@ -591,4 +593,10 @@ static inline struct nvme_ns *nvme_get_ns_from_dev(struct device *dev) - int __init nvme_core_init(void); - void nvme_core_exit(void); - -+#ifdef CONFIG_NVME_HWMON -+void nvme_hwmon_init(struct nvme_ctrl *ctrl); -+#else -+static inline void nvme_hwmon_init(struct nvme_ctrl *ctrl) { } -+#endif -+ - #endif /* _NVME_H */ --- -2.8.4 - diff --git a/patch/0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch b/patch/0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch deleted file mode 100644 index c156d8173..000000000 --- a/patch/0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 9f37a3899fae02ee41185b16f124116819c56fc6 Mon Sep 17 00:00:00 2001 -From: Vadim Pasternak -Date: Mon, 8 Feb 2021 00:45:47 +0200 -Subject: [PATCH backport 4.19 06/10] platform/mellanox: mlxreg-hotplug: Use - capability register for attribute creation - -Align with upstream commit 0a43f7be57edbe9fac6084a6f92694a3badee827. - -Create the 'sysfs' attributes according to configuration provided -through the capability register, which purpose is to indicate the -actual number of the components within the particular group. -Such components could be, for example the FAN or power supply units. -The motivation is to avoid adding a new code in the future in order to -distinct between the systems types supporting a different number of the -components like power supplies, FANs, ASICs, line cards. - -Signed-off-by: Vadim Pasternak -Signed-off-by: Andy Shevchenko ---- - drivers/platform/mellanox/mlxreg-hotplug.c | 48 ++++++++---------------------- - 1 file changed, 13 insertions(+), 35 deletions(-) - -diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c -index 3e99ad9fe553..ed888d719024 100644 ---- a/drivers/platform/mellanox/mlxreg-hotplug.c -+++ b/drivers/platform/mellanox/mlxreg-hotplug.c -@@ -1,34 +1,8 @@ -+// SPDX-License-Identifier: GPL-2.0+ - /* -- * Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved. -- * Copyright (c) 2016-2018 Vadim Pasternak -+ * Mellanox hotplug driver - * -- * Redistribution and use in source and binary forms, with or without -- * modification, are permitted provided that the following conditions are met: -- * -- * 1. Redistributions of source code must retain the above copyright -- * notice, this list of conditions and the following disclaimer. -- * 2. Redistributions in binary form must reproduce the above copyright -- * notice, this list of conditions and the following disclaimer in the -- * documentation and/or other materials provided with the distribution. -- * 3. Neither the names of the copyright holders nor the names of its -- * contributors may be used to endorse or promote products derived from -- * this software without specific prior written permission. -- * -- * Alternatively, this software may be distributed under the terms of the -- * GNU General Public License ("GPL") version 2 as published by the Free -- * Software Foundation. -- * -- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -- * POSSIBILITY OF SUCH DAMAGE. -+ * Copyright (C) 2016-2020 Mellanox Technologies - */ - - #include -@@ -220,6 +194,7 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) - struct mlxreg_core_hotplug_platform_data *pdata; - struct mlxreg_core_item *item; - struct mlxreg_core_data *data; -+ unsigned long mask; - u32 regval; - int num_attrs = 0, id = 0, i, j, k, ret; - -@@ -243,11 +218,11 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) - } - - data = item->data; -- /* Go over all units within the item. */ -- for (j = 0, k = 0; j < item->count; j++, data++) { -- /* Skip if bit in mask is not set. */ -- if (!(item->mask & BIT(j))) -- continue; -+ -+ /* Go over all unmasked units within item. */ -+ mask = item->mask; -+ k = 0; -+ for_each_set_bit(j, &mask, item->count) { - if (data->capability) { - /* - * Read capability register and skip non -@@ -257,8 +232,10 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) - data->capability, ®val); - if (ret) - return ret; -- if (!(regval & data->bit)) -+ if (!(regval & data->bit)) { -+ data++; - continue; -+ } - } - PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; - PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, -@@ -279,6 +256,7 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) - PRIV_DEV_ATTR(id).nr = i; - PRIV_DEV_ATTR(id).index = k; - sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); -+ data++; - id++; - k++; - } --- -2.11.0 - diff --git a/patch/Support-for-fullcone-nat.patch b/patch/Support-for-fullcone-nat.patch deleted file mode 100644 index eeb87730d..000000000 --- a/patch/Support-for-fullcone-nat.patch +++ /dev/null @@ -1,711 +0,0 @@ -From 67cc2bd46c7ad1431ddd1819cdfad6ecc08a7593 Mon Sep 17 00:00:00 2001 - -From: Kiran Kella -Date: Fri, 6 Sep 2019 20:54:19 -0700 - -Subject: [PATCH] netfilter: nf_nat: Support fullcone NAT - -Changes done in the kernel to ensure 3-tuple uniqueness of the conntrack -entries for the fullcone nat functionality. - -* Hashlist is maintained for the 3-tuple unique keys (Protocol/Source - IP/Port) for all the conntrack entries. - -* When NAT table rules are created with the fullcone option, the - SNAT/POSTROUTING stage ensures the ports from the pool are picked up in - such a way that the 3-tuple is uniquely assigned. - -* In the DNAT/POSTROUTING stage, the fullcone behavior is ensured by checking - and reusing the 3-tuple for the Source IP/Port in the original direction. - -* When the pool is exhausted of the 3-tuple assignments, the packets are - dropped, else, they will be going out of the router they being 5-tuple - unique (which is not intended). - -* Passing fullcone option using iptables is part of another PR (in - sonic-buildimage repo). - -The kernel changes mentioned above are done to counter the challenges -explained in the section *3.4.2.1 Handling NAT model mismatch between -the ASIC and the Kernel* in the NAT HLD [1]. - -[1]: https://github.com/kirankella/SONiC/blob/nat_doc_changes/doc/nat/nat_design_spec.md - -Signed-off-by: Kiran Kella -[forward port to Linux v4.19, https://github.com/Azure/sonic-linux-kernel/pull/147] -Signed-off-by: Akhilesh Samineni ---- - include/net/netfilter/nf_conntrack.h | 3 + - include/net/netfilter/nf_nat.h | 6 + - include/net/netfilter/nf_nat_l4proto.h | 12 +- - include/uapi/linux/netfilter/nf_nat.h | 1 - net/ipv4/netfilter/nf_nat_proto_gre.c | 8 + - net/ipv4/netfilter/nf_nat_proto_icmp.c | 6 + - net/ipv6/netfilter/nf_nat_proto_icmpv6.c | 5 + - net/netfilter/nf_nat_core.c | 173 +++++++++++++++++++++++++++--- - net/netfilter/nf_nat_proto_common.c | 32 ++++-- - net/netfilter/nf_nat_proto_dccp.c | 6 + - net/netfilter/nf_nat_proto_sctp.c | 6 + - net/netfilter/nf_nat_proto_tcp.c | 6 + - net/netfilter/nf_nat_proto_udp.c | 12 +- - net/netfilter/nf_nat_proto_unknown.c | 4 - - 14 files changed, 220 insertions(+), 60 deletions(-) - -diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h -index ac4d70aee..beee8670b 100644 ---- a/include/net/netfilter/nf_conntrack.h -+++ b/include/net/netfilter/nf_conntrack.h -@@ -84,6 +84,9 @@ struct nf_conn { - #if IS_ENABLED(CONFIG_NF_NAT) - struct hlist_node nat_bysource; - #endif -+ /* To optionally ensure 3-tuple uniqueness on the translated source */ -+ struct hlist_node nat_by_manip_src; -+ - /* all members below initialized via memset */ - struct { } __nfct_init_offset; - -diff --git a/include/net/netfilter/nf_nat.h b/include/net/netfilter/nf_nat.h -index a17eb2f8d..7c3cc3c7b 100644 ---- a/include/net/netfilter/nf_nat.h -+++ b/include/net/netfilter/nf_nat.h -@@ -51,6 +51,12 @@ struct nf_conn_nat *nf_ct_nat_ext_add(struct nf_conn *ct); - int nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple, - const struct nf_conn *ignored_conntrack); - -+/* Is this 3-tuple already taken? (not by us)*/ -+int -+nf_nat_used_3_tuple(const struct nf_conntrack_tuple *tuple, -+ const struct nf_conn *ignored_conntrack, -+ enum nf_nat_manip_type maniptype); -+ - static inline struct nf_conn_nat *nfct_nat(const struct nf_conn *ct) - { - #if defined(CONFIG_NF_NAT) || defined(CONFIG_NF_NAT_MODULE) -diff --git a/include/net/netfilter/nf_nat_l4proto.h b/include/net/netfilter/nf_nat_l4proto.h -index b4d6b29bc..fbcbb9ad9 100644 ---- a/include/net/netfilter/nf_nat_l4proto.h -+++ b/include/net/netfilter/nf_nat_l4proto.h -@@ -32,7 +32,7 @@ struct nf_nat_l4proto { - * possible. Per-protocol part of tuple is initialized to the - * incoming packet. - */ -- void (*unique_tuple)(const struct nf_nat_l3proto *l3proto, -+ int (*unique_tuple)(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, - enum nf_nat_manip_type maniptype, -@@ -70,11 +70,11 @@ bool nf_nat_l4proto_in_range(const struct nf_conntrack_tuple *tuple, - const union nf_conntrack_man_proto *min, - const union nf_conntrack_man_proto *max); - --void nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto, -- struct nf_conntrack_tuple *tuple, -- const struct nf_nat_range2 *range, -- enum nf_nat_manip_type maniptype, -- const struct nf_conn *ct, u16 *rover); -+int nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto, -+ struct nf_conntrack_tuple *tuple, -+ const struct nf_nat_range2 *range, -+ enum nf_nat_manip_type maniptype, -+ const struct nf_conn *ct, u16 *rover); - - int nf_nat_l4proto_nlattr_to_range(struct nlattr *tb[], - struct nf_nat_range2 *range); -diff --git a/include/uapi/linux/netfilter/nf_nat.h b/include/uapi/linux/netfilter/nf_nat.h -index 4a95c0db1..1cda390e1 100644 ---- a/include/uapi/linux/netfilter/nf_nat.h -+++ b/include/uapi/linux/netfilter/nf_nat.h -@@ -11,6 +11,7 @@ - #define NF_NAT_RANGE_PERSISTENT (1 << 3) - #define NF_NAT_RANGE_PROTO_RANDOM_FULLY (1 << 4) - #define NF_NAT_RANGE_PROTO_OFFSET (1 << 5) -+#define NF_NAT_RANGE_FULLCONE (1 << 6) - - #define NF_NAT_RANGE_PROTO_RANDOM_ALL \ - (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY) -diff --git a/net/ipv4/netfilter/nf_nat_proto_gre.c b/net/ipv4/netfilter/nf_nat_proto_gre.c -index 00fda6331..d2ca4f600 100644 ---- a/net/ipv4/netfilter/nf_nat_proto_gre.c -+++ b/net/ipv4/netfilter/nf_nat_proto_gre.c -@@ -38,7 +38,7 @@ MODULE_AUTHOR("Harald Welte "); - MODULE_DESCRIPTION("Netfilter NAT protocol helper module for GRE"); - - /* generate unique tuple ... */ --static void -+static int - gre_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, -@@ -52,7 +52,7 @@ gre_unique_tuple(const struct nf_nat_l3proto *l3proto, - /* If there is no master conntrack we are not PPTP, - do not change tuples */ - if (!ct->master) -- return; -+ return 0; - - if (maniptype == NF_NAT_MANIP_SRC) - keyptr = &tuple->src.u.gre.key; -@@ -73,11 +73,11 @@ gre_unique_tuple(const struct nf_nat_l3proto *l3proto, - for (i = 0; ; ++key) { - *keyptr = htons(min + key % range_size); - if (++i == range_size || !nf_nat_used_tuple(tuple, ct)) -- return; -+ return 1; - } - - pr_debug("%p: no NAT mapping\n", ct); -- return; -+ return 0; - } - - /* manipulate a GRE packet according to maniptype */ -diff --git a/net/ipv4/netfilter/nf_nat_proto_icmp.c b/net/ipv4/netfilter/nf_nat_proto_icmp.c -index 6d7cf1d79..403783cda 100644 ---- a/net/ipv4/netfilter/nf_nat_proto_icmp.c -+++ b/net/ipv4/netfilter/nf_nat_proto_icmp.c -@@ -27,7 +27,7 @@ icmp_in_range(const struct nf_conntrack_tuple *tuple, - ntohs(tuple->src.u.icmp.id) <= ntohs(max->icmp.id); - } - --static void -+static int - icmp_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, -@@ -48,9 +48,9 @@ icmp_unique_tuple(const struct nf_nat_l3proto *l3proto, - tuple->src.u.icmp.id = htons(ntohs(range->min_proto.icmp.id) + - (id % range_size)); - if (++i == range_size || !nf_nat_used_tuple(tuple, ct)) -- return; -+ return 1; - } -- return; -+ return 0; - } - - static bool -diff --git a/net/ipv6/netfilter/nf_nat_proto_icmpv6.c b/net/ipv6/netfilter/nf_nat_proto_icmpv6.c -index d9bf42ba4..7ff30a023 100644 ---- a/net/ipv6/netfilter/nf_nat_proto_icmpv6.c -+++ b/net/ipv6/netfilter/nf_nat_proto_icmpv6.c -@@ -29,7 +29,7 @@ icmpv6_in_range(const struct nf_conntrack_tuple *tuple, - ntohs(tuple->src.u.icmp.id) <= ntohs(max->icmp.id); - } - --static void -+static int - icmpv6_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, -@@ -50,8 +50,9 @@ icmpv6_unique_tuple(const struct nf_nat_l3proto *l3proto, - tuple->src.u.icmp.id = htons(ntohs(range->min_proto.icmp.id) + - (id % range_size)); - if (++i == range_size || !nf_nat_used_tuple(tuple, ct)) -- return; -+ return 1; - } -+ return 0; - } - - static bool -diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c -index 2268b10a9..3ff1554ab 100644 ---- a/net/netfilter/nf_nat_core.c -+++ b/net/netfilter/nf_nat_core.c -@@ -43,6 +43,7 @@ static const struct nf_nat_l4proto __rcu **nf_nat_l4protos[NFPROTO_NUMPROTO] - static unsigned int nat_net_id __read_mostly; - - static struct hlist_head *nf_nat_bysource __read_mostly; -+static struct hlist_head *nf_nat_by_manip_src __read_mostly; - static unsigned int nf_nat_htable_size __read_mostly; - static unsigned int nf_nat_hash_rnd __read_mostly; - -@@ -155,6 +156,31 @@ hash_by_src(const struct net *n, const struct nf_conntrack_tuple *tuple) - return reciprocal_scale(hash, nf_nat_htable_size); - } - -+static inline unsigned int -+hash_by_dst(const struct net *n, const struct nf_conntrack_tuple *tuple) -+{ -+ unsigned int hash; -+ -+ get_random_once(&nf_nat_hash_rnd, sizeof(nf_nat_hash_rnd)); -+ -+ hash = jhash2((u32 *)&tuple->dst, sizeof(tuple->dst) / sizeof(u32), -+ tuple->dst.protonum ^ nf_nat_hash_rnd ^ net_hash_mix(n)); -+ -+ return reciprocal_scale(hash, nf_nat_htable_size); -+} -+ -+static inline int -+same_reply_dst(const struct nf_conn *ct, -+ const struct nf_conntrack_tuple *tuple) -+{ -+ const struct nf_conntrack_tuple *t; -+ -+ t = &ct->tuplehash[IP_CT_DIR_REPLY].tuple; -+ return (t->dst.protonum == tuple->dst.protonum && -+ nf_inet_addr_cmp(&t->dst.u3, &tuple->dst.u3) && -+ t->dst.u.all == tuple->dst.u.all); -+} -+ - /* Is this tuple already taken? (not by us) */ - int - nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple, -@@ -171,7 +197,40 @@ nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple, - nf_ct_invert_tuplepr(&reply, tuple); - return nf_conntrack_tuple_taken(&reply, ignored_conntrack); - } -+ -+/* Is this 3-tuple already taken? (not by us) */ -+int -+nf_nat_used_3_tuple(const struct nf_conntrack_tuple *tuple, -+ const struct nf_conn *ignored_conntrack, -+ enum nf_nat_manip_type maniptype) -+{ -+ const struct nf_conn *ct; -+ const struct nf_conntrack_zone *zone; -+ unsigned int h; -+ struct net *net = nf_ct_net(ignored_conntrack); -+ -+ /* 3-tuple uniqueness is required for translated source only */ -+ if (maniptype != NF_NAT_MANIP_SRC) { -+ return 0; -+ } -+ zone = nf_ct_zone(ignored_conntrack); -+ -+ /* The tuple passed here is the inverted reply (with translated source) */ -+ h = hash_by_src(net, tuple); -+ hlist_for_each_entry_rcu(ct, &nf_nat_by_manip_src[h], nat_by_manip_src) { -+ struct nf_conntrack_tuple reply; -+ nf_ct_invert_tuplepr(&reply, tuple); -+ /* Compare against the destination in the reply */ -+ if (same_reply_dst(ct, &reply) && -+ net_eq(net, nf_ct_net(ct)) && -+ nf_ct_zone_equal(ct, zone, IP_CT_DIR_ORIGINAL)) { -+ return 1; -+ } -+ } -+ return 0; -+} - EXPORT_SYMBOL(nf_nat_used_tuple); -+EXPORT_SYMBOL(nf_nat_used_3_tuple); - - /* If we source map this tuple so reply looks like reply_tuple, will - * that meet the constraints of range. -@@ -237,6 +296,36 @@ find_appropriate_src(struct net *net, - return 0; - } - -+/* Only called for DST manip */ -+static int -+find_appropriate_dst(struct net *net, -+ const struct nf_conntrack_zone *zone, -+ const struct nf_nat_l3proto *l3proto, -+ const struct nf_nat_l4proto *l4proto, -+ const struct nf_conntrack_tuple *tuple, -+ struct nf_conntrack_tuple *result) -+{ -+ struct nf_conntrack_tuple reply; -+ unsigned int h; -+ const struct nf_conn *ct; -+ -+ nf_ct_invert_tuplepr(&reply, tuple); -+ h = hash_by_src(net, &reply); -+ -+ hlist_for_each_entry_rcu(ct, &nf_nat_by_manip_src[h], nat_by_manip_src) { -+ if (same_reply_dst(ct, tuple) && -+ net_eq(net, nf_ct_net(ct)) && -+ nf_ct_zone_equal(ct, zone, IP_CT_DIR_REPLY)) { -+ /* Copy destination part from original tuple. */ -+ nf_ct_invert_tuplepr(result, -+ &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); -+ result->src = tuple->src; -+ return 1; -+ } -+ } -+ return 0; -+} -+ - /* For [FUTURE] fragmentation handling, we want the least-used - * src-ip/dst-ip/proto triple. Fairness doesn't come into it. Thus - * if the range specifies 1.2.3.4 ports 10000-10005 and 1.2.3.5 ports -@@ -314,10 +403,15 @@ find_best_ips_proto(const struct nf_conntrack_zone *zone, - /* Manipulate the tuple into the range given. For NF_INET_POST_ROUTING, - * we change the source to map into the range. For NF_INET_PRE_ROUTING - * and NF_INET_LOCAL_OUT, we change the destination to map into the -- * range. It might not be possible to get a unique tuple, but we try. -+ * range. It might not be possible to get a unique 5-tuple, but we try. - * At worst (or if we race), we will end up with a final duplicate in -- * __ip_conntrack_confirm and drop the packet. */ --static void -+ * __ip_conntrack_confirm and drop the packet. -+ * If the range is of type fullcone, if we end up with a 3-tuple -+ * duplicate, we do not wait till the packet reaches the -+ * nf_conntrack_confirm to drop the packet. Instead return the packet -+ * to be dropped at this stage. -+ */ -+static int - get_unique_tuple(struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_tuple *orig_tuple, - const struct nf_nat_range2 *range, -@@ -327,8 +421,11 @@ get_unique_tuple(struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone; - const struct nf_nat_l3proto *l3proto; - const struct nf_nat_l4proto *l4proto; -+ struct nf_nat_range2 nat_range; - struct net *net = nf_ct_net(ct); - -+ memcpy(&nat_range, range, sizeof(struct nf_nat_range2)); -+ - zone = nf_ct_zone(ct); - - rcu_read_lock(); -@@ -345,48 +442,77 @@ get_unique_tuple(struct nf_conntrack_tuple *tuple, - * manips not an issue. - */ - if (maniptype == NF_NAT_MANIP_SRC && -- !(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { -+ !(nat_range.flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { - /* try the original tuple first */ -- if (in_range(l3proto, l4proto, orig_tuple, range)) { -+ if (in_range(l3proto, l4proto, orig_tuple, &nat_range)) { - if (!nf_nat_used_tuple(orig_tuple, ct)) { - *tuple = *orig_tuple; - goto out; - } - } else if (find_appropriate_src(net, zone, l3proto, l4proto, -- orig_tuple, tuple, range)) { -+ orig_tuple, tuple, &nat_range)) { - pr_debug("get_unique_tuple: Found current src map\n"); - if (!nf_nat_used_tuple(tuple, ct)) - goto out; - } - } - -+ if (maniptype == NF_NAT_MANIP_DST) { -+ if (nat_range.flags & NF_NAT_RANGE_FULLCONE) { -+ /* Destination IP range does not apply when fullcone flag is set. */ -+ nat_range.min_addr.ip = nat_range.max_addr.ip = orig_tuple->dst.u3.ip; -+ nat_range.min_proto.all = nat_range.max_proto.all = 0; -+ -+ /* If this dstip/proto/dst-proto-part is mapped currently -+ * as a translated source for a given tuple, use that -+ */ -+ if (find_appropriate_dst(net, zone, l3proto, l4proto, -+ orig_tuple, tuple)) { -+ if (!nf_nat_used_tuple(tuple, ct)) { -+ goto out; -+ } -+ } else { -+ /* If not mapped, proceed with the original tuple */ -+ *tuple = *orig_tuple; -+ goto out; -+ } -+ } -+ } -+ - /* 2) Select the least-used IP/proto combination in the given range */ - *tuple = *orig_tuple; -- find_best_ips_proto(zone, tuple, range, ct, maniptype); -+ find_best_ips_proto(zone, tuple, &nat_range, ct, maniptype); - - /* 3) The per-protocol part of the manip is made to map into - * the range to make a unique tuple. - */ - - /* Only bother mapping if it's not already in range and unique */ -- if (!(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { -- if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) { -- if (!(range->flags & NF_NAT_RANGE_PROTO_OFFSET) && -+ if (!(nat_range.flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { -+ if (nat_range.flags & NF_NAT_RANGE_PROTO_SPECIFIED) { -+ if (!(nat_range.flags & NF_NAT_RANGE_PROTO_OFFSET) && - l4proto->in_range(tuple, maniptype, -- &range->min_proto, -- &range->max_proto) && -- (range->min_proto.all == range->max_proto.all || -- !nf_nat_used_tuple(tuple, ct))) -- goto out; -+ &(nat_range.min_proto), -+ &(nat_range.max_proto))) { -+ if (nat_range.flags & NF_NAT_RANGE_FULLCONE) { -+ if (!nf_nat_used_3_tuple(tuple, ct, maniptype)) -+ goto out; -+ } else { -+ if ((nat_range.min_proto.all == nat_range.max_proto.all) || -+ !nf_nat_used_tuple(tuple, ct)) -+ goto out; -+ } -+ } - } else if (!nf_nat_used_tuple(tuple, ct)) { - goto out; - } - } - - /* Last chance: get protocol to try to obtain unique tuple. */ -- l4proto->unique_tuple(l3proto, tuple, range, maniptype, ct); -+ return l4proto->unique_tuple(l3proto, tuple, &nat_range, maniptype, ct); - out: - rcu_read_unlock(); -+ return 1; - } - - struct nf_conn_nat *nf_ct_nat_ext_add(struct nf_conn *ct) -@@ -428,7 +554,9 @@ nf_nat_setup_info(struct nf_conn *ct, - nf_ct_invert_tuplepr(&curr_tuple, - &ct->tuplehash[IP_CT_DIR_REPLY].tuple); - -- get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype); -+ if (! get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype)) { -+ return NF_DROP; -+ } - - if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) { - struct nf_conntrack_tuple reply; -@@ -450,12 +578,16 @@ nf_nat_setup_info(struct nf_conn *ct, - - if (maniptype == NF_NAT_MANIP_SRC) { - unsigned int srchash; -+ unsigned int manip_src_hash; - spinlock_t *lock; - -+ manip_src_hash = hash_by_src(net, &new_tuple); - srchash = hash_by_src(net, - &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); - lock = &nf_nat_locks[srchash % CONNTRACK_LOCKS]; - spin_lock_bh(lock); -+ hlist_add_head_rcu(&ct->nat_by_manip_src, -+ &nf_nat_by_manip_src[manip_src_hash]); - hlist_add_head_rcu(&ct->nat_bysource, - &nf_nat_bysource[srchash]); - spin_unlock_bh(lock); -@@ -644,6 +776,7 @@ static void __nf_nat_cleanup_conntrack(struct nf_conn *ct) - h = hash_by_src(nf_ct_net(ct), &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); - spin_lock_bh(&nf_nat_locks[h % CONNTRACK_LOCKS]); - hlist_del_rcu(&ct->nat_bysource); -+ hlist_del_rcu(&ct->nat_by_manip_src); - spin_unlock_bh(&nf_nat_locks[h % CONNTRACK_LOCKS]); - } - -@@ -1055,9 +1188,14 @@ static int __init nf_nat_init(void) - if (!nf_nat_bysource) - return -ENOMEM; - -+ nf_nat_by_manip_src = nf_ct_alloc_hashtable(&nf_nat_htable_size, 0); -+ if (!nf_nat_by_manip_src) -+ return -ENOMEM; -+ - ret = nf_ct_extend_register(&nat_extend); - if (ret < 0) { - kvfree(nf_nat_bysource); -+ kvfree(nf_nat_by_manip_src); - pr_err("Unable to register extension\n"); - return ret; - } -@@ -1096,6 +1234,7 @@ static void __exit nf_nat_cleanup(void) - kfree(nf_nat_l4protos[i]); - synchronize_net(); - kvfree(nf_nat_bysource); -+ kvfree(nf_nat_by_manip_src); - unregister_pernet_subsys(&nat_net_ops); - } - -diff --git a/net/netfilter/nf_nat_proto_common.c b/net/netfilter/nf_nat_proto_common.c -index 5d849d835..6ee918302 100644 ---- a/net/netfilter/nf_nat_proto_common.c -+++ b/net/netfilter/nf_nat_proto_common.c -@@ -34,12 +34,12 @@ bool nf_nat_l4proto_in_range(const struct nf_conntrack_tuple *tuple, - } - EXPORT_SYMBOL_GPL(nf_nat_l4proto_in_range); - --void nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto, -- struct nf_conntrack_tuple *tuple, -- const struct nf_nat_range2 *range, -- enum nf_nat_manip_type maniptype, -- const struct nf_conn *ct, -- u16 *rover) -+int nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto, -+ struct nf_conntrack_tuple *tuple, -+ const struct nf_nat_range2 *range, -+ enum nf_nat_manip_type maniptype, -+ const struct nf_conn *ct, -+ u16 *rover) - { - unsigned int range_size, min, max, i; - __be16 *portptr; -@@ -54,7 +54,7 @@ void nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto, - if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)) { - /* If it's dst rewrite, can't change port */ - if (maniptype == NF_NAT_MANIP_DST) -- return; -+ return 0; - - if (ntohs(*portptr) < 1024) { - /* Loose convention: >> 512 is credential passing */ -@@ -87,17 +87,27 @@ void nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto, - off = (ntohs(*portptr) - ntohs(range->base_proto.all)); - } else { - off = *rover; -+ if ((range->flags & NF_NAT_RANGE_FULLCONE) && (maniptype == NF_NAT_MANIP_SRC)) { -+ /* Try from the next L4 port in the range */ -+ off++; -+ } - } - -- for (i = 0; ; ++off) { -+ for (i = 0; (i != range_size); ++i, ++off) { - *portptr = htons(min + off % range_size); -- if (++i != range_size && nf_nat_used_tuple(tuple, ct)) -- continue; -+ if ((range->flags & NF_NAT_RANGE_FULLCONE) && (maniptype == NF_NAT_MANIP_SRC)) { -+ if (nf_nat_used_3_tuple(tuple, ct, maniptype)) -+ continue; -+ } else { -+ if (nf_nat_used_tuple(tuple, ct)) -+ continue; -+ } - if (!(range->flags & (NF_NAT_RANGE_PROTO_RANDOM_ALL| - NF_NAT_RANGE_PROTO_OFFSET))) - *rover = off; -- return; -+ return 1; - } -+ return 0; - } - EXPORT_SYMBOL_GPL(nf_nat_l4proto_unique_tuple); - -diff --git a/net/netfilter/nf_nat_proto_dccp.c b/net/netfilter/nf_nat_proto_dccp.c -index 67ea0d83a..68ef70bb5 100644 ---- a/net/netfilter/nf_nat_proto_dccp.c -+++ b/net/netfilter/nf_nat_proto_dccp.c -@@ -20,15 +20,15 @@ - - static u_int16_t dccp_port_rover; - --static void -+static int - dccp_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, - enum nf_nat_manip_type maniptype, - const struct nf_conn *ct) - { -- nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -- &dccp_port_rover); -+ return nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -+ &dccp_port_rover); - } - - static bool -diff --git a/net/netfilter/nf_nat_proto_sctp.c b/net/netfilter/nf_nat_proto_sctp.c -index 1c5d9b65f..a9d9070c3 100644 ---- a/net/netfilter/nf_nat_proto_sctp.c -+++ b/net/netfilter/nf_nat_proto_sctp.c -@@ -14,15 +14,15 @@ - - static u_int16_t nf_sctp_port_rover; - --static void -+static int - sctp_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, - enum nf_nat_manip_type maniptype, - const struct nf_conn *ct) - { -- nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -- &nf_sctp_port_rover); -+ return nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -+ &nf_sctp_port_rover); - } - - static bool -diff --git a/net/netfilter/nf_nat_proto_tcp.c b/net/netfilter/nf_nat_proto_tcp.c -index f15fcd475..1b0390554 100644 ---- a/net/netfilter/nf_nat_proto_tcp.c -+++ b/net/netfilter/nf_nat_proto_tcp.c -@@ -20,15 +20,15 @@ - - static u16 tcp_port_rover; - --static void -+static int - tcp_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, - enum nf_nat_manip_type maniptype, - const struct nf_conn *ct) - { -- nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -- &tcp_port_rover); -+ return nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -+ &tcp_port_rover); - } - - static bool -diff --git a/net/netfilter/nf_nat_proto_udp.c b/net/netfilter/nf_nat_proto_udp.c -index d85c31c24..ecdcdb2bf 100644 ---- a/net/netfilter/nf_nat_proto_udp.c -+++ b/net/netfilter/nf_nat_proto_udp.c -@@ -19,15 +19,15 @@ - - static u16 udp_port_rover; - --static void -+static int - udp_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, - enum nf_nat_manip_type maniptype, - const struct nf_conn *ct) - { -- nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -- &udp_port_rover); -+ return nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -+ &udp_port_rover); - } - - static void -@@ -96,15 +96,15 @@ static bool udplite_manip_pkt(struct sk_buff *skb, - return true; - } - --static void -+static int - udplite_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, - enum nf_nat_manip_type maniptype, - const struct nf_conn *ct) - { -- nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -- &udplite_port_rover); -+ return nf_nat_l4proto_unique_tuple(l3proto, tuple, range, maniptype, ct, -+ &udplite_port_rover); - } - - const struct nf_nat_l4proto nf_nat_l4proto_udplite = { -diff --git a/net/netfilter/nf_nat_proto_unknown.c b/net/netfilter/nf_nat_proto_unknown.c -index c5db3e251..377a2938c 100644 ---- a/net/netfilter/nf_nat_proto_unknown.c -+++ b/net/netfilter/nf_nat_proto_unknown.c -@@ -25,7 +25,7 @@ static bool unknown_in_range(const struct nf_conntrack_tuple *tuple, - return true; - } - --static void unknown_unique_tuple(const struct nf_nat_l3proto *l3proto, -+static int unknown_unique_tuple(const struct nf_nat_l3proto *l3proto, - struct nf_conntrack_tuple *tuple, - const struct nf_nat_range2 *range, - enum nf_nat_manip_type maniptype, -@@ -34,7 +34,7 @@ static void unknown_unique_tuple(const struct nf_nat_l3proto *l3proto, - /* Sorry: we can't help you; if it's not unique, we can't frob - * anything. - */ -- return; -+ return 0; - } - - static bool diff --git a/patch/cisco-hwmon-pmbus_core-pec-support-check.patch b/patch/cisco-hwmon-pmbus_core-pec-support-check.patch deleted file mode 100644 index 3d836b9c1..000000000 --- a/patch/cisco-hwmon-pmbus_core-pec-support-check.patch +++ /dev/null @@ -1,44 +0,0 @@ -From a3e00e49a8647ea9ba6f08a36c1bf6884f91619a Mon Sep 17 00:00:00 2001 -From: Madhava Reddy Siddareddygari -Date: Tue, 1 Jun 2021 22:42:41 -0700 -Subject: [PATCH] hwmon: (pmbus_core) Check adapter PEC support - -Currently, for Packet Error Checking (PEC) only the controller -is checked for support. This causes problems on the cisco-8000 -platform where a SMBUS transaction errors are observed. This is -because PEC has to be enabled only if both controller and -adapter supports it. - -Added code to check PEC capability for adapter and enable it -only if both controller and adapter supports PEC. - -Signed-off-by: Madhava Reddy Siddareddygari ---- - drivers/hwmon/pmbus/pmbus_core.c | 10 +++++++--- - 1 file changed, 7 insertions(+), 3 deletions(-) - -diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c -index df4a6de24..2f98b4785 100644 ---- a/drivers/hwmon/pmbus/pmbus_core.c -+++ b/drivers/hwmon/pmbus/pmbus_core.c -@@ -2082,10 +2082,14 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, - data->has_status_word = true; - } - -- /* Enable PEC if the controller supports it */ -+ /* Enable PEC if the controller and bus supports it */ - ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); -- if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) -- client->flags |= I2C_CLIENT_PEC; -+ if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { -+ if (i2c_check_functionality(client->adapter, -+ I2C_FUNC_SMBUS_PEC)) { -+ client->flags |= I2C_CLIENT_PEC; -+ } -+ } - - if (data->info->pages) - pmbus_clear_faults(client); --- -2.26.2 - diff --git a/patch/driver-arista-disable-smbus-mux-for-hudson2.patch b/patch/driver-arista-disable-smbus-mux-for-hudson2.patch deleted file mode 100644 index e4d3bc46e..000000000 --- a/patch/driver-arista-disable-smbus-mux-for-hudson2.patch +++ /dev/null @@ -1,37 +0,0 @@ -Disable the SMBus mux on the Hudson2 cpu - -Support for multiplexed SMBus adapter was added in linux-4.5 (2fee61d2), -Unfortunately it enables mux support on the 7050QX-32S, where it wasn't enabled -previously. It changes bus numbers and i2c-1 (in 3.18) becomes i2c-4 (in 4.9). -It isn't expected by EOS, since bus number is hardcoded in many places. - -Fix it by disabling mux support for the CPU used by the 7050QX-32S. - -Signed-off-by: Vasiliy Khoruzhick -Signed-off-by: Samuel Angebault ---- - drivers/i2c/busses/i2c-piix4.c | 10 +++++++++- - 1 file changed, 9 insertions(+), 1 deletion(-) - -diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c -index 90946a8b9..2e1f55aee 100644 ---- a/drivers/i2c/busses/i2c-piix4.c -+++ b/drivers/i2c/busses/i2c-piix4.c -@@ -869,8 +869,16 @@ static int piix4_add_adapters_sb800(struct pci_dev *dev, unsigned short smba, - struct i2c_piix4_adapdata *adapdata; - int port; - int retval; -+ int adapters = PIIX4_MAX_ADAPTERS; - -- for (port = 0; port < PIIX4_MAX_ADAPTERS; port++) { -+ /* Arista BUG204903: don't use muxed adapters on Hudson 2 -+ * because we want AUX bus to be i2c-1 -+ */ -+ if (dev->vendor == PCI_VENDOR_ID_AMD && -+ dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS) -+ adapters = 1; -+ -+ for (port = 0; port < adapters; port++) { - retval = piix4_add_adapter(dev, smba, true, port, notify_imc, - piix4_main_port_names_sb800[port], - &piix4_main_adapters[port]); diff --git a/patch/driver-hwmon-max6620-fix-rpm-calc.patch b/patch/driver-hwmon-max6620-fix-rpm-calc.patch deleted file mode 100644 index 9f6fd3166..000000000 --- a/patch/driver-hwmon-max6620-fix-rpm-calc.patch +++ /dev/null @@ -1,196 +0,0 @@ -MAX6620 fix rpm calculation accuracy - -From: Cumulus Networks - -The driver only fills the most significant 8 bits of the fan tach -count (11 bit value). Fixing the driver to use all of 11 bits for -more accuracy. ---- - drivers/hwmon/max6620.c | 105 +++++++++++++++++++++-------------------------- - 1 file changed, 46 insertions(+), 59 deletions(-) - -diff --git a/drivers/hwmon/max6620.c b/drivers/hwmon/max6620.c -index 3c337c7b0..76c1f7f54 100644 ---- a/drivers/hwmon/max6620.c -+++ b/drivers/hwmon/max6620.c -@@ -46,6 +46,8 @@ - - /* clock: The clock frequency of the chip the driver should assume */ - static int clock = 8192; -+static u32 sr = 2; -+static u32 np = 2; - - module_param(clock, int, S_IRUGO); - -@@ -213,22 +215,22 @@ static ssize_t get_fan(struct device *dev, struct device_attribute *devattr, cha - - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct max6620_data *data = max6620_update_device(dev); -- int rpm; -- -- /* -- * Calculation details: -- * -- * Each tachometer counts over an interval given by the "count" -- * register (0.25, 0.5, 1 or 2 seconds). This module assumes -- * that the fans produce two pulses per revolution (this seems -- * to be the most common). -- */ -- if(data->tach[attr->index] == 0 || data->tach[attr->index] == 255) { -+ struct i2c_client *client = to_i2c_client(dev); -+ u32 rpm = 0; -+ u32 tach = 0; -+ u32 tach1 = 0; -+ u32 tach2 = 0; -+ -+ tach1 = i2c_smbus_read_byte_data(client, tach_reg[attr->index]); -+ tach1 = (tach1 << 3) & 0x7f8; -+ tach2 = i2c_smbus_read_byte_data(client, tach_reg[attr->index] + 1); -+ tach2 = (tach2 >> 5) & 0x7; -+ tach = tach1 | tach2; -+ if (tach == 0) { - rpm = 0; - } else { -- rpm = ((clock / (data->tach[attr->index] << 3)) * 30 * DIV_FROM_REG(data->fandyn[attr->index])); -+ rpm = (60 * sr * clock)/(tach * np); - } -- - return sprintf(buf, "%d\n", rpm); - } - -@@ -236,22 +238,21 @@ static ssize_t get_target(struct device *dev, struct device_attribute *devattr, - - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct max6620_data *data = max6620_update_device(dev); -- int kscale, ktach, rpm; -- -- /* -- * Use the datasheet equation: -- * -- * FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)] -- * -- * then multiply by 60 to give rpm. -- */ -- -- kscale = DIV_FROM_REG(data->fandyn[attr->index]); -- ktach = data->target[attr->index]; -- if(ktach == 0) { -+ struct i2c_client *client = to_i2c_client(dev); -+ u32 rpm; -+ u32 target; -+ u32 target1; -+ u32 target2; -+ -+ target1 = i2c_smbus_read_byte_data(client, target_reg[attr->index]); -+ target1 = (target1 << 3) & 0x7f8; -+ target2 = i2c_smbus_read_byte_data(client, target_reg[attr->index] + 1); -+ target2 = (target2 >> 5) & 0x7; -+ target = target1 | target2; -+ if (target == 0) { - rpm = 0; - } else { -- rpm = ((60 * kscale * clock) / (ktach << 3)); -+ rpm = (60 * sr * clock)/(target * np); - } - return sprintf(buf, "%d\n", rpm); - } -@@ -261,9 +262,11 @@ static ssize_t set_target(struct device *dev, struct device_attribute *devattr, - struct i2c_client *client = to_i2c_client(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct max6620_data *data = i2c_get_clientdata(client); -- int kscale, ktach; -- unsigned long rpm; -+ u32 rpm; - int err; -+ u32 target; -+ u32 target1; -+ u32 target2; - - err = kstrtoul(buf, 10, &rpm); - if (err) -@@ -271,25 +274,13 @@ static ssize_t set_target(struct device *dev, struct device_attribute *devattr, - - rpm = SENSORS_LIMIT(rpm, FAN_RPM_MIN, FAN_RPM_MAX); - -- /* -- * Divide the required speed by 60 to get from rpm to rps, then -- * use the datasheet equation: -- * -- * KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1 -- */ -- - mutex_lock(&data->update_lock); - -- kscale = DIV_FROM_REG(data->fandyn[attr->index]); -- ktach = ((60 * kscale * clock) / rpm); -- if (ktach < 0) -- ktach = 0; -- if (ktach > 255) -- ktach = 255; -- data->target[attr->index] = ktach; -- -- i2c_smbus_write_byte_data(client, target_reg[attr->index], data->target[attr->index]); -- i2c_smbus_write_byte_data(client, target_reg[attr->index]+0x01, 0x00); -+ target = (60 * sr * 8192)/(rpm * np); -+ target1 = (target >> 3) & 0xff; -+ target2 = (target << 5) & 0xe0; -+ i2c_smbus_write_byte_data(client, target_reg[attr->index], target1); -+ i2c_smbus_write_byte_data(client, target_reg[attr->index] + 1, target2); - - mutex_unlock(&data->update_lock); - -@@ -609,8 +600,11 @@ static int max6620_init_client(struct i2c_client *client) { - } - - -- -- if (i2c_smbus_write_byte_data(client, MAX6620_REG_CONFIG, config)) { -+ /* -+ * Set bit 4, disable other fans from going full speed on a fail -+ * failure. -+ */ -+ if (i2c_smbus_write_byte_data(client, MAX6620_REG_CONFIG, config | 0x10)) { - dev_err(&client->dev, "Config write error, aborting.\n"); - return err; - } -@@ -618,28 +612,21 @@ static int max6620_init_client(struct i2c_client *client) { - data->config = config; - for (i = 0; i < 4; i++) { - data->fancfg[i] = i2c_smbus_read_byte_data(client, config_reg[i]); -- data->fancfg[i] |= 0x80; // enable TACH monitoring -+ data->fancfg[i] |= 0xa8; // enable TACH monitoring - i2c_smbus_write_byte_data(client, config_reg[i], data->fancfg[i]); - data->fandyn[i] = i2c_smbus_read_byte_data(client, dyn_reg[i]); -- data-> fandyn[i] |= 0x1C; -+ /* 2 counts (001) and Rate change 100 (0.125 secs) */ -+ data-> fandyn[i] = 0x30; - i2c_smbus_write_byte_data(client, dyn_reg[i], data->fandyn[i]); - data->tach[i] = i2c_smbus_read_byte_data(client, tach_reg[i]); - data->volt[i] = i2c_smbus_read_byte_data(client, volt_reg[i]); - data->target[i] = i2c_smbus_read_byte_data(client, target_reg[i]); - data->dac[i] = i2c_smbus_read_byte_data(client, dac_reg[i]); - -- -- - } -- -- -- - return 0; - } - -- -- -- - static struct max6620_data *max6620_update_device(struct device *dev) - { - int i; -@@ -678,7 +665,7 @@ static struct max6620_data *max6620_update_device(struct device *dev) - return data; - } - --module_i2c_driver(max6620_driver); -+// module_i2c_driver(max6620_driver); - - static int __init max6620_init(void) - { diff --git a/patch/driver-hwmon-max6620-update.patch b/patch/driver-hwmon-max6620-update.patch deleted file mode 100644 index 3060afe31..000000000 --- a/patch/driver-hwmon-max6620-update.patch +++ /dev/null @@ -1,113 +0,0 @@ -Update MAX6620 driver to support newer kernel version - -From: Shuotian Cheng - - ---- - drivers/hwmon/max6620.c | 25 +++++++++++-------------- - 1 file changed, 11 insertions(+), 14 deletions(-) - -diff --git a/drivers/hwmon/max6620.c b/drivers/hwmon/max6620.c -index 76c1f7f54..fb4919520 100644 ---- a/drivers/hwmon/max6620.c -+++ b/drivers/hwmon/max6620.c -@@ -183,7 +183,7 @@ static struct i2c_driver max6620_driver = { - .name = "max6620", - }, - .probe = max6620_probe, -- .remove = __devexit_p(max6620_remove), -+ .remove = max6620_remove, - .id_table = max6620_id, - .address_list = normal_i2c, - }; -@@ -231,6 +231,7 @@ static ssize_t get_fan(struct device *dev, struct device_attribute *devattr, cha - } else { - rpm = (60 * sr * clock)/(tach * np); - } -+ - return sprintf(buf, "%d\n", rpm); - } - -@@ -262,17 +263,17 @@ static ssize_t set_target(struct device *dev, struct device_attribute *devattr, - struct i2c_client *client = to_i2c_client(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct max6620_data *data = i2c_get_clientdata(client); -- u32 rpm; -+ unsigned long rpm; - int err; -- u32 target; -- u32 target1; -- u32 target2; -+ unsigned long target; -+ unsigned long target1; -+ unsigned long target2; - - err = kstrtoul(buf, 10, &rpm); - if (err) - return err; - -- rpm = SENSORS_LIMIT(rpm, FAN_RPM_MIN, FAN_RPM_MAX); -+ rpm = clamp_val(rpm, FAN_RPM_MIN, FAN_RPM_MAX); - - mutex_lock(&data->update_lock); - -@@ -326,7 +327,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, con - if (err) - return err; - -- pwm = SENSORS_LIMIT(pwm, 0, 255); -+ pwm = clamp_val(pwm, 0, 255); - - mutex_lock(&data->update_lock); - -@@ -534,7 +535,7 @@ static struct attribute_group max6620_attr_grp = { - * Real code - */ - --static int __devinit max6620_probe(struct i2c_client *client, const struct i2c_device_id *id) { -+static int max6620_probe(struct i2c_client *client, const struct i2c_device_id *id) { - - struct max6620_data *data; - int err; -@@ -575,7 +576,7 @@ dev_info(&client->dev, "Sysfs entries created\n"); - return err; - } - --static int __devexit max6620_remove(struct i2c_client *client) { -+static int max6620_remove(struct i2c_client *client) { - - struct max6620_data *data = i2c_get_clientdata(client); - -@@ -599,7 +600,6 @@ static int max6620_init_client(struct i2c_client *client) { - return err; - } - -- - /* - * Set bit 4, disable other fans from going full speed on a fail - * failure. -@@ -615,14 +615,13 @@ static int max6620_init_client(struct i2c_client *client) { - data->fancfg[i] |= 0xa8; // enable TACH monitoring - i2c_smbus_write_byte_data(client, config_reg[i], data->fancfg[i]); - data->fandyn[i] = i2c_smbus_read_byte_data(client, dyn_reg[i]); -- /* 2 counts (001) and Rate change 100 (0.125 secs) */ -+ /* 2 counts (001) and Rate change 100 (0.125 secs) */ - data-> fandyn[i] = 0x30; - i2c_smbus_write_byte_data(client, dyn_reg[i], data->fandyn[i]); - data->tach[i] = i2c_smbus_read_byte_data(client, tach_reg[i]); - data->volt[i] = i2c_smbus_read_byte_data(client, volt_reg[i]); - data->target[i] = i2c_smbus_read_byte_data(client, target_reg[i]); - data->dac[i] = i2c_smbus_read_byte_data(client, dac_reg[i]); -- - } - return 0; - } -@@ -665,8 +664,6 @@ static struct max6620_data *max6620_update_device(struct device *dev) - return data; - } - --// module_i2c_driver(max6620_driver); -- - static int __init max6620_init(void) - { - return i2c_add_driver(&max6620_driver); diff --git a/patch/driver-hwmon-max6620.patch b/patch/driver-hwmon-max6620.patch deleted file mode 100644 index 6e7c0922d..000000000 --- a/patch/driver-hwmon-max6620.patch +++ /dev/null @@ -1,753 +0,0 @@ -Driver for MAX6620 Fan sensor - -From: Cumulus Networks - - ---- - drivers/hwmon/Kconfig | 10 + - drivers/hwmon/Makefile | 1 - drivers/hwmon/max6620.c | 702 +++++++++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 713 insertions(+) - create mode 100644 drivers/hwmon/max6620.c - -diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig -index c7adaca2a..d0b4b1b34 100644 ---- a/drivers/hwmon/Kconfig -+++ b/drivers/hwmon/Kconfig -@@ -905,6 +905,16 @@ config SENSORS_MAX6650 - This driver can also be built as a module. If so, the module - will be called max6650. - -+config SENSORS_MAX6620 -+ tristate "Maxim MAX6620 sensor chip" -+ depends on I2C -+ help -+ If you say yes here you get support for the MAX6620 -+ sensor chips. -+ -+ This driver can also be built as a module. If so, the module -+ will be called max6620. -+ - config SENSORS_MAX6697 - tristate "Maxim MAX6697 and compatibles" - depends on I2C -diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile -index 93f7f41ea..6eca4ea6d 100644 ---- a/drivers/hwmon/Makefile -+++ b/drivers/hwmon/Makefile -@@ -124,6 +124,7 @@ obj-$(CONFIG_SENSORS_MAX6621) += max6621.o - obj-$(CONFIG_SENSORS_MAX6639) += max6639.o - obj-$(CONFIG_SENSORS_MAX6642) += max6642.o - obj-$(CONFIG_SENSORS_MAX6650) += max6650.o -+obj-$(CONFIG_SENSORS_MAX6620) += max6620.o - obj-$(CONFIG_SENSORS_MAX6697) += max6697.o - obj-$(CONFIG_SENSORS_MAX31790) += max31790.o - obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o -diff --git a/drivers/hwmon/max6620.c b/drivers/hwmon/max6620.c -new file mode 100644 -index 000000000..3c337c7b0 ---- /dev/null -+++ b/drivers/hwmon/max6620.c -@@ -0,0 +1,702 @@ -+/* -+ * max6620.c - Linux Kernel module for hardware monitoring. -+ * -+ * (C) 2012 by L. Grunenberg -+ * -+ * based on code written by : -+ * 2007 by Hans J. Koch -+ * John Morris -+ * Copyright (c) 2003 Spirent Communications -+ * and Claus Gindhart -+ * -+ * This module has only been tested with the MAX6620 chip. -+ * -+ * The datasheet was last seen at: -+ * -+ * http://pdfserv.maxim-ic.com/en/ds/MAX6620.pdf -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* -+ * Insmod parameters -+ */ -+ -+ -+/* clock: The clock frequency of the chip the driver should assume */ -+static int clock = 8192; -+ -+module_param(clock, int, S_IRUGO); -+ -+static const unsigned short normal_i2c[] = {0x0a, 0x1a, 0x2a, I2C_CLIENT_END}; -+ -+/* -+ * MAX 6620 registers -+ */ -+ -+#define MAX6620_REG_CONFIG 0x00 -+#define MAX6620_REG_FAULT 0x01 -+#define MAX6620_REG_CONF_FAN0 0x02 -+#define MAX6620_REG_CONF_FAN1 0x03 -+#define MAX6620_REG_CONF_FAN2 0x04 -+#define MAX6620_REG_CONF_FAN3 0x05 -+#define MAX6620_REG_DYN_FAN0 0x06 -+#define MAX6620_REG_DYN_FAN1 0x07 -+#define MAX6620_REG_DYN_FAN2 0x08 -+#define MAX6620_REG_DYN_FAN3 0x09 -+#define MAX6620_REG_TACH0 0x10 -+#define MAX6620_REG_TACH1 0x12 -+#define MAX6620_REG_TACH2 0x14 -+#define MAX6620_REG_TACH3 0x16 -+#define MAX6620_REG_VOLT0 0x18 -+#define MAX6620_REG_VOLT1 0x1A -+#define MAX6620_REG_VOLT2 0x1C -+#define MAX6620_REG_VOLT3 0x1E -+#define MAX6620_REG_TAR0 0x20 -+#define MAX6620_REG_TAR1 0x22 -+#define MAX6620_REG_TAR2 0x24 -+#define MAX6620_REG_TAR3 0x26 -+#define MAX6620_REG_DAC0 0x28 -+#define MAX6620_REG_DAC1 0x2A -+#define MAX6620_REG_DAC2 0x2C -+#define MAX6620_REG_DAC3 0x2E -+ -+/* -+ * Config register bits -+ */ -+ -+#define MAX6620_CFG_RUN 0x80 -+#define MAX6620_CFG_POR 0x40 -+#define MAX6620_CFG_TIMEOUT 0x20 -+#define MAX6620_CFG_FULLFAN 0x10 -+#define MAX6620_CFG_OSC 0x08 -+#define MAX6620_CFG_WD_MASK 0x06 -+#define MAX6620_CFG_WD_2 0x02 -+#define MAX6620_CFG_WD_6 0x04 -+#define MAX6620_CFG_WD10 0x06 -+#define MAX6620_CFG_WD 0x01 -+ -+ -+/* -+ * Failure status register bits -+ */ -+ -+#define MAX6620_FAIL_TACH0 0x10 -+#define MAX6620_FAIL_TACH1 0x20 -+#define MAX6620_FAIL_TACH2 0x40 -+#define MAX6620_FAIL_TACH3 0x80 -+#define MAX6620_FAIL_MASK0 0x01 -+#define MAX6620_FAIL_MASK1 0x02 -+#define MAX6620_FAIL_MASK2 0x04 -+#define MAX6620_FAIL_MASK3 0x08 -+ -+ -+/* Minimum and maximum values of the FAN-RPM */ -+#define FAN_RPM_MIN 240 -+#define FAN_RPM_MAX 30000 -+ -+#define DIV_FROM_REG(reg) (1 << ((reg & 0xE0) >> 5)) -+ -+static int max6620_probe(struct i2c_client *client, const struct i2c_device_id *id); -+static int max6620_init_client(struct i2c_client *client); -+static int max6620_remove(struct i2c_client *client); -+static struct max6620_data *max6620_update_device(struct device *dev); -+ -+static const u8 config_reg[] = { -+ MAX6620_REG_CONF_FAN0, -+ MAX6620_REG_CONF_FAN1, -+ MAX6620_REG_CONF_FAN2, -+ MAX6620_REG_CONF_FAN3, -+}; -+ -+static const u8 dyn_reg[] = { -+ MAX6620_REG_DYN_FAN0, -+ MAX6620_REG_DYN_FAN1, -+ MAX6620_REG_DYN_FAN2, -+ MAX6620_REG_DYN_FAN3, -+}; -+ -+static const u8 tach_reg[] = { -+ MAX6620_REG_TACH0, -+ MAX6620_REG_TACH1, -+ MAX6620_REG_TACH2, -+ MAX6620_REG_TACH3, -+}; -+ -+static const u8 volt_reg[] = { -+ MAX6620_REG_VOLT0, -+ MAX6620_REG_VOLT1, -+ MAX6620_REG_VOLT2, -+ MAX6620_REG_VOLT3, -+}; -+ -+static const u8 target_reg[] = { -+ MAX6620_REG_TAR0, -+ MAX6620_REG_TAR1, -+ MAX6620_REG_TAR2, -+ MAX6620_REG_TAR3, -+}; -+ -+static const u8 dac_reg[] = { -+ MAX6620_REG_DAC0, -+ MAX6620_REG_DAC1, -+ MAX6620_REG_DAC2, -+ MAX6620_REG_DAC3, -+}; -+ -+/* -+ * Driver data (common to all clients) -+ */ -+ -+static const struct i2c_device_id max6620_id[] = { -+ { "max6620", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(i2c, max6620_id); -+ -+static struct i2c_driver max6620_driver = { -+ .class = I2C_CLASS_HWMON, -+ .driver = { -+ .name = "max6620", -+ }, -+ .probe = max6620_probe, -+ .remove = __devexit_p(max6620_remove), -+ .id_table = max6620_id, -+ .address_list = normal_i2c, -+}; -+ -+/* -+ * Client data (each client gets its own) -+ */ -+ -+struct max6620_data { -+ struct device *hwmon_dev; -+ struct mutex update_lock; -+ int nr_fans; -+ char valid; /* zero until following fields are valid */ -+ unsigned long last_updated; /* in jiffies */ -+ -+ /* register values */ -+ u8 speed[4]; -+ u8 config; -+ u8 fancfg[4]; -+ u8 fandyn[4]; -+ u8 tach[4]; -+ u8 volt[4]; -+ u8 target[4]; -+ u8 dac[4]; -+ u8 fault; -+}; -+ -+static ssize_t get_fan(struct device *dev, struct device_attribute *devattr, char *buf) { -+ -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ struct max6620_data *data = max6620_update_device(dev); -+ int rpm; -+ -+ /* -+ * Calculation details: -+ * -+ * Each tachometer counts over an interval given by the "count" -+ * register (0.25, 0.5, 1 or 2 seconds). This module assumes -+ * that the fans produce two pulses per revolution (this seems -+ * to be the most common). -+ */ -+ if(data->tach[attr->index] == 0 || data->tach[attr->index] == 255) { -+ rpm = 0; -+ } else { -+ rpm = ((clock / (data->tach[attr->index] << 3)) * 30 * DIV_FROM_REG(data->fandyn[attr->index])); -+ } -+ -+ return sprintf(buf, "%d\n", rpm); -+} -+ -+static ssize_t get_target(struct device *dev, struct device_attribute *devattr, char *buf) { -+ -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ struct max6620_data *data = max6620_update_device(dev); -+ int kscale, ktach, rpm; -+ -+ /* -+ * Use the datasheet equation: -+ * -+ * FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)] -+ * -+ * then multiply by 60 to give rpm. -+ */ -+ -+ kscale = DIV_FROM_REG(data->fandyn[attr->index]); -+ ktach = data->target[attr->index]; -+ if(ktach == 0) { -+ rpm = 0; -+ } else { -+ rpm = ((60 * kscale * clock) / (ktach << 3)); -+ } -+ return sprintf(buf, "%d\n", rpm); -+} -+ -+static ssize_t set_target(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { -+ -+ struct i2c_client *client = to_i2c_client(dev); -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ struct max6620_data *data = i2c_get_clientdata(client); -+ int kscale, ktach; -+ unsigned long rpm; -+ int err; -+ -+ err = kstrtoul(buf, 10, &rpm); -+ if (err) -+ return err; -+ -+ rpm = SENSORS_LIMIT(rpm, FAN_RPM_MIN, FAN_RPM_MAX); -+ -+ /* -+ * Divide the required speed by 60 to get from rpm to rps, then -+ * use the datasheet equation: -+ * -+ * KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1 -+ */ -+ -+ mutex_lock(&data->update_lock); -+ -+ kscale = DIV_FROM_REG(data->fandyn[attr->index]); -+ ktach = ((60 * kscale * clock) / rpm); -+ if (ktach < 0) -+ ktach = 0; -+ if (ktach > 255) -+ ktach = 255; -+ data->target[attr->index] = ktach; -+ -+ i2c_smbus_write_byte_data(client, target_reg[attr->index], data->target[attr->index]); -+ i2c_smbus_write_byte_data(client, target_reg[attr->index]+0x01, 0x00); -+ -+ mutex_unlock(&data->update_lock); -+ -+ return count; -+} -+ -+/* -+ * Get/set the fan speed in open loop mode using pwm1 sysfs file. -+ * Speed is given as a relative value from 0 to 255, where 255 is maximum -+ * speed. Note that this is done by writing directly to the chip's DAC, -+ * it won't change the closed loop speed set by fan1_target. -+ * Also note that due to rounding errors it is possible that you don't read -+ * back exactly the value you have set. -+ */ -+ -+static ssize_t get_pwm(struct device *dev, struct device_attribute *devattr, char *buf) { -+ -+ int pwm; -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ struct max6620_data *data = max6620_update_device(dev); -+ -+ /* -+ * Useful range for dac is 0-180 for 12V fans and 0-76 for 5V fans. -+ * Lower DAC values mean higher speeds. -+ */ -+ pwm = ((int)data->volt[attr->index]); -+ -+ if (pwm < 0) -+ pwm = 0; -+ -+ return sprintf(buf, "%d\n", pwm); -+} -+ -+static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { -+ -+ struct i2c_client *client = to_i2c_client(dev); -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ struct max6620_data *data = i2c_get_clientdata(client); -+ unsigned long pwm; -+ int err; -+ -+ err = kstrtoul(buf, 10, &pwm); -+ if (err) -+ return err; -+ -+ pwm = SENSORS_LIMIT(pwm, 0, 255); -+ -+ mutex_lock(&data->update_lock); -+ -+ data->dac[attr->index] = pwm; -+ -+ -+ i2c_smbus_write_byte_data(client, dac_reg[attr->index], data->dac[attr->index]); -+ i2c_smbus_write_byte_data(client, dac_reg[attr->index]+1, 0x00); -+ -+ mutex_unlock(&data->update_lock); -+ -+ return count; -+} -+ -+/* -+ * Get/Set controller mode: -+ * Possible values: -+ * 0 = Fan always on -+ * 1 = Open loop, Voltage is set according to speed, not regulated. -+ * 2 = Closed loop, RPM for all fans regulated by fan1 tachometer -+ */ -+ -+static ssize_t get_enable(struct device *dev, struct device_attribute *devattr, char *buf) { -+ -+ struct max6620_data *data = max6620_update_device(dev); -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ int mode = (data->fancfg[attr->index] & 0x80 ) >> 7; -+ int sysfs_modes[2] = {1, 2}; -+ -+ return sprintf(buf, "%d\n", sysfs_modes[mode]); -+} -+ -+static ssize_t set_enable(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { -+ -+ struct i2c_client *client = to_i2c_client(dev); -+ struct max6620_data *data = i2c_get_clientdata(client); -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ int max6620_modes[3] = {0, 1, 0}; -+ unsigned long mode; -+ int err; -+ -+ err = kstrtoul(buf, 10, &mode); -+ if (err) -+ return err; -+ -+ if (mode > 2) -+ return -EINVAL; -+ -+ mutex_lock(&data->update_lock); -+ -+ data->fancfg[attr->index] = i2c_smbus_read_byte_data(client, config_reg[attr->index]); -+ data->fancfg[attr->index] = (data->fancfg[attr->index] & ~0x80) -+ | (max6620_modes[mode] << 7); -+ -+ i2c_smbus_write_byte_data(client, config_reg[attr->index], data->fancfg[attr->index]); -+ -+ mutex_unlock(&data->update_lock); -+ -+ return count; -+} -+ -+/* -+ * Read/write functions for fan1_div sysfs file. The MAX6620 has no such -+ * divider. We handle this by converting between divider and counttime: -+ * -+ * (counttime == k) <==> (divider == 2^k), k = 0, 1, 2, 3, 4 or 5 -+ * -+ * Lower values of k allow to connect a faster fan without the risk of -+ * counter overflow. The price is lower resolution. You can also set counttime -+ * using the module parameter. Note that the module parameter "prescaler" also -+ * influences the behaviour. Unfortunately, there's no sysfs attribute -+ * defined for that. See the data sheet for details. -+ */ -+ -+static ssize_t get_div(struct device *dev, struct device_attribute *devattr, char *buf) { -+ -+ struct max6620_data *data = max6620_update_device(dev); -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ -+ return sprintf(buf, "%d\n", DIV_FROM_REG(data->fandyn[attr->index])); -+} -+ -+static ssize_t set_div(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { -+ -+ struct i2c_client *client = to_i2c_client(dev); -+ struct max6620_data *data = i2c_get_clientdata(client); -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ unsigned long div; -+ int err; -+ u8 div_bin; -+ -+ err = kstrtoul(buf, 10, &div); -+ if (err) -+ return err; -+ -+ mutex_lock(&data->update_lock); -+ switch (div) { -+ case 1: -+ div_bin = 0; -+ break; -+ case 2: -+ div_bin = 1; -+ break; -+ case 4: -+ div_bin = 2; -+ break; -+ case 8: -+ div_bin = 3; -+ break; -+ case 16: -+ div_bin = 4; -+ break; -+ case 32: -+ div_bin = 5; -+ break; -+ default: -+ mutex_unlock(&data->update_lock); -+ return -EINVAL; -+ } -+ data->fandyn[attr->index] &= 0x1F; -+ data->fandyn[attr->index] |= div_bin << 5; -+ i2c_smbus_write_byte_data(client, dyn_reg[attr->index], data->fandyn[attr->index]); -+ mutex_unlock(&data->update_lock); -+ -+ return count; -+} -+ -+/* -+ * Get alarm stati: -+ * Possible values: -+ * 0 = no alarm -+ * 1 = alarm -+ */ -+ -+static ssize_t get_alarm(struct device *dev, struct device_attribute *devattr, char *buf) { -+ -+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); -+ struct max6620_data *data = max6620_update_device(dev); -+ struct i2c_client *client = to_i2c_client(dev); -+ int alarm = 0; -+ -+ if (data->fault & (1 << attr->index)) { -+ mutex_lock(&data->update_lock); -+ alarm = 1; -+ data->fault &= ~(1 << attr->index); -+ data->fault |= i2c_smbus_read_byte_data(client, -+ MAX6620_REG_FAULT); -+ mutex_unlock(&data->update_lock); -+ } -+ -+ return sprintf(buf, "%d\n", alarm); -+} -+ -+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0); -+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1); -+static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2); -+static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3); -+static SENSOR_DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO, get_target, set_target, 0); -+static SENSOR_DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, get_div, set_div, 0); -+// static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 0); -+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 0); -+static SENSOR_DEVICE_ATTR(fan2_target, S_IWUSR | S_IRUGO, get_target, set_target, 1); -+static SENSOR_DEVICE_ATTR(fan2_div, S_IWUSR | S_IRUGO, get_div, set_div, 1); -+// static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 1); -+static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 1); -+static SENSOR_DEVICE_ATTR(fan3_target, S_IWUSR | S_IRUGO, get_target, set_target, 2); -+static SENSOR_DEVICE_ATTR(fan3_div, S_IWUSR | S_IRUGO, get_div, set_div, 2); -+// static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 2); -+static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 2); -+static SENSOR_DEVICE_ATTR(fan4_target, S_IWUSR | S_IRUGO, get_target, set_target, 3); -+static SENSOR_DEVICE_ATTR(fan4_div, S_IWUSR | S_IRUGO, get_div, set_div, 3); -+// static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 3); -+static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 3); -+ -+static struct attribute *max6620_attrs[] = { -+ &sensor_dev_attr_fan1_input.dev_attr.attr, -+ &sensor_dev_attr_fan2_input.dev_attr.attr, -+ &sensor_dev_attr_fan3_input.dev_attr.attr, -+ &sensor_dev_attr_fan4_input.dev_attr.attr, -+ &sensor_dev_attr_fan1_target.dev_attr.attr, -+ &sensor_dev_attr_fan1_div.dev_attr.attr, -+// &sensor_dev_attr_pwm1_enable.dev_attr.attr, -+ &sensor_dev_attr_pwm1.dev_attr.attr, -+ &sensor_dev_attr_fan2_target.dev_attr.attr, -+ &sensor_dev_attr_fan2_div.dev_attr.attr, -+// &sensor_dev_attr_pwm2_enable.dev_attr.attr, -+ &sensor_dev_attr_pwm2.dev_attr.attr, -+ &sensor_dev_attr_fan3_target.dev_attr.attr, -+ &sensor_dev_attr_fan3_div.dev_attr.attr, -+// &sensor_dev_attr_pwm3_enable.dev_attr.attr, -+ &sensor_dev_attr_pwm3.dev_attr.attr, -+ &sensor_dev_attr_fan4_target.dev_attr.attr, -+ &sensor_dev_attr_fan4_div.dev_attr.attr, -+// &sensor_dev_attr_pwm4_enable.dev_attr.attr, -+ &sensor_dev_attr_pwm4.dev_attr.attr, -+ NULL -+}; -+ -+static struct attribute_group max6620_attr_grp = { -+ .attrs = max6620_attrs, -+}; -+ -+ -+/* -+ * Real code -+ */ -+ -+static int __devinit max6620_probe(struct i2c_client *client, const struct i2c_device_id *id) { -+ -+ struct max6620_data *data; -+ int err; -+ -+ data = devm_kzalloc(&client->dev, sizeof(struct max6620_data), GFP_KERNEL); -+ if (!data) { -+ dev_err(&client->dev, "out of memory.\n"); -+ return -ENOMEM; -+ } -+ -+ i2c_set_clientdata(client, data); -+ mutex_init(&data->update_lock); -+ data->nr_fans = id->driver_data; -+ -+ /* -+ * Initialize the max6620 chip -+ */ -+ dev_info(&client->dev, "About to initialize module\n"); -+ -+ err = max6620_init_client(client); -+ if (err) -+ return err; -+ dev_info(&client->dev, "Module initialized\n"); -+ -+ err = sysfs_create_group(&client->dev.kobj, &max6620_attr_grp); -+ if (err) -+ return err; -+dev_info(&client->dev, "Sysfs entries created\n"); -+ -+ data->hwmon_dev = hwmon_device_register(&client->dev); -+ if (!IS_ERR(data->hwmon_dev)) -+ return 0; -+ -+ err = PTR_ERR(data->hwmon_dev); -+ dev_err(&client->dev, "error registering hwmon device.\n"); -+ -+ sysfs_remove_group(&client->dev.kobj, &max6620_attr_grp); -+ return err; -+} -+ -+static int __devexit max6620_remove(struct i2c_client *client) { -+ -+ struct max6620_data *data = i2c_get_clientdata(client); -+ -+ hwmon_device_unregister(data->hwmon_dev); -+ -+ sysfs_remove_group(&client->dev.kobj, &max6620_attr_grp); -+ return 0; -+} -+ -+static int max6620_init_client(struct i2c_client *client) { -+ -+ struct max6620_data *data = i2c_get_clientdata(client); -+ int config; -+ int err = -EIO; -+ int i; -+ -+ config = i2c_smbus_read_byte_data(client, MAX6620_REG_CONFIG); -+ -+ if (config < 0) { -+ dev_err(&client->dev, "Error reading config, aborting.\n"); -+ return err; -+ } -+ -+ -+ -+ if (i2c_smbus_write_byte_data(client, MAX6620_REG_CONFIG, config)) { -+ dev_err(&client->dev, "Config write error, aborting.\n"); -+ return err; -+ } -+ -+ data->config = config; -+ for (i = 0; i < 4; i++) { -+ data->fancfg[i] = i2c_smbus_read_byte_data(client, config_reg[i]); -+ data->fancfg[i] |= 0x80; // enable TACH monitoring -+ i2c_smbus_write_byte_data(client, config_reg[i], data->fancfg[i]); -+ data->fandyn[i] = i2c_smbus_read_byte_data(client, dyn_reg[i]); -+ data-> fandyn[i] |= 0x1C; -+ i2c_smbus_write_byte_data(client, dyn_reg[i], data->fandyn[i]); -+ data->tach[i] = i2c_smbus_read_byte_data(client, tach_reg[i]); -+ data->volt[i] = i2c_smbus_read_byte_data(client, volt_reg[i]); -+ data->target[i] = i2c_smbus_read_byte_data(client, target_reg[i]); -+ data->dac[i] = i2c_smbus_read_byte_data(client, dac_reg[i]); -+ -+ -+ -+ } -+ -+ -+ -+ return 0; -+} -+ -+ -+ -+ -+static struct max6620_data *max6620_update_device(struct device *dev) -+{ -+ int i; -+ struct i2c_client *client = to_i2c_client(dev); -+ struct max6620_data *data = i2c_get_clientdata(client); -+ -+ mutex_lock(&data->update_lock); -+ -+ if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { -+ -+ for (i = 0; i < 4; i++) { -+ data->fancfg[i] = i2c_smbus_read_byte_data(client, config_reg[i]); -+ data->fandyn[i] = i2c_smbus_read_byte_data(client, dyn_reg[i]); -+ data->tach[i] = i2c_smbus_read_byte_data(client, tach_reg[i]); -+ data->volt[i] = i2c_smbus_read_byte_data(client, volt_reg[i]); -+ data->target[i] = i2c_smbus_read_byte_data(client, target_reg[i]); -+ data->dac[i] = i2c_smbus_read_byte_data(client, dac_reg[i]); -+ } -+ -+ -+ /* -+ * Alarms are cleared on read in case the condition that -+ * caused the alarm is removed. Keep the value latched here -+ * for providing the register through different alarm files. -+ */ -+ u8 fault_reg; -+ fault_reg = i2c_smbus_read_byte_data(client, MAX6620_REG_FAULT); -+ data->fault |= (fault_reg >> 4) & (fault_reg & 0x0F); -+ -+ data->last_updated = jiffies; -+ data->valid = 1; -+ } -+ -+ mutex_unlock(&data->update_lock); -+ -+ return data; -+} -+ -+module_i2c_driver(max6620_driver); -+ -+static int __init max6620_init(void) -+{ -+ return i2c_add_driver(&max6620_driver); -+} -+module_init(max6620_init); -+ -+/** -+ * sht21_init() - clean up driver -+ * -+ * Called when module is removed. -+ */ -+static void __exit max6620_exit(void) -+{ -+ i2c_del_driver(&max6620_driver); -+} -+module_exit(max6620_exit); -+ -+MODULE_AUTHOR("Lucas Grunenberg"); -+MODULE_DESCRIPTION("MAX6620 sensor driver"); -+MODULE_LICENSE("GPL"); diff --git a/patch/driver-i2c-mux-pca954x-allow-management-of-device-idle-stat.patch b/patch/driver-i2c-mux-pca954x-allow-management-of-device-idle-stat.patch deleted file mode 100644 index e8df99cd8..000000000 --- a/patch/driver-i2c-mux-pca954x-allow-management-of-device-idle-stat.patch +++ /dev/null @@ -1,207 +0,0 @@ -From f1fb64b04bf414ab04e31ac107bb28137105c5fd Mon Sep 17 00:00:00 2001 -From: Robert Shearman -Date: Thu, 28 Feb 2019 11:43:43 +0000 -Subject: [PATCH] i2c: mux: pca954x: allow management of device idle state via - sysfs - -The behaviour, by default, to not deselect after each transfer is -unsafe when there is a device with an address that conflicts with -another device on another mux on the same parent bus, and it -may not be convenient to use devicetree to set the deselect mux, -e.g. when running on x86_64 when ACPI is used to discover most of the -device hierarchy. - -Therefore, provide the ability to set the idle state behaviour using a -new sysfs file, idle_state as a complement to the method of -instantiating the device via sysfs. The possible behaviours are -disconnect, i.e. to deselect all channels from the mux, as-is (the -default), i.e. leave the last channel selected, and set a -predetermined channel. - -The current behaviour of leaving the channel as-is after each -transaction is preserved. - -Signed-off-by: Robert Shearman -Signed-off-by: Peter Rosin ---- - .../ABI/testing/sysfs-bus-i2c-devices-pca954x | 20 +++++ - drivers/i2c/muxes/i2c-mux-pca954x.c | 85 +++++++++++++++++-- - 2 files changed, 97 insertions(+), 8 deletions(-) - create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x - -diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x b/Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x -new file mode 100644 -index 000000000000..0b0de8cd0d13 ---- /dev/null -+++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x -@@ -0,0 +1,20 @@ -+What: /sys/bus/i2c/.../idle_state -+Date: January 2019 -+KernelVersion: 5.2 -+Contact: Robert Shearman -+Description: -+ Value that exists only for mux devices that can be -+ written to control the behaviour of the multiplexer on -+ idle. Possible values: -+ -2 - disconnect on idle, i.e. deselect the last used -+ channel, which is useful when there is a device -+ with an address that conflicts with another -+ device on another mux on the same parent bus. -+ -1 - leave the mux as-is, which is the most optimal -+ setting in terms of I2C operations and is the -+ default mode. -+ 0.. - set the mux to a predetermined channel, -+ which is useful if there is one channel that is -+ used almost always, and you want to reduce the -+ latency for normal operations after rare -+ transactions on other channels -diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c -index e32fef560684..923aa3a5a3dc 100644 ---- a/drivers/i2c/muxes/i2c-mux-pca954x.c -+++ b/drivers/i2c/muxes/i2c-mux-pca954x.c -@@ -49,6 +49,7 @@ - #include - #include - #include -+#include - - #define PCA954X_MAX_NCHANS 8 - -@@ -84,7 +85,9 @@ struct pca954x { - const struct chip_desc *chip; - - u8 last_chan; /* last register value */ -- u8 deselect; -+ /* MUX_IDLE_AS_IS, MUX_IDLE_DISCONNECT or >= 0 for channel */ -+ s8 idle_state; -+ - struct i2c_client *client; - - struct irq_domain *irq; -@@ -253,15 +256,71 @@ static int pca954x_deselect_mux(struct i2c_mux_core *muxc, u32 chan) - { - struct pca954x *data = i2c_mux_priv(muxc); - struct i2c_client *client = data->client; -+ s8 idle_state; -+ -+ idle_state = READ_ONCE(data->idle_state); -+ if (idle_state >= 0) -+ /* Set the mux back to a predetermined channel */ -+ return pca954x_select_chan(muxc, idle_state); -+ -+ if (idle_state == MUX_IDLE_DISCONNECT) { -+ /* Deselect active channel */ -+ data->last_chan = 0; -+ return pca954x_reg_write(muxc->parent, client, -+ data->last_chan); -+ } - -- if (!(data->deselect & (1 << chan))) -- return 0; -+ /* otherwise leave as-is */ - -- /* Deselect active channel */ -- data->last_chan = 0; -- return pca954x_reg_write(muxc->parent, client, data->last_chan); -+ return 0; -+} -+ -+static ssize_t idle_state_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct i2c_client *client = to_i2c_client(dev); -+ struct i2c_mux_core *muxc = i2c_get_clientdata(client); -+ struct pca954x *data = i2c_mux_priv(muxc); -+ -+ return sprintf(buf, "%d\n", READ_ONCE(data->idle_state)); -+} -+ -+static ssize_t idle_state_store(struct device *dev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) -+{ -+ struct i2c_client *client = to_i2c_client(dev); -+ struct i2c_mux_core *muxc = i2c_get_clientdata(client); -+ struct pca954x *data = i2c_mux_priv(muxc); -+ int val; -+ int ret; -+ -+ ret = kstrtoint(buf, 0, &val); -+ if (ret < 0) -+ return ret; -+ -+ if (val != MUX_IDLE_AS_IS && val != MUX_IDLE_DISCONNECT && -+ (val < 0 || val >= data->chip->nchans)) -+ return -EINVAL; -+ -+ i2c_lock_bus(muxc->parent, I2C_LOCK_SEGMENT); -+ -+ WRITE_ONCE(data->idle_state, val); -+ /* -+ * Set the mux into a state consistent with the new -+ * idle_state. -+ */ -+ if (data->last_chan || val != MUX_IDLE_DISCONNECT) -+ ret = pca954x_deselect_mux(muxc, 0); -+ -+ i2c_unlock_bus(muxc->parent, I2C_LOCK_SEGMENT); -+ -+ return ret < 0 ? ret : count; - } - -+static DEVICE_ATTR_RW(idle_state); -+ - static irqreturn_t pca954x_irq_handler(int irq, void *dev_id) - { - struct pca954x *data = dev_id; -@@ -328,8 +387,11 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc) - static void pca954x_cleanup(struct i2c_mux_core *muxc) - { - struct pca954x *data = i2c_mux_priv(muxc); -+ struct i2c_client *client = data->client; - int c, irq; - -+ device_remove_file(&client->dev, &dev_attr_idle_state); -+ - if (data->irq) { - for (c = 0; c < data->chip->nchans; c++) { - irq = irq_find_mapping(data->irq, c); -@@ -410,9 +472,12 @@ static int pca954x_probe(struct i2c_client *client, - } - - data->last_chan = 0; /* force the first selection */ -+ data->idle_state = MUX_IDLE_AS_IS; - - idle_disconnect_dt = np && - of_property_read_bool(np, "i2c-mux-idle-disconnect"); -+ if (idle_disconnect_dt) -+ data->idle_state = MUX_IDLE_DISCONNECT; - - ret = pca954x_irq_setup(muxc); - if (ret) -@@ -420,8 +485,6 @@ static int pca954x_probe(struct i2c_client *client, - - /* Now create an adapter for each channel */ - for (num = 0; num < data->chip->nchans; num++) { -- data->deselect |= idle_disconnect_dt << num; -- - ret = i2c_mux_add_adapter(muxc, 0, num, 0); - if (ret) - goto fail_cleanup; -@@ -436,6 +499,12 @@ static int pca954x_probe(struct i2c_client *client, - goto fail_cleanup; - } - -+ /* -+ * The attr probably isn't going to be needed in most cases, -+ * so don't fail completely on error. -+ */ -+ device_create_file(dev, &dev_attr_idle_state); -+ - dev_info(dev, "registered %d multiplexed busses for I2C %s %s\n", - num, data->chip->muxtype == pca954x_ismux - ? "mux" : "switch", client->name); --- -2.25.1 - diff --git a/patch/driver-i2c-mux-pca954x-remove-support-for-unused-platform-d.patch b/patch/driver-i2c-mux-pca954x-remove-support-for-unused-platform-d.patch deleted file mode 100644 index 4be7bdcee..000000000 --- a/patch/driver-i2c-mux-pca954x-remove-support-for-unused-platform-d.patch +++ /dev/null @@ -1,72 +0,0 @@ -From ddd7c492d419eaf697733ba4dbad31662d355fc4 Mon Sep 17 00:00:00 2001 - -From: Robert Shearman - -Subject: [PATCH] i2c: mux: pca954x: remove support for unused platform data - -There are no in-tree users of the pca954x platform data and the -per-channel deselect configuration complicates efforts to export the -configuration to user-space in a way that could be applied to other -muxes. Therefore, remove support for the pca954x platform data. - -Signed-off-by: Robert Shearman -Signed-off-by: Peter Rosin ---- - drivers/i2c/muxes/i2c-mux-pca954x.c | 23 +++-------------------- - 1 file changed, 3 insertions(+), 20 deletions(-) - -diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c -index 24bd9275f..a0b6d0c18 100644 ---- a/drivers/i2c/muxes/i2c-mux-pca954x.c -+++ b/drivers/i2c/muxes/i2c-mux-pca954x.c -@@ -46,7 +46,6 @@ - #include - #include - #include --#include - #include - #include - #include -@@ -348,14 +347,13 @@ static int pca954x_probe(struct i2c_client *client, - const struct i2c_device_id *id) - { - struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); -- struct pca954x_platform_data *pdata = dev_get_platdata(&client->dev); - struct device *dev = &client->dev; - struct device_node *np = dev->of_node; - bool idle_disconnect_dt; - struct gpio_desc *gpio; -- int num, force, class; - struct i2c_mux_core *muxc; - struct pca954x *data; -+ int num; - int ret; - - if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) -@@ -422,24 +420,9 @@ static int pca954x_probe(struct i2c_client *client, - - /* Now create an adapter for each channel */ - for (num = 0; num < data->chip->nchans; num++) { -- bool idle_disconnect_pd = false; -- -- force = 0; /* dynamic adap number */ -- class = 0; /* no class by default */ -- if (pdata) { -- if (num < pdata->num_modes) { -- /* force static number */ -- force = pdata->modes[num].adap_id; -- class = pdata->modes[num].class; -- } else -- /* discard unconfigured channels */ -- break; -- idle_disconnect_pd = pdata->modes[num].deselect_on_exit; -- } -- data->deselect |= (idle_disconnect_pd || -- idle_disconnect_dt) << num; -+ data->deselect |= idle_disconnect_dt << num; - -- ret = i2c_mux_add_adapter(muxc, force, num, class); -+ ret = i2c_mux_add_adapter(muxc, 0, num, 0); - if (ret) - goto fail_cleanup; - } diff --git a/patch/driver-i2c-ocores-5-1.patch b/patch/driver-i2c-ocores-5-1.patch deleted file mode 100644 index 78ae7241c..000000000 --- a/patch/driver-i2c-ocores-5-1.patch +++ /dev/null @@ -1,540 +0,0 @@ -Add Backward porting the I2C ocores controller driver from kernel upstream version 5.1 to 4.19. - -Containing upstream patches: -- e7663ef i2c: ocores: stop transfer on timeout -- 2dc9834 i2c: ocores: do not handle IRQ if IF is not set -- 69c8c0c i2c: ocores: add polling interface -- 2c7e492 i2c: ocores: add SPDX tag -- fac9c29 i2c: ocores: checkpatch fixes -- 809445d i2c: ocores: Add support for IO mapper registers -- 237b5f6 i2c: ocores: Add support for bus clock via platform data -- 088a8a7 i2c: ocores: turn incomplete kdoc into a comment - -Signed-off-by: Jakkapan Jangmuang ---- - -diff --git a/drivers/i2c/busses/i2c-ocores.c b/drivers/i2c/busses/i2c-ocores.c -index 87f9caacb..4e1a077fb 100644 ---- a/drivers/i2c/busses/i2c-ocores.c -+++ b/drivers/i2c/busses/i2c-ocores.c -@@ -1,3 +1,4 @@ -+// SPDX-License-Identifier: GPL-2.0 - /* - * i2c-ocores.c: I2C bus driver for OpenCores I2C controller - * (https://opencores.org/project/i2c/overview) -@@ -6,13 +7,10 @@ - * - * Support for the GRLIB port of the controller by - * Andreas Larsson -- * -- * This file is licensed under the terms of the GNU General Public License -- * version 2. This program is licensed "as is" without any warranty of any -- * kind, whether express or implied. - */ - - #include -+#include - #include - #include - #include -@@ -25,17 +23,28 @@ - #include - #include - #include -+#include -+#include -+ -+#define OCORES_FLAG_POLL BIT(0) - -+/* -+ * 'process_lock' exists because ocores_process() and ocores_process_timeout() -+ * can't run in parallel. -+ */ - struct ocores_i2c { - void __iomem *base; -+ int iobase; - u32 reg_shift; - u32 reg_io_width; -+ unsigned long flags; - wait_queue_head_t wait; - struct i2c_adapter adap; - struct i2c_msg *msg; - int pos; - int nmsgs; - int state; /* see STATE_ */ -+ spinlock_t process_lock; - struct clk *clk; - int ip_clock_khz; - int bus_clock_khz; -@@ -127,6 +136,16 @@ static inline u8 oc_getreg_32be(struct ocores_i2c *i2c, int reg) - return ioread32be(i2c->base + (reg << i2c->reg_shift)); - } - -+static void oc_setreg_io_8(struct ocores_i2c *i2c, int reg, u8 value) -+{ -+ outb(value, i2c->iobase + reg); -+} -+ -+static inline u8 oc_getreg_io_8(struct ocores_i2c *i2c, int reg) -+{ -+ return inb(i2c->iobase + reg); -+} -+ - static inline void oc_setreg(struct ocores_i2c *i2c, int reg, u8 value) - { - i2c->setreg(i2c, reg, value); -@@ -137,23 +156,29 @@ static inline u8 oc_getreg(struct ocores_i2c *i2c, int reg) - return i2c->getreg(i2c, reg); - } - --static void ocores_process(struct ocores_i2c *i2c) -+static void ocores_process(struct ocores_i2c *i2c, u8 stat) - { - struct i2c_msg *msg = i2c->msg; -- u8 stat = oc_getreg(i2c, OCI2C_STATUS); -+ unsigned long flags; -+ -+ /* -+ * If we spin here is because we are in timeout, so we are going -+ * to be in STATE_ERROR. See ocores_process_timeout() -+ */ -+ spin_lock_irqsave(&i2c->process_lock, flags); - - if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) { - /* stop has been sent */ - oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK); - wake_up(&i2c->wait); -- return; -+ goto out; - } - - /* error? */ - if (stat & OCI2C_STAT_ARBLOST) { - i2c->state = STATE_ERROR; - oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); -- return; -+ goto out; - } - - if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) { -@@ -163,10 +188,11 @@ static void ocores_process(struct ocores_i2c *i2c) - if (stat & OCI2C_STAT_NACK) { - i2c->state = STATE_ERROR; - oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); -- return; -+ goto out; - } -- } else -+ } else { - msg->buf[i2c->pos++] = oc_getreg(i2c, OCI2C_DATA); -+ } - - /* end of msg? */ - if (i2c->pos == msg->len) { -@@ -183,15 +209,15 @@ static void ocores_process(struct ocores_i2c *i2c) - i2c->state = STATE_START; - - oc_setreg(i2c, OCI2C_DATA, addr); -- oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); -- return; -- } else -- i2c->state = (msg->flags & I2C_M_RD) -- ? STATE_READ : STATE_WRITE; -+ oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); -+ goto out; -+ } -+ i2c->state = (msg->flags & I2C_M_RD) -+ ? STATE_READ : STATE_WRITE; - } else { - i2c->state = STATE_DONE; - oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); -- return; -+ goto out; - } - } - -@@ -202,20 +228,148 @@ static void ocores_process(struct ocores_i2c *i2c) - oc_setreg(i2c, OCI2C_DATA, msg->buf[i2c->pos++]); - oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_WRITE); - } -+ -+out: -+ spin_unlock_irqrestore(&i2c->process_lock, flags); - } - - static irqreturn_t ocores_isr(int irq, void *dev_id) - { - struct ocores_i2c *i2c = dev_id; -+ u8 stat = oc_getreg(i2c, OCI2C_STATUS); -+ -+ if (!(stat & OCI2C_STAT_IF)) -+ return IRQ_NONE; - -- ocores_process(i2c); -+ ocores_process(i2c, stat); - - return IRQ_HANDLED; - } - --static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) -+/** -+ * Process timeout event -+ * @i2c: ocores I2C device instance -+ */ -+static void ocores_process_timeout(struct ocores_i2c *i2c) - { -- struct ocores_i2c *i2c = i2c_get_adapdata(adap); -+ unsigned long flags; -+ -+ spin_lock_irqsave(&i2c->process_lock, flags); -+ i2c->state = STATE_ERROR; -+ oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); -+ spin_unlock_irqrestore(&i2c->process_lock, flags); -+} -+ -+/** -+ * Wait until something change in a given register -+ * @i2c: ocores I2C device instance -+ * @reg: register to query -+ * @mask: bitmask to apply on register value -+ * @val: expected result -+ * @timeout: timeout in jiffies -+ * -+ * Timeout is necessary to avoid to stay here forever when the chip -+ * does not answer correctly. -+ * -+ * Return: 0 on success, -ETIMEDOUT on timeout -+ */ -+static int ocores_wait(struct ocores_i2c *i2c, -+ int reg, u8 mask, u8 val, -+ const unsigned long timeout) -+{ -+ unsigned long j; -+ -+ j = jiffies + timeout; -+ while (1) { -+ u8 status = oc_getreg(i2c, reg); -+ -+ if ((status & mask) == val) -+ break; -+ -+ if (time_after(jiffies, j)) -+ return -ETIMEDOUT; -+ } -+ return 0; -+} -+ -+/** -+ * Wait until is possible to process some data -+ * @i2c: ocores I2C device instance -+ * -+ * Used when the device is in polling mode (interrupts disabled). -+ * -+ * Return: 0 on success, -ETIMEDOUT on timeout -+ */ -+static int ocores_poll_wait(struct ocores_i2c *i2c) -+{ -+ u8 mask; -+ int err; -+ -+ if (i2c->state == STATE_DONE || i2c->state == STATE_ERROR) { -+ /* transfer is over */ -+ mask = OCI2C_STAT_BUSY; -+ } else { -+ /* on going transfer */ -+ mask = OCI2C_STAT_TIP; -+ /* -+ * We wait for the data to be transferred (8bit), -+ * then we start polling on the ACK/NACK bit -+ */ -+ udelay((8 * 1000) / i2c->bus_clock_khz); -+ } -+ -+ /* -+ * once we are here we expect to get the expected result immediately -+ * so if after 1ms we timeout then something is broken. -+ */ -+ err = ocores_wait(i2c, OCI2C_STATUS, mask, 0, msecs_to_jiffies(1)); -+ if (err) -+ dev_warn(i2c->adap.dev.parent, -+ "%s: STATUS timeout, bit 0x%x did not clear in 1ms\n", -+ __func__, mask); -+ return err; -+} -+ -+/** -+ * It handles an IRQ-less transfer -+ * @i2c: ocores I2C device instance -+ * -+ * Even if IRQ are disabled, the I2C OpenCore IP behavior is exactly the same -+ * (only that IRQ are not produced). This means that we can re-use entirely -+ * ocores_isr(), we just add our polling code around it. -+ * -+ * It can run in atomic context -+ */ -+static void ocores_process_polling(struct ocores_i2c *i2c) -+{ -+ while (1) { -+ irqreturn_t ret; -+ int err; -+ -+ err = ocores_poll_wait(i2c); -+ if (err) { -+ i2c->state = STATE_ERROR; -+ break; /* timeout */ -+ } -+ -+ ret = ocores_isr(-1, i2c); -+ if (ret == IRQ_NONE) -+ break; /* all messages have been transferred */ -+ } -+} -+ -+static int ocores_xfer_core(struct ocores_i2c *i2c, -+ struct i2c_msg *msgs, int num, -+ bool polling) -+{ -+ int ret; -+ u8 ctrl; -+ -+ ctrl = oc_getreg(i2c, OCI2C_CONTROL); -+ if (polling) -+ oc_setreg(i2c, OCI2C_CONTROL, ctrl & ~OCI2C_CTRL_IEN); -+ else -+ oc_setreg(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_IEN); - - i2c->msg = msgs; - i2c->pos = 0; -@@ -225,11 +379,35 @@ static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) - oc_setreg(i2c, OCI2C_DATA, i2c_8bit_addr_from_msg(i2c->msg)); - oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); - -- if (wait_event_timeout(i2c->wait, (i2c->state == STATE_ERROR) || -- (i2c->state == STATE_DONE), HZ)) -- return (i2c->state == STATE_DONE) ? num : -EIO; -- else -- return -ETIMEDOUT; -+ if (polling) { -+ ocores_process_polling(i2c); -+ } else { -+ ret = wait_event_timeout(i2c->wait, -+ (i2c->state == STATE_ERROR) || -+ (i2c->state == STATE_DONE), HZ); -+ if (ret == 0) { -+ ocores_process_timeout(i2c); -+ return -ETIMEDOUT; -+ } -+ } -+ -+ return (i2c->state == STATE_DONE) ? num : -EIO; -+} -+ -+static int ocores_xfer_polling(struct i2c_adapter *adap, -+ struct i2c_msg *msgs, int num) -+{ -+ return ocores_xfer_core(i2c_get_adapdata(adap), msgs, num, true); -+} -+ -+static int ocores_xfer(struct i2c_adapter *adap, -+ struct i2c_msg *msgs, int num) -+{ -+ struct ocores_i2c *i2c = i2c_get_adapdata(adap); -+ -+ if (i2c->flags & OCORES_FLAG_POLL) -+ return ocores_xfer_polling(adap, msgs, num); -+ return ocores_xfer_core(i2c, msgs, num, false); - } - - static int ocores_init(struct device *dev, struct ocores_i2c *i2c) -@@ -239,7 +417,8 @@ static int ocores_init(struct device *dev, struct ocores_i2c *i2c) - u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); - - /* make sure the device is disabled */ -- oc_setreg(i2c, OCI2C_CONTROL, ctrl & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN)); -+ ctrl &= ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN); -+ oc_setreg(i2c, OCI2C_CONTROL, ctrl); - - prescale = (i2c->ip_clock_khz / (5 * i2c->bus_clock_khz)) - 1; - prescale = clamp(prescale, 0, 0xffff); -@@ -257,7 +436,7 @@ static int ocores_init(struct device *dev, struct ocores_i2c *i2c) - - /* Init the device */ - oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK); -- oc_setreg(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_IEN | OCI2C_CTRL_EN); -+ oc_setreg(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_EN); - - return 0; - } -@@ -294,13 +473,16 @@ static const struct of_device_id ocores_i2c_match[] = { - MODULE_DEVICE_TABLE(of, ocores_i2c_match); - - #ifdef CONFIG_OF --/* Read and write functions for the GRLIB port of the controller. Registers are -+/* -+ * Read and write functions for the GRLIB port of the controller. Registers are - * 32-bit big endian and the PRELOW and PREHIGH registers are merged into one -- * register. The subsequent registers has their offset decreased accordingly. */ -+ * register. The subsequent registers have their offsets decreased accordingly. -+ */ - static u8 oc_getreg_grlib(struct ocores_i2c *i2c, int reg) - { - u32 rd; - int rreg = reg; -+ - if (reg != OCI2C_PRELOW) - rreg--; - rd = ioread32be(i2c->base + (rreg << i2c->reg_shift)); -@@ -314,6 +496,7 @@ static void oc_setreg_grlib(struct ocores_i2c *i2c, int reg, u8 value) - { - u32 curr, wr; - int rreg = reg; -+ - if (reg != OCI2C_PRELOW) - rreg--; - if (reg == OCI2C_PRELOW || reg == OCI2C_PREHIGH) { -@@ -402,7 +585,7 @@ static int ocores_i2c_of_probe(struct platform_device *pdev, - return 0; - } - #else --#define ocores_i2c_of_probe(pdev,i2c) -ENODEV -+#define ocores_i2c_of_probe(pdev, i2c) -ENODEV - #endif - - static int ocores_i2c_probe(struct platform_device *pdev) -@@ -414,25 +597,41 @@ static int ocores_i2c_probe(struct platform_device *pdev) - int ret; - int i; - -- irq = platform_get_irq(pdev, 0); -- if (irq < 0) -- return irq; -- - i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); - if (!i2c) - return -ENOMEM; - -+ spin_lock_init(&i2c->process_lock); -+ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -- i2c->base = devm_ioremap_resource(&pdev->dev, res); -- if (IS_ERR(i2c->base)) -- return PTR_ERR(i2c->base); -+ if (res) { -+ i2c->base = devm_ioremap_resource(&pdev->dev, res); -+ if (IS_ERR(i2c->base)) -+ return PTR_ERR(i2c->base); -+ } else { -+ res = platform_get_resource(pdev, IORESOURCE_IO, 0); -+ if (!res) -+ return -EINVAL; -+ i2c->iobase = res->start; -+ if (!devm_request_region(&pdev->dev, res->start, -+ resource_size(res), -+ pdev->name)) { -+ dev_err(&pdev->dev, "Can't get I/O resource.\n"); -+ return -EBUSY; -+ } -+ i2c->setreg = oc_setreg_io_8; -+ i2c->getreg = oc_getreg_io_8; -+ } - - pdata = dev_get_platdata(&pdev->dev); - if (pdata) { - i2c->reg_shift = pdata->reg_shift; - i2c->reg_io_width = pdata->reg_io_width; - i2c->ip_clock_khz = pdata->clock_khz; -- i2c->bus_clock_khz = 100; -+ if (pdata->bus_khz) -+ i2c->bus_clock_khz = pdata->bus_khz; -+ else -+ i2c->bus_clock_khz = 100; - } else { - ret = ocores_i2c_of_probe(pdev, i2c); - if (ret) -@@ -470,18 +669,29 @@ static int ocores_i2c_probe(struct platform_device *pdev) - } - } - -+ init_waitqueue_head(&i2c->wait); -+ -+ irq = platform_get_irq(pdev, 0); -+ if (irq == -ENXIO) { -+ i2c->flags |= OCORES_FLAG_POLL; -+ } else { -+ if (irq < 0) -+ return irq; -+ } -+ -+ if (!(i2c->flags & OCORES_FLAG_POLL)) { -+ ret = devm_request_irq(&pdev->dev, irq, ocores_isr, 0, -+ pdev->name, i2c); -+ if (ret) { -+ dev_err(&pdev->dev, "Cannot claim IRQ\n"); -+ goto err_clk; -+ } -+ } -+ - ret = ocores_init(&pdev->dev, i2c); - if (ret) - goto err_clk; - -- init_waitqueue_head(&i2c->wait); -- ret = devm_request_irq(&pdev->dev, irq, ocores_isr, 0, -- pdev->name, i2c); -- if (ret) { -- dev_err(&pdev->dev, "Cannot claim IRQ\n"); -- goto err_clk; -- } -- - /* hook up driver to tree */ - platform_set_drvdata(pdev, i2c); - i2c->adap = ocores_adapter; -@@ -510,10 +720,11 @@ static int ocores_i2c_probe(struct platform_device *pdev) - static int ocores_i2c_remove(struct platform_device *pdev) - { - struct ocores_i2c *i2c = platform_get_drvdata(pdev); -+ u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); - - /* disable i2c logic */ -- oc_setreg(i2c, OCI2C_CONTROL, oc_getreg(i2c, OCI2C_CONTROL) -- & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN)); -+ ctrl &= ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN); -+ oc_setreg(i2c, OCI2C_CONTROL, ctrl); - - /* remove adapter & data */ - i2c_del_adapter(&i2c->adap); -@@ -531,7 +742,8 @@ static int ocores_i2c_suspend(struct device *dev) - u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); - - /* make sure the device is disabled */ -- oc_setreg(i2c, OCI2C_CONTROL, ctrl & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN)); -+ ctrl &= ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN); -+ oc_setreg(i2c, OCI2C_CONTROL, ctrl); - - if (!IS_ERR(i2c->clk)) - clk_disable_unprepare(i2c->clk); -diff --git a/include/linux/platform_data/i2c-ocores.h b/include/linux/platform_data/i2c-ocores.h -index 113d6b12f..e6326cbaf 100644 ---- a/include/linux/platform_data/i2c-ocores.h -+++ b/include/linux/platform_data/i2c-ocores.h -@@ -1,11 +1,8 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ - /* - * i2c-ocores.h - definitions for the i2c-ocores interface - * - * Peter Korsgaard -- * -- * This file is licensed under the terms of the GNU General Public License -- * version 2. This program is licensed "as is" without any warranty of any -- * kind, whether express or implied. - */ - - #ifndef _LINUX_I2C_OCORES_H -@@ -15,6 +12,7 @@ struct ocores_i2c_platform_data { - u32 reg_shift; /* register offset shift value */ - u32 reg_io_width; /* register io read/write width */ - u32 clock_khz; /* input clock in kHz */ -+ u32 bus_khz; /* bus clock in kHz */ - bool big_endian; /* registers are big endian */ - u8 num_devices; /* number of devices in the devices list */ - struct i2c_board_info const *devices; /* devices connected to the bus */ diff --git a/patch/driver-ixgbe-external-phy.patch b/patch/driver-ixgbe-external-phy.patch deleted file mode 100644 index 6b6f9f8ef..000000000 --- a/patch/driver-ixgbe-external-phy.patch +++ /dev/null @@ -1,37 +0,0 @@ -Signed-off-by: Jostar Yang - -From: Jostar Yang - - ---- - drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c | 3 +++ - drivers/net/ethernet/intel/ixgbe/ixgbe_type.h | 2 ++ - 2 files changed, 5 insertions(+) - -diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c -index 919a7af84..83865b150 100644 ---- a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c -+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c -@@ -379,6 +379,9 @@ static enum ixgbe_phy_type ixgbe_get_phy_type_from_id(u32 phy_id) - case X557_PHY_ID2: - phy_type = ixgbe_phy_x550em_ext_t; - break; -+ case BCM54616S_E_PHY_ID: -+ phy_type = ixgbe_phy_ext_1g_t; -+ break; - default: - phy_type = ixgbe_phy_unknown; - break; -diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_type.h b/drivers/net/ethernet/intel/ixgbe/ixgbe_type.h -index 41bcbb337..c6280ec7b 100644 ---- a/drivers/net/ethernet/intel/ixgbe/ixgbe_type.h -+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_type.h -@@ -1404,6 +1404,8 @@ struct ixgbe_nvm_version { - #define ATH_PHY_ID 0x03429050 - #define AQ_FW_REV 0x20 - -+#define BCM54616S_E_PHY_ID 0x03625D10 -+ - /* Special PHY Init Routine */ - #define IXGBE_PHY_INIT_OFFSET_NL 0x002B - #define IXGBE_PHY_INIT_END_NL 0xFFFF diff --git a/patch/driver-pca954x-i2c-mux-force-deselect-on-exit-flag.patch b/patch/driver-pca954x-i2c-mux-force-deselect-on-exit-flag.patch deleted file mode 100644 index f0c4fff72..000000000 --- a/patch/driver-pca954x-i2c-mux-force-deselect-on-exit-flag.patch +++ /dev/null @@ -1,34 +0,0 @@ -Adding PCA9548 I2C MUX force deselect-on-exit flag. - -From: wadelnn - - ---- - drivers/i2c/muxes/i2c-mux-pca954x.c | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c -index 431791d2f..f28d93c9c 100644 ---- a/drivers/i2c/muxes/i2c-mux-pca954x.c -+++ b/drivers/i2c/muxes/i2c-mux-pca954x.c -@@ -198,6 +198,11 @@ static const struct i2c_device_id pca954x_id[] = { - }; - MODULE_DEVICE_TABLE(i2c, pca954x_id); - -+/* Force deselect-on-exit feature for PCA954X devices. Default is disabled. */ -+static unsigned int force_deselect_on_exit = 0; -+module_param(force_deselect_on_exit, uint, S_IRUGO); -+MODULE_PARM_DESC(force_deselect_on_exit, "Force deselect-on-exit feature for PCA954X devices. Default is disabled."); -+ - #ifdef CONFIG_OF - static const struct of_device_id pca954x_of_match[] = { - { .compatible = "nxp,pca9540", .data = &chips[pca_9540] }, -@@ -476,7 +481,7 @@ static int pca954x_probe(struct i2c_client *client, - - idle_disconnect_dt = np && - of_property_read_bool(np, "i2c-mux-idle-disconnect"); -- if (idle_disconnect_dt) -+ if (idle_disconnect_dt || force_deselect_on_exit) - data->idle_state = MUX_IDLE_DISCONNECT; - - ret = pca954x_irq_setup(muxc); diff --git a/patch/driver-support-tun-config-carrier-enable.patch b/patch/driver-support-tun-config-carrier-enable.patch deleted file mode 100644 index 41db8ea67..000000000 --- a/patch/driver-support-tun-config-carrier-enable.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 7f7d6a7dd958ff68da96a78442ffa6a404aa976a Mon Sep 17 00:00:00 2001 - -From: admin - -Subject: [PATCH] TUNTAP driver support carrier configuration ---- - drivers/net/tun.c | 10 ++++++++++ - include/uapi/linux/if_tun.h | 1 + - 2 files changed, 11 insertions(+) - -diff --git a/drivers/net/tun.c b/drivers/net/tun.c -index 5fa7047ea..f79f0cf57 100644 ---- a/drivers/net/tun.c -+++ b/drivers/net/tun.c -@@ -3128,6 +3128,16 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, - ret = 0; - break; - -+ case TUNSETCARRIER: -+ if (arg) { -+ netif_carrier_on(tun->dev); -+ } else { -+ netif_carrier_off(tun->dev); -+ } -+ -+ tun_debug(KERN_INFO, tun, "carrier %s\n", arg ? "on" : "off"); -+ break; -+ - case TUNSETSTEERINGEBPF: - ret = tun_set_ebpf(tun, &tun->steering_prog, argp); - break; -diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h -index ee432cd30..23a6753b3 100644 ---- a/include/uapi/linux/if_tun.h -+++ b/include/uapi/linux/if_tun.h -@@ -59,6 +59,7 @@ - #define TUNGETVNETBE _IOR('T', 223, int) - #define TUNSETSTEERINGEBPF _IOR('T', 224, int) - #define TUNSETFILTEREBPF _IOR('T', 225, int) -+#define TUNSETCARRIER _IOW('T', 226, int) - - /* TUNSETIFF ifr flags */ - #define IFF_TUN 0x0001 diff --git a/patch/kconfig-inclusions b/patch/kconfig-inclusions deleted file mode 100644 index e57063b67..000000000 --- a/patch/kconfig-inclusions +++ /dev/null @@ -1,69 +0,0 @@ -[common] - -[amd64] -# For Arista -CONFIG_I2C_MUX_PCA9541=m -CONFIG_SENSORS_MAX6697=m -CONFIG_SENSORS_DPS1900=m -CONFIG_PCI_REASSIGN_PREF_MEM=y -# For cig-cs6436 -CONFIG_SERIAL_8250_LPSS=m -# SFF_8436 -CONFIG_EEPROM_SFF_8436=m -# For Dell S6000 -CONFIG_SENSORS_MAX6620=m -CONFIG_PMBUS=m -CONFIG_SENSORS_PMBUS=m -CONFIG_SENSORS_DNI_DPS460=m -CONFIG_I2C_MUX_GPIO=m -CONFIG_GPIO_SYSFS=y -CONFIG_GPIO_SCH=m -# For Dell Z9100 -CONFIG_I2C_MUX_PCA954x=m -# For Ingrasys S9100 -CONFIG_I2C_GPIO=m -CONFIG_GPIO_PCA953X=m -# For Inventec d7032 -CONFIG_GPIO_ICH=m -# For mitac ly1200 -CONFIG_SENSORS_MAX31790=m -# For optoe -CONFIG_EEPROM_OPTOE=m -# For Mellanox -CONFIG_MLXSW_CORE=m -CONFIG_MLXSW_CORE_HWMON=y -CONFIG_MLXSW_CORE_THERMAL=y -CONFIG_MLXSW_CORE_QSFP=y -CONFIG_MLXSW_I2C=m -CONFIG_MLXSW_MINIMAL=m -CONFIG_I2C_MUX_REG=m -CONFIG_I2C_MUX_MLXCPLD=m -CONFIG_I2C_MLXCPLD=m -CONFIG_SENSORS_IIO_HWMON=m -CONFIG_SENSORS_MLXREG_FAN=m -CONFIG_SENSORS_LM25066=m -CONFIG_SENSORS_TPS53679=m -CONFIG_SENSORS_UCD9000=m -CONFIG_SENSORS_UCD9200=m -CONFIG_SENSORS_XDPE122=m -CONFIG_SENSORS_MP2975=m -CONFIG_MLX_WDT=y -CONFIG_LEDS_MLXREG=m -CONFIG_DW_DMAC_PCI=m -CONFIG_MLX_PLATFORM=m -CONFIG_MELLANOX_PLATFORM=y -CONFIG_MLXREG_HOTPLUG=m -CONFIG_MLXREG_IO=m -CONFIG_MAX1363=m - -[arm64] - -[armhf] -CONFIG_EEPROM_SFF_8436=m -CONFIG_EEPROM_OPTOE=m -CONFIG_I2C_MUX_GPIO=m - -[marvell-armhf] -CONFIG_ARCH_NR_GPIO=0 -CONFIG_MTD_CMDLINE_PARTS=y -CONFIG_MTD_OF_PARTS=y diff --git a/patch/kernel-add-kexec-reboot-string.patch b/patch/kernel-add-kexec-reboot-string.patch deleted file mode 100644 index c95bc07cb..000000000 --- a/patch/kernel-add-kexec-reboot-string.patch +++ /dev/null @@ -1,22 +0,0 @@ -From cc1ab16f0e641a8b3cd90c802c2e48a4c89c238b Mon Sep 17 00:00:00 2001 - -From: Joe LeVeque - -Subject: [PATCH] Add kexec reboot string ---- - kernel/kexec_core.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c -index 23a83a4da..6c05e6516 100644 ---- a/kernel/kexec_core.c -+++ b/kernel/kexec_core.c -@@ -1154,7 +1154,7 @@ int kernel_kexec(void) - #endif - { - kexec_in_progress = true; -- kernel_restart_prepare(NULL); -+ kernel_restart_prepare("kexec reboot"); - migrate_to_reboot_cpu(); - - /* diff --git a/patch/macsec-Netlink-support-of-XPN-cipher-suites-IEEE-802.patch b/patch/macsec-Netlink-support-of-XPN-cipher-suites-IEEE-802.patch deleted file mode 100644 index 86d5890a3..000000000 --- a/patch/macsec-Netlink-support-of-XPN-cipher-suites-IEEE-802.patch +++ /dev/null @@ -1,438 +0,0 @@ -From 48ef50fa866aae087f63c7de8a47e76537f88691 Mon Sep 17 00:00:00 2001 - -From: Era Mayflower - -Subject: [PATCH] macsec: Netlink support of XPN cipher suites (IEEE 802.1AEbw) - -Netlink support of extended packet number cipher suites, -allows adding and updating XPN macsec interfaces. - -Added support in: - * Creating interfaces with GCM-AES-XPN-128 and GCM-AES-XPN-256 suites. - * Setting and getting 64bit packet numbers with of SAs. - * Setting (only on SA creation) and getting ssci of SAs. - * Setting salt when installing a SAK. - -Added 2 cipher suite identifiers according to 802.1AE-2018 table 14-1: - * MACSEC_CIPHER_ID_GCM_AES_XPN_128 - * MACSEC_CIPHER_ID_GCM_AES_XPN_256 - -In addition, added 2 new netlink attribute types: - * MACSEC_SA_ATTR_SSCI - * MACSEC_SA_ATTR_SALT - -Depends on: macsec: Support XPN frame handling - IEEE 802.1AEbw. - -Signed-off-by: Era Mayflower -Signed-off-by: David S. Miller ---- - drivers/net/macsec.c | 164 +++++++++++++++++++++++++++++++++++++--- - include/uapi/linux/if_macsec.h | 8 +- - 2 files changed, 157 insertions(+), 15 deletions(-) - -diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c -index a41a6cd78..0b86908b9 100644 ---- a/drivers/net/macsec.c -+++ b/drivers/net/macsec.c -@@ -25,6 +25,9 @@ - - #include - -+#define MACSEC_DEFAULT_PN_LEN 4 -+#define MACSEC_XPN_PN_LEN 8 -+ - #define MACSEC_SALT_LEN 12 - #define MACSEC_NUM_AN 4 /* 2 bits for the association number */ - -@@ -444,11 +447,13 @@ static struct macsec_cb *macsec_skb_cb(struct sk_buff *skb) - #define MACSEC_PORT_ES (htons(0x0001)) - #define MACSEC_PORT_SCB (0x0000) - #define MACSEC_UNDEF_SCI ((__force sci_t)0xffffffffffffffffULL) -+#define MACSEC_UNDEF_SSCI ((__force ssci_t)0xffffffff) - - #define MACSEC_GCM_AES_128_SAK_LEN 16 - #define MACSEC_GCM_AES_256_SAK_LEN 32 - - #define DEFAULT_SAK_LEN MACSEC_GCM_AES_128_SAK_LEN -+#define DEFAULT_XPN false - #define DEFAULT_SEND_SCI true - #define DEFAULT_ENCRYPT false - #define DEFAULT_ENCODING_SA 0 -@@ -1438,6 +1443,7 @@ static int init_rx_sa(struct macsec_rx_sa *rx_sa, char *sak, int key_len, - return PTR_ERR(rx_sa->key.tfm); - } - -+ rx_sa->ssci = MACSEC_UNDEF_SSCI; - rx_sa->active = false; - rx_sa->next_pn = 1; - refcount_set(&rx_sa->refcnt, 1); -@@ -1536,6 +1542,7 @@ static int init_tx_sa(struct macsec_tx_sa *tx_sa, char *sak, int key_len, - return PTR_ERR(tx_sa->key.tfm); - } - -+ tx_sa->ssci = MACSEC_UNDEF_SSCI; - tx_sa->active = false; - refcount_set(&tx_sa->refcnt, 1); - spin_lock_init(&tx_sa->lock); -@@ -1579,6 +1586,16 @@ static int nla_put_sci(struct sk_buff *skb, int attrtype, sci_t value, - return nla_put_u64_64bit(skb, attrtype, (__force u64)value, padattr); - } - -+static ssci_t nla_get_ssci(const struct nlattr *nla) -+{ -+ return (__force ssci_t)nla_get_u32(nla); -+} -+ -+static int nla_put_ssci(struct sk_buff *skb, int attrtype, ssci_t value) -+{ -+ return nla_put_u32(skb, attrtype, (__force u64)value); -+} -+ - static struct macsec_tx_sa *get_txsa_from_nl(struct net *net, - struct nlattr **attrs, - struct nlattr **tb_sa, -@@ -1694,11 +1711,14 @@ static const struct nla_policy macsec_genl_rxsc_policy[NUM_MACSEC_RXSC_ATTR] = { - static const struct nla_policy macsec_genl_sa_policy[NUM_MACSEC_SA_ATTR] = { - [MACSEC_SA_ATTR_AN] = { .type = NLA_U8 }, - [MACSEC_SA_ATTR_ACTIVE] = { .type = NLA_U8 }, -- [MACSEC_SA_ATTR_PN] = { .type = NLA_U32 }, -+ [MACSEC_SA_ATTR_PN] = { .type = NLA_MIN_LEN, .len = 4 }, - [MACSEC_SA_ATTR_KEYID] = { .type = NLA_BINARY, - .len = MACSEC_KEYID_LEN, }, - [MACSEC_SA_ATTR_KEY] = { .type = NLA_BINARY, - .len = MACSEC_MAX_KEY_LEN, }, -+ [MACSEC_SA_ATTR_SSCI] = { .type = NLA_U32 }, -+ [MACSEC_SA_ATTR_SALT] = { .type = NLA_BINARY, -+ .len = MACSEC_SALT_LEN, }, - }; - - static int parse_sa_config(struct nlattr **attrs, struct nlattr **tb_sa) -@@ -1737,7 +1757,8 @@ static bool validate_add_rxsa(struct nlattr **attrs) - if (nla_get_u8(attrs[MACSEC_SA_ATTR_AN]) >= MACSEC_NUM_AN) - return false; - -- if (attrs[MACSEC_SA_ATTR_PN] && nla_get_u32(attrs[MACSEC_SA_ATTR_PN]) == 0) -+ if (attrs[MACSEC_SA_ATTR_PN] && -+ *(u64 *)nla_data(attrs[MACSEC_SA_ATTR_PN]) == 0) - return false; - - if (attrs[MACSEC_SA_ATTR_ACTIVE]) { -@@ -1759,6 +1780,7 @@ static int macsec_add_rxsa(struct sk_buff *skb, struct genl_info *info) - struct macsec_rx_sc *rx_sc; - struct macsec_rx_sa *rx_sa; - unsigned char assoc_num; -+ int pn_len; - struct nlattr *tb_rxsc[MACSEC_RXSC_ATTR_MAX + 1]; - struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1]; - int err; -@@ -1791,6 +1813,29 @@ static int macsec_add_rxsa(struct sk_buff *skb, struct genl_info *info) - return -EINVAL; - } - -+ pn_len = secy->xpn ? MACSEC_XPN_PN_LEN : MACSEC_DEFAULT_PN_LEN; -+ if (nla_len(tb_sa[MACSEC_SA_ATTR_PN]) != pn_len) { -+ pr_notice("macsec: nl: add_rxsa: bad pn length: %d != %d\n", -+ nla_len(tb_sa[MACSEC_SA_ATTR_PN]), pn_len); -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ -+ if (secy->xpn) { -+ if (!tb_sa[MACSEC_SA_ATTR_SSCI] || !tb_sa[MACSEC_SA_ATTR_SALT]) { -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ -+ if (nla_len(tb_sa[MACSEC_SA_ATTR_SALT]) != MACSEC_SALT_LEN) { -+ pr_notice("macsec: nl: add_rxsa: bad salt length: %d != %d\n", -+ nla_len(tb_sa[MACSEC_SA_ATTR_SALT]), -+ MACSEC_SA_ATTR_SALT); -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ } -+ - rx_sa = rtnl_dereference(rx_sc->sa[assoc_num]); - if (rx_sa) { - rtnl_unlock(); -@@ -1813,13 +1858,19 @@ static int macsec_add_rxsa(struct sk_buff *skb, struct genl_info *info) - - if (tb_sa[MACSEC_SA_ATTR_PN]) { - spin_lock_bh(&rx_sa->lock); -- rx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ rx_sa->next_pn = nla_get_u64(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&rx_sa->lock); - } - - if (tb_sa[MACSEC_SA_ATTR_ACTIVE]) - rx_sa->active = !!nla_get_u8(tb_sa[MACSEC_SA_ATTR_ACTIVE]); - -+ if (secy->xpn) { -+ rx_sa->ssci = nla_get_ssci(tb_sa[MACSEC_SA_ATTR_SSCI]); -+ nla_memcpy(rx_sa->key.salt.bytes, tb_sa[MACSEC_SA_ATTR_SALT], -+ MACSEC_SALT_LEN); -+ } -+ - nla_memcpy(rx_sa->key.id, tb_sa[MACSEC_SA_ATTR_KEYID], MACSEC_KEYID_LEN); - rx_sa->sc = rx_sc; - rcu_assign_pointer(rx_sc->sa[assoc_num], rx_sa); -@@ -1915,6 +1966,7 @@ static int macsec_add_txsa(struct sk_buff *skb, struct genl_info *info) - struct macsec_tx_sc *tx_sc; - struct macsec_tx_sa *tx_sa; - unsigned char assoc_num; -+ int pn_len; - struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1]; - int err; - -@@ -1946,6 +1998,29 @@ static int macsec_add_txsa(struct sk_buff *skb, struct genl_info *info) - return -EINVAL; - } - -+ pn_len = secy->xpn ? MACSEC_XPN_PN_LEN : MACSEC_DEFAULT_PN_LEN; -+ if (nla_len(tb_sa[MACSEC_SA_ATTR_PN]) != pn_len) { -+ pr_notice("macsec: nl: add_txsa: bad pn length: %d != %d\n", -+ nla_len(tb_sa[MACSEC_SA_ATTR_PN]), pn_len); -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ -+ if (secy->xpn) { -+ if (!tb_sa[MACSEC_SA_ATTR_SSCI] || !tb_sa[MACSEC_SA_ATTR_SALT]) { -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ -+ if (nla_len(tb_sa[MACSEC_SA_ATTR_SALT]) != MACSEC_SALT_LEN) { -+ pr_notice("macsec: nl: add_txsa: bad salt length: %d != %d\n", -+ nla_len(tb_sa[MACSEC_SA_ATTR_SALT]), -+ MACSEC_SA_ATTR_SALT); -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ } -+ - tx_sa = rtnl_dereference(tx_sc->sa[assoc_num]); - if (tx_sa) { - rtnl_unlock(); -@@ -1966,10 +2041,16 @@ static int macsec_add_txsa(struct sk_buff *skb, struct genl_info *info) - return err; - } - -+ if (secy->xpn) { -+ tx_sa->ssci = nla_get_ssci(tb_sa[MACSEC_SA_ATTR_SSCI]); -+ nla_memcpy(tx_sa->key.salt.bytes, tb_sa[MACSEC_SA_ATTR_SALT], -+ MACSEC_SALT_LEN); -+ } -+ - nla_memcpy(tx_sa->key.id, tb_sa[MACSEC_SA_ATTR_KEYID], MACSEC_KEYID_LEN); - - spin_lock_bh(&tx_sa->lock); -- tx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ tx_sa->next_pn = nla_get_u64(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&tx_sa->lock); - - if (tb_sa[MACSEC_SA_ATTR_ACTIVE]) -@@ -2107,7 +2188,9 @@ static bool validate_upd_sa(struct nlattr **attrs) - { - if (!attrs[MACSEC_SA_ATTR_AN] || - attrs[MACSEC_SA_ATTR_KEY] || -- attrs[MACSEC_SA_ATTR_KEYID]) -+ attrs[MACSEC_SA_ATTR_KEYID] || -+ attrs[MACSEC_SA_ATTR_SSCI] || -+ attrs[MACSEC_SA_ATTR_SALT]) - return false; - - if (nla_get_u8(attrs[MACSEC_SA_ATTR_AN]) >= MACSEC_NUM_AN) -@@ -2152,8 +2235,18 @@ static int macsec_upd_txsa(struct sk_buff *skb, struct genl_info *info) - } - - if (tb_sa[MACSEC_SA_ATTR_PN]) { -+ int pn_len; -+ -+ pn_len = secy->xpn ? MACSEC_XPN_PN_LEN : MACSEC_DEFAULT_PN_LEN; -+ if (nla_len(tb_sa[MACSEC_SA_ATTR_PN]) != pn_len) { -+ pr_notice("macsec: nl: upd_txsa: bad pn length: %d != %d\n", -+ nla_len(tb_sa[MACSEC_SA_ATTR_PN]), pn_len); -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ - spin_lock_bh(&tx_sa->lock); -- tx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ tx_sa->next_pn = nla_get_u64(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&tx_sa->lock); - } - -@@ -2200,8 +2293,18 @@ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info) - } - - if (tb_sa[MACSEC_SA_ATTR_PN]) { -+ int pn_len; -+ -+ pn_len = secy->xpn ? MACSEC_XPN_PN_LEN : MACSEC_DEFAULT_PN_LEN; -+ if (nla_len(tb_sa[MACSEC_SA_ATTR_PN]) != pn_len) { -+ pr_notice("macsec: nl: upd_rxsa: bad pn length: %d != %d\n", -+ nla_len(tb_sa[MACSEC_SA_ATTR_PN]), pn_len); -+ rtnl_unlock(); -+ return -EINVAL; -+ } -+ - spin_lock_bh(&rx_sa->lock); -- rx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ rx_sa->next_pn = nla_get_u64(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&rx_sa->lock); - } - -@@ -2467,10 +2570,10 @@ static int nla_put_secy(struct macsec_secy *secy, struct sk_buff *skb) - - switch (secy->key_len) { - case MACSEC_GCM_AES_128_SAK_LEN: -- csid = MACSEC_DEFAULT_CIPHER_ID; -+ csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; - break; - case MACSEC_GCM_AES_256_SAK_LEN: -- csid = MACSEC_CIPHER_ID_GCM_AES_256; -+ csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; - break; - default: - goto cancel; -@@ -2552,6 +2655,8 @@ static int dump_secy(struct macsec_secy *secy, struct net_device *dev, - for (i = 0, j = 1; i < MACSEC_NUM_AN; i++) { - struct macsec_tx_sa *tx_sa = rtnl_dereference(tx_sc->sa[i]); - struct nlattr *txsa_nest; -+ u64 pn; -+ int pn_len; - - if (!tx_sa) - continue; -@@ -2562,9 +2667,18 @@ static int dump_secy(struct macsec_secy *secy, struct net_device *dev, - goto nla_put_failure; - } - -+ if (secy->xpn) { -+ pn = tx_sa->next_pn; -+ pn_len = MACSEC_XPN_PN_LEN; -+ } else { -+ pn = tx_sa->next_pn_halves.lower; -+ pn_len = MACSEC_DEFAULT_PN_LEN; -+ } -+ - if (nla_put_u8(skb, MACSEC_SA_ATTR_AN, i) || -- nla_put_u32(skb, MACSEC_SA_ATTR_PN, tx_sa->next_pn_halves.lower) || -+ nla_put(skb, MACSEC_SA_ATTR_PN, pn_len, &pn) || - nla_put(skb, MACSEC_SA_ATTR_KEYID, MACSEC_KEYID_LEN, tx_sa->key.id) || -+ (secy->xpn && nla_put_ssci(skb, MACSEC_SA_ATTR_SSCI, tx_sa->ssci)) || - nla_put_u8(skb, MACSEC_SA_ATTR_ACTIVE, tx_sa->active)) { - nla_nest_cancel(skb, txsa_nest); - nla_nest_cancel(skb, txsa_list); -@@ -2636,6 +2750,8 @@ static int dump_secy(struct macsec_secy *secy, struct net_device *dev, - for (i = 0, k = 1; i < MACSEC_NUM_AN; i++) { - struct macsec_rx_sa *rx_sa = rtnl_dereference(rx_sc->sa[i]); - struct nlattr *rxsa_nest; -+ u64 pn; -+ int pn_len; - - if (!rx_sa) - continue; -@@ -2664,9 +2780,18 @@ static int dump_secy(struct macsec_secy *secy, struct net_device *dev, - } - nla_nest_end(skb, attr); - -+ if (secy->xpn) { -+ pn = rx_sa->next_pn; -+ pn_len = MACSEC_XPN_PN_LEN; -+ } else { -+ pn = rx_sa->next_pn_halves.lower; -+ pn_len = MACSEC_DEFAULT_PN_LEN; -+ } -+ - if (nla_put_u8(skb, MACSEC_SA_ATTR_AN, i) || -- nla_put_u32(skb, MACSEC_SA_ATTR_PN, rx_sa->next_pn_halves.lower) || -+ nla_put(skb, MACSEC_SA_ATTR_PN, pn_len, &pn) || - nla_put(skb, MACSEC_SA_ATTR_KEYID, MACSEC_KEYID_LEN, rx_sa->key.id) || -+ (secy->xpn && nla_put_ssci(skb, MACSEC_SA_ATTR_SSCI, rx_sa->ssci)) || - nla_put_u8(skb, MACSEC_SA_ATTR_ACTIVE, rx_sa->active)) { - nla_nest_cancel(skb, rxsa_nest); - nla_nest_cancel(skb, rxsc_nest); -@@ -3173,9 +3298,19 @@ static int macsec_changelink_common(struct net_device *dev, - case MACSEC_CIPHER_ID_GCM_AES_128: - case MACSEC_DEFAULT_CIPHER_ID: - secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; -+ secy->xpn = false; - break; - case MACSEC_CIPHER_ID_GCM_AES_256: - secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; -+ secy->xpn = false; -+ break; -+ case MACSEC_CIPHER_ID_GCM_AES_XPN_128: -+ secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; -+ secy->xpn = true; -+ break; -+ case MACSEC_CIPHER_ID_GCM_AES_XPN_256: -+ secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; -+ secy->xpn = true; - break; - default: - return -EINVAL; -@@ -3314,6 +3449,7 @@ static int macsec_add_dev(struct net_device *dev, sci_t sci, u8 icv_len) - secy->validate_frames = MACSEC_VALIDATE_DEFAULT; - secy->protect_frames = true; - secy->replay_protect = false; -+ secy->xpn = DEFAULT_XPN; - - secy->sci = sci; - secy->tx_sc.active = true; -@@ -3448,6 +3584,8 @@ static int macsec_validate_attr(struct nlattr *tb[], struct nlattr *data[], - switch (csid) { - case MACSEC_CIPHER_ID_GCM_AES_128: - case MACSEC_CIPHER_ID_GCM_AES_256: -+ case MACSEC_CIPHER_ID_GCM_AES_XPN_128: -+ case MACSEC_CIPHER_ID_GCM_AES_XPN_256: - case MACSEC_DEFAULT_CIPHER_ID: - if (icv_len < MACSEC_MIN_ICV_LEN || - icv_len > MACSEC_STD_ICV_LEN) -@@ -3521,10 +3659,10 @@ static int macsec_fill_info(struct sk_buff *skb, - - switch (secy->key_len) { - case MACSEC_GCM_AES_128_SAK_LEN: -- csid = MACSEC_DEFAULT_CIPHER_ID; -+ csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; - break; - case MACSEC_GCM_AES_256_SAK_LEN: -- csid = MACSEC_CIPHER_ID_GCM_AES_256; -+ csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; - break; - default: - goto nla_put_failure; -diff --git a/include/uapi/linux/if_macsec.h b/include/uapi/linux/if_macsec.h -index 98e4d5d7c..df638ba7a 100644 ---- a/include/uapi/linux/if_macsec.h -+++ b/include/uapi/linux/if_macsec.h -@@ -22,9 +22,11 @@ - - #define MACSEC_KEYID_LEN 16 - --/* cipher IDs as per IEEE802.1AEbn-2011 */ -+/* cipher IDs as per IEEE802.1AE-2018 (Table 14-1) */ - #define MACSEC_CIPHER_ID_GCM_AES_128 0x0080C20001000001ULL - #define MACSEC_CIPHER_ID_GCM_AES_256 0x0080C20001000002ULL -+#define MACSEC_CIPHER_ID_GCM_AES_XPN_128 0x0080C20001000003ULL -+#define MACSEC_CIPHER_ID_GCM_AES_XPN_256 0x0080C20001000004ULL - - /* deprecated cipher ID for GCM-AES-128 */ - #define MACSEC_DEFAULT_CIPHER_ID 0x0080020001000001ULL -@@ -87,11 +89,13 @@ enum macsec_sa_attrs { - MACSEC_SA_ATTR_UNSPEC, - MACSEC_SA_ATTR_AN, /* config/dump, u8 0..3 */ - MACSEC_SA_ATTR_ACTIVE, /* config/dump, u8 0..1 */ -- MACSEC_SA_ATTR_PN, /* config/dump, u32 */ -+ MACSEC_SA_ATTR_PN, /* config/dump, u32/u64 (u64 if XPN) */ - MACSEC_SA_ATTR_KEY, /* config, data */ - MACSEC_SA_ATTR_KEYID, /* config/dump, 128-bit */ - MACSEC_SA_ATTR_STATS, /* dump, nested, macsec_sa_stats_attr */ - MACSEC_SA_ATTR_PAD, -+ MACSEC_SA_ATTR_SSCI, /* config/dump, u32 - XPN only */ -+ MACSEC_SA_ATTR_SALT, /* config, 96-bit - XPN only */ - __MACSEC_SA_ATTR_END, - NUM_MACSEC_SA_ATTR = __MACSEC_SA_ATTR_END, - MACSEC_SA_ATTR_MAX = __MACSEC_SA_ATTR_END - 1, diff --git a/patch/macsec-Support-XPN-frame-handling-IEEE-802.1AEbw.patch b/patch/macsec-Support-XPN-frame-handling-IEEE-802.1AEbw.patch deleted file mode 100644 index a7c9d628a..000000000 --- a/patch/macsec-Support-XPN-frame-handling-IEEE-802.1AEbw.patch +++ /dev/null @@ -1,425 +0,0 @@ -From a21ecf0e033807b976967286e6c392f48ee2049f Mon Sep 17 00:00:00 2001 - -From: Era Mayflower - -Subject: [PATCH] macsec: Support XPN frame handling - IEEE 802.1AEbw - -Support extended packet number cipher suites (802.1AEbw) frames handling. -This does not include the needed netlink patches. - - * Added xpn boolean field to `struct macsec_secy`. - * Added ssci field to `struct_macsec_tx_sa` (802.1AE figure 10-5). - * Added ssci field to `struct_macsec_rx_sa` (802.1AE figure 10-5). - * Added salt field to `struct macsec_key` (802.1AE 10.7 NOTE 1). - * Created pn_t type for easy access to lower and upper halves. - * Created salt_t type for easy access to the "ssci" and "pn" parts. - * Created `macsec_fill_iv_xpn` function to create IV in XPN mode. - * Support in PN recovery and preliminary replay check in XPN mode. - -In addition, according to IEEE 802.1AEbw figure 10-5, the PN of incoming -frame can be 0 when XPN cipher suite is used, so fixed the function -`macsec_validate_skb` to fail on PN=0 only if XPN is off. - -Signed-off-by: Era Mayflower -Signed-off-by: David S. Miller ---- - drivers/net/macsec.c | 156 ++++++++++++++++++++++++++++++++++++++++---------- - 1 file changed, 125 insertions(+), 31 deletions(-) - -diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c -index 4c5b67a2d..bc3aa124e 100644 ---- a/drivers/net/macsec.c -+++ b/drivers/net/macsec.c -@@ -21,10 +21,38 @@ - #include - #include - #include -+#include - - #include - -+#define MACSEC_SALT_LEN 12 -+#define MACSEC_NUM_AN 4 /* 2 bits for the association number */ -+ - typedef u64 __bitwise sci_t; -+typedef u32 __bitwise ssci_t; -+ -+typedef union salt { -+ struct { -+ u32 ssci; -+ u64 pn; -+ } __packed; -+ u8 bytes[MACSEC_SALT_LEN]; -+} __packed salt_t; -+ -+typedef union pn { -+ struct { -+#if defined(__LITTLE_ENDIAN_BITFIELD) -+ u32 lower; -+ u32 upper; -+#elif defined(__BIG_ENDIAN_BITFIELD) -+ u32 upper; -+ u32 lower; -+#else -+#error "Please fix " -+#endif -+ }; -+ u64 full64; -+} pn_t; - - #define MACSEC_SCI_LEN 8 - -@@ -63,7 +91,6 @@ struct macsec_eth_header { - #define GCM_AES_IV_LEN 12 - #define DEFAULT_ICV_LEN 16 - --#define MACSEC_NUM_AN 4 /* 2 bits for the association number */ - - #define for_each_rxsc(secy, sc) \ - for (sc = rcu_dereference_bh(secy->rx_sc); \ -@@ -74,6 +101,16 @@ struct macsec_eth_header { - sc; \ - sc = rtnl_dereference(sc->next)) - -+#define pn_same_half(pn1, pn2) (!(((pn1) >> 31) ^ ((pn2) >> 31))) -+ -+struct gcm_iv_xpn { -+ union { -+ u8 short_secure_channel_id[4]; -+ ssci_t ssci; -+ }; -+ __be64 pn; -+} __packed; -+ - struct gcm_iv { - union { - u8 secure_channel_id[8]; -@@ -86,10 +123,12 @@ struct gcm_iv { - * struct macsec_key - SA key - * @id: user-provided key identifier - * @tfm: crypto struct, key storage -+ * @salt: salt used to generate IV in XPN cipher suites - */ - struct macsec_key { - u8 id[MACSEC_KEYID_LEN]; - struct crypto_aead *tfm; -+ salt_t salt; - }; - - struct macsec_rx_sc_stats { -@@ -142,12 +181,17 @@ struct macsec_dev_stats { - * @next_pn: packet number expected for the next packet - * @lock: protects next_pn manipulations - * @key: key structure -+ * @ssci: short secure channel identifier - * @stats: per-SA stats - */ - struct macsec_rx_sa { - struct macsec_key key; -+ ssci_t ssci; - spinlock_t lock; -- u32 next_pn; -+ union { -+ pn_t next_pn_halves; -+ u64 next_pn; -+ }; - refcount_t refcnt; - bool active; - struct macsec_rx_sa_stats __percpu *stats; -@@ -183,12 +227,17 @@ struct macsec_rx_sc { - * @next_pn: packet number to use for the next packet - * @lock: protects next_pn manipulations - * @key: key structure -+ * @ssci: short secure channel identifier - * @stats: per-SA stats - */ - struct macsec_tx_sa { - struct macsec_key key; -+ ssci_t ssci; - spinlock_t lock; -- u32 next_pn; -+ union { -+ pn_t next_pn_halves; -+ u64 next_pn; -+ }; - refcount_t refcnt; - bool active; - struct macsec_tx_sa_stats __percpu *stats; -@@ -232,6 +281,7 @@ struct macsec_tx_sc { - * @key_len: length of keys used by the cipher suite - * @icv_len: length of ICV used by the cipher suite - * @validate_frames: validation mode -+ * @xpn: enable XPN for this SecY - * @operational: MAC_Operational flag - * @protect_frames: enable protection for this SecY - * @replay_protect: enable packet number checks on receive -@@ -246,6 +296,7 @@ struct macsec_secy { - u16 key_len; - u16 icv_len; - enum macsec_validation_type validate_frames; -+ bool xpn; - bool operational; - bool protect_frames; - bool replay_protect; -@@ -487,7 +538,7 @@ static void macsec_set_shortlen(struct macsec_eth_header *h, size_t data_len) - } - - /* validate MACsec packet according to IEEE 802.1AE-2006 9.12 */ --static bool macsec_validate_skb(struct sk_buff *skb, u16 icv_len) -+static bool macsec_validate_skb(struct sk_buff *skb, u16 icv_len, bool xpn) - { - struct macsec_eth_header *h = (struct macsec_eth_header *)skb->data; - int len = skb->len - 2 * ETH_ALEN; -@@ -512,8 +563,8 @@ static bool macsec_validate_skb(struct sk_buff *skb, u16 icv_len) - if (h->unused) - return false; - -- /* rx.pn != 0 (figure 10-5) */ -- if (!h->packet_number) -+ /* rx.pn != 0 if not XPN (figure 10-5 with 802.11AEbw-2013 amendment) */ -+ if (!h->packet_number && !xpn) - return false; - - /* length check, f) g) h) i) */ -@@ -525,6 +576,15 @@ static bool macsec_validate_skb(struct sk_buff *skb, u16 icv_len) - #define MACSEC_NEEDED_HEADROOM (macsec_extra_len(true)) - #define MACSEC_NEEDED_TAILROOM MACSEC_STD_ICV_LEN - -+static void macsec_fill_iv_xpn(unsigned char *iv, ssci_t ssci, u64 pn, -+ salt_t salt) -+{ -+ struct gcm_iv_xpn *gcm_iv = (struct gcm_iv_xpn *)iv; -+ -+ gcm_iv->ssci = ssci ^ salt.ssci; -+ gcm_iv->pn = cpu_to_be64(pn) ^ salt.pn; -+} -+ - static void macsec_fill_iv(unsigned char *iv, sci_t sci, u32 pn) - { - struct gcm_iv *gcm_iv = (struct gcm_iv *)iv; -@@ -538,14 +598,18 @@ static struct macsec_eth_header *macsec_ethhdr(struct sk_buff *skb) - return (struct macsec_eth_header *)skb_mac_header(skb); - } - --static u32 tx_sa_update_pn(struct macsec_tx_sa *tx_sa, struct macsec_secy *secy) -+static pn_t tx_sa_update_pn(struct macsec_tx_sa *tx_sa, -+ struct macsec_secy *secy) - { -- u32 pn; -+ pn_t pn; - - spin_lock_bh(&tx_sa->lock); -- pn = tx_sa->next_pn; -+ pn = tx_sa->next_pn_halves; -+ if (secy->xpn) -+ tx_sa->next_pn++; -+ else -+ tx_sa->next_pn_halves.lower++; - -- tx_sa->next_pn++; - if (tx_sa->next_pn == 0) { - pr_debug("PN wrapped, transitioning to !oper\n"); - tx_sa->active = false; -@@ -664,7 +728,7 @@ static struct sk_buff *macsec_encrypt(struct sk_buff *skb, - struct macsec_tx_sa *tx_sa; - struct macsec_dev *macsec = macsec_priv(dev); - bool sci_present; -- u32 pn; -+ pn_t pn; - - secy = &macsec->secy; - tx_sc = &secy->tx_sc; -@@ -706,12 +770,12 @@ static struct sk_buff *macsec_encrypt(struct sk_buff *skb, - memmove(hh, eth, 2 * ETH_ALEN); - - pn = tx_sa_update_pn(tx_sa, secy); -- if (pn == 0) { -+ if (pn.full64 == 0) { - macsec_txsa_put(tx_sa); - kfree_skb(skb); - return ERR_PTR(-ENOLINK); - } -- macsec_fill_sectag(hh, secy, pn, sci_present); -+ macsec_fill_sectag(hh, secy, pn.lower, sci_present); - macsec_set_shortlen(hh, unprotected_len - 2 * ETH_ALEN); - - skb_put(skb, secy->icv_len); -@@ -742,7 +806,10 @@ static struct sk_buff *macsec_encrypt(struct sk_buff *skb, - return ERR_PTR(-ENOMEM); - } - -- macsec_fill_iv(iv, secy->sci, pn); -+ if (secy->xpn) -+ macsec_fill_iv_xpn(iv, tx_sa->ssci, pn.full64, tx_sa->key.salt); -+ else -+ macsec_fill_iv(iv, secy->sci, pn.lower); - - sg_init_table(sg, ret); - ret = skb_to_sgvec(skb, sg, 0, skb->len); -@@ -794,13 +861,14 @@ static bool macsec_post_decrypt(struct sk_buff *skb, struct macsec_secy *secy, u - u32 lowest_pn = 0; - - spin_lock(&rx_sa->lock); -- if (rx_sa->next_pn >= secy->replay_window) -- lowest_pn = rx_sa->next_pn - secy->replay_window; -+ if (rx_sa->next_pn_halves.lower >= secy->replay_window) -+ lowest_pn = rx_sa->next_pn_halves.lower - secy->replay_window; - - /* Now perform replay protection check again - * (see IEEE 802.1AE-2006 figure 10-5) - */ -- if (secy->replay_protect && pn < lowest_pn) { -+ if (secy->replay_protect && pn < lowest_pn && -+ (!secy->xpn || pn_same_half(pn, lowest_pn))) { - spin_unlock(&rx_sa->lock); - u64_stats_update_begin(&rxsc_stats->syncp); - rxsc_stats->stats.InPktsLate++; -@@ -849,8 +917,15 @@ static bool macsec_post_decrypt(struct sk_buff *skb, struct macsec_secy *secy, u - } - u64_stats_update_end(&rxsc_stats->syncp); - -- if (pn >= rx_sa->next_pn) -- rx_sa->next_pn = pn + 1; -+ // Instead of "pn >=" - to support pn overflow in xpn -+ if (pn + 1 > rx_sa->next_pn_halves.lower) { -+ rx_sa->next_pn_halves.lower = pn + 1; -+ } else if (secy->xpn && -+ !pn_same_half(pn, rx_sa->next_pn_halves.lower)) { -+ rx_sa->next_pn_halves.upper++; -+ rx_sa->next_pn_halves.lower = pn + 1; -+ } -+ - spin_unlock(&rx_sa->lock); - } - -@@ -937,6 +1012,7 @@ static struct sk_buff *macsec_decrypt(struct sk_buff *skb, - unsigned char *iv; - struct aead_request *req; - struct macsec_eth_header *hdr; -+ u32 hdr_pn; - u16 icv_len = secy->icv_len; - - macsec_skb_cb(skb)->valid = false; -@@ -956,7 +1032,21 @@ static struct sk_buff *macsec_decrypt(struct sk_buff *skb, - } - - hdr = (struct macsec_eth_header *)skb->data; -- macsec_fill_iv(iv, sci, ntohl(hdr->packet_number)); -+ hdr_pn = ntohl(hdr->packet_number); -+ -+ if (secy->xpn) { -+ pn_t recovered_pn = rx_sa->next_pn_halves; -+ -+ recovered_pn.lower = hdr_pn; -+ if (hdr_pn < rx_sa->next_pn_halves.lower && -+ !pn_same_half(hdr_pn, rx_sa->next_pn_halves.lower)) -+ recovered_pn.upper++; -+ -+ macsec_fill_iv_xpn(iv, rx_sa->ssci, recovered_pn.full64, -+ rx_sa->key.salt); -+ } else { -+ macsec_fill_iv(iv, sci, hdr_pn); -+ } - - sg_init_table(sg, ret); - ret = skb_to_sgvec(skb, sg, 0, skb->len); -@@ -1088,6 +1178,7 @@ static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb) - unsigned int len; - sci_t sci; - u32 pn; -+ u32 hdr_pn; - bool cbit; - struct pcpu_rx_sc_stats *rxsc_stats; - struct pcpu_secy_stats *secy_stats; -@@ -1161,7 +1252,7 @@ static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb) - secy_stats = this_cpu_ptr(macsec->stats); - rxsc_stats = this_cpu_ptr(rx_sc->stats); - -- if (!macsec_validate_skb(skb, secy->icv_len)) { -+ if (!macsec_validate_skb(skb, secy->icv_len, secy->xpn)) { - u64_stats_update_begin(&secy_stats->syncp); - secy_stats->stats.InPktsBadTag++; - u64_stats_update_end(&secy_stats->syncp); -@@ -1193,13 +1284,16 @@ static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb) - } - - /* First, PN check to avoid decrypting obviously wrong packets */ -- pn = ntohl(hdr->packet_number); -+ hdr_pn = ntohl(hdr->packet_number); - if (secy->replay_protect) { - bool late; - - spin_lock(&rx_sa->lock); -- late = rx_sa->next_pn >= secy->replay_window && -- pn < (rx_sa->next_pn - secy->replay_window); -+ late = rx_sa->next_pn_halves.lower >= secy->replay_window && -+ hdr_pn < (rx_sa->next_pn_halves.lower - secy->replay_window); -+ -+ if (secy->xpn) -+ late = late && pn_same_half(rx_sa->next_pn_halves.lower, hdr_pn); - spin_unlock(&rx_sa->lock); - - if (late) { -@@ -1228,7 +1322,7 @@ static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb) - return RX_HANDLER_CONSUMED; - } - -- if (!macsec_post_decrypt(skb, secy, pn)) -+ if (!macsec_post_decrypt(skb, secy, hdr_pn)) - goto drop; - - deliver: -@@ -1723,7 +1817,7 @@ static int macsec_add_rxsa(struct sk_buff *skb, struct genl_info *info) - - if (tb_sa[MACSEC_SA_ATTR_PN]) { - spin_lock_bh(&rx_sa->lock); -- rx_sa->next_pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ rx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&rx_sa->lock); - } - -@@ -1879,7 +1973,7 @@ static int macsec_add_txsa(struct sk_buff *skb, struct genl_info *info) - nla_memcpy(tx_sa->key.id, tb_sa[MACSEC_SA_ATTR_KEYID], MACSEC_KEYID_LEN); - - spin_lock_bh(&tx_sa->lock); -- tx_sa->next_pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ tx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&tx_sa->lock); - - if (tb_sa[MACSEC_SA_ATTR_ACTIVE]) -@@ -2063,7 +2157,7 @@ static int macsec_upd_txsa(struct sk_buff *skb, struct genl_info *info) - - if (tb_sa[MACSEC_SA_ATTR_PN]) { - spin_lock_bh(&tx_sa->lock); -- tx_sa->next_pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ tx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&tx_sa->lock); - } - -@@ -2111,7 +2205,7 @@ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info) - - if (tb_sa[MACSEC_SA_ATTR_PN]) { - spin_lock_bh(&rx_sa->lock); -- rx_sa->next_pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); -+ rx_sa->next_pn_halves.lower = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]); - spin_unlock_bh(&rx_sa->lock); - } - -@@ -2473,7 +2567,7 @@ static int dump_secy(struct macsec_secy *secy, struct net_device *dev, - } - - if (nla_put_u8(skb, MACSEC_SA_ATTR_AN, i) || -- nla_put_u32(skb, MACSEC_SA_ATTR_PN, tx_sa->next_pn) || -+ nla_put_u32(skb, MACSEC_SA_ATTR_PN, tx_sa->next_pn_halves.lower) || - nla_put(skb, MACSEC_SA_ATTR_KEYID, MACSEC_KEYID_LEN, tx_sa->key.id) || - nla_put_u8(skb, MACSEC_SA_ATTR_ACTIVE, tx_sa->active)) { - nla_nest_cancel(skb, txsa_nest); -@@ -2575,7 +2669,7 @@ static int dump_secy(struct macsec_secy *secy, struct net_device *dev, - nla_nest_end(skb, attr); - - if (nla_put_u8(skb, MACSEC_SA_ATTR_AN, i) || -- nla_put_u32(skb, MACSEC_SA_ATTR_PN, rx_sa->next_pn) || -+ nla_put_u32(skb, MACSEC_SA_ATTR_PN, rx_sa->next_pn_halves.lower) || - nla_put(skb, MACSEC_SA_ATTR_KEYID, MACSEC_KEYID_LEN, rx_sa->key.id) || - nla_put_u8(skb, MACSEC_SA_ATTR_ACTIVE, rx_sa->active)) { - nla_nest_cancel(skb, rxsa_nest); diff --git a/patch/net-sch_generic-fix-the-missing-new-qdisc-assignment.patch b/patch/net-sch_generic-fix-the-missing-new-qdisc-assignment.patch deleted file mode 100644 index 91af5dfba..000000000 --- a/patch/net-sch_generic-fix-the-missing-new-qdisc-assignment.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 81504d1952d712c8bb9c3966896efee8a37ea966 Mon Sep 17 00:00:00 2001 -From: Yunsheng Lin -Date: Tue, 3 Nov 2020 11:25:38 +0800 -Subject: net: sch_generic: fix the missing new qdisc assignment bug - -When commit 2fb541c862c9 ("net: sch_generic: aviod concurrent reset and -enqueue op for lockless qdisc") is backported to stable kernel, one -assignment is missing, which causes two problems reported by Joakim and -Vishwanath, see [1] and [2]. - -So add the assignment back to fix it. - -1. https://www.spinics.net/lists/netdev/msg693916.html -2. https://www.spinics.net/lists/netdev/msg695131.html - -Fixes: 749cc0b0c7f3 ("net: sch_generic: aviod concurrent reset and enqueue op for lockless qdisc") -Signed-off-by: Yunsheng Lin -Acked-by: Jakub Kicinski -Tested-by: Brian Norris -Signed-off-by: Greg Kroah-Hartman ---- - net/sched/sch_generic.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c -index bd96fd261dba3..4e15913e7519e 100644 ---- a/net/sched/sch_generic.c -+++ b/net/sched/sch_generic.c -@@ -1116,10 +1116,13 @@ static void dev_deactivate_queue(struct net_device *dev, - void *_qdisc_default) - { - struct Qdisc *qdisc = rtnl_dereference(dev_queue->qdisc); -+ struct Qdisc *qdisc_default = _qdisc_default; - - if (qdisc) { - if (!(qdisc->flags & TCQ_F_BUILTIN)) - set_bit(__QDISC_STATE_DEACTIVATED, &qdisc->state); -+ -+ rcu_assign_pointer(dev_queue->qdisc, qdisc_default); - } - } - --- -cgit 1.2.3-1.el7 - diff --git a/patch/netlink-add-NLA_MIN_LEN.patch b/patch/netlink-add-NLA_MIN_LEN.patch deleted file mode 100644 index 1e637ce16..000000000 --- a/patch/netlink-add-NLA_MIN_LEN.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 6f455f5f4e9c28aefaefbe18ce7304b499645d75 Mon Sep 17 00:00:00 2001 - -From: Johannes Berg - -Subject: [PATCH] netlink: add NLA_MIN_LEN - -Rather than using NLA_UNSPEC for this type of thing, use NLA_MIN_LEN -so we can make NLA_UNSPEC be NLA_REJECT under certain conditions for -future attributes. - -While at it, also use NLA_EXACT_LEN for the struct example. - -Signed-off-by: Johannes Berg -Signed-off-by: David S. Miller ---- - include/net/netlink.h | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/include/net/netlink.h b/include/net/netlink.h -index 39e1d875d..46c65b7cb 100644 ---- a/include/net/netlink.h -+++ b/include/net/netlink.h -@@ -180,6 +180,7 @@ enum { - NLA_S32, - NLA_S64, - NLA_BITFIELD32, -+ NLA_MIN_LEN, - __NLA_TYPE_MAX, - }; - -@@ -215,7 +216,7 @@ enum { - * static const struct nla_policy my_policy[ATTR_MAX+1] = { - * [ATTR_FOO] = { .type = NLA_U16 }, - * [ATTR_BAR] = { .type = NLA_STRING, .len = BARSIZ }, -- * [ATTR_BAZ] = { .len = sizeof(struct mystruct) }, -+ * [ATTR_BAZ] = { .type = NLA_EXACT_LEN, .len = sizeof(struct mystruct) }, - * [ATTR_GOO] = { .type = NLA_BITFIELD32, .validation_data = &myvalidflags }, - * }; - */ diff --git a/patch/preconfig/changelog.patch b/patch/preconfig/changelog.patch deleted file mode 100644 index 9096b5dec..000000000 --- a/patch/preconfig/changelog.patch +++ /dev/null @@ -1,34 +0,0 @@ -From a96e033ed01d4a68a0825c187bebe45a527fb868 Mon Sep 17 00:00:00 2001 - -From: Qi Luo - -Subject: [PATCH] Update changelog - -Signed-off-by: Qi Luo ---- - debian/changelog | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/debian/changelog b/debian/changelog -index 026b840..88c8a43 100644 ---- a/debian/changelog -+++ b/debian/changelog -@@ -1248,6 +1248,18 @@ - - -- Salvatore Bonaccorso Fri, 19 Jul 2019 13:41:00 +0200 - -+linux (4.9.168-1+deb9u3) sonic; urgency=high -+ -+ * add driver patches for MLNX SN2700 -+ -+ -- Guohan Lu Sun, 19 Dec 2015 01:50:04 +0100 -+ -+linux (4.9.168-1+deb9u3) sonic; urgency=high -+ -+ * add support for S6000 -+ -+ -- Shuotian Cheng Sun, 19 Dec 2015 01:50:04 +0100 -+ - linux (4.9.168-1+deb9u3) stretch-security; urgency=high - - [ Salvatore Bonaccorso ] diff --git a/patch/preconfig/packaging-update-abiname-to-11-2.patch b/patch/preconfig/packaging-update-abiname-to-11-2.patch deleted file mode 100644 index 0224c3e32..000000000 --- a/patch/preconfig/packaging-update-abiname-to-11-2.patch +++ /dev/null @@ -1,20 +0,0 @@ -Update debian abiname to 9-2 - -From: Guohan Lu - - ---- - debian/config/defines | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/debian/config/defines b/debian/config/defines -index cfc4409..fae58f7 100644 ---- a/debian/config/defines -+++ b/debian/config/defines -@@ -1,5 +1,5 @@ - [abi] --abiname: 11 -+abiname: 11-2 - ignore-changes: - __cpuhp_* - bpf_analyzer diff --git a/patch/preconfig/packaging-update-abiname.patch b/patch/preconfig/packaging-update-abiname.patch deleted file mode 100644 index 5114b2226..000000000 --- a/patch/preconfig/packaging-update-abiname.patch +++ /dev/null @@ -1,34 +0,0 @@ -Update debian abiname to 9-2 - -From: Olivier Singla - - ---- - debian/config/defines | 2 +- - debian/control.md5sum | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) - -diff --git a/debian/config/defines b/debian/config/defines -index cd4938ecc..e54c10256 100644 ---- a/debian/config/defines -+++ b/debian/config/defines -@@ -1,5 +1,5 @@ - [abi] --abiname: 12 -+abiname: 12-2 - ignore-changes: - __cpuhp_* - bpf_analyzer -diff --git a/debian/control.md5sum b/debian/control.md5sum -index e86f5f1db..caddd5360 100644 ---- a/debian/control.md5sum -+++ b/debian/control.md5sum -@@ -25,7 +25,7 @@ afb508873a44a1f196c2a525fec4b415 debian/templates/image.prerm.in - 51797b6c275eb14e99009e78553904cc debian/templates/perf.lintian-overrides.in - 814dda166c7e3ef02e6e259e805ac66a debian/templates/tests-control.image.in - befa899554918fd2a5264e5dca3c4eef debian/templates/tests-control.main.in --d078f945701eb7a9569bd0bc04e8b9a9 debian/config/defines -+139d99a05bc29dcd945ff8a0ebdd9ce1 debian/config/defines - da871dd4234a8d1ed82d76fa536fa411 debian/config/alpha/defines - 6a030dc96bbeb2340082dc7c5c67a640 debian/config/amd64/defines - 3f9e6c98b1abe4a32b12add5d1ada119 debian/config/arm64/defines diff --git a/patch/preconfig/series b/patch/preconfig/series deleted file mode 100644 index 2177aeab5..000000000 --- a/patch/preconfig/series +++ /dev/null @@ -1 +0,0 @@ -packaging-update-abiname.patch diff --git a/patch/series b/patch/series deleted file mode 100755 index e0ce3557f..000000000 --- a/patch/series +++ /dev/null @@ -1,96 +0,0 @@ -# This series applies on GIT commit 18c5597832fcf6988111b05a9a1607ae148723c - -driver-arista-net-tg3-dma-mask-4g-sb800.patch -driver-arista-net-tg3-disallow-broadcom-default-mac.patch -driver-arista-net-tg3-access-regs-indirectly.patch -driver-arista-disable-smbus-mux-for-hudson2.patch -driver-arista-pci-reassign-pref-mem.patch -driver-support-sff-8436-eeprom.patch -driver-support-sff-8436-eeprom-update.patch -driver-sff-8436-use-nvmem-framework.patch -driver-sff-8436-use-nvmem_device_read.patch -driver-support-sff-8436-read-write-fix.patch -driver-i2c-bus-intel-ismt-add-delay-param.patch -driver-i2c-ocores-5-1.patch -driver-i2c-mux-pca954x-remove-support-for-unused-platform-d.patch -driver-i2c-mux-pca954x-allow-management-of-device-idle-stat.patch -driver-pca954x-i2c-mux-force-deselect-on-exit-flag.patch -kernel-add-kexec-reboot-string.patch -driver-hwmon-max6620.patch -driver-hwmon-max6620-fix-rpm-calc.patch -driver-hwmon-max6620-update.patch -driver-hwmon-max6658-fix-write-convrate.patch -driver-hwmon-pmbus-dni_dps460.patch -driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch -driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch -driver-hwmon-pmbus-dps1900.patch -driver-hwmon-pmbus-dps200.patch -driver-support-tun-config-carrier-enable.patch -driver-support-optoe.patch -driver-support-optoe-EOF_fix.patch -driver-support-optoe-chunk-offset-fix.patch -driver-support-optoe-QSFP_DD.patch -driver-net-tg3-add-param-short-preamble-and-reset.patch -0001-hwmon-lm75-add-support-for-PCT2075.patch -0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch -0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch -0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch -macsec-Support-XPN-frame-handling-IEEE-802.1AEbw.patch -netlink-add-NLA_MIN_LEN.patch -macsec-Netlink-support-of-XPN-cipher-suites-IEEE-802.patch -Support-for-fullcone-nat.patch -driver-ixgbe-external-phy.patch -net-sch_generic-fix-the-missing-new-qdisc-assignment.patch -# -# This series applies on GIT commit 1451b36b2b0d62178e42f648d8a18131af18f7d8 -# Tkernel-sched-core-fix-cgroup-fork-race.patch -# Tconfig-mlnx-sn2700.patch -# Tdriver-at24-fix-odd-length-two-byte-access.patch -# Tdriver-hwmon-lm75b-update.patch -# Tdriver-hwmon-pmbus-add-dps460-support.patch -# Tdriver-hwmon-pmbus-ucd9200-mlnx.patch -# Trtnetlink-catch-EOPNOTSUPP-errors.patch -# Tbridge-per-port-multicast-broadcast-flood-flags.patch -# -# Mellanox patches for 4.19 -0001-Mellanox-platform-Backport-patches-for-new-Mellanox-.patch -0003-mlxsw-minimal-Fix-validation-for-FW-minor-version.patch -0004-mlxsw-core-Add-the-hottest-thermal-zone-detection.patch -0005-hwmon-pmbus-core-Add-support-for-vid-mode-detection-.patch -0006-platform-mellanox-mlxreg-hotplug-Use-capability-r.patch -0007-mlxsw-core-thermal-Set-default-thermal-trips-at-init.patch -0008-mlxsw-qsfp_sysfs-Remove-obsolete-code-for-QSFP-EEPRO.patch -0009-platform-mellanox-mlxreg-io-Add-support-for-compl.patch -0010-platform-mellanox-mlxreg-hotplug-Add-environmental-d.patch -0011-mlxsw-core-thermal-Separate-temperature-trend-read-c.patch -0012-Add-support-for-new-transceivers-types-QSFP-DD-and-Q.patch -0013-watchdog-mlx-wdt-support-new-watchdog-type-with-long.patch -0014-mlxsw-i2c-Fix-comment-misspelling.patch -0015-mlxsw-i2c-Modify-input-parameter-name-in-initializat.patch -0016-mlxsw-i2c-Extend-input-parameters-list-of-command-AP.patch -0017-mlxsw-i2c-Extend-initialization-by-querying-resource.patch -0018-mlxsw-i2c-Extend-initialization-with-querying-firmware.patch -0019-mlxsw-i2c-Allow-flexible-setting-of-I2C-transactions.patch -0020-mlxsw-core-Set-different-thermal-polling-time-based.patch -0021-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch -0022-mlxsw-core-Increase-critical-threshold-for-ASIC-ther.patch -0023-mlxsw-core-Add-validation-of-transceiver-temperature.patch -0024-mlxsw-core-Fix-memory-leak-on-module-removal.patch -0025-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch -0026-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch -0027-mlxsw-core-Remove-critical-trip-point-from-thermal-z.patch -0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch -0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch -0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch -0031-backport-nvme-Add-hardware-monitoring-support.patch -0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch - -# -# Marvell platform patches for 4.19 -armhf_secondary_boot_online.patch -############################################################ -# -# Internal patches will be added below (placeholder) -# Do not add any public patch below this marker -# -############################################################ diff --git a/patches-debian/disable-secureboot-config-checks.patch b/patches-debian/disable-secureboot-config-checks.patch new file mode 100644 index 000000000..213cbfd65 --- /dev/null +++ b/patches-debian/disable-secureboot-config-checks.patch @@ -0,0 +1,15 @@ +Index: linux-6.12.41/debian/bin/buildcheck.py +=================================================================== +--- linux-6.12.41.orig/debian/bin/buildcheck.py ++++ linux-6.12.41/debian/bin/buildcheck.py +@@ -23,9 +23,7 @@ class CheckSecureBootConfig: + with (self.dir / '.config').open() as fh: + kconfig.read(fh) + +- for name, value in [('EFI_STUB', True), +- ('LOCK_DOWN_IN_EFI_SECURE_BOOT', True), +- ('SYSTEM_TRUSTED_KEYS', '""')]: ++ for name, value in [('EFI_STUB', True),]: + if name not in kconfig: + out.write(f'Secure Boot: CONFIG_{name} is not defined\n') + fail = 1 diff --git a/patches-sonic/0001-Add-device-tree-for-Nokia-BMC-H6-128-platform.patch b/patches-sonic/0001-Add-device-tree-for-Nokia-BMC-H6-128-platform.patch new file mode 100644 index 000000000..e453e3b8c --- /dev/null +++ b/patches-sonic/0001-Add-device-tree-for-Nokia-BMC-H6-128-platform.patch @@ -0,0 +1,371 @@ +From 9be07cbc41161d2aaeca2a6e48fd698af24f75ae Mon Sep 17 00:00:00 2001 +From: Natarajan Subbiramani +Date: Fri, 24 Apr 2026 10:44:28 -0400 +Subject: [PATCH] Add device tree for Nokia BMC H6-128 platform + +The device tree is based on ast2700 evb board. +Hardware deviations from ast2700-evb: +- Removed unused reserved memory nodes +- Removed PWM FAN definitions (controlled from HCPU) +- Customize gpio1 pin and pinctrl definitions. +- Removed I3C node definitions +- Removed unused LPC definitions +- eMMC 8bit bus width + +Signed-off-by: Natarajan Subbiramani +--- + arch/arm64/boot/dts/aspeed/Makefile | 1 + + .../dts/aspeed/nokia-ast2700-h6-128-r0.dts | 329 ++++++++++++++++++ + 2 files changed, 330 insertions(+) + create mode 100644 arch/arm64/boot/dts/aspeed/nokia-ast2700-h6-128-r0.dts + +diff --git a/arch/arm64/boot/dts/aspeed/Makefile b/arch/arm64/boot/dts/aspeed/Makefile +index 3e904b273f1f..5caf80a7ff69 100644 +--- a/arch/arm64/boot/dts/aspeed/Makefile ++++ b/arch/arm64/boot/dts/aspeed/Makefile +@@ -3,6 +3,7 @@ + dtb-$(CONFIG_ARCH_ASPEED) += \ + ast2700-evb.dtb \ + nexthop-b27-r0.dtb \ ++ nokia-ast2700-h6-128-r0.dtb \ + ast2700-evb_ast1700-evb.dtb \ + ast2700-evb_ast1700-evb-dual.dtb \ + ast2700-raw.dtb \ +diff --git a/arch/arm64/boot/dts/aspeed/nokia-ast2700-h6-128-r0.dts b/arch/arm64/boot/dts/aspeed/nokia-ast2700-h6-128-r0.dts +new file mode 100644 +index 000000000000..79b5bd7fb94a +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nokia-ast2700-h6-128-r0.dts +@@ -0,0 +1,329 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Nokia BMC H6-128 devicetreee based on ast2700-evb.dts ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++#include ++ ++/ { ++ model = "Nokia-BMC-H6-128"; ++ compatible = "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++#if 0 ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++#endif ++ }; ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++&uart2 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ u-boot@0 { ++ reg = <0x0 0x400000>; // 4MB ++ label = "u-boot"; ++ }; ++ u-boot-env@400000 { ++ reg = <0x400000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ kernel@420000 { ++ reg = <0x420000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ rofs@d20000 { ++ reg = <0xd20000 0x24a0000>; // 36.625MB ++ label = "rofs"; ++ }; ++ rwfs@31c0000 { ++ reg = <0x31c0000 0xE40000>; // 14.25MB ++ label = "rwfs"; ++ }; ++ pfm@4000000 { ++ reg = <0x4000000 0x20000>; // 128KB ++ label = "pfm"; ++ }; ++ reserved-1@4020000 { ++ reg = <0x4020000 0x200000>; // 2048KB ++ label = "reserved-1"; ++ }; ++ rc-image@4220000 { ++ reg = <0x4220000 0x3de0000>; // 63360KB ++ label = "rc-image"; ++ }; ++ image-stg@8000000 { ++ reg = <0x8000000 0x3de0000>; // 63360KB ++ label = "img-stg"; ++ }; ++ pfr-stg@bde0000 { ++ reg = <0xbde0000 0x100000>; // 1024KB ++ label = "pfr-stg"; ++ }; ++ cpld-stg@bee0000 { ++ reg = <0xbee0000 0x400000>; // 4096KB ++ label = "cpld-stg"; ++ }; ++ afm-stg@c2e0000 { ++ reg = <0xc2e0000 0x20000>; // 128KB ++ label = "afm-stg"; ++ }; ++ afm-rc@c300000 { ++ reg = <0xc300000 0x20000>; // 128KB ++ label = "afm-rc"; ++ }; ++ reserved-2@c320000 { ++ reg = <0xc320000 0x3ce0000>; // 62336KB ++ label = "reserved-2"; ++ }; ++ }; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bios"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ tpm@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <33000000>; ++ reg = <0>; ++ status = "okay"; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ use-ncsi; ++ phy-mode = "rmii"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rmii0_default>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ use-ncsi; ++ phy-mode = "rmii"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rmii1_default>; ++}; ++ ++&syscon1 { ++ assigned-clocks = <&syscon1 SCU1_CLK_MACHCLK>, ++ <&syscon1 SCU1_CLK_RGMII>, ++ <&syscon1 SCU1_CLK_RMII>; ++ assigned-clock-rates = <200000000>, <125000000>, <50000000>; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&i2c5 { ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c9 { ++ status = "okay"; ++}; ++ ++&i2c10 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++ ++ eeprom@50 { ++ compatible = "atmel,24c512"; ++ reg = <0x50>; ++ }; ++}; ++ ++&i2c13 { ++ status = "okay"; ++ ++ eeprom@56 { ++ compatible = "atmel,24c04"; ++ reg = <0x56>; ++ }; ++}; ++ ++&i2c14 { ++ status = "okay"; ++}; ++ ++&i2c15 { ++ status = "okay"; ++}; ++ ++&vhuba0 { ++ status = "okay"; ++}; ++ ++&vhubb0 { ++ status = "okay"; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++#if 0 ++&otp { ++ status = "okay"; ++}; ++#endif ++ ++&gpio1 { ++ gpio-line-names = ++ /*A0-A7*/ "","","","","","","","", ++ /*B0-B7*/ "","","","","","","","", ++ /*C0-C7*/ "","","","","","","","", ++ /*D0-D7*/ "CPLD_UPGRADE_TDO","CPLD_UPGRADE_TMS","CPLD_UPGRADE_TCK","CPLD_UPGRADE_TDI","BMC_CPLD_UPGRADE","","","", ++ /*E0-E7*/ "","","","","CPUBMC_SPIMUX_SEL","","","", ++ /*F0-F7*/ "","","","","","","","", ++ /*G0-G7*/ "","","","","","","","", ++ /*H0-H7*/ "","","","","","","","", ++ /*I0-I7*/ "","","","","","","","", ++ /*J0-J7*/ "","","","","","","","", ++ /*K0-K7*/ "","","","","","","","", ++ /*L0-L7*/ "","","","","","","","", ++ /*M0-M7*/ "","","","","","","","", ++ /*N0-N7*/ "","","","","","","","", ++ /*O0-O7*/ "","","","","","","","", ++ /*P0-P7*/ "","","","","","","","", ++ /*Q0-Q7*/ "","","BMC_MNTRST","BMC_MTCK","BMC_MTMS","BMC_MTDI","BMC_MTDO","", ++ /*R0-R7*/ "","","","","","","","", ++ /*S0-S7*/ "","","","","","","","", ++ /*T0-T7*/ "","","","","","","","", ++ /*U0-U7*/ "","","","","","","","", ++ /*V0-V7*/ "","","","","","","","", ++ /*W0-W7*/ "","","","","","","","", ++ /*X0-X7*/ "","","","","","","","", ++ /*Y0-Y7*/ "","","","","","","","", ++ /*Z0-Z7*/ "","","","","","","","", ++ /*AA0-AA7*/ "","","","","","","","", ++ /*AB0-AB7*/ "","","","","","","","", ++ /*AC0-AC7*/ "","","","","","","","", ++ /*AD0-AD7*/ "","","","","","","","", ++ /*AE0-AE7*/ "","","","","","","",""; ++}; ++ ++&emmc_controller { ++ status = "okay"; ++}; ++ ++&emmc { ++ status = "okay"; ++ bus-width = <8>; ++ pinctrl-0 = <&pinctrl_emmcg8_default>; ++ non-removable; ++ max-frequency = <26000000>; ++}; +-- +2.25.1 + diff --git a/patches-sonic/0001-Add-support-for-the-TI-TPS53659.patch b/patches-sonic/0001-Add-support-for-the-TI-TPS53659.patch new file mode 100644 index 000000000..e9d59ac4c --- /dev/null +++ b/patches-sonic/0001-Add-support-for-the-TI-TPS53659.patch @@ -0,0 +1,59 @@ +From 982710678c41ae201d90fe8220e7fb7a4929e95b Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 08:25:24 +0000 +Subject: [PATCH 01/27] Add support for the TI TPS53659 + +This patch adds support for the Texas Instruments TPS53659. + +Datasheet: https://www.ti.com/lit/gpn/tps53659 + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + drivers/hwmon/pmbus/tps53679.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c +index 5c94662..2c8597f 100644 +--- a/drivers/hwmon/pmbus/tps53679.c ++++ b/drivers/hwmon/pmbus/tps53679.c +@@ -16,7 +16,7 @@ + #include "pmbus.h" + + enum chips { +- tps53647, tps53667, tps53676, tps53679, tps53681, tps53688 ++ tps53647, tps53659, tps53667, tps53676, tps53679, tps53681, tps53688 + }; + + #define TPS53647_PAGE_NUM 1 +@@ -252,6 +252,11 @@ static int tps53679_probe(struct i2c_client *client) + case tps53676: + info->identify = tps53676_identify; + break; ++ case tps53659: ++ info->pages = TPS53679_PAGE_NUM; ++ info->identify = tps53679_identify; ++ info->func[0] |= PMBUS_HAVE_PIN; ++ break; + case tps53679: + case tps53688: + info->pages = TPS53679_PAGE_NUM; +@@ -274,6 +279,7 @@ static int tps53679_probe(struct i2c_client *client) + {"bmr474", tps53676}, + {"tps53647", tps53647}, + {"tps53667", tps53667}, ++ {"tps53659", tps53659}, + {"tps53676", tps53676}, + {"tps53679", tps53679}, + {"tps53681", tps53681}, +@@ -286,6 +292,7 @@ static int tps53679_probe(struct i2c_client *client) + static const struct of_device_id __maybe_unused tps53679_of_match[] = { + {.compatible = "ti,tps53647", .data = (void *)tps53647}, + {.compatible = "ti,tps53667", .data = (void *)tps53667}, ++ {.compatible = "ti,tps53659", .data = (void *)tps53659}, + {.compatible = "ti,tps53676", .data = (void *)tps53676}, + {.compatible = "ti,tps53679", .data = (void *)tps53679}, + {.compatible = "ti,tps53681", .data = (void *)tps53681}, +-- +1.8.3.1 + diff --git a/patches-sonic/0001-Change-the-system.map-file-permission-only-readable-.patch b/patches-sonic/0001-Change-the-system.map-file-permission-only-readable-.patch new file mode 100644 index 000000000..aa439ae78 --- /dev/null +++ b/patches-sonic/0001-Change-the-system.map-file-permission-only-readable-.patch @@ -0,0 +1,42 @@ +From 0ec2a0c7a1380d55072fa3661abf8a33215b3dd6 Mon Sep 17 00:00:00 2001 +From: xumia +Date: Sun, 10 Dec 2023 01:02:27 +0000 +Subject: [PATCH] Change the system.map file permission only readable by root + +--- + debian/rules.real | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/debian/rules.real b/debian/rules.real +index 98ee4ac7a..5f1d8a665 100644 +--- a/debian/rules.real ++++ b/debian/rules.real +@@ -191,7 +191,7 @@ endif + dh_icons + dh_link + dh_compress +- dh_fixperms ++ dh_fixperms -XSystem.map-* + dh_missing + dh_strip $(DH_STRIP_ARGS) -Xvmlinux -Xvmlinuz + dh_makeshlibs -Xvdso -Xvmlinux -Xvmlinuz +@@ -383,6 +383,7 @@ endif + sed '/CONFIG_\(MODULE_SIG_\(ALL\|KEY\)\|SYSTEM_TRUSTED_KEYS\|BUILD_SALT\)[ =]/d' $(DIR)/.config > $(DESTDIR)/boot/config-$(REAL_VERSION) + echo "ffffffffffffffff B The real System.map is in the linux-image--dbg package" \ + > $(DESTDIR)/boot/System.map-$(REAL_VERSION) ++ chmod 600 $(DESTDIR)/boot/System.map-$(REAL_VERSION) + rm $(DESTDIR)/lib/firmware -rf + + dh_installdirs usr/share/bug/$(PACKAGE_NAME) +@@ -435,7 +436,7 @@ binary_image-dbg: $(STAMPS_DIR)/build_$(ARCH)_$(FEATURESET)_$(FLAVOUR) + $(dh_binary_pre) + dh_installdirs usr/lib/debug usr/lib/debug/boot usr/share/lintian/overrides/ + install -m644 $(DIR)/vmlinux $(DEBUG_DIR)/boot/vmlinux-$(REAL_VERSION) +- install -m644 $(DIR)/System.map $(DEBUG_DIR)/boot/System.map-$(REAL_VERSION) ++ install -m600 $(DIR)/System.map $(DEBUG_DIR)/boot/System.map-$(REAL_VERSION) + + # cmd_sign=: Don't sign modules + # suffix-y=: Don't compress modules +-- +2.25.1 + diff --git a/patches-sonic/0001-DTS-Aspeed-Nvidia-spc6-a1-bmc-dts.patch b/patches-sonic/0001-DTS-Aspeed-Nvidia-spc6-a1-bmc-dts.patch new file mode 100644 index 000000000..63ad83da2 --- /dev/null +++ b/patches-sonic/0001-DTS-Aspeed-Nvidia-spc6-a1-bmc-dts.patch @@ -0,0 +1,634 @@ +From 90dd2881db42e4ca0be7663cd5b31282e9d324a0 Mon Sep 17 00:00:00 2001 +From: Oleksandr Ivantsiv +Date: Fri, 8 May 2026 00:15:02 +0300 +Subject: [PATCH 1/2] DTS: Aspeed: Nvidia spc6 bmc dts + +Add DTS for AST2700 A1 for Nvidia. + +Signed-off-by: Oleksandr Ivantsiv +--- + arch/arm64/boot/dts/aspeed/Makefile | 7 + + .../aspeed-nvidia-spc6-a1-bmc-core.dtsi | 125 +++++ + .../dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts | 463 ++++++++++++++++++ + 3 files changed, 595 insertions(+) + create mode 100644 arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc-core.dtsi + create mode 100644 arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts + +diff --git a/arch/arm64/boot/dts/aspeed/Makefile b/arch/arm64/boot/dts/aspeed/Makefile +index 5caf80a7f..a481a41b5 100644 +--- a/arch/arm64/boot/dts/aspeed/Makefile ++++ b/arch/arm64/boot/dts/aspeed/Makefile +@@ -27,3 +27,10 @@ dtb-$(CONFIG_ARCH_ASPEED) += \ + ast2700a1-evb-s0.dtb \ + ast2700a1-evb-s1.dtb \ + ast2700a1-ci-host.dtb ++ ++### ++### Nvidia DTBs ++### ++ ++dtb-$(CONFIG_ARCH_ASPEED) += \ ++ aspeed-nvidia-spc6-a1-bmc.dtb +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc-core.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc-core.dtsi +new file mode 100644 +index 000000000..d3b9eb76a +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc-core.dtsi +@@ -0,0 +1,125 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++ ++&gpio1 { ++ gpio-line-names = ++ ++ /* gpio-line-names are the combination of - , "" is the placeholder for the unused pins*/ ++ ++ /* 208 (26*8) 3.3V GPIOs */ ++ ++ /*A0-A7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*B0-B7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*C0-C7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*D0-D7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*E0-E7*/ ++ "", "GP_BMC_STBY_PWR_EN", "GP_STBY_PG", "GP_BMC_PRESNT0_L", "", "", "", "", ++ ++ /*F0-F7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*G0-G7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*H0-H7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*I0-I7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*J0-J7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*K0-K7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*L0-L7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*M0-M7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*N0-N7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*O0-O7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*P0-P7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*Q0-Q7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*R0-R7*/ ++ "GP_BMC_EXP_INT_L", "", "", "GP_BMC_READY", "GP_APML_SMB_ALERT_L", "", "", "GP_MGMT_CPLD_BMC_INT_L", ++ ++ /*S0-S7*/ ++ "GP_BMC_GPIO_EXP_RESET_L", "GP_BMC_PWR_CYCLE_BMC", "", "", "", "", "", "", ++ ++ /*T0-T7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*U0-U7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*V0-V7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*W0-W7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*X0-X7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*Y0-Y7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*Z0-Z7*/ ++ "", "", "", "", "", "", "", ""; ++ ++ bmc_ready { ++ gpio-hog; ++ gpios = ; ++ output-high; ++ line-name = "gpio-bmc-ready"; ++ }; ++ ++ stby_enable { ++ gpio-hog; ++ gpios = ; ++ output-high; ++ line-name = "gpio-stby-enable"; ++ }; ++ ++}; ++ ++&gpio0 { ++ /* 36 1.8V GPIOs */ ++ gpio-line-names = ++ /*A0-A7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*B0-B7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*C0-C7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*D0-D7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*E0-E7*/ ++ "", "", "", "", "", "", "", ""; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts +new file mode 100644 +index 000000000..753abd6bd +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts +@@ -0,0 +1,463 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/dts-v1/; ++ ++#include "aspeed-nvidia-spc6-a1-bmc-core.dtsi" ++#include ++#include ++#include ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700-A1 Spc6 CPU BMC"; ++ compatible = "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = &uart12; ++ bootargs = "console=ttyS12,115200 earlyprintk"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ }; ++}; ++ ++/* ++ * SPI1: BIOS recovery (CS0, CPLD selects which flash) and TPM (CS1, AST2700 only). ++ * AST2700 uses &spi1 (aspeed-g7.dtsi); AST2600 uses &spi1raw. ++ * Remove default flash@0/flash@1 from dtsi so spidev and tpm are the only children. ++ */ ++&spi1 { ++ status = "okay"; ++ /delete-node/ flash@0; ++ /delete-node/ flash@1; ++ spidev@0 { ++ /* BIOS flash: CPLD (spi_chnl_select) selects which of two flashes */ ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ status = "okay"; ++ }; ++ /* TPM on chip select 1; both ST33 and Infineon SLB9672 use tpm_tis_spi */ ++ tpm@1 { ++ compatible = "st,st33htpm-spi", "infineon,slb9670", "tcg,tpm_tis-spi"; ++ reg = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++#if 0 ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x300000>; // 3MB ++ label = "u-boot"; ++ }; ++ ++ /* TODO: move to eMMC */ ++ u-boot-env@300000 { ++ reg = <0x300000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ ++ kernel@320000 { ++ reg = <0x320000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ rofs@c20000 { ++ reg = <0xc20000 0x3000000>; // 48MB ++ label = "rofs"; ++ }; ++ rwfs@3c20000 { ++ reg = <0x3C20000 0x3E0000>; // ~4MB ++ label = "rwfs"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++#endif ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ // BMC Debug Console ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "disabled"; ++}; ++ ++&mdio1 { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "disabled"; ++}; ++ ++&mac1 { ++ status = "disabled"; ++}; ++ ++&sgmii { ++ status = "okay"; ++ #phy-cells = <0>; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ reset-gpios = <&exp0 10 GPIO_ACTIVE_LOW>; ++ reset-delay-us = <30000>; ++ reset-post-delay-us = <30000>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mac2 { ++ status = "okay"; ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++ phys = <&sgmii>; ++ phy-names = "sgmii-phy"; ++}; ++ ++/* ++ * Enable port A as device (via the virtual hub) and port B as ++ * host by default on the eval board. This can be easily changed ++ * by replacing the override below with &ehci0 { ... } to enable ++ * host on both ports. ++ */ ++&vhuba0 { ++ status = "okay"; ++}; ++ ++/* USB PORT B - MCU and Power bank */ ++/* This is an assembly option */ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++/* I2C bus connecting BMC to CPU for IPMI */ ++/* Note : On Chameleon SPC6 on 2600 it will be i2c-0, on 2700 i2c-16 */ ++&i2c1 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz standard mode */ ++ /* BMC as I2C slave for IPMB */ ++ ipmb@20 { ++ compatible = "ipmb-dev"; ++ reg = <0x20>; /* BMC slave address (7-bit) */ ++ slave-dev; /* This is a slave device */ ++ }; ++}; ++ ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz */ ++ aspeed,hw-timeout-ms = <1000>; ++ multi-master; ++}; ++ ++&i2c4 { ++ status = "okay"; ++/* ++ // To be added later ++ rtc@6f { ++ compatible = "nxp,pcf85053a"; ++ reg = <0x6f>; ++ }; ++*/ ++}; ++ ++&i2c5 { ++ status = "okay"; ++ exp0: pcal6524@20 { ++ status = "okay"; ++ compatible = "nxp,pcal6524"; ++ reg = <0x20>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ gpio-line-names = ++ "RSRVD_0", "GP_EROT_RST_IN_L", ++ "GP_3V3_BMC_PHY_PG", "GP_BMC_PWR_CYCLE_3V3_MASK_L", ++ "GP_BMC_SPI_BUF_OE_L", "GP_BMC_SPI_TPM_BUF_OE_L", ++ "SE_CPLD_EROT_BOOT_COMPLETE", "GP_BMC_WP_CTRL_L", ++ "GP_BMC_REC_SPI_MUX1_SEL", "RSRVD_1", ++ "S_BMC_GPIO_PHY_RST_L", "GP_JTAG_BMC_CPLD_BURN_EN_L", ++ "GP_PWR_BANK_PRESENT_L", "RSRVD_2", ++ "RSRVD_3", "RSRVD_4", ++ "RSRVD_5", "RSRVD_6", ++ "RSRVD_7", "GP_PROD_CS_FLASH0_EN", ++ "RSRVD_8", "GP_PROD_CS_FLASH1_EN", ++ "RSRVD_9", "RSRVD_10"; ++ }; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c8 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++/* Used by I3C */ ++&i2c9 { ++ status = "disabled"; ++}; ++ ++&i2c10 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c11 { ++ status = "disabled"; ++}; ++ ++&i2c12 { ++ status = "disabled"; ++}; ++ ++/* CPU master to BMC slave recovery interface */ ++/* Note: On Chameleon SPC6 multi - on 2600 it will be i2c-9, on 2700 - i2c-13 */ ++&i2c13 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++ /* ++ * BMC slave device (slave-24c02 at 0x42) is to be instantiated via ++ * bmc-i2c-slave-setup.service at boot. This approach ensures the ++ * I2C_CLIENT_SLAVE flag (0x1042) is properly set, which is required ++ * for the Aspeed I2C controller to register the hardware slave address. ++ * ++ * DTS-based instantiation doesn't set the I2C_CLIENT_SLAVE flag, causing: ++ * "i2c_slave_register: client slave flag not set. You might see address collisions" ++ * and preventing proper slave address registration in hardware. ++ * ++ * Note: "multi-master" is not required. ++ * ++ * Manual instantiation: echo slave-24c02 0x1042 > /sys/bus/i2c/devices/i2c-9/new_device ++ * Consider to provide service: /etc/systemd/system/bmc-i2c-slave-setup.service ++ */ ++}; ++ ++&i2c14 { ++ status = "okay"; ++ cpld: nvsw_bmc_hid189@31 { ++ compatible = "nvidia,hid189"; ++ reg = <0x31>; ++ interrupt-parent = <&gpio1>; ++ interrupts = ; ++ }; ++}; ++ ++&i2c15 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++// Enable emmc ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++ non-removable; ++ bus-width = <4>; ++ max-frequency = <52000000>; ++}; ++ ++/* eSPI Controller - Communication with host CPU */ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++/* RTC over eSPI - Time synchronization with host */ ++&rtc_over_espi0 { ++ status = "okay"; ++}; ++ ++// TODO: FIXME probably N/A for in 2700 ++#if 0 ++&lpc_ctrl { ++ status = "okay"; ++ memory-region = <&espi_memory>; ++}; ++ ++&lpc_reset { ++ status = "okay"; ++}; ++#endif ++ ++/* ++ * LPC SNOOP for POST code capture (simpler alternative to PCC) ++ * ++ * SNOOP driver benefits: ++ * - Mainline Linux driver (well-tested, stable) ++ * - Two independent channels for monitoring multiple ports ++ * - Simple FIFO-based interface via /dev/aspeed-lpc-snoop0/1 ++ * ++ * Usage: ++ * modprobe aspeed-lpc-snoop ++ * hexdump -C /dev/aspeed-lpc-snoop0 # Monitor port 0x80 ++ * hexdump -C /dev/aspeed-lpc-snoop1 # Monitor port 0x81 (if configured) ++ */ ++&pcie_lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>; /* Capture POST codes from port 0x80 */ ++ /* ++ * Optional: Add second port for dual-channel monitoring ++ * snoop-ports = <0x80>, <0x81>; ++ */ ++}; ++ ++/* ++ * PCC disabled - cannot run simultaneously with SNOOP ++ * (both use same hardware registers: SNPWADR, HICR5, HICR6) ++ */ ++&pcie_lpc0_pcc { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <1>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <2>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ /* Primary IPMI interface */ ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <3>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++// TODO: FIXME ++#if 0 ++&pinctrl0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_espi0_default>, ++ <&pinctrl_espialt_default>; pinctrl_salt0_default ???? ++}; ++#endif ++ ++/* ++ * I3C Controller for MCTP communication ++ * Note: pinctrl is already configured in aspeed-g7.dtsi base DTS ++ */ ++&i3c1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ i3c-scl-hz = <12500000>; ++ mctp-controller; ++}; +-- +2.49.0 + diff --git a/patches-sonic/0001-PCI-AER-Simplify-pci_print_aer.patch b/patches-sonic/0001-PCI-AER-Simplify-pci_print_aer.patch new file mode 100644 index 000000000..0a7a17357 --- /dev/null +++ b/patches-sonic/0001-PCI-AER-Simplify-pci_print_aer.patch @@ -0,0 +1,78 @@ +From 642a9f17e1bce39ecc639ed1e6f959edcd039aef Mon Sep 17 00:00:00 2001 +From: Bjorn Helgaas +Date: Thu, 22 May 2025 18:21:15 -0500 +Subject: [PATCH 01/12] PCI/AER: Simplify pci_print_aer() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Simplify pci_print_aer() by initializing the struct aer_err_info "info" +with a designated initializer list (it was previously initialized with +memset()) and using pci_name(). + +Signed-off-by: Bjorn Helgaas +Tested-by: Krzysztof Wilczyński +Reviewed-by: Ilpo Järvinen +Reviewed-by: Jonathan Cameron +Reviewed-by: Kuppuswamy Sathyanarayanan +Link: https://patch.msgid.link/20250522232339.1525671-10-helgaas@kernel.org +(cherry picked from commit ad9839137cf9fb0f0c2d531bd04bc4382e6f2de9) +--- + drivers/pci/pcie/aer.c | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 13b8586924ea..7d7fc4a9fec2 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -730,7 +730,7 @@ void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) + if (info->id && info->error_dev_num > 1 && info->id == id) + pci_err(dev, " Error of this Agent is reported first\n"); + +- trace_aer_event(dev_name(&dev->dev), (info->status & ~info->mask), ++ trace_aer_event(pci_name(dev), (info->status & ~info->mask), + info->severity, info->tlp_header_valid, &info->tlp); + } + +@@ -766,7 +766,10 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + { + int layer, agent, tlp_header_valid = 0; + u32 status, mask; +- struct aer_err_info info; ++ struct aer_err_info info = { ++ .severity = aer_severity, ++ .first_error = PCI_ERR_CAP_FEP(aer->cap_control), ++ }; + + if (aer_severity == AER_CORRECTABLE) { + status = aer->cor_status; +@@ -777,14 +780,11 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + tlp_header_valid = status & AER_LOG_TLP_MASKS; + } + +- layer = AER_GET_LAYER_ERROR(aer_severity, status); +- agent = AER_GET_AGENT(aer_severity, status); +- +- memset(&info, 0, sizeof(info)); +- info.severity = aer_severity; + info.status = status; + info.mask = mask; +- info.first_error = PCI_ERR_CAP_FEP(aer->cap_control); ++ ++ layer = AER_GET_LAYER_ERROR(aer_severity, status); ++ agent = AER_GET_AGENT(aer_severity, status); + + pci_err(dev, "aer_status: 0x%08x, aer_mask: 0x%08x\n", status, mask); + __aer_print_error(dev, &info); +@@ -798,7 +798,7 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + if (tlp_header_valid) + __print_tlp_header(dev, &aer->header_log); + +- trace_aer_event(dev_name(&dev->dev), (status & ~mask), ++ trace_aer_event(pci_name(dev), (status & ~mask), + aer_severity, tlp_header_valid, &aer->header_log); + } + EXPORT_SYMBOL_NS_GPL(pci_print_aer, CXL); +-- +2.47.0 + diff --git a/patches-sonic/0001-PM-runtime-Add-new-devm-functions.patch b/patches-sonic/0001-PM-runtime-Add-new-devm-functions.patch new file mode 100644 index 000000000..fcd458066 --- /dev/null +++ b/patches-sonic/0001-PM-runtime-Add-new-devm-functions.patch @@ -0,0 +1,114 @@ +From 69a837b75edcea8861ea3a851d10fd138e7f7406 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bence=20Cs=C3=B3k=C3=A1s?= +Date: Mon, 20 Oct 2025 09:02:38 -0400 +Subject: [PATCH] PM: runtime: Add new devm functions +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +[ Upstream commit 73db799bf5efc5a04654bb3ff6c9bf63a0dfa473 ] + +Add `devm_pm_runtime_set_active_enabled()` and +`devm_pm_runtime_get_noresume()` for simplifying +common cases in drivers. + +Signed-off-by: Bence Csókás +Link: https://patch.msgid.link/20250327195928.680771-3-csokas.bence@prolan.hu +Signed-off-by: Rafael J. Wysocki +Stable-dep-of: 0792c1984a45 ("iio: imu: inv_icm42600: Simplify pm_runtime setup") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/base/power/runtime.c | 44 ++++++++++++++++++++++++++++++++++++ + include/linux/pm_runtime.h | 4 ++++ + 2 files changed, 48 insertions(+) + +diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c +index c7ec69597a95..d8aaa5b61628 100644 +--- a/drivers/base/power/runtime.c ++++ b/drivers/base/power/runtime.c +@@ -1554,6 +1554,32 @@ void pm_runtime_enable(struct device *dev) + } + EXPORT_SYMBOL_GPL(pm_runtime_enable); + ++static void pm_runtime_set_suspended_action(void *data) ++{ ++ pm_runtime_set_suspended(data); ++} ++ ++/** ++ * devm_pm_runtime_set_active_enabled - set_active version of devm_pm_runtime_enable. ++ * ++ * @dev: Device to handle. ++ */ ++int devm_pm_runtime_set_active_enabled(struct device *dev) ++{ ++ int err; ++ ++ err = pm_runtime_set_active(dev); ++ if (err) ++ return err; ++ ++ err = devm_add_action_or_reset(dev, pm_runtime_set_suspended_action, dev); ++ if (err) ++ return err; ++ ++ return devm_pm_runtime_enable(dev); ++} ++EXPORT_SYMBOL_GPL(devm_pm_runtime_set_active_enabled); ++ + static void pm_runtime_disable_action(void *data) + { + pm_runtime_dont_use_autosuspend(data); +@@ -1576,6 +1602,24 @@ int devm_pm_runtime_enable(struct device *dev) + } + EXPORT_SYMBOL_GPL(devm_pm_runtime_enable); + ++static void pm_runtime_put_noidle_action(void *data) ++{ ++ pm_runtime_put_noidle(data); ++} ++ ++/** ++ * devm_pm_runtime_get_noresume - devres-enabled version of pm_runtime_get_noresume. ++ * ++ * @dev: Device to handle. ++ */ ++int devm_pm_runtime_get_noresume(struct device *dev) ++{ ++ pm_runtime_get_noresume(dev); ++ ++ return devm_add_action_or_reset(dev, pm_runtime_put_noidle_action, dev); ++} ++EXPORT_SYMBOL_GPL(devm_pm_runtime_get_noresume); ++ + /** + * pm_runtime_forbid - Block runtime PM of a device. + * @dev: Device to handle. +diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h +index d0b29cd1fd20..142ab39a3b38 100644 +--- a/include/linux/pm_runtime.h ++++ b/include/linux/pm_runtime.h +@@ -94,7 +94,9 @@ extern void pm_runtime_new_link(struct device *dev); + extern void pm_runtime_drop_link(struct device_link *link); + extern void pm_runtime_release_supplier(struct device_link *link); + ++int devm_pm_runtime_set_active_enabled(struct device *dev); + extern int devm_pm_runtime_enable(struct device *dev); ++int devm_pm_runtime_get_noresume(struct device *dev); + + /** + * pm_suspend_ignore_children - Set runtime PM behavior regarding children. +@@ -278,7 +280,9 @@ static inline void __pm_runtime_disable(struct device *dev, bool c) {} + static inline void pm_runtime_allow(struct device *dev) {} + static inline void pm_runtime_forbid(struct device *dev) {} + ++static inline int devm_pm_runtime_set_active_enabled(struct device *dev) { return 0; } + static inline int devm_pm_runtime_enable(struct device *dev) { return 0; } ++static inline int devm_pm_runtime_get_noresume(struct device *dev) { return 0; } + + static inline void pm_suspend_ignore_children(struct device *dev, bool enable) {} + static inline void pm_runtime_get_noresume(struct device *dev) {} +-- +2.43.0 + diff --git a/patches-sonic/0001-amd_sfh_hid_client_init_log_no_error_for_EOPNOTSUPP.patch b/patches-sonic/0001-amd_sfh_hid_client_init_log_no_error_for_EOPNOTSUPP.patch new file mode 100644 index 000000000..379797e2c --- /dev/null +++ b/patches-sonic/0001-amd_sfh_hid_client_init_log_no_error_for_EOPNOTSUPP.patch @@ -0,0 +1,33 @@ +From: Christopher Croy +Date: Mon, 19 Jan 2026 12:00:00 -0000 +Subject: [PATCH] AMD SFH: Do nog log error in the case of EOPNOTSUPP for sfh_init_work + +This was originally fixed in https://github.com/torvalds/linux/commit/e51d8d3ea3d773334d2c047c8d1623dba66f592a +From the commit linked above: + "HID: amd_sfh: Don't show client init failed as error when discovery fails + When sensor discovery fails, this means that the system doesn't have + any sensors connected and a user should only be notified at most one time. + A message is already displayed at WARN level of "failed to discover, + sensors not enabled". It's pointless to show that the client init failed + at ERR level for the same condition. + + Check the return code and don't display this message in those conditions." + +The fix was washed out here: https://github.com/torvalds/linux/commit/2105e8e00da4673266e217653292fef6acefde03 + +This patch re-introduces the fix. + +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +index 01234567..89abcdef +--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +@@ -352,7 +352,8 @@ + rc = amd_sfh_hid_client_init(mp2); + if (rc) { + amd_sfh_clear_intr(mp2); +- dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc); ++ if (rc != -EOPNOTSUPP) ++ dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc); + return; + } + diff --git a/patch/armhf_secondary_boot_online.patch b/patches-sonic/0001-armhf_secondary_boot_online.patch similarity index 93% rename from patch/armhf_secondary_boot_online.patch rename to patches-sonic/0001-armhf_secondary_boot_online.patch index 43ffac41d..e72743793 100644 --- a/patch/armhf_secondary_boot_online.patch +++ b/patches-sonic/0001-armhf_secondary_boot_online.patch @@ -1,6 +1,7 @@ From 500d48948f43d18a07894b5fd4f891afbade1d79 Mon Sep 17 00:00:00 2001 + From: Antony Rheneus -Date: Thu, 1 Jul 2021 20:34:01 +0530 + Subject: [PATCH 1/1] From 28c199cfda1746644211893b6614acbac970515b Mon Sep 17 00:00:00 2001 Subject: [PATCH 1/1] CPU1 failed to come online MIME-Version: 1.0 @@ -11,7 +12,7 @@ Issue: On contiuous reboot once in a while CPU1 (secondary cpu) doesn't come online The issue comes after migrating to Debian gcc 8.3.6, the issue was not seen till gcc7 -Until introducing the Debian GCC 8.x version – the execution process didn’t influence +Until introducing the Debian GCC 8.x version – the execution process didn’t influence mmu intialization before starting the socondary processor. The new GCC version makes some optimizations that execute the SW faster and once in a while, causing the Secondary CORE to start while the MMU is still in the initialization stage. @@ -20,11 +21,11 @@ compensate the optimized code and enables the MMU to init successfully before th Signed-off-by: Antony Rheneus --- - arch/arm/mach-mvebu/platsmp-a9.c | 4 ++++ + arch/arm/mach-mvebu/platsmp-a9.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/mach-mvebu/platsmp-a9.c b/arch/arm/mach-mvebu/platsmp-a9.c -index d715dec..9c076c6 100644 +index d715dec1c..9c076c6fb 100644 --- a/arch/arm/mach-mvebu/platsmp-a9.c +++ b/arch/arm/mach-mvebu/platsmp-a9.c @@ -17,12 +17,15 @@ @@ -51,6 +52,3 @@ index d715dec..9c076c6 100644 /* * Doing this before deasserting the CPUs is needed to wake up CPUs * in the offline state after using CPU hotplug. --- -2.7.4 - diff --git a/patches-sonic/0001-aspeed-ast2700-support.patch b/patches-sonic/0001-aspeed-ast2700-support.patch new file mode 100644 index 000000000..c180aa7a7 --- /dev/null +++ b/patches-sonic/0001-aspeed-ast2700-support.patch @@ -0,0 +1,90505 @@ +From: Chandrasekaran Swaminathan +Date: Wed, 09 Sep 2026 15:51:49 +0000 +Subject: [PATCH] Aspeed AST2700 support: Aspeed-namespace files (new and updated) + +All files under the Aspeed namespace (new files plus updates to +Aspeed drivers already present upstream), from a fresh Aspeed +merge at v00.07.04 on the 6.12.94 SONiC kernel. + +Signed-off-by: Chandrasekaran Swaminathan +--- +diff --git a/arch/arm64/boot/dts/aspeed/Makefile b/arch/arm64/boot/dts/aspeed/Makefile +--- a/arch/arm64/boot/dts/aspeed/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/Makefile 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,29 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++dtb-$(CONFIG_ARCH_ASPEED) += \ ++ ast2700-evb.dtb \ ++ ast2700-evb_ast1700-evb.dtb \ ++ ast2700-evb_ast1700-evb-dual.dtb \ ++ ast2700-raw.dtb \ ++ ast2700-evb-s0.dtb \ ++ ast2700-evb-s1.dtb \ ++ ast2700-ncsi.dtb \ ++ ast2700-dcscm.dtb \ ++ ast2700-dcscm_ast1700-evb.dtb \ ++ ast2700-dcscm_ast1700-evb-dual.dtb \ ++ ast2700-dcscm_ast1800-evb.dtb \ ++ ast2700-evb-256-abr.dtb \ ++ ast2700-slt.dtb \ ++ ast2700-fpga.dtb \ ++ ast2700-ci-host.dtb \ ++ ast2700a1-evb.dtb \ ++ ast2700a1-raw.dtb \ ++ ast2700a1-ncsi.dtb \ ++ ast2700a1-dcscm.dtb \ ++ ast2700a1-dcscm_ast1700a1-evb.dtb \ ++ ast2700a1-dcscm_ast1700a1-evb-dual.dtb \ ++ ast2700a1-dcscm_ast1800-evb.dtb \ ++ ast2700a1-evb-256-abr.dtb \ ++ ast2700a1-evb-s0.dtb \ ++ ast2700a1-evb-s1.dtb \ ++ ast2700a1-ci-host.dtb +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-128.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-128.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-128.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-128.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,32 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x400000>; // 4MB ++ label = "u-boot"; ++ }; ++ ++ u-boot-env@400000 { ++ reg = <0x400000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ ++ kernel@420000 { ++ reg = <0x420000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ ++ rofs@d20000 { ++ reg = <0xd20000 0x52E0000>; // 82.875MB ++ label = "rofs"; ++ }; ++ ++ rwfs@6000000 { ++ reg = <0x6000000 0x2000000>; // 32MB ++ label = "rwfs"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-256-abr.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-256-abr.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-256-abr.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-evb-flash-layout-256-abr.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,57 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x400000>; // 4MB ++ label = "u-boot-a"; ++ }; ++ ++ u-boot-env@400000 { ++ reg = <0x400000 0x20000>; // 128KB ++ label = "u-boot-env-a"; ++ }; ++ ++ kernel@420000 { ++ reg = <0x420000 0x900000>; // 9MB ++ label = "kernel-a"; ++ }; ++ ++ rofs@d20000 { ++ reg = <0xd20000 0x52E0000>; // 82.875MB ++ label = "rofs-a"; ++ }; ++ ++ rwfs@6000000 { ++ reg = <0x6000000 0x2000000>; // 32MB ++ label = "rwfs-a"; ++ }; ++ ++ u-boot@8000000 { ++ reg = <0x8000000 0x400000>; // 4MB ++ label = "u-boot-b"; ++ }; ++ ++ u-boot-env@8200000 { ++ reg = <0x8400000 0x20000>; // 128KB ++ label = "u-boot-env-b"; ++ }; ++ ++ kernel@8420000 { ++ reg = <0x8420000 0x900000>; // 9MB ++ label = "kernel-b"; ++ }; ++ ++ rofs@8d20000 { ++ reg = <0x8d20000 0x52E0000>; // 82.875MB ++ label = "rofs-b"; ++ }; ++ ++ rwfs@E000000 { ++ reg = <0xE000000 0x2000000>; // 32MB ++ label = "rwfs-b"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-g7-pinctrl.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-g7-pinctrl.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-g7-pinctrl.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-g7-pinctrl.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,1435 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright 2025 ASPEED Corp. ++ ++&pinctrl0 { ++ pinctrl_emmc_default: emmc-default { ++ function = "EMMC"; ++ groups = "EMMCG1"; ++ }; ++ ++ pinctrl_emmcg4_default: emmc-default { ++ function = "EMMC"; ++ groups = "EMMCG4"; ++ }; ++ ++ pinctrl_emmcg8_default: emmcg8-default { ++ function = "EMMC"; ++ groups = "EMMCG8"; ++ }; ++ ++ pinctrl_emmcwpn_default: emmcwpn-default { ++ function = "EMMC"; ++ groups = "EMMCWPN"; ++ }; ++ ++ pinctrl_emmccdn_default: emmccdn-default { ++ function = "EMMC"; ++ groups = "EMMCCDN"; ++ }; ++ ++ pinctrl_vgaddc_default: vgaddc-default { ++ function = "VGADDC"; ++ groups = "VGADDC"; ++ }; ++ ++ pinctrl_usb3axhd_default: usb3axhd-default { ++ function = "USB3A"; ++ groups = "USB3AXHD"; ++ }; ++ ++ pinctrl_usb3axhpd_default: usb3axhpd-default { ++ function = "USB3A"; ++ groups = "USB3AXHPD"; ++ }; ++ ++ pinctrl_usb3axh_default: usb3axh-default { ++ function = "USB3A"; ++ groups = "USB3AXH"; ++ }; ++ ++ pinctrl_usb3axhp_default: usb3axhp-default { ++ function = "USB3A"; ++ groups = "USB3AXHP"; ++ }; ++ ++ pinctrl_usb3axh2b_default: usb3axh2b-default { ++ function = "USB3A"; ++ groups = "USB3AXH2B"; ++ }; ++ ++ pinctrl_usb3axhp2b_default: usb3axhp2b-default { ++ function = "USB3A"; ++ groups = "USB3AXHP2B"; ++ }; ++ ++ pinctrl_usb2axhd1_default: usb2axhd1-default { ++ function = "USB2A"; ++ groups = "USB2AXHD1"; ++ }; ++ ++ pinctrl_usb2axhpd1_default: usb2axhpd1-default { ++ function = "USB2A"; ++ groups = "USB2AXHPD1"; ++ }; ++ ++ pinctrl_usb2ad1_default: usb2ad1-default { ++ function = "USB2A"; ++ groups = "USB2AD1"; ++ }; ++ ++ pinctrl_usb2axh_default: usb2axh-default { ++ function = "USB2A"; ++ groups = "USB2AXH"; ++ }; ++ ++ pinctrl_usb2axhp_default: usb2axhp-default { ++ function = "USB2A"; ++ groups = "USB2AXHP"; ++ }; ++ ++ pinctrl_usb2axh2b_default: usb2axh2b-default { ++ function = "USB2A"; ++ groups = "USB2AXH2B"; ++ }; ++ ++ pinctrl_usb2axhp2b_default: usb2axhp2b-default { ++ function = "USB2A"; ++ groups = "USB2AXHP2B"; ++ }; ++ ++ pinctrl_usb2ahpd0_default: usb2ahpd0-default { ++ function = "USB2A"; ++ groups = "USB2AHPD0"; ++ }; ++ ++ pinctrl_usb2ad0_default: usb2ad0-default { ++ function = "USB2A"; ++ groups = "USB2AD0"; ++ }; ++ ++ pinctrl_usb2ah_default: usb2ah-default { ++ function = "USB2A"; ++ groups = "USB2AH"; ++ }; ++ ++ pinctrl_usb2ahp_default: usb2ahp-default { ++ function = "USB2A"; ++ groups = "USB2AHP"; ++ }; ++ ++ pinctrl_usb3bxhd_default: usb3bxhd-default { ++ function = "USB3B"; ++ groups = "USB3BXHD"; ++ }; ++ ++ pinctrl_usb3bxhpd_default: usb3bxhpd-default { ++ function = "USB3B"; ++ groups = "USB3BXHPD"; ++ }; ++ ++ pinctrl_usb3bxh_default: usb3bxh-default { ++ function = "USB3B"; ++ groups = "USB3BXH"; ++ }; ++ ++ pinctrl_usb3bxhp_default: usb3bxhp-default { ++ function = "USB3B"; ++ groups = "USB3BXHP"; ++ }; ++ ++ pinctrl_usb3bxh2a_default: usb3bxh2a-default { ++ function = "USB3B"; ++ groups = "USB3BXH2A"; ++ }; ++ ++ pinctrl_usb3bxhp2a_default: usb3bxhp2a-default { ++ function = "USB3B"; ++ groups = "USB3BXHP2A"; ++ }; ++ ++ pinctrl_usb2bxhd1_default: usb2bxhd1-default { ++ function = "USB2B"; ++ groups = "USB2BXHD1"; ++ }; ++ ++ pinctrl_usb2bxhpd1_default: usb2bxhpd1-default { ++ function = "USB2B"; ++ groups = "USB2BXHPD1"; ++ }; ++ ++ pinctrl_usb2bd1_default: usb2bd1-default { ++ function = "USB2B"; ++ groups = "USB2BD1"; ++ }; ++ ++ pinctrl_usb2bxh_default: usb2bxh-default { ++ function = "USB2B"; ++ groups = "USB2BXH"; ++ }; ++ ++ pinctrl_usb2bxhp_default: usb2bxhp-default { ++ function = "USB2B"; ++ groups = "USB2BXHP"; ++ }; ++ ++ pinctrl_usb2bxh2a_default: usb2bxh2a-default { ++ function = "USB2B"; ++ groups = "USB2BXH2A"; ++ }; ++ ++ pinctrl_usb2bxhp2a_default: usb2bxhp2a-default { ++ function = "USB2B"; ++ groups = "USB2BXHP2A"; ++ }; ++ ++ pinctrl_usb2bhpd0_default: usb2bhpd0-default { ++ function = "USB2B"; ++ groups = "USB2BHPD0"; ++ }; ++ ++ pinctrl_usb2bd0_default: usb2bd0-default { ++ function = "USB2B"; ++ groups = "USB2BD0"; ++ }; ++ ++ pinctrl_usb2bh_default: usb2bh-default { ++ function = "USB2B"; ++ groups = "USB2BH"; ++ }; ++ ++ pinctrl_usb2bhp_default: usb2bhp-default { ++ function = "USB2B"; ++ groups = "USB2BHP"; ++ }; ++ ++ pinctrl_jtagm0_default: jtagm0-default { ++ function = "JTAG0"; ++ groups = "JTAGM0"; ++ }; ++ ++ pinctrl_jtag_psp_default: jtag-psp-default { ++ function = "JTAG0"; ++ groups = "PSP"; ++ }; ++ ++ pinctrl_jtag_ssp_default: jtag-ssp-default { ++ function = "JTAG0"; ++ groups = "SSP"; ++ }; ++ ++ pinctrl_jtag_tsp_default: jtag-tsp-default { ++ function = "JTAG0"; ++ groups = "TSP"; ++ }; ++ ++ pinctrl_jtag_ddr_default: jtag-ddr-default { ++ function = "JTAG0"; ++ groups = "DDR"; ++ }; ++ ++ pinctrl_jtag_usb3a_default: jtag-usb3a-default { ++ function = "JTAG0"; ++ groups = "USB3A"; ++ }; ++ ++ pinctrl_jtag_usb3b_default: jtag-usb3b-default { ++ function = "JTAG0"; ++ groups = "USB3B"; ++ }; ++ ++ pinctrl_jtag_pciea_default: jtag-pciea-default { ++ function = "JTAG0"; ++ groups = "PCIEA"; ++ }; ++ ++ pinctrl_jtag_pcieb_default: jtag-pcieb-default { ++ function = "JTAG0"; ++ groups = "PCIEB"; ++ }; ++ ++ pinctrl_pcierc0_perst_default: pcierc0-perst-default { ++ function = "PCIERC"; ++ groups = "PCIERC0PERST"; ++ }; ++ ++ pinctrl_pcierc1_perst_default: pcierc1-perst-default { ++ function = "PCIERC"; ++ groups = "PCIERC1PERST"; ++ }; ++}; ++ ++&pinctrl1 { ++ pinctrl_sgpm0_default: sgpm0-default { ++ function = "SGPM0"; ++ groups = "SGPM0"; ++ }; ++ ++ pinctrl_dsgpm0_default: dsgpm0-default { ++ function = "SGPM0"; ++ groups = "DSGPM0"; ++ }; ++ ++ pinctrl_sgpm1_default: sgpm1-default { ++ function = "SGPM1"; ++ groups = "SGPM1"; ++ }; ++ ++ pinctrl_sgps_default: sgps-default { ++ function = "SGPS"; ++ groups = "SGPS"; ++ }; ++ ++ pinctrl_adc0_default: adc0-default { ++ function = "ADC0"; ++ groups = "ADC0"; ++ }; ++ ++ pinctrl_adc1_default: adc1-default { ++ function = "ADC1"; ++ groups = "ADC1"; ++ }; ++ ++ pinctrl_adc2_default: adc2-default { ++ function = "ADC2"; ++ groups = "ADC2"; ++ }; ++ ++ pinctrl_adc3_default: adc3-default { ++ function = "ADC3"; ++ groups = "ADC3"; ++ }; ++ ++ pinctrl_adc4_default: adc4-default { ++ function = "ADC4"; ++ groups = "ADC4"; ++ }; ++ ++ pinctrl_adc5_default: adc5-default { ++ function = "ADC5"; ++ groups = "ADC5"; ++ }; ++ ++ pinctrl_adc6_default: adc6-default { ++ function = "ADC6"; ++ groups = "ADC6"; ++ }; ++ ++ pinctrl_adc7_default: adc7-default { ++ function = "ADC7"; ++ groups = "ADC7"; ++ }; ++ ++ pinctrl_adc8_default: adc8-default { ++ function = "ADC8"; ++ groups = "ADC8"; ++ }; ++ ++ pinctrl_adc9_default: adc9-default { ++ function = "ADC9"; ++ groups = "ADC9"; ++ }; ++ ++ pinctrl_adc10_default: adc10-default { ++ function = "ADC10"; ++ groups = "ADC10"; ++ }; ++ ++ pinctrl_adc11_default: adc11-default { ++ function = "ADC11"; ++ groups = "ADC11"; ++ }; ++ ++ pinctrl_adc12_default: adc12-default { ++ function = "ADC12"; ++ groups = "ADC12"; ++ }; ++ ++ pinctrl_adc13_default: adc13-default { ++ function = "ADC13"; ++ groups = "ADC13"; ++ }; ++ ++ pinctrl_adc14_default: adc14-default { ++ function = "ADC14"; ++ groups = "ADC14"; ++ }; ++ ++ pinctrl_adc15_default: adc15-default { ++ function = "ADC15"; ++ groups = "ADC15"; ++ }; ++ ++ pinctrl_pwm0_default: pwm0-default { ++ function = "PWM0"; ++ groups = "PWM0"; ++ }; ++ ++ pinctrl_pwm1_default: pwm1-default { ++ function = "PWM1"; ++ groups = "PWM1"; ++ }; ++ ++ pinctrl_pwm2_default: pwm2-default { ++ function = "PWM2"; ++ groups = "PWM2"; ++ }; ++ ++ pinctrl_pwm3_default: pwm3-default { ++ function = "PWM3"; ++ groups = "PWM3"; ++ }; ++ ++ pinctrl_pwm4_default: pwm4-default { ++ function = "PWM4"; ++ groups = "PWM4"; ++ }; ++ ++ pinctrl_pwm5_default: pwm5-default { ++ function = "PWM5"; ++ groups = "PWM5"; ++ }; ++ ++ pinctrl_pwm6_default: pwm6-default { ++ function = "PWM6"; ++ groups = "PWM6"; ++ }; ++ ++ pinctrl_pwm7_default: pwm7-default { ++ function = "PWM7"; ++ groups = "PWM7"; ++ }; ++ ++ pinctrl_pwm8_default: pwm8-default { ++ function = "PWM8"; ++ groups = "PWM8"; ++ }; ++ ++ pinctrl_pwm9_default: pwm9-default { ++ function = "PWM9"; ++ groups = "PWM9"; ++ }; ++ ++ pinctrl_pwm10_default: pwm10-default { ++ function = "PWM10"; ++ groups = "PWM10"; ++ }; ++ ++ pinctrl_pwm11_default: pwm11-default { ++ function = "PWM11"; ++ groups = "PWM11"; ++ }; ++ ++ pinctrl_pwm12_default: pwm12-default { ++ function = "PWM12"; ++ groups = "PWM12"; ++ }; ++ ++ pinctrl_pwm13_default: pwm13-default { ++ function = "PWM13"; ++ groups = "PWM13"; ++ }; ++ ++ pinctrl_pwm14_default: pwm14-default { ++ function = "PWM14"; ++ groups = "PWM14"; ++ }; ++ ++ pinctrl_pwm15_default: pwm15-default { ++ function = "PWM15"; ++ groups = "PWM15"; ++ }; ++ ++ pinctrl_tach0_default: tach0-default { ++ function = "TACH0"; ++ groups = "TACH0"; ++ }; ++ ++ pinctrl_tach1_default: tach1-default { ++ function = "TACH1"; ++ groups = "TACH1"; ++ }; ++ ++ pinctrl_tach2_default: tach2-default { ++ function = "TACH2"; ++ groups = "TACH2"; ++ }; ++ ++ pinctrl_tach3_default: tach3-default { ++ function = "TACH3"; ++ groups = "TACH3"; ++ }; ++ ++ pinctrl_tach4_default: tach4-default { ++ function = "TACH4"; ++ groups = "TACH4"; ++ }; ++ ++ pinctrl_tach5_default: tach5-default { ++ function = "TACH5"; ++ groups = "TACH5"; ++ }; ++ ++ pinctrl_tach6_default: tach6-default { ++ function = "TACH6"; ++ groups = "TACH6"; ++ }; ++ ++ pinctrl_tach7_default: tach7-default { ++ function = "TACH7"; ++ groups = "TACH7"; ++ }; ++ ++ pinctrl_tach8_default: tach8-default { ++ function = "TACH8"; ++ groups = "TACH8"; ++ }; ++ ++ pinctrl_tach9_default: tach9-default { ++ function = "TACH9"; ++ groups = "TACH9"; ++ }; ++ ++ pinctrl_tach10_default: tach10-default { ++ function = "TACH10"; ++ groups = "TACH10"; ++ }; ++ ++ pinctrl_tach11_default: tach11-default { ++ function = "TACH11"; ++ groups = "TACH11"; ++ }; ++ ++ pinctrl_tach12_default: tach12-default { ++ function = "TACH12"; ++ groups = "TACH12"; ++ }; ++ ++ pinctrl_tach13_default: tach13-default { ++ function = "TACH13"; ++ groups = "TACH13"; ++ }; ++ ++ pinctrl_tach14_default: tach14-default { ++ function = "TACH14"; ++ groups = "TACH14"; ++ }; ++ ++ pinctrl_tach15_default: tach15-default { ++ function = "TACH15"; ++ groups = "TACH15"; ++ }; ++ ++ pinctrl_jtagm1_default: jtagm1-default { ++ function = "JTAGM1"; ++ groups = "JTAGM1"; ++ }; ++ ++ pinctrl_jtagm1trst_default: jtagm1trst-default { ++ function = "JTAGM1"; ++ groups = "JTAGM1TRST"; ++ }; ++ ++ pinctrl_mdio0_default: mdio0-default { ++ function = "MDIO0"; ++ groups = "MDIO0"; ++ }; ++ ++ pinctrl_mdio1_default: mdio1-default { ++ function = "MDIO1"; ++ groups = "MDIO1"; ++ }; ++ ++ pinctrl_mdio2_default: mdio2-default { ++ function = "MDIO2"; ++ groups = "MDIO2"; ++ }; ++ ++ pinctrl_rgmii0_default: rgmii0-default { ++ function = "RGMII0"; ++ groups = "RGMII0"; ++ }; ++ ++ pinctrl_rgmii1_default: rgmii1-default { ++ function = "RGMII1"; ++ groups = "RGMII1"; ++ }; ++ ++ pinctrl_rmii0_default: rmii0-default { ++ function = "RMII0"; ++ groups = "RMII0"; ++ }; ++ ++ pinctrl_rmii0_rclko_default: rmii0-rclko-default { ++ function = "RMII0RCLKO"; ++ groups = "RMII0RCLKO"; ++ }; ++ ++ pinctrl_rmii1_default: rmii1-default { ++ function = "RMII1"; ++ groups = "RMII1"; ++ }; ++ ++ pinctrl_rmii1_rclko_default: rmii1-rclko-default { ++ function = "RMII1RCLKO"; ++ groups = "RMII1RCLKO"; ++ }; ++ ++ pinctrl_sgmii_default: sgmii-default { ++ function = "SGMII"; ++ groups = "SGMII"; ++ }; ++ ++ pinctrl_fwspi_quad_default: fwspi-quad-default { ++ function = "FWQSPI"; ++ groups = "FWQSPI"; ++ }; ++ ++ pinctrl_fsi0_default: fsi0-default { ++ function = "FSI0"; ++ groups = "FSI0"; ++ }; ++ ++ pinctrl_fsi1_default: fsi1-default { ++ function = "FSI1"; ++ groups = "FSI1"; ++ }; ++ ++ pinctrl_fsi2_default: fsi2-default { ++ function = "FSI2"; ++ groups = "FSI2"; ++ }; ++ ++ pinctrl_fsi3_default: fsi3-default { ++ function = "FSI3"; ++ groups = "FSI3"; ++ }; ++ ++ pinctrl_spi0_default: spi0-default { ++ function = "SPI0"; ++ groups = "SPI0"; ++ }; ++ ++ pinctrl_spi0_quad_default: spi0-quad-default { ++ function = "QSPI0"; ++ groups = "QSPI0"; ++ }; ++ ++ pinctrl_spi0_cs1_default: spi0-cs1-default { ++ function = "SPI0CS1"; ++ groups = "SPI0CS1"; ++ }; ++ ++ pinctrl_spi1_default: spi1-default { ++ function = "SPI1"; ++ groups = "SPI1"; ++ }; ++ ++ pinctrl_spi1_quad_default: spi1-quad-default { ++ function = "QSPI1"; ++ groups = "QSPI1"; ++ }; ++ ++ pinctrl_spi1_cs1_default: spi1-cs1-default { ++ function = "SPI1CS1"; ++ groups = "SPI1CS1"; ++ }; ++ ++ pinctrl_spi2_default: spi2-default { ++ function = "SPI2"; ++ groups = "SPI2"; ++ }; ++ ++ pinctrl_spi2_quad_default: spi2-quad-default { ++ function = "QSPI2"; ++ groups = "QSPI2"; ++ }; ++ ++ pinctrl_spi2_cs1_default: spi2-cs1-default { ++ function = "SPI2CS1"; ++ groups = "SPI2CS1"; ++ }; ++ ++ pinctrl_espi0_default: espi0-default { ++ function = "ESPI0"; ++ groups = "ESPI0"; ++ }; ++ ++ pinctrl_espi1_default: espi1-default { ++ function = "ESPI1"; ++ groups = "ESPI1"; ++ }; ++ ++ pinctrl_lpc0_default: lpc0-default { ++ function = "LPC0"; ++ groups = "LPC0"; ++ }; ++ ++ pinctrl_lpc1_default: lpc1-default { ++ function = "LPC1"; ++ groups = "LPC1"; ++ }; ++ ++ pinctrl_vpi_default: vpi-default { ++ function = "VPI"; ++ groups = "VPI"; ++ }; ++ ++ pinctrl_sd_default: sd-default { ++ function = "SD"; ++ groups = "SD"; ++ }; ++ ++ pinctrl_hvi3c0_default: hvi3c0-default { ++ function = "I3C0"; ++ groups = "HVI3C0"; ++ }; ++ ++ pinctrl_hvi3c1_default: hvi3c1-default { ++ function = "I3C1"; ++ groups = "HVI3C1"; ++ }; ++ ++ pinctrl_hvi3c2_default: hvi3c2-default { ++ function = "I3C2"; ++ groups = "HVI3C2"; ++ }; ++ ++ pinctrl_hvi3c3_default: hvi3c3-default { ++ function = "I3C3"; ++ groups = "HVI3C3"; ++ }; ++ ++ pinctrl_i3c4_default: i3c4-default { ++ function = "I3C4"; ++ groups = "I3C4"; ++ }; ++ ++ pinctrl_i3c5_default: i3c5-default { ++ function = "I3C5"; ++ groups = "I3C5"; ++ }; ++ ++ pinctrl_i3c6_default: i3c6-default { ++ function = "I3C6"; ++ groups = "I3C6"; ++ }; ++ ++ pinctrl_i3c7_default: i3c7-default { ++ function = "I3C7"; ++ groups = "I3C7"; ++ }; ++ ++ pinctrl_i3c8_default: i3c8-default { ++ function = "I3C8"; ++ groups = "I3C8"; ++ }; ++ ++ pinctrl_i3c9_default: i3c9-default { ++ function = "I3C9"; ++ groups = "I3C9"; ++ }; ++ ++ pinctrl_i3c10_default: i3c10-default { ++ function = "I3C10"; ++ groups = "I3C10"; ++ }; ++ ++ pinctrl_i3c11_default: i3c11-default { ++ function = "I3C11"; ++ groups = "I3C11"; ++ }; ++ ++ pinctrl_hvi3c12_default: hvi3c12-default { ++ function = "I3C12"; ++ groups = "HVI3C12"; ++ }; ++ ++ pinctrl_hvi3c13_default: hvi3c13-default { ++ function = "I3C13"; ++ groups = "HVI3C13"; ++ }; ++ ++ pinctrl_hvi3c14_default: hvi3c14-default { ++ function = "I3C14"; ++ groups = "HVI3C14"; ++ }; ++ ++ pinctrl_hvi3c15_default: hvi3c15-default { ++ function = "I3C15"; ++ groups = "HVI3C15"; ++ }; ++ ++ pinctrl_tach0_default: tach0-default { ++ function = "TACH0"; ++ groups = "TACH0"; ++ }; ++ ++ pinctrl_tach1_default: tach1-default { ++ function = "TACH1"; ++ groups = "TACH1"; ++ }; ++ ++ pinctrl_tach2_default: tach2-default { ++ function = "TACH2"; ++ groups = "TACH2"; ++ }; ++ ++ pinctrl_tach3_default: tach3-default { ++ function = "TACH3"; ++ groups = "TACH3"; ++ }; ++ ++ pinctrl_tach4_default: tach4-default { ++ function = "TACH4"; ++ groups = "TACH4"; ++ }; ++ ++ pinctrl_tach5_default: tach5-default { ++ function = "TACH5"; ++ groups = "TACH5"; ++ }; ++ ++ pinctrl_tach6_default: tach6-default { ++ function = "TACH6"; ++ groups = "TACH6"; ++ }; ++ ++ pinctrl_tach7_default: tach7-default { ++ function = "TACH7"; ++ groups = "TACH7"; ++ }; ++ ++ pinctrl_tach8_default: tach8-default { ++ function = "TACH8"; ++ groups = "TACH8"; ++ }; ++ ++ pinctrl_tach9_default: tach9-default { ++ function = "TACH9"; ++ groups = "TACH9"; ++ }; ++ ++ pinctrl_tach10_default: tach10-default { ++ function = "TACH10"; ++ groups = "TACH10"; ++ }; ++ ++ pinctrl_tach11_default: tach11-default { ++ function = "TACH11"; ++ groups = "TACH11"; ++ }; ++ ++ pinctrl_tach12_default: tach12-default { ++ function = "TACH12"; ++ groups = "TACH12"; ++ }; ++ ++ pinctrl_tach13_default: tach13-default { ++ function = "TACH13"; ++ groups = "TACH13"; ++ }; ++ ++ pinctrl_tach14_default: tach14-default { ++ function = "TACH14"; ++ groups = "TACH14"; ++ }; ++ ++ pinctrl_tach15_default: tach15-default { ++ function = "TACH15"; ++ groups = "TACH15"; ++ }; ++ ++ pinctrl_thru0_default: thru0-default { ++ function = "THRU0"; ++ groups = "THRU0"; ++ }; ++ ++ pinctrl_thru1_default: thru1-default { ++ function = "THRU1"; ++ groups = "THRU1"; ++ }; ++ ++ pinctrl_thru2_default: thru2-default { ++ function = "THRU2"; ++ groups = "THRU2"; ++ }; ++ ++ pinctrl_thru3_default: thru3-default { ++ function = "THRU3"; ++ groups = "THRU3"; ++ }; ++ ++ pinctrl_ncts5_default: ncts5-default { ++ function = "NCTS5"; ++ groups = "NCTS5"; ++ }; ++ ++ pinctrl_ndcd5_default: ndcd5-default { ++ function = "NDCD5"; ++ groups = "NDCD5"; ++ }; ++ ++ pinctrl_ndsr5_default: ndsr5-default { ++ function = "NDSR5"; ++ groups = "NDSR5"; ++ }; ++ ++ pinctrl_nri5_default: nri5-default { ++ function = "NRI5"; ++ groups = "NRI5"; ++ }; ++ ++ pinctrl_i2c0_default: i2c0-default { ++ function = "I2C0"; ++ groups = "I2C0"; ++ }; ++ ++ pinctrl_i2c1_default: i2c1-default { ++ function = "I2C1"; ++ groups = "I2C1"; ++ }; ++ ++ pinctrl_i2c2_default: i2c2-default { ++ function = "I2C2"; ++ groups = "I2C2"; ++ }; ++ ++ pinctrl_i2c3_default: i2c3-default { ++ function = "I2C3"; ++ groups = "I2C3"; ++ }; ++ ++ pinctrl_i2c4_default: i2c4-default { ++ function = "I2C4"; ++ groups = "I2C4"; ++ }; ++ ++ pinctrl_i2c5_default: i2c5-default { ++ function = "I2C5"; ++ groups = "I2C5"; ++ }; ++ ++ pinctrl_i2c6_default: i2c6-default { ++ function = "I2C6"; ++ groups = "I2C6"; ++ }; ++ ++ pinctrl_i2c7_default: i2c7-default { ++ function = "I2C7"; ++ groups = "I2C7"; ++ }; ++ ++ pinctrl_i2c8_default: i2c8-default { ++ function = "I2C8"; ++ groups = "I2C8"; ++ }; ++ ++ pinctrl_i2c9_default: i2c9-default { ++ function = "I2C9"; ++ groups = "I2C9"; ++ }; ++ ++ pinctrl_i2c10_default: i2c10-default { ++ function = "I2C10"; ++ groups = "I2C10"; ++ }; ++ ++ pinctrl_i2c11_default: i2c11-default { ++ function = "I2C11"; ++ groups = "I2C11"; ++ }; ++ ++ pinctrl_i2c12_default: i2c12-default { ++ function = "I2C12"; ++ groups = "I2C12"; ++ }; ++ ++ pinctrl_i2c13_default: i2c13-default { ++ function = "I2C13"; ++ groups = "I2C13"; ++ }; ++ ++ pinctrl_i2c14_default: i2c14-default { ++ function = "I2C14"; ++ groups = "I2C14"; ++ }; ++ ++ pinctrl_i2c15_default: i2c15-default { ++ function = "I2C15"; ++ groups = "I2C15"; ++ }; ++ ++ pinctrl_salt0_default: salt0-default { ++ function = "SALT0"; ++ groups = "SALT0"; ++ }; ++ ++ pinctrl_salt1_default: salt1-default { ++ function = "SALT1"; ++ groups = "SALT1"; ++ }; ++ ++ pinctrl_salt2_default: salt2-default { ++ function = "SALT2"; ++ groups = "SALT2"; ++ }; ++ ++ pinctrl_salt3_default: salt3-default { ++ function = "SALT3"; ++ groups = "SALT3"; ++ }; ++ ++ pinctrl_salt4_default: salt4-default { ++ function = "SALT4"; ++ groups = "SALT4"; ++ }; ++ ++ pinctrl_salt5_default: salt5-default { ++ function = "SALT5"; ++ groups = "SALT5"; ++ }; ++ ++ pinctrl_salt6_default: salt6-default { ++ function = "SALT6"; ++ groups = "SALT6"; ++ }; ++ ++ pinctrl_salt7_default: salt7-default { ++ function = "SALT7"; ++ groups = "SALT7"; ++ }; ++ ++ pinctrl_salt8_default: salt8-default { ++ function = "SALT8"; ++ groups = "SALT8"; ++ }; ++ ++ pinctrl_salt9_default: salt9-default { ++ function = "SALT9"; ++ groups = "SALT9"; ++ }; ++ ++ pinctrl_salt10_default: salt10-default { ++ function = "SALT10"; ++ groups = "SALT10"; ++ }; ++ ++ pinctrl_salt11_default: salt11-default { ++ function = "SALT11"; ++ groups = "SALT11"; ++ }; ++ ++ pinctrl_salt12_default: salt12-default { ++ function = "SALT12"; ++ groups = "SALT12"; ++ }; ++ ++ pinctrl_salt13_default: salt13-default { ++ function = "SALT13"; ++ groups = "SALT13"; ++ }; ++ ++ pinctrl_salt14_default: salt14-default { ++ function = "SALT14"; ++ groups = "SALT14"; ++ }; ++ ++ pinctrl_salt15_default: salt15-default { ++ function = "SALT15"; ++ groups = "SALT15"; ++ }; ++ ++ pinctrl_ltpipsi2c0_default: ltpipsi2c0_default { ++ function = "I2C0"; ++ groups = "LTPI_PS_I2C0"; ++ }; ++ ++ pinctrl_ltpipsi2c1_default: ltpipsi2c1_default { ++ function = "I2C1"; ++ groups = "LTPI_PS_I2C1"; ++ }; ++ ++ pinctrl_ltpipsi2c2_default: ltpipsi2c2_default { ++ function = "I2C2"; ++ groups = "LTPI_PS_I2C2"; ++ }; ++ ++ pinctrl_ltpipsi2c3_default: ltpipsi2c3_default { ++ function = "I2C3"; ++ groups = "LTPI_PS_I2C3"; ++ }; ++ ++ pinctrl_can_default: can-default { ++ function = "CANBUS"; ++ groups = "CANBUS"; ++ }; ++ ++ pinctrl_canstby_default: canstby-default { ++ function = "CANBUS"; ++ groups = "CANBUSSTBY"; ++ }; ++ ++ pinctrl_di2c0_default: di2c0-default { ++ function = "I2C0"; ++ groups = "DI2C0"; ++ }; ++ ++ pinctrl_di2c1_default: di2c1-default { ++ function = "I2C1"; ++ groups = "DI2C1"; ++ }; ++ ++ pinctrl_di2c2_default: di2c2-default { ++ function = "I2C2"; ++ groups = "DI2C2"; ++ }; ++ ++ pinctrl_di2c3_default: di2c3-default { ++ function = "I2C3"; ++ groups = "DI2C3"; ++ }; ++ pinctrl_di2c8_default: di2c8-default { ++ function = "I2C8"; ++ groups = "DI2C8"; ++ }; ++ ++ pinctrl_di2c9_default: di2c9-default { ++ function = "I2C9"; ++ groups = "DI2C9"; ++ }; ++ ++ pinctrl_di2c10_default: di2c10-default { ++ function = "I2C10"; ++ groups = "DI2C10"; ++ }; ++ ++ pinctrl_di2c11_default: di2c11-default { ++ function = "I2C11"; ++ groups = "DI2C11"; ++ }; ++ ++ pinctrl_di2c12_default: di2c12-default { ++ function = "I2C12"; ++ groups = "DI2C12"; ++ }; ++ ++ pinctrl_di2c13_default: di2c13-default { ++ function = "I2C13"; ++ groups = "DI2C13"; ++ }; ++ ++ pinctrl_di2c14_default: di2c14-default { ++ function = "I2C14"; ++ groups = "DI2C14"; ++ }; ++ ++ pinctrl_di2c15_default: di2c15-default { ++ function = "I2C15"; ++ groups = "DI2C15"; ++ }; ++ ++ pinctrl_ncts0_default: ncts0-default { ++ function = "UART0"; ++ groups = "NCTS0"; ++ }; ++ ++ pinctrl_ndcd0_default: ndcd0-default { ++ function = "UART0"; ++ groups = "NDCD0"; ++ }; ++ ++ pinctrl_ndsr0_default: ndsr0-default { ++ function = "UART0"; ++ groups = "NDSR0"; ++ }; ++ ++ pinctrl_nri0_default: nri0-default { ++ function = "UART0"; ++ groups = "NRI0"; ++ }; ++ ++ pinctrl_ndtr0_default: ndtr0-default { ++ function = "UART0"; ++ groups = "NDTR0"; ++ }; ++ ++ pinctrl_nrts0_default: nrts0-default { ++ function = "UART0"; ++ groups = "NRTS0"; ++ }; ++ ++ pinctrl_txd0_default: txd0-default { ++ function = "UART0"; ++ groups = "TXD0"; ++ }; ++ ++ pinctrl_rxd0_default: rxd0-default { ++ function = "UART0"; ++ groups = "RXD0"; ++ }; ++ ++ pinctrl_ncts1_default: ncts1-default { ++ function = "UART1"; ++ groups = "NCTS1"; ++ }; ++ ++ pinctrl_ndcd1_default: ndcd1-default { ++ function = "UART1"; ++ groups = "NDCD1"; ++ }; ++ ++ pinctrl_ndsr1_default: ndsr1-default { ++ function = "UART1"; ++ groups = "NDSR1"; ++ }; ++ ++ pinctrl_nri1_default: nri1-default { ++ function = "UART1"; ++ groups = "NRI1"; ++ }; ++ ++ pinctrl_ndtr1_default: ndtr1-default { ++ function = "UART1"; ++ groups = "NDTR1"; ++ }; ++ ++ pinctrl_nrts1_default: nrts1-default { ++ function = "UART1"; ++ groups = "NRTS1"; ++ }; ++ ++ pinctrl_txd1_default: txd1-default { ++ function = "UART1"; ++ groups = "TXD1"; ++ }; ++ ++ pinctrl_rxd1_default: rxd1-default { ++ function = "UART1"; ++ groups = "RXD1"; ++ }; ++ ++ pinctrl_txd2_default: txd2-default { ++ function = "UART2"; ++ groups = "TXD2"; ++ }; ++ ++ pinctrl_rxd2_default: rxd2-default { ++ function = "UART2"; ++ groups = "RXD2"; ++ }; ++ ++ pinctrl_txd3_default: txd3-default { ++ function = "UART3"; ++ groups = "TXD3"; ++ }; ++ ++ pinctrl_rxd3_default: rxd3-default { ++ function = "UART3"; ++ groups = "RXD3"; ++ }; ++ ++ pinctrl_ncts5_default: ncts5-default { ++ function = "UART5"; ++ groups = "NCTS5"; ++ }; ++ ++ pinctrl_ndcd5_default: ndcd5-default { ++ function = "UART5"; ++ groups = "NDCD5"; ++ }; ++ ++ pinctrl_ndsr5_default: ndsr5-default { ++ function = "UART5"; ++ groups = "NDSR5"; ++ }; ++ ++ pinctrl_nri5_default: nri5-default { ++ function = "UART5"; ++ groups = "NRI5"; ++ }; ++ ++ pinctrl_ndtr5_default: ndtr5-default { ++ function = "UART5"; ++ groups = "NDTR5"; ++ }; ++ ++ pinctrl_nrts5_default: nrts5-default { ++ function = "UART5"; ++ groups = "NRTS5"; ++ }; ++ ++ pinctrl_txd5_default: txd5-default { ++ function = "UART5"; ++ groups = "TXD5"; ++ }; ++ ++ pinctrl_rxd5_default: rxd5-default { ++ function = "UART5"; ++ groups = "RXD5"; ++ }; ++ ++ pinctrl_ncts6_default: ncts6-default { ++ function = "UART6"; ++ groups = "NCTS6"; ++ }; ++ ++ pinctrl_ndcd6_default: ndcd6-default { ++ function = "UART6"; ++ groups = "NDCD6"; ++ }; ++ ++ pinctrl_ndsr6_default: ndsr6-default { ++ function = "UART6"; ++ groups = "NDSR6"; ++ }; ++ ++ pinctrl_nri6_default: nri6-default { ++ function = "UART6"; ++ groups = "NRI6"; ++ }; ++ ++ pinctrl_ndtr6_default: ndtr6-default { ++ function = "UART6"; ++ groups = "NDTR6"; ++ }; ++ ++ pinctrl_nrts6_default: nrts6-default { ++ function = "UART6"; ++ groups = "NRTS6"; ++ }; ++ ++ pinctrl_txd6_default: txd6-default { ++ function = "UART6"; ++ groups = "TXD6"; ++ }; ++ ++ pinctrl_rxd6_default: rxd6-default { ++ function = "UART6"; ++ groups = "RXD6"; ++ }; ++ ++ pinctrl_txd7_default: txd7-default { ++ function = "UART7"; ++ groups = "TXD7"; ++ }; ++ ++ pinctrl_rxd7_default: rxd7-default { ++ function = "UART7"; ++ groups = "RXD7"; ++ }; ++ ++ pinctrl_txd8_default: txd8-default { ++ function = "UART8"; ++ groups = "TXD8"; ++ }; ++ ++ pinctrl_rxd8_default: rxd8-default { ++ function = "UART8"; ++ groups = "RXD8"; ++ }; ++ ++ pinctrl_txd9_default: txd9-default { ++ function = "UART9"; ++ groups = "TXD9"; ++ }; ++ ++ pinctrl_rxd9_default: rxd9-default { ++ function = "UART9"; ++ groups = "RXD9"; ++ }; ++ ++ pinctrl_txd10_default: txd10-default { ++ function = "UART10"; ++ groups = "TXD10"; ++ }; ++ ++ pinctrl_rxd10_default: rxd10-default { ++ function = "UART10"; ++ groups = "RXD10"; ++ }; ++ ++ pinctrl_txd11_default: txd11-default { ++ function = "UART11"; ++ groups = "TXD11"; ++ }; ++ ++ pinctrl_rxd11_default: rxd11-default { ++ function = "UART11"; ++ groups = "RXD11"; ++ }; ++ ++ pinctrl_pcierc2_perst_default: pcierc2-perst-default { ++ function = "PCIERC"; ++ groups = "PE2SGRSTN"; ++ }; ++ ++ pinctrl_usb2cud_default: usb2cud-default { ++ function = "USB2C"; ++ groups = "USB2CUD"; ++ }; ++ ++ pinctrl_usb2cd_default: usb2cd-default { ++ function = "USB2C"; ++ groups = "USB2CD"; ++ }; ++ ++ pinctrl_usb2ch_default: usb2ch-default { ++ function = "USB2C"; ++ groups = "USB2CH"; ++ }; ++ ++ pinctrl_usb2cu_default: usb2cu-default { ++ function = "USB2C"; ++ groups = "USB2CU"; ++ }; ++ ++ pinctrl_usb2dd_default: usb2dd-default { ++ function = "USB2D"; ++ groups = "USB2DD"; ++ }; ++ ++ pinctrl_usb2dh_default: usb2dh-default { ++ function = "USB2D"; ++ groups = "USB2DH"; ++ }; ++ ++ pinctrl_wdtrst0n_default: wdtrst0n-default { ++ function = "WDTRST0N"; ++ groups = "WDTRST0N"; ++ }; ++ ++ pinctrl_wdtrst1n_default: wdtrst1n-default { ++ function = "WDTRST1N"; ++ groups = "WDTRST1N"; ++ }; ++ ++ pinctrl_wdtrst2n_default: wdtrst2n-default { ++ function = "WDTRST2N"; ++ groups = "WDTRST2N"; ++ }; ++ ++ pinctrl_wdtrst3n_default: wdtrst3n-default { ++ function = "WDTRST3N"; ++ groups = "WDTRST3N"; ++ }; ++ ++ pinctrl_wdtrst4n_default: wdtrst4n-default { ++ function = "WDTRST4N"; ++ groups = "WDTRST4N"; ++ }; ++ ++ pinctrl_wdtrst5n_default: wdtrst5n-default { ++ function = "WDTRST5N"; ++ groups = "WDTRST5N"; ++ }; ++ ++ pinctrl_wdtrst6n_default: wdtrst6n-default { ++ function = "WDTRST6N"; ++ groups = "WDTRST6N"; ++ }; ++ ++ pinctrl_wdtrst7n_default: wdtrst7n-default { ++ function = "WDTRST7N"; ++ groups = "WDTRST7N"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-g7.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-g7.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-g7.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-g7.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,2867 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++#include ++#include ++#include ++#include ++#include ++ ++/ { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ interrupt-parent = <&gic>; ++ ++ aliases { ++ serial0 = &uart0; ++ serial1 = &uart1; ++ serial2 = &uart2; ++ serial3 = &uart3; ++ serial4 = &uart4; ++ serial5 = &uart5; ++ serial6 = &uart6; ++ serial7 = &uart7; ++ serial8 = &uart8; ++ serial9 = &uart9; ++ serial10 = &uart10; ++ serial11 = &uart11; ++ serial12 = &uart12; ++ serial13 = &uart13; ++ serial14 = &uart14; ++ serial15 = &vuart0; ++ serial16 = &vuart1; ++ serial17 = &vuart2; ++ serial18 = &vuart3; ++ serial19 = &pcie_vuart0; ++ serial20 = &pcie_vuart1; ++ serial21 = &pcie_vuart2; ++ serial22 = &pcie_vuart3; ++ i2c0 = &i2c0; ++ i2c1 = &i2c1; ++ i2c2 = &i2c2; ++ i2c3 = &i2c3; ++ i2c4 = &i2c4; ++ i2c5 = &i2c5; ++ i2c6 = &i2c6; ++ i2c7 = &i2c7; ++ i2c8 = &i2c8; ++ i2c9 = &i2c9; ++ i2c10 = &i2c10; ++ i2c11 = &i2c11; ++ i2c12 = &i2c12; ++ i2c13 = &i2c13; ++ i2c14 = &i2c14; ++ i2c15 = &i2c15; ++ i3c0 = &i3c0; ++ i3c1 = &i3c1; ++ i3c2 = &i3c2; ++ i3c3 = &i3c3; ++ i3c4 = &i3c4; ++ i3c5 = &i3c5; ++ i3c6 = &i3c6; ++ i3c7 = &i3c7; ++ i3c8 = &i3c8; ++ i3c9 = &i3c9; ++ i3c10 = &i3c10; ++ i3c11 = &i3c11; ++ i3c12 = &i3c12; ++ i3c13 = &i3c13; ++ i3c14 = &i3c14; ++ i3c15 = &i3c15; ++ mdio0 = &mdio0; ++ mdio1 = &mdio1; ++ mdio2 = &mdio2; ++ mctp0 = &mctp0; ++ mctp1 = &mctp1; ++ mctp2 = &mctp2; ++ video0 = &video0; ++ video1 = &video1; ++ rvas0 = &rvas0; ++ rvas1 = &rvas1; ++ xdma0 = &xdma0; ++ xdma1 = &xdma1; ++ xdma2 = &xdma2; ++ bmcdev0 = &bmc_dev0; ++ bmcdev1 = &bmc_dev1; ++ edac = &edac; ++ }; ++ ++ cpus { ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ cpu0: cpu@0 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a35"; ++ reg = <0x0 0x0>; ++ enable-method = "psci"; ++ i-cache-size = <0x8000>; ++ i-cache-line-size = <64>; ++ i-cache-sets = <256>; ++ d-cache-size = <0x8000>; ++ d-cache-line-size = <64>; ++ d-cache-sets = <128>; ++ next-level-cache = <&l2>; ++ }; ++ ++ cpu1: cpu@1 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a35"; ++ reg = <0x0 0x1>; ++ enable-method = "psci"; ++ i-cache-size = <0x8000>; ++ i-cache-line-size = <64>; ++ i-cache-sets = <256>; ++ d-cache-size = <0x8000>; ++ d-cache-line-size = <64>; ++ d-cache-sets = <128>; ++ next-level-cache = <&l2>; ++ }; ++ ++ cpu2: cpu@2 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a35"; ++ reg = <0x0 0x2>; ++ enable-method = "psci"; ++ i-cache-size = <0x8000>; ++ i-cache-line-size = <64>; ++ i-cache-sets = <256>; ++ d-cache-size = <0x8000>; ++ d-cache-line-size = <64>; ++ d-cache-sets = <128>; ++ next-level-cache = <&l2>; ++ }; ++ ++ cpu3: cpu@3 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a35"; ++ reg = <0x0 0x3>; ++ enable-method = "psci"; ++ i-cache-size = <0x8000>; ++ i-cache-line-size = <64>; ++ i-cache-sets = <256>; ++ d-cache-size = <0x8000>; ++ d-cache-line-size = <64>; ++ d-cache-sets = <128>; ++ next-level-cache = <&l2>; ++ }; ++ ++ l2: l2-cache0 { ++ compatible = "cache"; ++ cache-level = <2>; ++ cache-unified; ++ cache-size = <0x80000>; ++ cache-line-size = <64>; ++ cache-sets = <1024>; ++ }; ++ }; ++ ++ firmware { ++ optee: optee { ++ compatible = "linaro,optee-tz"; ++ method = "smc"; ++ }; ++ ++ psci { ++ compatible = "arm,psci-1.0", "arm,psci-0.2"; ++ method = "smc"; ++ }; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ atf: trusted-firmware-a@430000000 { ++ reg = <0x4 0x30000000 0x0 0x80000>; ++ no-map; ++ }; ++ ++ optee_core: optee-core@430080000 { ++ reg = <0x4 0x30080000 0x0 0x1000000>; ++ no-map; ++ }; ++ }; ++ ++ arm-pmu { ++ compatible = "arm,cortex-a35-pmu"; ++ interrupts = ; ++ }; ++ ++ timer { ++ compatible = "arm,armv8-timer"; ++ interrupts = , ++ , ++ , ++ ; ++ arm,cpu-registers-not-fw-configured; ++ always-on; ++ }; ++ ++ gic: interrupt-controller@12200000 { ++ compatible = "arm,gic-v3"; ++ reg = <0 0x12200000 0 0x10000>, /* GICD */ ++ <0 0x12280000 0 0x80000>, /* GICR */ ++ <0 0x40440000 0 0x1000>; /* GICC */ ++ interrupts = ; ++ #interrupt-cells = <3>; ++ interrupt-controller; ++ }; ++ ++ soc0: soc@10000000 { ++ compatible = "simple-bus"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ sram0: sram@10000000 { ++ compatible = "mmio-sram"; ++ reg = <0x0 0x10000000 0x0 0x20000>; ++ ranges = <0x0 0x0 0x0 0x10000000 0x0 0x20000>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ no-memory-wc; ++ ++ exported@0 { ++ reg = <0 0x0 0 0x20000>; ++ export; ++ }; ++ }; ++ ++ uphy3a: usb-phy3@12010000 { ++ compatible = "aspeed,ast2700-usb3-phy"; ++ reg = <0x0 0x12010000 0x0 0xC0>; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTA_PHY3>; ++ aspeed,scu = <&syscon0>; ++ #phy-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ vhuba1: usb-vhub@12011000 { ++ compatible = "aspeed,ast2700-usb-vhub"; ++ reg = <0x0 0x12011000 0x0 0x820>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTA_VHUB>; ++ aspeed,vhub-downstream-ports = <7>; ++ aspeed,vhub-generic-endpoints = <21>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2axhpd1_default>; ++ aspeed,enlarge-fifo; ++ status = "disabled"; ++ }; ++ ++ uphy2a: usb-phy2@12011800 { ++ compatible = "aspeed,ast2700-uphy2a"; ++ reg = <0x0 0x12011800 0x0 0x10>; ++ ctrl = <0x4 0xbc001e00>; /* xHCI to vHub1 clock rate: 60MHz */ ++ aspeed,scu = <&syscon0>; ++ status = "disabled"; ++ }; ++ ++ uphy3b: usb-phy3@12020000 { ++ compatible = "aspeed,ast2700-usb3-phy"; ++ reg = <0x0 0x12020000 0x0 0xC0>; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTBUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTB_PHY3>; ++ aspeed,scu = <&syscon0>; ++ #phy-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ vhubb1: usb-vhub@12021000 { ++ compatible = "aspeed,ast2700-usb-vhub"; ++ reg = <0x0 0x12021000 0x0 0x820>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTBUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTB_VHUB>; ++ aspeed,vhub-downstream-ports = <7>; ++ aspeed,vhub-generic-endpoints = <21>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2bxhpd1_default>; ++ aspeed,enlarge-fifo; ++ status = "disabled"; ++ }; ++ ++ uphy2b: usb-phy2@12021800 { ++ compatible = "aspeed,ast2700-uphy2b"; ++ reg = <0x0 0x12021800 0x0 0x10>; ++ ctrl = <0x4 0xbc001e00>; /* xHCI to vHub1 clock rate: 60MHz */ ++ aspeed,scu = <&syscon0>; ++ status = "disabled"; ++ }; ++ ++ xhci0: usb@12030000 { ++ compatible = "aspeed,ast2700-xhci", "snps,dwc3"; ++ reg = <0x0 0x12030000 0x0 0x10000>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>, ++ <&syscon0 SCU0_CLK_U2PHY_REFCLK>, ++ <&syscon0 SCU0_CLK_U2PHY_CLK12M>; ++ clock-names = "bus_early", "ref", "suspend"; ++ resets = <&syscon0 SCU0_RESET_PORTA_XHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb3axh_default &pinctrl_usb2axh_default>; ++ phys = <&uphy3a>; ++ phy-names = "usb3-phy"; ++ dr_mode = "host"; ++ status = "disabled"; ++ }; ++ ++ usb3ahp: usb3ahp { ++ compatible = "aspeed,ast2700-usb3hp"; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTA_XHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb3axhp_default>; ++ aspeed,device = <&pcie_cfg0>; ++ phys = <&uphy3a>; ++ phy-names = "usb3-phy"; ++ status = "disabled"; ++ }; ++ ++ uhci0: usb@12040000 { ++ compatible = "aspeed,ast2700-uhci", "generic-uhci"; ++ reg = <0x0 0x12040000 0x0 0x100>; ++ interrupts = ; ++ #ports = <2>; ++ clocks = <&syscon0 SCU0_CLK_GATE_UHCICLK>; ++ resets = <&syscon0 SCU0_RESET_UHCI>; ++ status = "disabled"; ++ }; ++ ++ xhci1: usb@12050000 { ++ compatible = "aspeed,ast2700-xhci", "snps,dwc3"; ++ reg = <0x0 0x12050000 0x0 0x10000>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTBUSB2CLK>, ++ <&syscon0 SCU0_CLK_U2PHY_REFCLK>, ++ <&syscon0 SCU0_CLK_U2PHY_CLK12M>; ++ clock-names = "bus_early", "ref", "suspend"; ++ resets = <&syscon0 SCU0_RESET_PORTB_XHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb3bxh_default &pinctrl_usb2bxh_default>; ++ phys = <&uphy3b>; ++ phy-names = "usb3-phy"; ++ dr_mode = "host"; ++ status = "disabled"; ++ }; ++ ++ usb3bhp: usb3bhp { ++ compatible = "aspeed,ast2700-usb3hp"; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTBUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTB_XHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb3bxhp_default>; ++ aspeed,device = <&pcie_cfg1>; ++ phys = <&uphy3b>; ++ phy-names = "usb3-phy"; ++ status = "disabled"; ++ }; ++ ++ vhuba0: usb-vhub@12060000 { ++ compatible = "aspeed,ast2700-usb-vhub"; ++ reg = <0x0 0x12060000 0x0 0x820>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTA_VHUB_EHCI>; ++ aspeed,vhub-downstream-ports = <7>; ++ aspeed,vhub-generic-endpoints = <21>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2ad0_default>; ++ aspeed,txfifo-retry-quirk; ++ status = "disabled"; ++ }; ++ ++ ehci0: usb@12061000 { ++ compatible = "aspeed,ast2700-ehci", "generic-ehci"; ++ reg = <0x0 0x12061000 0x0 0x100>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTA_VHUB_EHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2ah_default>; ++ status = "disabled"; ++ }; ++ ++ usb2ahp: usb2ahp { ++ compatible = "aspeed,ast2700-usb2hp"; ++ reg = <0x0 0x12061800 0x0 0x90>; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTA_VHUB_EHCI>; ++ aspeed,device = <&pcie_cfg0>; ++ status = "disabled"; ++ }; ++ ++ vhubb0: usb-vhub@12062000 { ++ compatible = "aspeed,ast2700-usb-vhub"; ++ reg = <0x0 0x12062000 0x0 0x820>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTBUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTB_VHUB_EHCI>; ++ aspeed,vhub-downstream-ports = <7>; ++ aspeed,vhub-generic-endpoints = <21>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2bd0_default>; ++ aspeed,txfifo-retry-quirk; ++ status = "disabled"; ++ }; ++ ++ ehci1: usb@12063000 { ++ compatible = "aspeed,ast2700-ehci", "generic-ehci"; ++ reg = <0x0 0x12063000 0x0 0x100>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTBUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTB_VHUB_EHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2bh_default>; ++ status = "disabled"; ++ }; ++ ++ usb2bhp: usb2bhp { ++ compatible = "aspeed,ast2700-usb2hp"; ++ reg = <0x0 0x12063800 0x0 0x90>; ++ clocks = <&syscon0 SCU0_CLK_GATE_PORTBUSB2CLK>; ++ resets = <&syscon0 SCU0_RESET_PORTB_VHUB_EHCI>; ++ aspeed,device = <&pcie_cfg1>; ++ status = "disabled"; ++ }; ++ ++ hace: crypto@12070000 { ++ compatible = "aspeed,ast2700-hace"; ++ reg = <0x0 0x12070000 0x0 0x200>, <0x0 0x12c1f000 0x0 0x10>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_HACCLK>; ++ resets = <&syscon0 SCU0_RESET_HACE>; ++ status = "disabled"; ++ }; ++ ++ rsss: crypto@12080000 { ++ compatible = "aspeed,ast2700-rsss"; ++ reg = <0x0 0x12080000 0 0x1000>; ++ clocks = <&syscon0 SCU0_CLK_GATE_RSACLK>; ++ interrupts = ; ++ resets = <&syscon0 SCU0_RESET_RSA>, ++ <&syscon0 SCU0_RESET_SHA3>, ++ <&syscon0 SCU0_RESET_SM3>, ++ <&syscon0 SCU0_RESET_SM4>; ++ reset-names = "rsa", "sha3", "sm3", "sm4"; ++ status = "disabled"; ++ }; ++ ++ emmc_controller: sdc@12090000 { ++ compatible = "aspeed,ast2600-sd-controller"; ++ reg = <0 0x12090000 0 0x100>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges = <0 0 0 0x12090000 0 0x10000>; ++ clocks = <&syscon0 SCU0_CLK_GATE_EMMCCLK>; ++ resets = <&syscon0 SCU0_RESET_EMMC>; ++ status = "disabled"; ++ ++ emmc: sdhci@12090100 { ++ compatible = "aspeed,ast2600-emmc"; ++ reg = <0 0x100 0 0x100>; ++ sdhci,auto-cmd12; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_EMMCCLK>; ++ pinctrl-names = "default"; ++ status = "disabled"; ++ }; ++ }; ++ ++ video0: video@120a0000 { ++ compatible = "aspeed,ast2700-video-engine", "syscon"; ++ reg = <0x0 0x120a0000 0x0 0x400>, <0x0 0x14c3a800 0x0 0x400>; ++ clocks = <&syscon0 SCU0_CLK_GATE_VCLK>, ++ <&syscon0 SCU0_CLK_GATE_ECLK>, ++ <&syscon0 SCU0_CLK_GATE_CRT1CLK>; ++ clock-names = "vclk", "eclk", "crt2clk"; ++ interrupts-extended = <&gic GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>, <&intc1_5 22>; ++ resets = <&syscon0 SCU0_RESET_VIDEO>; ++ aspeed,scu = <&syscon0>; ++ status = "disabled"; ++ }; ++ ++ video1: video@120a1000 { ++ compatible = "aspeed,ast2700-video-engine"; ++ reg = <0x0 0x120a1000 0x0 0x400>; ++ clocks = <&syscon0 SCU0_CLK_GATE_VCLK>, ++ <&syscon0 SCU0_CLK_GATE_ECLK>, ++ <&syscon0 SCU0_CLK_GATE_CRT1CLK>; ++ clock-names = "vclk", "eclk", "crt2clk"; ++ interrupts = ; ++ resets = <&syscon0 SCU0_RESET_VIDEO>; ++ aspeed,scu = <&syscon0>; ++ status = "disabled"; ++ }; ++ ++ rvas0: rvas@120b8000 { ++ compatible = "aspeed,ast2700-rvas"; ++ reg = <0x0 0x120b8000 0x0 0x800>, ++ <0x0 0x12c14000 0x0 0x800>, ++ <0x0 0x120a0000 0x0 0x1000>; ++ clocks = <&syscon0 SCU0_CLK_GATE_RVAS0CLK>, ++ <&syscon0 SCU0_CLK_GATE_VCLK>, ++ <&syscon0 SCU0_CLK_GATE_ECLK>; ++ clock-names = "rvasclk", "vclk", "eclk"; ++ interrupts = , , ; ++ resets = <&syscon0 SCU0_RESET_RVAS0>, <&syscon0 SCU0_RESET_VIDEO>; ++ status = "disabled"; ++ }; ++ ++ rvas1: rvas@121b8000 { ++ compatible = "aspeed,ast2700-rvas"; ++ reg = <0x0 0x121b8000 0x0 0x800>, ++ <0x0 0x12c14800 0x0 0x800>, ++ <0x0 0x120a1000 0x0 0x1000>; ++ clocks = <&syscon0 SCU0_CLK_GATE_RVAS1CLK>, ++ <&syscon0 SCU0_CLK_GATE_VCLK>, ++ <&syscon0 SCU0_CLK_GATE_ECLK>; ++ clock-names = "rvas2clk", "vclk", "eclk"; ++ interrupts = , , ; ++ resets = <&syscon0 SCU0_RESET_RVAS1>, <&syscon0 SCU0_RESET_VIDEO>; ++ status = "disabled"; ++ }; ++ ++ pcie0: pcie@120e0000 { ++ compatible = "aspeed,ast2700-pcie"; ++ device_type = "pci"; ++ reg = <0x0 0x120e0000 0x0 0x100>; ++ linux,pci-domain = <0>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ bus-range = <0x00 0xff>; ++ ranges = <0x02000000 0x0 0x60000000 0x0 0x60000000 0x0 0x20000000>; ++ interrupts = ; ++ resets = <&syscon0 SCU0_RESET_H2X0>, ++ <&syscon0 SCU0_RESET_PCIE0RST>; ++ reset-names = "h2x", "perst"; ++ status = "disabled"; ++ ++ clocks = <&syscon0 SCU0_CLK_GATE_BCLK>; ++ pinctrl-0 = <&pinctrl_pcierc0_perst_default>; ++ pinctrl-names = "default"; ++ ++ #interrupt-cells = <1>; ++ msi-controller; ++ ++ aspeed,pciephy = <&pcie_phy0>; ++ aspeed,pciecfg = <&pcie_cfg0>; ++ ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0 0 0 1 &pcie_intc0 0>, ++ <0 0 0 2 &pcie_intc0 1>, ++ <0 0 0 3 &pcie_intc0 2>, ++ <0 0 0 4 &pcie_intc0 3>; ++ pcie_intc0: interrupt-controller { ++ interrupt-controller; ++ #address-cells = <0>; ++ #interrupt-cells = <1>; ++ }; ++ }; ++ ++ pcie1: pcie@120f0000 { ++ compatible = "aspeed,ast2700-pcie"; ++ device_type = "pci"; ++ reg = <0x0 0x120f0000 0x0 0x100>; ++ linux,pci-domain = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ bus-range = <0x00 0xff>; ++ ranges = <0x02000000 0 0x80000000 0x0 0x80000000 0x0 0x20000000>; ++ interrupts = ; ++ resets = <&syscon0 SCU0_RESET_H2X1>, ++ <&syscon0 SCU0_RESET_PCIE1RST>; ++ reset-names = "h2x", "perst"; ++ status = "disabled"; ++ ++ clocks = <&syscon0 SCU0_CLK_GATE_BCLK>; ++ pinctrl-0 = <&pinctrl_pcierc1_perst_default>; ++ pinctrl-names = "default"; ++ ++ #interrupt-cells = <1>; ++ msi-controller; ++ ++ aspeed,pciephy = <&pcie_phy1>; ++ aspeed,pciecfg = <&pcie_cfg1>; ++ ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0 0 0 1 &pcie_intc1 0>, ++ <0 0 0 2 &pcie_intc1 1>, ++ <0 0 0 3 &pcie_intc1 2>, ++ <0 0 0 4 &pcie_intc1 3>; ++ pcie_intc1: interrupt-controller { ++ interrupt-controller; ++ #address-cells = <0>; ++ #interrupt-cells = <1>; ++ }; ++ }; ++ ++ xdma0: xdma@12c04000 { ++ compatible = "aspeed,ast2700-xdma0"; ++ reg = <0x0 0x12c04000 0x0 0x100>; ++ clocks = <&syscon0 SCU0_CLK_GATE_BCLK>; ++ resets = <&syscon0 SCU0_RESET_XDMA0>; ++ reset-names = "device"; ++ interrupts-extended = <&gic GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>, ++ <&scu_ic0 ASPEED_AST2700_SCU_IC0_PCIE_PERST_LO_TO_HI>; ++ aspeed,pcie-device = "bmc"; ++ aspeed,scu = <&syscon0>; ++ status = "disabled"; ++ }; ++ ++ xdma1: xdma@12c05000 { ++ compatible = "aspeed,ast2700-xdma1"; ++ reg = <0x0 0x12c05000 0x0 0x100>; ++ clocks = <&syscon0 SCU0_CLK_GATE_BCLK>; ++ resets = <&syscon0 SCU0_RESET_XDMA1>; ++ reset-names = "device"; ++ interrupts-extended = <&gic GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>, ++ <&scu_ic1 ASPEED_AST2700_SCU_IC1_PCIE_RCRST_LO_TO_HI>; ++ aspeed,pcie-device = "bmc"; ++ aspeed,scu = <&syscon0>; ++ status = "disabled"; ++ }; ++ ++ intc0_11: interrupt-controller@12101b00 { ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x0 0x12101b00 0x0 0x10>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ interrupts = , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ ; ++ }; ++ ++ bmc_dev0: bmc-dev@12110000 { ++ compatible = "aspeed,ast2700-bmc-device"; ++ reg = <0x0 0x12110000 0x0 0xb000>; ++ interrupts-extended = <&gic GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>, ++ <&scu_ic0 ASPEED_AST2700_SCU_IC0_PCIE_PERST_LO_TO_HI>; ++ aspeed,device = <&pcie_cfg0>; ++ aspeed,e2m = <&e2m_config0>; ++ aspeed,scu = <&syscon0>; ++ pcie2lpc; ++ status = "disabled"; ++ }; ++ ++ bmc_dev1: bmc-dev@12120000 { ++ compatible = "aspeed,ast2700-bmc-device"; ++ reg = <0x0 0x12120000 0x0 0xb000>; ++ interrupts-extended = <&gic GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>, ++ <&scu_ic1 ASPEED_AST2700_SCU_IC1_PCIE_RCRST_LO_TO_HI>; ++ aspeed,device = <&pcie_cfg1>; ++ aspeed,e2m = <&e2m_config1>; ++ aspeed,scu = <&syscon0>; ++ pcie2lpc; ++ status = "disabled"; ++ }; ++ ++ edac: sdram@12c00000 { ++ compatible = "aspeed,ast2700-sdram-edac", "syscon"; ++ reg = <0 0x12c00000 0 0x1000>; ++ interrupts = ; ++ }; ++ ++ syscon0: syscon@12c02000 { ++ compatible = "aspeed,ast2700-scu0", "syscon", "simple-mfd"; ++ reg = <0x0 0x12c02000 0x0 0x1000>; ++ ranges = <0x0 0x0 0x12c02000 0x1000>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ #clock-cells = <1>; ++ #reset-cells = <1>; ++ ++ silicon-id@0 { ++ compatible = "aspeed,ast2700-silicon-id", "aspeed,silicon-id"; ++ reg = <0x0 0x4>; ++ }; ++ ++ scu_ic0: interrupt-controller@1D0 { ++ compatible = "aspeed,ast2700-scu-ic0"; ++ reg = <0x1d0 0xc>; ++ interrupts = ; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ }; ++ ++ scu_ic1: interrupt-controller@1E0 { ++ compatible = "aspeed,ast2700-scu-ic1"; ++ reg = <0x1e0 0xc>; ++ interrupts = ; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ }; ++ ++ pinctrl0: pinctrl@400 { ++ compatible = "aspeed,ast2700-soc0-pinctrl"; ++ reg = <0x400 0x600>; ++ }; ++ }; ++ ++ pcie_cfg0: syscon@12c02a00 { ++ compatible = "syscon"; ++ reg = <0 0x12c02a00 0 0x80>; ++ }; ++ ++ pcie_cfg1: syscon@12c02a80 { ++ compatible = "syscon"; ++ reg = <0 0x12c02a80 0 0x80>; ++ }; ++ ++ mctp0: mctp0@12c06000 { ++ compatible = "aspeed,ast2700-mctp0"; ++ reg = <0x0 0x12c06000 0x0 0x40>; ++ interrupts-extended = <&gic GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>, <&scu_ic0 ASPEED_AST2700_SCU_IC0_PCIE_PERST_LO_TO_HI>; ++ interrupt-names = "mctp", "pcie"; ++ resets = <&syscon0 SCU0_RESET_MCTP0>; ++ aspeed,scu = <&syscon0>; ++ aspeed,pcieh = <&pcie_phy0>; ++ status = "disabled"; ++ }; ++ ++ mctp1: mctp1@12c07000 { ++ compatible = "aspeed,ast2700-mctp1"; ++ reg = <0x0 0x12c07000 0x0 0x40>; ++ interrupts-extended = <&gic GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>, <&scu_ic1 ASPEED_AST2700_SCU_IC1_PCIE_RCRST_LO_TO_HI>; ++ interrupt-names = "mctp", "pcie"; ++ resets = <&syscon0 SCU0_RESET_MCTP1>; ++ aspeed,scu = <&syscon0>; ++ aspeed,pcieh = <&pcie_phy1>; ++ status = "disabled"; ++ }; ++ ++ ufs_controller: cnr@12c08000 { ++ compatible = "aspeed,ast2700-ufscnr"; ++ reg = <0 0x12c08000 0 0x100>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges = <0 0 0 0x12c08000 0 0x300>; ++ clocks = <&syscon0 SCU0_CLK_GATE_UFSCLK>; ++ resets = <&syscon0 SCU0_RESET_UFS>; ++ status = "disabled"; ++ ++ ufs: ufshc@12c08200 { ++ compatible = "aspeed,ast2700-ufshc"; ++ reg = <0 0x200 0 0x100>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_AXI1>; ++ clock-names = "core_clk"; ++ freq-table-hz = <0 0>; ++ status = "disabled"; ++ }; ++ }; ++ ++ display_port: dp@12c0a000 { ++ compatible = "aspeed,ast2700-displayport", "syscon"; ++ reg = <0x0 0x12c0a000 0x0 0x200>; ++ status = "disabled"; ++ }; ++ ++ display_port_mcu: dpmcu@11000000 { ++ compatible = "aspeed,ast2700-displayport-mcu", "syscon"; ++ reg = <0x0 0x11000000 0x0 0xf00>; ++ status = "disabled"; ++ }; ++ ++ gfx: display@12c09000 { ++ compatible = "aspeed,ast2700-gfx", "syscon"; ++ reg = <0 0x12c09000 0 0x100>; ++ reg-io-width = <4>; ++ clocks = <&syscon0 SCU0_CLK_GATE_CRT0CLK>; ++ resets = <&syscon0 SCU0_RESET_CRT0>; ++ syscon = <&syscon0>; ++ syscon_io = <&syscon1>; ++ status = "disabled"; ++ interrupts-extended = <&gic GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>, ++ <&scu_ic0 ASPEED_AST2700_SCU_IC0_PCIE_PERST_LO_TO_HI>, ++ <&scu_ic0 ASPEED_AST2700_SCU_IC0_PCIE_PERST_HI_TO_LO>; ++ }; ++ ++ disp_intf: disp-intf { ++ compatible = "aspeed,ast2700-disp-intf", "syscon"; ++ reg = <0x0 0x12c1d000 0x0 0x40>; ++ syscon = <&syscon0>; ++ status = "disabled"; ++ }; ++ ++ rtc: rtc@12c0f000 { ++ compatible = "aspeed,ast2700-rtc"; ++ reg = <0 0x12c0f000 0 0x18>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ gpio0: gpio@12c11000 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2700-gpio"; ++ reg = <0x0 0x12c11000 0x0 0x1000>; ++ interrupts = ; ++ gpio-ranges = <&pinctrl0 0 0 12>; ++ ngpios = <12>; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ pcie_phy0: phy@12c15000 { ++ compatible = "aspeed,ast2700-pcie-phy", "syscon"; ++ reg = <0x0 0x12c15000 0x0 0x800>; ++ }; ++ ++ pcie_phy1: phy@12c15800 { ++ compatible = "aspeed,ast2700-pcie-phy", "syscon"; ++ reg = <0x0 0x12c15800 0x0 0x800>; ++ }; ++ ++ pcie_vuart0: serial@12c18000 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x12c18000 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ status = "disabled"; ++ }; ++ ++ pcie_vuart1: serial@12c18100 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x12c18100 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ status = "disabled"; ++ }; ++ ++ pcie_vuart2: serial@12c18200 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x12c18200 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ status = "disabled"; ++ }; ++ ++ pcie_vuart3: serial@12c18300 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x12c18300 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc0: pcie-lpc@12c19000 { ++ compatible = "aspeed,ast2700-lpc", "simple-mfd", "syscon"; ++ reg = <0x0 0x12c19000 0x0 0x800>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x12c19000 0x800>; ++ ++ pcie_lpc0_kcs0: pcie-kcs@24 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc0_kcs1: pcie-kcs@28 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x28 0x1>, <0x34 0x1>, <0x40 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc0_kcs2: pcie-kcs@2c { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x2c 0x1>, <0x38 0x1>, <0x44 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc0_kcs3: pcie-kcs@114 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x114 0x1>, <0x118 0x1>, <0x11c 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc0_snoop: pcie-snoop@80 { ++ compatible = "aspeed,ast2600-lpc-snoop"; ++ reg = <0x80 0x80>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc0_pcc: pcie-pcc@0 { ++ compatible = "aspeed,ast2600-lpc-pcc"; ++ reg = <0x0 0x140>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc0_ibt: pcie-ibt@140 { ++ compatible = "aspeed,ast2600-ibt-bmc"; ++ reg = <0x140 0x18>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ }; ++ ++ pcie_lpc1: pcie-lpc@12c19800 { ++ compatible = "aspeed,ast2700-lpc", "simple-mfd", "syscon"; ++ reg = <0x0 0x12c19800 0x0 0x800>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x12c19800 0x800>; ++ ++ pcie_lpc1_kcs0: pcie-kcs@24 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc1_kcs1: pcie-kcs@28 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x28 0x1>, <0x34 0x1>, <0x40 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc1_kcs2: pcie-kcs@2c { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x2c 0x1>, <0x38 0x1>, <0x44 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc1_kcs3: pcie-kcs@114 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x114 0x1>, <0x118 0x1>, <0x11c 0x1>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc1_snoop: pcie-snoop@80 { ++ compatible = "aspeed,ast2600-lpc-snoop"; ++ reg = <0x80 0x80>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc1_pcc: pcie-pcc@0 { ++ compatible = "aspeed,ast2600-lpc-pcc"; ++ reg = <0x0 0x140>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ pcie_lpc1_ibt: pcie-ibt@140 { ++ compatible = "aspeed,ast2600-ibt-bmc"; ++ reg = <0x140 0x18>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ }; ++ ++ uart4: serial@12c1a000 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x12c1a000 0x0 0x1000>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon0 SCU0_CLK_GATE_UART4CLK>; ++ interrupts = ; ++ no-loopback-test; ++ status = "disabled"; ++ }; ++ ++ mbox0: mbox@12c1c200 { ++ compatible = "aspeed,ast2700-mailbox"; ++ reg = <0x0 0x12c1c200 0x0 0x100>, <0x0 0x12c1c300 0x0 0x100>; ++ reg-names = "tx", "rx"; ++ interrupts = ; ++ #mbox-cells = <1>; ++ mbox-name = "ssp"; ++ }; ++ ++ mbox1: mbox@12c1c600 { ++ compatible = "aspeed,ast2700-mailbox"; ++ reg = <0x0 0x12c1c600 0x0 0x100>, <0x0 0x12c1c700 0x0 0x100>; ++ reg-names = "tx", "rx"; ++ interrupts = ; ++ #mbox-cells = <1>; ++ mbox-name = "tsp"; ++ }; ++ ++ ecdsa: crypto@12c1e000 { ++ compatible = "aspeed,ast2700-ecdsa"; ++ reg = <0x0 0x12c1e000 0x0 0x1000>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_GATE_ECDSACLK>; ++ resets = <&syscon0 SCU0_RESET_ECC>; ++ status = "disabled"; ++ }; ++ ++ jtag0: jtag@12c20000 { ++ compatible = "aspeed,ast2700-jtag"; ++ reg= <0x0 0x12c20000 0x0 0x40>; ++ interrupts = ; ++ clocks = <&syscon0 SCU0_CLK_AHB>; ++ resets = <&syscon0 SCU0_RESET_JTAG>; ++ pinctrl-0 = <&pinctrl_jtagm0_default>; ++ pinctrl-names = "default"; ++ status = "disabled"; ++ }; ++ ++ e2m_config0: e2m-config@12c21000 { ++ compatible = "syscon", "simple-mfd"; ++ reg = <0 0x12c21000 0 0x300>; ++ ranges = <0 0 0 0x12c21000 0 0x300>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ aspeed,device = <&pcie_cfg0>; ++ ++ pcie0_mmbi0: pcie0-mmbi@0 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <0>; ++ pid = <3>; ++ bar = <0x3c>; ++ msi = <28>; ++ status = "disabled"; ++ }; ++ ++ pcie0_mmbi1: pcie0-mmbi@1 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <1>; ++ pid = <4>; ++ bar = <0x4c>; ++ msi = <29>; ++ status = "disabled"; ++ }; ++ ++ pcie0_mmbi2: pcie0-mmbi@2 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <2>; ++ pid = <5>; ++ bar = <0x5c>; ++ msi = <30>; ++ status = "disabled"; ++ }; ++ ++ pcie0_mmbi3: pcie0-mmbi@3 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <3>; ++ pid = <6>; ++ bar = <0x6c>; ++ msi = <31>; ++ status = "disabled"; ++ }; ++ }; ++ ++ e2m_config1: e2m-config@12c22000 { ++ compatible = "syscon", "simple-mfd"; ++ reg = <0 0x12c22000 0 0x300>; ++ ranges = <0x0 0x0 0 0x12c22000 0 0x300>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ aspeed,device = <&pcie_cfg1>; ++ ++ pcie1_mmbi4: pcie1-mmbi@4 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <4>; ++ pid = <11>; ++ bar = <0x3c>; ++ msi = <28>; ++ status = "disabled"; ++ }; ++ ++ pcie1_mmbi5: pcie1-mmbi@5 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <5>; ++ pid = <12>; ++ bar = <0x4c>; ++ msi = <29>; ++ status = "disabled"; ++ }; ++ ++ pcie1_mmbi6: pcie1-mmbi@6 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <6>; ++ pid = <13>; ++ bar = <0x5c>; ++ msi = <30>; ++ status = "disabled"; ++ }; ++ ++ pcie1_mmbi7: pcie1-mmbi@7 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ index = <7>; ++ pid = <14>; ++ bar = <0x6c>; ++ msi = <31>; ++ status = "disabled"; ++ }; ++ }; ++ }; ++ ++ soc1: soc@14000000 { ++ compatible = "simple-bus"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ fmc: spi@14000000 { ++ reg = <0x0 0x14000000 0x0 0xc4>, <0x1 0x00000000 0x0 0x80000000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "aspeed,ast2700-fmc"; ++ status = "disabled"; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ interrupts-extended = <&intc1_3 25>; ++ num-cs = <3>; ++ ++ flash@0 { ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-rx-bus-width = <2>; ++ status = "disabled"; ++ }; ++ ++ flash@1 { ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-rx-bus-width = <2>; ++ status = "disabled"; ++ }; ++ ++ flash@2 { ++ reg = < 2 >; ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-rx-bus-width = <2>; ++ status = "disabled"; ++ }; ++ }; ++ ++ spi0: spi@14010000 { ++ reg = <0x0 0x14010000 0x0 0xc4>, <0x1 0x80000000 0x0 0x80000000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "aspeed,ast2700-spi"; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ interrupts-extended = <&intc1_3 26>; ++ status = "disabled"; ++ num-cs = <2>; ++ ++ flash@0 { ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-rx-bus-width = <2>; ++ status = "disabled"; ++ }; ++ ++ flash@1 { ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-rx-bus-width = <2>; ++ status = "disabled"; ++ }; ++ }; ++ ++ spi1: spi@14020000 { ++ reg = <0x0 0x14020000 0x0 0xc4>, <0x2 0x00000000 0x0 0x80000000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "aspeed,ast2700-spi"; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ interrupts-extended = <&intc1_3 27>; ++ status = "disabled"; ++ num-cs = <2>; ++ ++ flash@0 { ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-rx-bus-width = <2>; ++ status = "disabled"; ++ }; ++ ++ flash@1 { ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-rx-bus-width = <2>; ++ status = "disabled"; ++ }; ++ }; ++ ++ spi2: spi@14030000 { ++ reg = <0x0 0x14030000 0x0 0x1f0>, <0x2 0x80000000 0x0 0x80000000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ resets = <&syscon1 SCU1_RESET_SPI2>; ++ interrupts-extended = <&intc1_3 28>; ++ num-cs = <2>; ++ status = "disabled"; ++ }; ++ ++ can0: can@14c3e000 { ++ reg = <0x0 0x14c3e000 0x0 0x2000>; ++ compatible = "aspeed,canfd"; ++ status = "disabled"; ++ clocks = <&syscon1 SCU1_CLK_GATE_CANCLK>; ++ resets = <&syscon1 SCU1_RESET_CAN>; ++ interrupts-extended = <&intc1_3 31>; ++ pinctrl-0 = <&pinctrl_can_default>; ++ pinctrl-names = "default"; ++ }; ++ ++ mdio0: mdio@14040000 { ++ compatible = "aspeed,ast2700-mdio", "aspeed,ast2600-mdio"; ++ reg = <0 0x14040000 0 0x8>; ++ resets = <&syscon1 SCU1_RESET_MII>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_mdio0_default>; ++ status = "disabled"; ++ }; ++ ++ mdio1: mdio@14040008 { ++ compatible = "aspeed,ast2700-mdio", "aspeed,ast2600-mdio"; ++ reg = <0 0x14040008 0 0x8>; ++ resets = <&syscon1 SCU1_RESET_MII>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_mdio1_default>; ++ status = "disabled"; ++ }; ++ ++ mdio2: mdio@14040010 { ++ compatible = "aspeed,ast2700-mdio", "aspeed,ast2600-mdio"; ++ reg = <0 0x14040010 0 0x8>; ++ resets = <&syscon1 SCU1_RESET_MII>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_mdio2_default>; ++ status = "disabled"; ++ }; ++ ++ mac0: ethernet@14050000 { ++ compatible = "aspeed,ast2700-mac", "faraday,ftgmac100"; ++ reg = <0x0 0x14050000 0x0 0x200>; ++ interrupts-extended = <&intc1_4 0>; ++ ++ clocks = <&syscon1 SCU1_CLK_GATE_MAC0CLK>; ++ resets = <&syscon1 SCU1_RESET_MAC0>; ++ status = "disabled"; ++ }; ++ ++ mac1: ethernet@14060000 { ++ compatible = "aspeed,ast2700-mac", "faraday,ftgmac100"; ++ reg = <0x0 0x14060000 0x0 0x200>; ++ interrupts-extended = <&intc1_4 1>; ++ ++ clocks = <&syscon1 SCU1_CLK_GATE_MAC1CLK>; ++ resets = <&syscon1 SCU1_RESET_MAC1>; ++ status = "disabled"; ++ }; ++ ++ mac2: ethernet@14070000 { ++ compatible = "aspeed,ast2700-mac", "faraday,ftgmac100"; ++ reg = <0x0 0x14070000 0x0 0x200>; ++ interrupts-extended = <&intc1_4 2>; ++ ++ phys = <&sgmii>; ++ phy-names = "sgmii"; ++ ++ clocks = <&syscon1 SCU1_CLK_GATE_MAC2CLK>; ++ resets = <&syscon1 SCU1_RESET_MAC2>; ++ status = "disabled"; ++ }; ++ ++ sdio_controller: sdc@14080000 { ++ compatible = "aspeed,ast2600-sd-controller"; ++ reg = <0 0x14080000 0 0x100>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges = <0 0 0 0x14080000 0 0x10000>; ++ clocks = <&syscon1 SCU1_CLK_GATE_SDCLK>; ++ resets = <&syscon1 SCU1_RESET_SD>; ++ status = "disabled"; ++ ++ sdhci: sdhci@14080100 { ++ compatible = "aspeed,ast2600-sdhci", "sdhci"; ++ reg = <0 0x100 0 0x100>; ++ sdhci,auto-cmd12; ++ interrupts-extended = <&intc1_5 1>; ++ clocks = <&syscon1 SCU1_CLK_GATE_SDCLK>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sd_default>; ++ status = "disabled"; ++ }; ++ }; ++ ++ pwm_tach: pwm-tach-controller@140c0000 { ++ compatible = "aspeed,ast2700-pwm-tach"; ++ reg = <0x0 0x140c0000 0 0x100>; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ resets = <&syscon1 SCU1_RESET_PWM>; ++ #pwm-cells = <3>; ++ status = "disabled"; ++ }; ++ ++ pcie2: pcie@140d0000 { ++ compatible = "aspeed,ast2700-pcie"; ++ device_type = "pci"; ++ reg = <0x0 0x140d0000 0x0 0x100>; ++ linux,pci-domain = <2>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ bus-range = <0x00 0xff>; ++ ranges = <0x02000000 0 0xa0000000 0x0 0xa0000000 0x0 0x20000000>; ++ interrupts-extended = <&intc1_4 31>; ++ resets = <&syscon1 SCU1_RESET_H2X>, ++ <&syscon1 SCU1_RESET_PCIE2RST>; ++ reset-names = "h2x", "perst"; ++ status = "disabled"; ++ ++ clocks = <&syscon1 SCU1_CLK_GATE_PCICLK>; ++ pinctrl-0 = <&pinctrl_pcierc2_perst_default>; ++ pinctrl-names = "default"; ++ ++ #interrupt-cells = <1>; ++ msi-controller; ++ ++ aspeed,pciephy = <&pcie_phy2>; ++ aspeed,pciecfg = <&pcie_cfg2>; ++ ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0 0 0 1 &pcie_intc2 0>, ++ <0 0 0 2 &pcie_intc2 1>, ++ <0 0 0 3 &pcie_intc2 2>, ++ <0 0 0 4 &pcie_intc2 3>; ++ pcie_intc2: interrupt-controller { ++ interrupt-controller; ++ #address-cells = <0>; ++ #interrupt-cells = <1>; ++ }; ++ }; ++ ++ uhci1: usb@14110000 { ++ compatible = "aspeed,ast2700-uhci", "generic-uhci"; ++ reg = <0x0 0x14110000 0x0 0x100>; ++ interrupts-extended = <&intc1_4 27>; ++ #ports = <2>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UHCICLK>; ++ resets = <&syscon1 SCU1_RESET_UHCI>; ++ status = "disabled"; ++ }; ++ ++ vhubc: usb-vhub@14120000 { ++ compatible = "aspeed,ast2700-usb-vhub"; ++ reg = <0x0 0x14120000 0x0 0x820>; ++ interrupts-extended = <&intc1_4 28>; ++ clocks = <&syscon1 SCU1_CLK_GATE_PORTCUSB2CLK>; ++ resets = <&syscon1 SCU1_RESET_PORTC_VHUB_EHCI>; ++ aspeed,vhub-downstream-ports = <7>; ++ aspeed,vhub-generic-endpoints = <21>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2cd_default>; ++ status = "disabled"; ++ }; ++ ++ ehci2: usb@14121000 { ++ compatible = "aspeed,ast2700-ehci", "generic-ehci"; ++ reg = <0x0 0x14121000 0x0 0x100>; ++ interrupts-extended = <&intc1_4 28>; ++ clocks = <&syscon1 SCU1_CLK_GATE_PORTCUSB2CLK>; ++ resets = <&syscon1 SCU1_RESET_PORTC_VHUB_EHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2ch_default>; ++ status = "disabled"; ++ }; ++ ++ vhubd: usb-vhub@14122000 { ++ compatible = "aspeed,ast2700-usb-vhub"; ++ reg = <0x0 0x14122000 0x0 0x820>; ++ interrupts-extended = <&intc1_4 29>; ++ clocks = <&syscon1 SCU1_CLK_GATE_PORTDUSB2CLK>; ++ resets = <&syscon1 SCU1_RESET_PORTD_VHUB_EHCI>; ++ aspeed,vhub-downstream-ports = <7>; ++ aspeed,vhub-generic-endpoints = <21>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2dd_default>; ++ status = "disabled"; ++ }; ++ ++ ehci3: usb@14123000 { ++ compatible = "aspeed,ast2700-ehci", "generic-ehci"; ++ reg = <0x0 0x14123000 0x0 0x100>; ++ interrupts-extended = <&intc1_4 29>; ++ clocks = <&syscon1 SCU1_CLK_GATE_PORTDUSB2CLK>; ++ resets = <&syscon1 SCU1_RESET_PORTD_VHUB_EHCI>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_usb2dh_default>; ++ status = "disabled"; ++ }; ++ ++ sram1: sram@14b80000 { ++ compatible = "mmio-sram"; ++ reg = <0x0 0x14b80000 0x0 0x40000>; ++ ranges = <0x0 0x0 0x0 0x14b80000 0x0 0x40000>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ no-memory-wc; ++ ++ exported@0 { ++ reg = <0 0x0 0 0x40000>; ++ export; ++ }; ++ }; ++ ++ adc0: adc@14c00000 { ++ compatible = "aspeed,ast2700-adc0"; ++ reg = <0x0 0x14c00000 0 0x100>; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ resets = <&syscon1 SCU1_RESET_ADC>; ++ interrupts-extended = <&intc1_2 16>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <&syscon1>; ++ status = "disabled"; ++ }; ++ ++ adc1: adc@14c00100 { ++ compatible = "aspeed,ast2700-adc1"; ++ reg = <0x0 0x14c00100 0x0 0x100>; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ resets = <&syscon1 SCU1_RESET_ADC>; ++ interrupts-extended = <&intc1_2 16>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <&syscon1>; ++ status = "disabled"; ++ }; ++ ++ sgmii: phy@14c01000 { ++ compatible = "aspeed,ast2700-sgmii"; ++ reg = <0x0 0x14c01000 0x0 0x40>; ++ ++ phys = <&pcie_phy2>; ++ aspeed,scu = <&syscon1>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgmii_default>; ++ #phy-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ syscon1: syscon@14c02000 { ++ compatible = "aspeed,ast2700-scu1", "syscon", "simple-mfd"; ++ reg = <0x0 0x14c02000 0x0 0x1000>; ++ ranges = <0x0 0x0 0x14c02000 0x1000>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ #clock-cells = <1>; ++ #reset-cells = <1>; ++ ++ assigned-clocks = <&syscon1 SCU1_CLK_MACHCLK>, ++ <&syscon1 SCU1_CLK_RGMII>, ++ <&syscon1 SCU1_CLK_RMII>; ++ assigned-clock-rates = <200000000>, <125000000>, <50000000>; ++ ++ scu_ic2: interrupt-controller@100 { ++ compatible = "aspeed,ast2700-scu-ic2"; ++ reg = <0x100 0x8>; ++ interrupts-extended = <&intc1_5 0>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ }; ++ ++ scu_ic3: interrupt-controller@108 { ++ compatible = "aspeed,ast2700-scu-ic3"; ++ reg = <0x108 0x8>; ++ interrupts-extended = <&intc1_5 26>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ }; ++ ++ pinctrl1: pinctrl@400 { ++ compatible = "aspeed,ast2700-soc1-pinctrl"; ++ reg = <0x400 0x100>; ++ }; ++ }; ++ ++ rng: hwrng@14c020f0 { ++ compatible = "aspeed,ast2700-trng"; ++ reg = <0x0 0x14c020f0 0x0 0x8>; ++ status = "disabled"; ++ }; ++ ++ pcie_cfg2: syscon@14c02a80 { ++ compatible = "syscon"; ++ reg = <0 0x14c02a80 0 0x80>; ++ }; ++ ++ chassis: chassis@14c04010 { ++ compatible = "aspeed,ast2600-chassis"; ++ reg = <0 0x14c04010 0 0x4>; ++ interrupts-extended = <&intc1_5 5>; ++ status = "disabled"; ++ }; ++ ++ backed_sram0: sram@14c04100 { ++ compatible = "mmio-sram"; ++ reg = <0x0 0x14c04100 0x0 0x80>; ++ ranges = <0x0 0x0 0x0 0x14c04100 0x0 0x80>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ no-memory-wc; ++ ++ exported@0 { ++ reg = <0 0x0 0 0x80>; ++ export; ++ }; ++ }; ++ ++ backed_sram1: sram@14c04300 { ++ compatible = "mmio-sram"; ++ reg = <0x0 0x14c04300 0x0 0x80>; ++ ranges = <0x0 0x0 0x0 0x14c04300 0x0 0x80>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ no-memory-wc; ++ ++ exported@0 { ++ reg = <0 0x0 0 0x80>; ++ export; ++ }; ++ }; ++ ++ backed_sram2: sram@14c04500 { ++ compatible = "mmio-sram"; ++ reg = <0x0 0x14c04500 0x0 0x40>; ++ ranges = <0x0 0x0 0x0 0x14c04500 0x0 0x40>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ no-memory-wc; ++ ++ exported@0 { ++ reg = <0 0x0 0 0x40>; ++ export; ++ }; ++ }; ++ ++ espi0: espi@14c05000 { ++ compatible = "aspeed,ast2700-espi"; ++ reg = <0 0x14c05000 0 0x1000>; ++ interrupts-extended = <&intc1_0 10>, <&intc1_0 16>; ++ clocks = <&syscon1 SCU1_CLK_GATE_ESPI0CLK>; ++ resets = <&syscon1 SCU1_RESET_ESPI0>; ++ aspeed,syscon = <&syscon1>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_espi0_default>; ++ status = "disabled"; ++ }; ++ ++ espi1: espi@14c06000 { ++ compatible = "aspeed,ast2700-espi"; ++ reg = <0 0x14c06000 0 0x1000>; ++ interrupts-extended = <&intc1_1 10>, <&intc1_1 16>; ++ clocks = <&syscon1 SCU1_CLK_GATE_ESPI1CLK>; ++ resets = <&syscon1 SCU1_RESET_ESPI1>; ++ aspeed,syscon = <&syscon1>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_espi1_default>; ++ status = "disabled"; ++ }; ++ ++ otp: otp@14c07000 { ++ compatible = "aspeed,ast2700-otp"; ++ reg = <0 0x14c07000 0 0x1000>; ++ interrupts-extended = <&intc1_5 8>; ++ aspeed,scu0 = <&syscon0>; ++ aspeed,scu1 = <&syscon1>; ++ status = "disabled"; ++ }; ++ ++ jtag1: jtag@14c09000 { ++ compatible = "aspeed,ast2700-jtag"; ++ reg= <0x0 0x14c09000 0x0 0x40>; ++ interrupts-extended = <&intc1_5 2>; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ resets = <&syscon1 SCU1_RESET_JTAG1>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_jtagm1_default &pinctrl_jtagm1trst_default>; ++ status = "disabled"; ++ }; ++ ++ gpio1: gpio@14c0b000 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2700-gpio"; ++ reg = <0x0 0x14c0b000 0x0 0x1000>; ++ interrupts-extended = <&intc1_2 18>; ++ gpio-ranges = <&pinctrl1 0 0 216>; ++ ngpios = <216>; ++ clocks = <&syscon1 SCU1_CLK_AHB>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ ++ sgpiom0: sgpiom@14c0c000 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2700-sgpiom"; ++ reg = <0x0 0x14c0c000 0x0 0x100>; ++ interrupts-extended = <&intc1_2 21>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ ngpios = <256>; ++ bus-frequency = <12000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgpm0_default>; ++ status = "disabled"; ++ }; ++ ++ sgpiom1: sgpiom@14c0d000 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2700-sgpiom"; ++ reg = <0x0 0x14c0d000 0x0 0x100>; ++ interrupts-extended = <&intc1_2 24>; ++ #interrupt-cells = <2>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ interrupt-controller; ++ ngpios = <256>; ++ bus-frequency = <12000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgpm1_default>; ++ status = "disabled"; ++ }; ++ ++ i2c: bus@14c0f000 { ++ compatible = "simple-bus"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x14c0f000 0x1100>; ++ }; ++ ++ udma: uart-dma@14c12000 { ++ compatible = "aspeed,ast2700-udma"; ++ reg = <0x0 0x14c12000 0x0 0x1000>; ++ interrupts-extended = <&intc1_4 19>; ++ }; ++ ++ intc1: interrupt-controller@14c18000 { ++ compatible = "aspeed,ast2700-intc1"; ++ reg = <0 0x14c18000 0 0x400>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x14c18000 0x400>; ++ ++ intc1_0: interrupt-controller@100 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x100 0x10>; ++ interrupts-extended = <&intc0_11 0>; ++ }; ++ ++ intc1_1: interrupt-controller@110 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x110 0x10>; ++ interrupts-extended = <&intc0_11 1>; ++ }; ++ ++ intc1_2: interrupt-controller@120 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x120 0x10>; ++ interrupts-extended = <&intc0_11 2>; ++ }; ++ ++ intc1_3: interrupt-controller@130 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x130 0x10>; ++ interrupts-extended = <&intc0_11 3>; ++ }; ++ ++ intc1_4: interrupt-controller@140 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x140 0x10>; ++ interrupts-extended = <&intc0_11 4>; ++ }; ++ ++ intc1_5: interrupt-controller@150 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x150 0x10>; ++ interrupts-extended = <&intc0_11 5>; ++ }; ++ }; ++ ++ xdma2: xdma@14c19000 { ++ compatible = "aspeed,ast2700-xdma1"; ++ reg = <0x0 0x14c19000 0x0 0x100>; ++ clocks = <&syscon1 SCU1_CLK_GATE_PCICLK>; ++ resets = <&syscon1 SCU1_RESET_XDMA>; ++ reset-names = "device"; ++ interrupts-extended = <&intc1_4 21>, <&scu_ic2 ASPEED_AST2700_SCU_IC2_PCIE_PERST_LO_TO_HI>; ++ aspeed,pcie-device = "bmc"; ++ aspeed,scu = <&syscon1>; ++ status = "disabled"; ++ }; ++ ++ mctp2: mctp2@14c1a000 { ++ compatible = "aspeed,ast2700-mctp1"; ++ reg = <0x0 0x14c1a000 0x0 0x40>; ++ interrupts-extended = <&intc1_4 3>, <&scu_ic2 ASPEED_AST2700_SCU_IC2_PCIE_PERST_LO_TO_HI>; ++ interrupt-names = "mctp", "pcie"; ++ resets = <&syscon1 SCU1_RESET_MCTP>; ++ aspeed,scu = <&syscon1>; ++ aspeed,pcieh = <&pcie_phy2>; ++ status = "disabled"; ++ }; ++ ++ pcie_phy2: phy@14c1c000 { ++ compatible = "aspeed,ast2700-pcie-phy", "syscon"; ++ reg = <0x0 0x14c1c000 0x0 0x800>; ++ #phy-cells = <0>; ++ }; ++ ++ e2m_config2: e2m-config@14c1d000 { ++ compatible = "syscon", "simple-mfd"; ++ reg = <0 0x14c1d000 0 0x300>; ++ ranges = <0x0 0x0 0 0x14c1d000 0 0x300>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ aspeed,device = <&pcie_cfg2>; ++ ++ pinctrl-0 = <&pinctrl_pcierc2_perst_default>; ++ pinctrl-names = "default"; ++ ++ e2m_ic2: interrupt-controller@14 { ++ #interrupt-cells = <1>; ++ compatible = "aspeed,ast2700-e2m-ic"; ++ reg = <0 0x14 0 0x8>; ++ interrupts-extended = <&intc1_4 20>; ++ interrupt-controller; ++ }; ++ ++ pcie2_mmbi0: pcie2-mmbi@0 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ interrupts-extended = <&e2m_ic2 ASPEED_AST2700_E2M_MMBI_H2B_INT0>; ++ index = <0>; ++ pid = <2>; ++ bar = <0x1c>; ++ msi = <1>; ++ status = "disabled"; ++ }; ++ ++ pcie2_mmbi1: pcie2-mmbi@1 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ interrupts-extended = <&e2m_ic2 ASPEED_AST2700_E2M_MMBI_H2B_INT1>; ++ index = <1>; ++ pid = <3>; ++ bar = <0x50>; ++ msi = <2>; ++ status = "disabled"; ++ }; ++ ++ pcie2_mmbi2: pcie2-mmbi@2 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ interrupts-extended = <&e2m_ic2 ASPEED_AST2700_E2M_MMBI_H2B_INT2>; ++ index = <2>; ++ pid = <4>; ++ bar = <0x3c>; ++ msi = <3>; ++ status = "disabled"; ++ }; ++ ++ pcie2_mmbi3: pcie2-mmbi@3 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ interrupts-extended = <&e2m_ic2 ASPEED_AST2700_E2M_MMBI_H2B_INT3>; ++ index = <3>; ++ pid = <5>; ++ bar = <0x4c>; ++ msi = <4>; ++ status = "disabled"; ++ }; ++ ++ pcie2_mmbi4: pcie2-mmbi@4 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ interrupts-extended = <&e2m_ic2 ASPEED_AST2700_E2M_MMBI_H2B_INT4>; ++ index = <4>; ++ pid = <6>; ++ bar = <0x5c>; ++ msi = <5>; ++ status = "disabled"; ++ }; ++ ++ pcie2_mmbi5: pcie2-mmbi@5 { ++ compatible = "aspeed,ast2700-pcie-mmbi"; ++ interrupts-extended = <&e2m_ic2 ASPEED_AST2700_E2M_MMBI_H2B_INT5>; ++ index = <5>; ++ pid = <7>; ++ bar = <0x6c>; ++ msi = <6>; ++ status = "disabled"; ++ }; ++ }; ++ ++ peci0: peci-controller@14c1f000 { ++ compatible = "aspeed,ast2600-peci"; ++ reg = <0x0 0x14c1f000 0x0 0x100>; ++ interrupts-extended = <&intc1_5 4>; ++ clocks = <&syscon1 SCU1_CLK_PECI>; ++ resets = <&syscon1 SCU1_RESET_PECI>; ++ cmd-timeout-ms = <1000>; ++ clock-frequency = <1000000>; ++ status = "disabled"; ++ }; ++ ++ i3c0: i3c0@14c20000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c20000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 0>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C0CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C0>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c0_default>; ++ status = "disabled"; ++ }; ++ ++ i3c1: i3c1@14c21000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c21000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 1>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C1CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C1>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c1_default>; ++ status = "disabled"; ++ }; ++ ++ i3c2: i3c2@14c22000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c22000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 2>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C2CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C2>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c2_default>; ++ status = "disabled"; ++ }; ++ ++ i3c3: i3c3@14c23000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c23000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 3>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C3CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C3>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c3_default>; ++ status = "disabled"; ++ }; ++ ++ i3c4: i3c4@14c24000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c24000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C4CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C4>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c4_default>; ++ status = "disabled"; ++ }; ++ ++ i3c5: i3c5@14c25000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c25000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 5>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C5CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C5>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c5_default>; ++ status = "disabled"; ++ }; ++ ++ i3c6: i3c6@14c26000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c26000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 6>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C6CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C6>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c6_default>; ++ status = "disabled"; ++ }; ++ ++ i3c7: i3c7@14c27000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c27000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 7>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C7CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C7>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c7_default>; ++ status = "disabled"; ++ }; ++ ++ i3c8: i3c8@14c28000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c28000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 8>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C8CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C8>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c8_default>; ++ status = "disabled"; ++ }; ++ ++ i3c9: i3c9@14c29000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c29000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 9>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C9CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C9>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c9_default>; ++ status = "disabled"; ++ }; ++ ++ i3c10: i3c10@14c2a000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c2a000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 10>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C10CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C10>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c10_default>; ++ status = "disabled"; ++ }; ++ ++ i3c11: i3c11@14c2b000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c2b000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 11>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C11CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C11>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i3c11_default>; ++ status = "disabled"; ++ }; ++ ++ i3c12: i3c12@14c2c000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c2c000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 12>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C12CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C12>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c12_default>; ++ status = "disabled"; ++ }; ++ ++ i3c13: i3c13@14c2d000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c2d000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 13>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C13CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C13>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c13_default>; ++ status = "disabled"; ++ }; ++ ++ i3c14: i3c14@14c2e000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c2e000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 14>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C14CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C14>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c14_default>; ++ status = "disabled"; ++ }; ++ ++ i3c15: i3c15@14c2f000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x14c2f000 0x0 0x1000>; ++ interrupts-extended = <&intc1_3 15>; ++ clocks = <&syscon1 SCU1_CLK_GATE_I3C15CLK>; ++ resets = <&syscon1 SCU1_RESET_I3C15>, <&syscon1 SCU1_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_hvi3c15_default>; ++ status = "disabled"; ++ }; ++ ++ vuart0: serial@14c30000 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c30000 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts-extended = <&intc1_0 17>; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ dma-channel = <12>; ++ status = "disabled"; ++ }; ++ ++ vuart1: serial@14c30100 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c30100 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts-extended = <&intc1_0 18>; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ dma-channel = <13>; ++ status = "disabled"; ++ }; ++ ++ vuart2: serial@14c30200 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c30200 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts-extended = <&intc1_1 17>; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ dma-channel = <14>; ++ status = "disabled"; ++ }; ++ ++ vuart3: serial@14c30300 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c30300 0x0 0x40>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ interrupts-extended = <&intc1_1 18>; ++ clocks = <&syscon0 SCU0_CLK_APB>; ++ virtual; ++ dma-channel = <15>; ++ status = "disabled"; ++ }; ++ ++ lpc0: lpc@14c31000 { ++ compatible = "aspeed,ast2700-lpc", "simple-mfd", "syscon"; ++ reg = <0x0 0x14c31000 0x0 0x1000>; ++ reg-io-width = <4>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x14c31000 0x1000>; ++ ++ lpc0_kcs0: lpc-kcs@24 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>; ++ interrupts-extended = <&intc1_0 4>; ++ status = "disabled"; ++ }; ++ ++ lpc0_kcs1: lpc-kcs@28 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x28 0x1>, <0x34 0x1>, <0x40 0x1>; ++ interrupts-extended = <&intc1_0 5>; ++ status = "disabled"; ++ }; ++ ++ lpc0_kcs2: lpc-kcs@2c { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x2c 0x1>, <0x38 0x1>, <0x44 0x1>; ++ interrupts-extended = <&intc1_0 6>; ++ status = "disabled"; ++ }; ++ ++ lpc0_kcs3: lpc-kcs@114 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x114 0x1>, <0x118 0x1>, <0x11c 0x1>; ++ interrupts-extended = <&intc1_0 7>; ++ status = "disabled"; ++ }; ++ ++ lpc0_snoop: lpc-snoop@80 { ++ compatible = "aspeed,ast2600-lpc-snoop"; ++ reg = <0x80 0x80>; ++ interrupts-extended = <&intc1_0 1>; ++ status = "disabled"; ++ }; ++ ++ lpc0_pcc: lpc-pcc@0 { ++ compatible = "aspeed,ast2600-lpc-pcc"; ++ reg = <0x0 0x140>; ++ interrupts-extended = <&intc1_0 3>; ++ status = "disabled"; ++ }; ++ ++ lpc0_reset: reset-controller@98 { ++ compatible = "aspeed,ast2600-lpc-reset"; ++ reg = <0x98 0x4>; ++ #reset-cells = <1>; ++ }; ++ ++ lpc0_uart_routing: uart-routing@98 { ++ compatible = "aspeed,ast2700n0-uart-routing"; ++ reg = <0x98 0x8>; ++ status = "disabled"; ++ }; ++ ++ lpc0_ibt: ibt@140 { ++ compatible = "aspeed,ast2600-ibt-bmc"; ++ reg = <0x140 0x18>; ++ interrupts-extended = <&intc1_0 2>; ++ status = "disabled"; ++ }; ++ ++ lpc0_mbox: mbox@200 { ++ compatible = "aspeed,ast2700-mbox"; ++ reg = <0x200 0xc0>; ++ interrupts-extended = <&intc1_0 20>; ++ status = "disabled"; ++ }; ++ }; ++ ++ lpc1: lpc@14c32000 { ++ compatible = "aspeed,ast2700-lpc", "simple-mfd", "syscon"; ++ reg = <0x0 0x14c32000 0x0 0x1000>; ++ reg-io-width = <4>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x14c32000 0x1000>; ++ ++ lpc1_kcs0: kcs@24 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>; ++ interrupts-extended = <&intc1_1 4>; ++ status = "disabled"; ++ }; ++ ++ lpc1_kcs1: kcs@28 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x28 0x1>, <0x34 0x1>, <0x40 0x1>; ++ interrupts-extended = <&intc1_1 5>; ++ status = "disabled"; ++ }; ++ ++ lpc1_kcs2: kcs@2c { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x2c 0x1>, <0x38 0x1>, <0x44 0x1>; ++ interrupts-extended = <&intc1_1 6>; ++ status = "disabled"; ++ }; ++ ++ lpc1_kcs3: kcs@114 { ++ compatible = "aspeed,ast2600-kcs-bmc"; ++ reg = <0x114 0x1>, <0x118 0x1>, <0x11c 0x1>; ++ interrupts-extended = <&intc1_1 7>; ++ status = "disabled"; ++ }; ++ ++ lpc1_snoop: lpc-snoop@80 { ++ compatible = "aspeed,ast2600-lpc-snoop"; ++ reg = <0x80 0x80>; ++ interrupts-extended = <&intc1_1 1>; ++ status = "disabled"; ++ }; ++ ++ lpc1_pcc: lpc-pcc@0 { ++ compatible = "aspeed,ast2600-lpc-pcc"; ++ reg = <0x0 0x140>; ++ interrupts-extended = <&intc1_1 3>; ++ status = "disabled"; ++ }; ++ ++ lpc1_reset: reset-controller@98 { ++ compatible = "aspeed,ast2600-lpc-reset"; ++ reg = <0x98 0x4>; ++ #reset-cells = <1>; ++ }; ++ ++ lpc1_uart_routing: uart-routing@98 { ++ compatible = "aspeed,ast2700n1-uart-routing"; ++ reg = <0x98 0x8>; ++ status = "disabled"; ++ }; ++ ++ lpc1_ibt: ibt@140 { ++ compatible = "aspeed,ast2600-ibt-bmc"; ++ reg = <0x140 0x18>; ++ interrupts-extended = <&intc1_1 2>; ++ status = "disabled"; ++ }; ++ ++ lpc1_mbox: mbox@200 { ++ compatible = "aspeed,ast2700-mbox"; ++ reg = <0x200 0xc0>; ++ interrupts-extended = <&intc1_1 20>; ++ status = "disabled"; ++ }; ++ }; ++ ++ uart0: serial@14c33000 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33000 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART0CLK>; ++ resets = <&lpc0_reset 4>; ++ interrupts-extended = <&intc1_4 7>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd0_default &pinctrl_rxd0_default>; ++ dma-channel = <0>; ++ status = "disabled"; ++ }; ++ ++ uart1: serial@14c33100 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33100 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART1CLK>; ++ resets = <&lpc0_reset 5>; ++ interrupts-extended = <&intc1_4 8>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd1_default &pinctrl_rxd1_default>; ++ dma-channel = <1>; ++ status = "disabled"; ++ }; ++ ++ uart2: serial@14c33200 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33200 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART2CLK>; ++ resets = <&lpc0_reset 6>; ++ interrupts-extended = <&intc1_4 9>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd2_default &pinctrl_rxd2_default>; ++ dma-channel = <2>; ++ status = "disabled"; ++ }; ++ ++ uart3: serial@14c33300 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33300 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART3CLK>; ++ resets = <&lpc0_reset 7>; ++ interrupts-extended = <&intc1_4 10>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd3_default &pinctrl_rxd3_default>; ++ dma-channel = <3>; ++ status = "disabled"; ++ }; ++ ++ uart5: serial@14c33400 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33400 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART5CLK>; ++ resets = <&lpc1_reset 4>; ++ interrupts-extended = <&intc1_4 11>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd5_default &pinctrl_rxd5_default>; ++ dma-channel = <4>; ++ status = "disabled"; ++ }; ++ ++ uart6: serial@14c33500 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33500 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART6CLK>; ++ resets = <&lpc1_reset 5>; ++ interrupts-extended = <&intc1_4 12>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd6_default &pinctrl_rxd6_default>; ++ dma-channel = <5>; ++ status = "disabled"; ++ }; ++ ++ uart7: serial@14c33600 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33600 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART7CLK>; ++ resets = <&lpc1_reset 6>; ++ interrupts-extended = <&intc1_4 13>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd7_default &pinctrl_rxd7_default>; ++ dma-channel = <6>; ++ status = "disabled"; ++ }; ++ ++ uart8: serial@14c33700 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33700 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART8CLK>; ++ resets = <&lpc1_reset 7>; ++ interrupts-extended = <&intc1_4 14>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd8_default &pinctrl_rxd8_default>; ++ dma-channel = <7>; ++ status = "disabled"; ++ }; ++ ++ uart9: serial@14c33800 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33800 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART9CLK>; ++ interrupts-extended = <&intc1_4 15>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd9_default &pinctrl_rxd9_default>; ++ dma-channel = <8>; ++ status = "disabled"; ++ }; ++ ++ uart10: serial@14c33900 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33900 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART10CLK>; ++ interrupts-extended = <&intc1_4 16>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd10_default &pinctrl_rxd10_default>; ++ dma-channel = <9>; ++ status = "disabled"; ++ }; ++ ++ uart11: serial@14c33a00 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33a00 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART11CLK>; ++ interrupts-extended = <&intc1_4 17>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_txd11_default &pinctrl_rxd11_default>; ++ dma-channel = <10>; ++ status = "disabled"; ++ }; ++ ++ uart12: serial@14c33b00 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33b00 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_GATE_UART12CLK>; ++ interrupts-extended = <&intc1_4 18>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ dma-channel = <11>; ++ status = "disabled"; ++ }; ++ ++ uart13: serial@14c33c00 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33c00 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_UART13>; ++ interrupts-extended = <&intc1_0 23>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ status = "disabled"; ++ }; ++ ++ uart14: serial@14c33d00 { ++ compatible = "aspeed,ast2700-uart"; ++ reg = <0x0 0x14c33d00 0x0 0x100>; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ clocks = <&syscon1 SCU1_CLK_UART14>; ++ interrupts-extended = <&intc1_1 23>; ++ no-loopback-test; ++ pinctrl-names = "default"; ++ status = "disabled"; ++ }; ++ ++ ltpi0: ltpi@14c34000 { ++ compatible = "aspeed-ltpi"; ++ reg = <0x0 0x14c34000 0x0 0x200>, ++ <0x0 0x14c34200 0x0 0x100>, ++ <0x0 0x14c34800 0x0 0x100>; ++ reg-names = "base", "phy", "top"; ++ clocks = <&syscon1 SCU1_CLK_GATE_LTPICLK>, ++ <&syscon1 SCU1_CLK_GATE_LTPIPHYCLK>; ++ clock-names = "ahb", "phy"; ++ aspeed,scu = <&syscon1>; ++ resets = <&syscon1 SCU1_RESET_LTPI0>; ++ interrupts-extended = <&intc1_5 12>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_gpio: ltpi0-gpio@14c34c00 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2700-ltpi-gpio"; ++ reg = <0x0 0x14c34c00 0x0 0x300>; ++ interrupts-extended = <&intc1_0 22>; ++ ngpios = <112>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ status = "disabled"; ++ }; ++ ++ ltpi1: ltpi@14c35000 { ++ compatible = "aspeed-ltpi"; ++ reg = <0x0 0x14c35000 0x0 0x200>, ++ <0x0 0x14c35200 0x0 0x100>, ++ <0x0 0x14c35800 0x0 0x100>; ++ reg-names = "base", "phy", "top"; ++ clocks = <&syscon1 SCU1_CLK_GATE_LTPICLK>, ++ <&syscon1 SCU1_CLK_GATE_LTPI1TXCLK>; ++ clock-names = "ahb", "phy"; ++ aspeed,scu = <&syscon1>; ++ resets = <&syscon1 SCU1_RESET_LTPI1>; ++ interrupts-extended = <&intc1_5 13>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_gpio: ltpi1-gpio@14c35c00 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2700-ltpi-gpio"; ++ reg = <0x0 0x14c35c00 0x0 0x300>; ++ interrupts-extended = <&intc1_1 22>; ++ ngpios = <112>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ status = "disabled"; ++ }; ++ ++ wdt0: watchdog@14c37000 { ++ compatible = "aspeed,ast2700-wdt"; ++ reg = <0x0 0x14c37000 0x0 0x80>; ++ }; ++ ++ wdt1: watchdog@14c37080 { ++ compatible = "aspeed,ast2700-wdt"; ++ reg = <0x0 0x14c37080 0x0 0x80>; ++ }; ++ ++ wdt2: watchdog@14c37100 { ++ compatible = "aspeed,ast2700-wdt"; ++ reg = <0x0 0x14c37100 0x0 0x80>; ++ status = "disabled"; ++ }; ++ ++ wdt3: watchdog@14c37180 { ++ compatible = "aspeed,ast2700-wdt"; ++ reg = <0x0 0x14c37180 0x0 0x80>; ++ status = "disabled"; ++ }; ++ ++ mbox2: mbox@14c39200 { ++ compatible = "aspeed,ast2700-mailbox"; ++ reg = <0x0 0x14c39200 0x0 0x100>, <0x0 0x14c39300 0x0 0x100>; ++ reg-names = "tx", "rx"; ++ interrupts-extended = <&intc1_5 17>; ++ #mbox-cells = <1>; ++ mbox-name = "bootmcu"; ++ }; ++ ++ sgpios: sgpios@14c3c000 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2700-sgpios"; ++ reg = <0x0 0x14c3c000 0x0 0x100>; ++ interrupts-extended = <&intc1_2 29>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ ngpios = <72>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgps_default>; ++ status = "disabled"; ++ }; ++ ++ fsim0: fsi@21800000 { ++ compatible = "aspeed,ast2700-fsi-master", "fsi-master"; ++ reg = <0x0 0x21800000 0x0 0x94>; ++ interrupts-extended = <&intc1_5 6>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_fsi0_default>; ++ clocks = <&syscon1 SCU1_CLK_GATE_FSICLK>; ++ resets = <&syscon1 SCU1_RESET_FSI>; ++ status = "disabled"; ++ }; ++ ++ fsim1: fsi@23800000 { ++ compatible = "aspeed,ast2700-fsi-master", "fsi-master"; ++ reg = <0x0 0x23800000 0x0 0x94>; ++ interrupts-extended = <&intc1_5 7>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_fsi2_default>; ++ clocks = <&syscon1 SCU1_CLK_GATE_FSICLK>; ++ resets = <&syscon1 SCU1_RESET_FSI>; ++ status = "disabled"; ++ }; ++ ++ rtc_over_espi0: rtc-over_espi@14c3d000 { ++ compatible = "aspeed,ast2700-rtc-over-espi"; ++ reg = <0x0 0x14c3d000 0x0 0x100>; ++ interval-ms = <1000>; ++ status = "disabled"; ++ }; ++ ++ rtc_over_espi1: rtc-over_espi@14c3d100 { ++ compatible = "aspeed,ast2700-rtc-over-espi"; ++ reg = <0x0 0x14c3d100 0x0 0x100>; ++ interval-ms = <1000>; ++ status = "disabled"; ++ }; ++ ++ }; ++}; ++ ++#include "aspeed-g7-pinctrl.dtsi" ++ ++&i2c { ++ i2c_global: i2c-global-regs@0 { ++ compatible = "aspeed,i2c-global", "simple-mfd", "syscon"; ++ reg = <0x0 0x100>; ++ }; ++ ++ i2c0: i2c-bus@100 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x100 0xA0>, <0x1C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 0>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c0_default>; ++ status = "disabled"; ++ }; ++ ++ i2c1: i2c-bus@200 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x200 0xA0>, <0x2C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 1>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c1_default>; ++ status = "disabled"; ++ }; ++ ++ i2c2: i2c-bus@300 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x300 0xA0>, <0x3C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 2>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c2_default>; ++ status = "disabled"; ++ }; ++ ++ i2c3: i2c-bus@400 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x400 0xA0>, <0x4C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 3>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c3_default>; ++ status = "disabled"; ++ }; ++ ++ i2c4: i2c-bus@500 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x500 0xA0>, <0x5C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ interrupts-extended = <&intc1_2 4>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c4_default>; ++ status = "disabled"; ++ }; ++ ++ i2c5: i2c-bus@600 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x600 0xA0>, <0x6C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 5>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c5_default>; ++ status = "disabled"; ++ }; ++ ++ i2c6: i2c-bus@700 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x700 0xA0>, <0x7C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 6>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c6_default>; ++ status = "disabled"; ++ }; ++ ++ i2c7: i2c-bus@800 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x800 0xA0>, <0x8C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 7>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c7_default>; ++ status = "disabled"; ++ }; ++ ++ i2c8: i2c-bus@900 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x900 0xA0>, <0x9C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 8>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c8_default>; ++ status = "disabled"; ++ }; ++ ++ i2c9: i2c-bus@a00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xA00 0xA0>, <0xAC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 9>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c9_default>; ++ status = "disabled"; ++ }; ++ ++ i2c10: i2c-bus@b00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xB00 0xA0>, <0xBC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 10>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c10_default>; ++ status = "disabled"; ++ }; ++ ++ i2c11: i2c-bus@c00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xC00 0xA0>, <0xCC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 11>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c11_default>; ++ status = "disabled"; ++ }; ++ ++ i2c12: i2c-bus@d00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xD00 0xA0>, <0xDC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 12>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c12_default>; ++ status = "disabled"; ++ }; ++ ++ i2c13: i2c-bus@e00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xE00 0xA0>, <0xEC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 13>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c13_default>; ++ status = "disabled"; ++ }; ++ ++ i2c14: i2c-bus@f00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xF00 0xA0>, <0xFC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 14>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c14_default>; ++ status = "disabled"; ++ }; ++ ++ i2c15: i2c-bus@1000 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x1000 0xA0>, <0x10C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <&i2c_global>; ++ clocks = <&syscon1 SCU1_CLK_APB>; ++ resets = <&syscon1 SCU1_RESET_I2C>; ++ interrupts-extended = <&intc1_2 15>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c15_default>; ++ status = "disabled"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-ltpi0-pinctrl.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-ltpi0-pinctrl.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-ltpi0-pinctrl.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-ltpi0-pinctrl.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,409 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++<pi0_pinctrl { ++ pinctrl_ltpi0_i2c0_default: ltpi0-i2c0-default { ++ function = "I2C0"; ++ groups = "I2C0"; ++ }; ++ ++ pinctrl_ltpi0_i2c1_default: ltpi0-i2c1-default { ++ function = "I2C1"; ++ groups = "I2C1"; ++ }; ++ ++ pinctrl_ltpi0_i2c2_default: ltpi0-i2c2-default { ++ function = "I2C2"; ++ groups = "I2C2"; ++ }; ++ ++ pinctrl_ltpi0_i2c3_default: ltpi0-i2c3-default { ++ function = "I2C3"; ++ groups = "I2C3"; ++ }; ++ ++ pinctrl_ltpi0_i2c4_default: ltpi0-i2c4-default { ++ function = "I2C4"; ++ groups = "I2C4"; ++ }; ++ ++ pinctrl_ltpi0_i2c5_default: ltpi0-i2c5-default { ++ function = "I2C5"; ++ groups = "I2C5"; ++ }; ++ ++ pinctrl_ltpi0_i2c6_default: ltpi0-i2c6-default { ++ function = "I2C6"; ++ groups = "I2C6"; ++ }; ++ ++ pinctrl_ltpi0_i2c7_default: ltpi0-i2c7-default { ++ function = "I2C7"; ++ groups = "I2C7"; ++ }; ++ ++ pinctrl_ltpi0_i2c8_default: ltpi0-i2c8-default { ++ function = "I2C8"; ++ groups = "I2C8"; ++ }; ++ ++ pinctrl_ltpi0_i2c9_default: ltpi0-i2c9-default { ++ function = "I2C9"; ++ groups = "I2C9"; ++ }; ++ ++ pinctrl_ltpi0_i2c10_default: ltpi0-i2c10-default { ++ function = "I2C10"; ++ groups = "I2C10"; ++ }; ++ ++ pinctrl_ltpi0_i2c11_default: ltpi0-i2c11-default { ++ function = "I2C11"; ++ groups = "I2C11"; ++ }; ++ ++ pinctrl_ltpi0_i2c12_default: ltpi0-i2c12-default { ++ function = "I2C12"; ++ groups = "I2C12"; ++ }; ++ ++ pinctrl_ltpi0_i2c13_default: ltpi0-i2c13-default { ++ function = "I2C13"; ++ groups = "I2C13"; ++ }; ++ ++ pinctrl_ltpi0_i2c14_default: ltpi0-i2c14-default { ++ function = "I2C14"; ++ groups = "I2C14"; ++ }; ++ ++ pinctrl_ltpi0_i2c15_default: ltpi0-i2c15-default { ++ function = "I2C15"; ++ groups = "I2C15"; ++ }; ++ ++ pinctrl_ltpi0_pwm0_default: ltpi0-pwm0-default { ++ function = "PWM0"; ++ groups = "PWM0"; ++ }; ++ ++ pinctrl_ltpi0_pwm1_default: ltpi0-pwm1-default { ++ function = "PWM1"; ++ groups = "PWM1"; ++ }; ++ ++ pinctrl_ltpi0_pwm2_default: ltpi0-pwm2-default { ++ function = "PWM2"; ++ groups = "PWM2"; ++ }; ++ ++ pinctrl_ltpi0_pwm3_default: ltpi0-pwm3-default { ++ function = "PWM3"; ++ groups = "PWM3"; ++ }; ++ ++ pinctrl_ltpi0_pwm4_default: ltpi0-pwm4-default { ++ function = "PWM4"; ++ groups = "PWM4"; ++ }; ++ ++ pinctrl_ltpi0_pwm5_default: ltpi0-pwm5-default { ++ function = "PWM5"; ++ groups = "PWM5"; ++ }; ++ ++ pinctrl_ltpi0_pwm6_default: ltpi0-pwm6-default { ++ function = "PWM6"; ++ groups = "PWM6"; ++ }; ++ ++ pinctrl_ltpi0_pwm7_default: ltpi0-pwm7-default { ++ function = "PWM7"; ++ groups = "PWM7"; ++ }; ++ ++ pinctrl_ltpi0_pwm8_default: ltpi0-pwm8-default { ++ function = "PWM8"; ++ groups = "PWM8"; ++ }; ++ ++ pinctrl_ltpi0_pwm9_default: ltpi0-pwm9-default { ++ function = "PWM9"; ++ groups = "PWM9"; ++ }; ++ ++ pinctrl_ltpi0_pwm10_default: ltpi0-pwm10-default { ++ function = "PWM10"; ++ groups = "PWM10"; ++ }; ++ ++ pinctrl_ltpi0_pwm11_default: ltpi0-pwm11-default { ++ function = "PWM11"; ++ groups = "PWM11"; ++ }; ++ ++ pinctrl_ltpi0_pwm12_default: ltpi0-pwm12-default { ++ function = "PWM12"; ++ groups = "PWM12"; ++ }; ++ ++ pinctrl_ltpi0_pwm13_default: ltpi0-pwm13-default { ++ function = "PWM13"; ++ groups = "PWM13"; ++ }; ++ ++ pinctrl_ltpi0_pwm14_default: ltpi0-pwm14-default { ++ function = "PWM14"; ++ groups = "PWM14"; ++ }; ++ ++ pinctrl_ltpi0_pwm15_default: ltpi0-pwm15-default { ++ function = "PWM15"; ++ groups = "PWM15"; ++ }; ++ ++ pinctrl_ltpi0_tach0_default: ltpi0-tach0-default { ++ function = "TACH0"; ++ groups = "TACH0"; ++ }; ++ ++ pinctrl_ltpi0_tach1_default: ltpi0-tach1-default { ++ function = "TACH1"; ++ groups = "TACH1"; ++ }; ++ ++ pinctrl_ltpi0_tach2_default: ltpi0-tach2-default { ++ function = "TACH2"; ++ groups = "TACH2"; ++ }; ++ ++ pinctrl_ltpi0_tach3_default: ltpi0-tach3-default { ++ function = "TACH3"; ++ groups = "TACH3"; ++ }; ++ ++ pinctrl_ltpi0_tach4_default: ltpi0-tach4-default { ++ function = "TACH4"; ++ groups = "TACH4"; ++ }; ++ ++ pinctrl_ltpi0_tach5_default: ltpi0-tach5-default { ++ function = "TACH5"; ++ groups = "TACH5"; ++ }; ++ ++ pinctrl_ltpi0_tach6_default: ltpi0-tach6-default { ++ function = "TACH6"; ++ groups = "TACH6"; ++ }; ++ ++ pinctrl_ltpi0_tach7_default: ltpi0-tach7-default { ++ function = "TACH7"; ++ groups = "TACH7"; ++ }; ++ ++ pinctrl_ltpi0_tach8_default: ltpi0-tach8-default { ++ function = "TACH8"; ++ groups = "TACH8"; ++ }; ++ ++ pinctrl_ltpi0_tach9_default: ltpi0-tach9-default { ++ function = "TACH9"; ++ groups = "TACH9"; ++ }; ++ ++ pinctrl_ltpi0_tach10_default: ltpi0-tach10-default { ++ function = "TACH10"; ++ groups = "TACH10"; ++ }; ++ ++ pinctrl_ltpi0_tach11_default: ltpi0-tach11-default { ++ function = "TACH11"; ++ groups = "TACH11"; ++ }; ++ ++ pinctrl_ltpi0_tach12_default: ltpi0-tach12-default { ++ function = "TACH12"; ++ groups = "TACH12"; ++ }; ++ ++ pinctrl_ltpi0_tach13_default: ltpi0-tach13-default { ++ function = "TACH13"; ++ groups = "TACH13"; ++ }; ++ ++ pinctrl_ltpi0_tach14_default: ltpi0-tach14-default { ++ function = "TACH14"; ++ groups = "TACH14"; ++ }; ++ ++ pinctrl_ltpi0_tach15_default: ltpi0-tach15-default { ++ function = "TACH15"; ++ groups = "TACH15"; ++ }; ++ ++ pinctrl_ltpi0_jtagm_default: ltpi0-jtagm-default { ++ function = "JTAGM"; ++ groups = "JTAGM"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c0_default: ltpi0-hvi3c0-default { ++ function = "I3C0"; ++ groups = "HVI3C0"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c1_default: ltpi0-hvi3c1-default { ++ function = "I3C1"; ++ groups = "HVI3C1"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c2_default: ltpi0-hvi3c2-default { ++ function = "I3C2"; ++ groups = "HVI3C2"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c3_default: ltpi0-hvi3c3-default { ++ function = "I3C3"; ++ groups = "HVI3C3"; ++ }; ++ ++ pinctrl_ltpi0_i3c4_default: ltpi0-i3c4-default { ++ function = "I3C4"; ++ groups = "I3C4"; ++ }; ++ ++ pinctrl_ltpi0_i3c5_default: ltpi0-i3c5-default { ++ function = "I3C5"; ++ groups = "I3C5"; ++ }; ++ ++ pinctrl_ltpi0_i3c6_default: ltpi0-i3c6-default { ++ function = "I3C6"; ++ groups = "I3C6"; ++ }; ++ ++ pinctrl_ltpi0_i3c7_default: ltpi0-i3c7-default { ++ function = "I3C7"; ++ groups = "I3C7"; ++ }; ++ ++ pinctrl_ltpi0_i3c8_default: ltpi0-i3c8-default { ++ function = "I3C8"; ++ groups = "I3C8"; ++ }; ++ ++ pinctrl_ltpi0_i3c9_default: ltpi0-i3c9-default { ++ function = "I3C9"; ++ groups = "I3C9"; ++ }; ++ ++ pinctrl_ltpi0_i3c10_default: ltpi0-i3c10-default { ++ function = "I3C10"; ++ groups = "I3C10"; ++ }; ++ ++ pinctrl_ltpi0_i3c11_default: ltpi0-i3c11-default { ++ function = "I3C11"; ++ groups = "I3C11"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c12_default: ltpi0-hvi3c12-default { ++ function = "I3C12"; ++ groups = "HVI3C12"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c13_default: ltpi0-hvi3c13-default { ++ function = "I3C13"; ++ groups = "HVI3C13"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c14_default: ltpi0-hvi3c14-default { ++ function = "I3C14"; ++ groups = "HVI3C14"; ++ }; ++ ++ pinctrl_ltpi0_hvi3c15_default: ltpi0-hvi3c15-default { ++ function = "I3C15"; ++ groups = "HVI3C15"; ++ }; ++ ++ pinctrl_ltpi0_adc0_default: ltpi0-adc0-default { ++ function = "ADC0"; ++ groups = "ADC0"; ++ }; ++ ++ pinctrl_ltpi0_adc1_default: ltpi0-adc1-default { ++ function = "ADC1"; ++ groups = "ADC1"; ++ }; ++ ++ pinctrl_ltpi0_adc2_default: ltpi0-adc2-default { ++ function = "ADC2"; ++ groups = "ADC2"; ++ }; ++ ++ pinctrl_ltpi0_adc3_default: ltpi0-adc3-default { ++ function = "ADC3"; ++ groups = "ADC3"; ++ }; ++ ++ pinctrl_ltpi0_adc4_default: ltpi0-adc4-default { ++ function = "ADC4"; ++ groups = "ADC4"; ++ }; ++ ++ pinctrl_ltpi0_adc5_default: ltpi0-adc5-default { ++ function = "ADC5"; ++ groups = "ADC5"; ++ }; ++ ++ pinctrl_ltpi0_adc6_default: ltpi0-adc6-default { ++ function = "ADC6"; ++ groups = "ADC6"; ++ }; ++ ++ pinctrl_ltpi0_adc7_default: ltpi0-adc7-default { ++ function = "ADC7"; ++ groups = "ADC7"; ++ }; ++ ++ pinctrl_ltpi0_adc8_default: ltpi0-adc8-default { ++ function = "ADC8"; ++ groups = "ADC8"; ++ }; ++ ++ pinctrl_ltpi0_adc9_default: ltpi0-adc9-default { ++ function = "ADC9"; ++ groups = "ADC9"; ++ }; ++ ++ pinctrl_ltpi0_adc10_default: ltpi0-adc10-default { ++ function = "ADC10"; ++ groups = "ADC10"; ++ }; ++ ++ pinctrl_ltpi0_adc11_default: ltpi0-adc11-default { ++ function = "ADC11"; ++ groups = "ADC11"; ++ }; ++ ++ pinctrl_ltpi0_adc12_default: ltpi0-adc12-default { ++ function = "ADC12"; ++ groups = "ADC12"; ++ }; ++ ++ pinctrl_ltpi0_adc13_default: ltpi0-adc13-default { ++ function = "ADC13"; ++ groups = "ADC13"; ++ }; ++ ++ pinctrl_ltpi0_adc14_default: ltpi0-adc14-default { ++ function = "ADC14"; ++ groups = "ADC14"; ++ }; ++ ++ pinctrl_ltpi0_adc15_default: ltpi0-adc15-default { ++ function = "ADC15"; ++ groups = "ADC15"; ++ }; ++ ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-ltpi0.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-ltpi0.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-ltpi0.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-ltpi0.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,660 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++#include ++#include ++ ++/ { ++ aliases { ++ i3c100 = <pi0_i3c0; ++ i3c101 = <pi0_i3c1; ++ i3c102 = <pi0_i3c2; ++ i3c103 = <pi0_i3c3; ++ i3c104 = <pi0_i3c4; ++ i3c105 = <pi0_i3c5; ++ i3c106 = <pi0_i3c6; ++ i3c107 = <pi0_i3c7; ++ i3c108 = <pi0_i3c8; ++ i3c109 = <pi0_i3c9; ++ i3c110 = <pi0_i3c10; ++ i3c111 = <pi0_i3c11; ++ i3c112 = <pi0_i3c12; ++ i3c113 = <pi0_i3c13; ++ i3c114 = <pi0_i3c14; ++ i3c115 = <pi0_i3c15; ++ }; ++ ++ ltpi0_bus: ltpi0_bus@30000000 { ++ compatible = "simple-bus"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ ltpi0_syscon: syscon@30c02000 { ++ compatible = "aspeed,ast1700-scu", "syscon", "simple-mfd"; ++ reg = <0x0 0x30c02000 0x0 0x1000>; ++ ranges = <0x0 0x0 0x0 0x30c02000 0x0 0x1000>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ #clock-cells = <1>; ++ #reset-cells = <1>; ++ ++ ltpi0_rst: reset-controller@200 { ++ reg = <0x0 0x200 0x0 0x40>; ++ }; ++ ++ ltpi0_clk: clock-controller@240 { ++ reg = <0x0 0x240 0x0 0x1c0>; ++ }; ++ ++ ltpi0_pinctrl: pinctrl@400 { ++ compatible = "aspeed,ast1700-pinctrl"; ++ reg = <0x0 0x400 0x0 0x100>; ++ }; ++ }; ++ ++ ltpi0_spi2: spi@30030000 { ++ reg = <0x0 0x30030000 0x0 0x1f0>, <0x2 0x80000000 0x0 0x04000000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ clocks = <<pi0_syscon AST1700_CLK_AHB>; ++ resets = <<pi0_syscon AST1700_RESET_SPI2>; ++ num-cs = <2>; ++ ltpi-base = <0x0 0x34000000>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_pwm_tach: pwm-tach-controller@300c0000 { ++ compatible = "aspeed,ast2700-pwm-tach"; ++ reg = <0x0 0x300c0000 0 0x100>; ++ clocks = <<pi0_syscon AST1700_CLK_AHB>; ++ resets = <<pi0_syscon AST1700_RESET_PWM>; ++ #pwm-cells = <3>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_adc0: adc@30c00000 { ++ compatible = "aspeed,ast2700-adc0"; ++ reg = <0x0 0x30c00000 0 0x100>; ++ clocks = <<pi0_syscon AST1700_CLK_AHB>; ++ resets = <<pi0_syscon AST1700_RESET_ADC>; ++ interrupts-extended = <<pi0_intc1_0 24>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <<pi0_syscon>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_adc1: adc@30c00100 { ++ compatible = "aspeed,ast2700-adc1"; ++ reg = <0x0 0x30c00100 0x0 0x100>; ++ clocks = <<pi0_syscon AST1700_CLK_AHB>; ++ resets = <<pi0_syscon AST1700_RESET_ADC>; ++ interrupts-extended = <<pi0_intc1_0 24>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <<pi0_syscon>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_jtag: jtag@30c09000 { ++ compatible = "aspeed,ast2700-jtag"; ++ reg= <0x0 0x30c09000 0x0 0x40>; ++ clocks = <<pi0_syscon AST1700_CLK_AHB>; ++ resets = <<pi0_syscon AST1700_RESET_JTAG1>; ++ interrupts-extended = <<pi0_intc1_0 23>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c: bus@30c0f000 { ++ compatible = "simple-bus"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x30c0f000 0x1100>; ++ }; ++ ++ ltpi0_intc1: interrupt-controller@30c18000 { ++ compatible = "simple-mfd"; ++ reg = <0 0x30c18000 0 0x400>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges = <0x0 0x0 0x0 0x30c18000 0x0 0x400>; ++ ++ ltpi0_intc1_0: interrupt-controller@100 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x0 0x100 0x0 0x10>; ++ interrupts-extended = <&intc0_11 6>; ++ }; ++ ++ ltpi0_intc1_1: interrupt-controller@110 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x0 0x110 0x0 0x10>; ++ interrupts-extended = <&intc0_11 7>; ++ }; ++ }; ++ ++ ltpi0_i3c0: i3c0@30c20000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c20000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 0>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C0CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C0>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c0_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c1: i3c1@30c21000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c21000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 1>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C1CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C1>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c1_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c2: i3c2@30c22000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c22000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 2>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C2CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C2>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c2_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c3: i3c3@30c23000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c23000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 3>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C3CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C3>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c3_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c4: i3c4@30c24000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c24000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 4>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C4CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C4>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c4_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c5: i3c5@30c25000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c25000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 5>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C5CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C5>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c5_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c6: i3c6@30c26000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c26000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 6>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C6CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C6>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c6_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c7: i3c7@30c27000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c27000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 7>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C7CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C7>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c7_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c8: i3c8@30c28000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c28000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 8>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C8CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C8>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c8_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c9: i3c9@30c29000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c29000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 9>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C9CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C9>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c9_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c10: i3c10@30c2a000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2a000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 10>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C10CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C10>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c10_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c11: i3c11@30c2b000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2b000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 11>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C11CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C11>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c11_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c12: i3c12@30c2c000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2c000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 12>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C12CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C12>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c12_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c13: i3c13@30c2d000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2d000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 13>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C13CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C13>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c13_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c14: i3c14@30c2e000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2e000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 14>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C14CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C14>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c14_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c15: i3c15@30c2f000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2f000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_1 15>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_I3C15CLK>; ++ resets = <<pi0_syscon AST1700_RESET_I3C15>, <<pi0_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c15_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_ltpi: ltpi@30c34000 { ++ compatible = "aspeed-ast1700-ltpi"; ++ reg = <0x0 0x30c34000 0x0 0x100>; ++ clocks = <<pi0_syscon AST1700_CLK_GATE_LTPICLK>, ++ <<pi0_syscon AST1700_CLK_GATE_LTPIPHYCLK>; ++ clock-names = "ahb", "phy"; ++ resets = <<pi0_syscon AST1700_RESET_LTPI>; ++ remote-controller; ++ aspeed,scu = <<pi0_syscon>; ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++ }; ++ ++ ltpi0_wdt0: watchdog@30c37000 { ++ compatible = "aspeed,ast2700-wdt"; ++ reg = <0x0 0x30c37000 0x0 0x40>; ++ }; ++ }; ++}; ++ ++#include "aspeed-ltpi0-pinctrl.dtsi" ++ ++<pi0_i2c { ++ ltpi0_i2c_global: i2c-global-regs@0 { ++ compatible = "aspeed,i2c-global", "simple-mfd", "syscon"; ++ reg = <0x0 0x100>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c0: i2c-bus@100 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x100 0xA0>, <0x1C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 0>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c0_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c1: i2c-bus@200 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x200 0xA0>, <0x2C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 1>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c1_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c2: i2c-bus@300 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x300 0xA0>, <0x3C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 2>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c2_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c3: i2c-bus@400 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x400 0xA0>, <0x4C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 3>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c3_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c4: i2c-bus@500 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x500 0xA0>, <0x5C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 4>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c4_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c5: i2c-bus@600 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x600 0xA0>, <0x6C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 5>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c5_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c6: i2c-bus@700 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x700 0xA0>, <0x7C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 6>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c6_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c7: i2c-bus@800 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x800 0xA0>, <0x8C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 7>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c7_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c8: i2c-bus@900 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x900 0xA0>, <0x9C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 8>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c8_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c9: i2c-bus@a00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xA00 0xA0>, <0xAC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 9>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c9_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c10: i2c-bus@b00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xB00 0xA0>, <0xBC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 10>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c10_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c11: i2c-bus@c00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xC00 0xA0>, <0xCC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 11>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c11_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c12: i2c-bus@d00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xD00 0xA0>, <0xDC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 12>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c12_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c13: i2c-bus@e00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xE00 0xA0>, <0xEC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 13>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c13_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c14: i2c-bus@f00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xF00 0xA0>, <0xFC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 14>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c14_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c15: i2c-bus@1000 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x1000 0xA0>, <0x10C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1700_CLK_APB>; ++ resets = <<pi0_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi0_intc1_0 15>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c15_default>; ++ status = "disabled"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-ltpi1-pinctrl.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-ltpi1-pinctrl.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-ltpi1-pinctrl.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-ltpi1-pinctrl.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,409 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++<pi1_pinctrl { ++ pinctrl_ltpi1_i2c0_default: ltpi1-i2c0-default { ++ function = "I2C0"; ++ groups = "I2C0"; ++ }; ++ ++ pinctrl_ltpi1_i2c1_default: ltpi1-i2c1-default { ++ function = "I2C1"; ++ groups = "I2C1"; ++ }; ++ ++ pinctrl_ltpi1_i2c2_default: ltpi1-i2c2-default { ++ function = "I2C2"; ++ groups = "I2C2"; ++ }; ++ ++ pinctrl_ltpi1_i2c3_default: ltpi1-i2c3-default { ++ function = "I2C3"; ++ groups = "I2C3"; ++ }; ++ ++ pinctrl_ltpi1_i2c4_default: ltpi1-i2c4-default { ++ function = "I2C4"; ++ groups = "I2C4"; ++ }; ++ ++ pinctrl_ltpi1_i2c5_default: ltpi1-i2c5-default { ++ function = "I2C5"; ++ groups = "I2C5"; ++ }; ++ ++ pinctrl_ltpi1_i2c6_default: ltpi1-i2c6-default { ++ function = "I2C6"; ++ groups = "I2C6"; ++ }; ++ ++ pinctrl_ltpi1_i2c7_default: ltpi1-i2c7-default { ++ function = "I2C7"; ++ groups = "I2C7"; ++ }; ++ ++ pinctrl_ltpi1_i2c8_default: ltpi1-i2c8-default { ++ function = "I2C8"; ++ groups = "I2C8"; ++ }; ++ ++ pinctrl_ltpi1_i2c9_default: ltpi1-i2c9-default { ++ function = "I2C9"; ++ groups = "I2C9"; ++ }; ++ ++ pinctrl_ltpi1_i2c10_default: ltpi1-i2c10-default { ++ function = "I2C10"; ++ groups = "I2C10"; ++ }; ++ ++ pinctrl_ltpi1_i2c11_default: ltpi1-i2c11-default { ++ function = "I2C11"; ++ groups = "I2C11"; ++ }; ++ ++ pinctrl_ltpi1_i2c12_default: ltpi1-i2c12-default { ++ function = "I2C12"; ++ groups = "I2C12"; ++ }; ++ ++ pinctrl_ltpi1_i2c13_default: ltpi1-i2c13-default { ++ function = "I2C13"; ++ groups = "I2C13"; ++ }; ++ ++ pinctrl_ltpi1_i2c14_default: ltpi1-i2c14-default { ++ function = "I2C14"; ++ groups = "I2C14"; ++ }; ++ ++ pinctrl_ltpi1_i2c15_default: ltpi1-i2c15-default { ++ function = "I2C15"; ++ groups = "I2C15"; ++ }; ++ ++ pinctrl_ltpi1_pwm0_default: ltpi1-pwm0-default { ++ function = "PWM0"; ++ groups = "PWM0"; ++ }; ++ ++ pinctrl_ltpi1_pwm1_default: ltpi1-pwm1-default { ++ function = "PWM1"; ++ groups = "PWM1"; ++ }; ++ ++ pinctrl_ltpi1_pwm2_default: ltpi1-pwm2-default { ++ function = "PWM2"; ++ groups = "PWM2"; ++ }; ++ ++ pinctrl_ltpi1_pwm3_default: ltpi1-pwm3-default { ++ function = "PWM3"; ++ groups = "PWM3"; ++ }; ++ ++ pinctrl_ltpi1_pwm4_default: ltpi1-pwm4-default { ++ function = "PWM4"; ++ groups = "PWM4"; ++ }; ++ ++ pinctrl_ltpi1_pwm5_default: ltpi1-pwm5-default { ++ function = "PWM5"; ++ groups = "PWM5"; ++ }; ++ ++ pinctrl_ltpi1_pwm6_default: ltpi1-pwm6-default { ++ function = "PWM6"; ++ groups = "PWM6"; ++ }; ++ ++ pinctrl_ltpi1_pwm7_default: ltpi1-pwm7-default { ++ function = "PWM7"; ++ groups = "PWM7"; ++ }; ++ ++ pinctrl_ltpi1_pwm8_default: ltpi1-pwm8-default { ++ function = "PWM8"; ++ groups = "PWM8"; ++ }; ++ ++ pinctrl_ltpi1_pwm9_default: ltpi1-pwm9-default { ++ function = "PWM9"; ++ groups = "PWM9"; ++ }; ++ ++ pinctrl_ltpi1_pwm10_default: ltpi1-pwm10-default { ++ function = "PWM10"; ++ groups = "PWM10"; ++ }; ++ ++ pinctrl_ltpi1_pwm11_default: ltpi1-pwm11-default { ++ function = "PWM11"; ++ groups = "PWM11"; ++ }; ++ ++ pinctrl_ltpi1_pwm12_default: ltpi1-pwm12-default { ++ function = "PWM12"; ++ groups = "PWM12"; ++ }; ++ ++ pinctrl_ltpi1_pwm13_default: ltpi1-pwm13-default { ++ function = "PWM13"; ++ groups = "PWM13"; ++ }; ++ ++ pinctrl_ltpi1_pwm14_default: ltpi1-pwm14-default { ++ function = "PWM14"; ++ groups = "PWM14"; ++ }; ++ ++ pinctrl_ltpi1_pwm15_default: ltpi1-pwm15-default { ++ function = "PWM15"; ++ groups = "PWM15"; ++ }; ++ ++ pinctrl_ltpi1_tach0_default: ltpi1-tach0-default { ++ function = "TACH0"; ++ groups = "TACH0"; ++ }; ++ ++ pinctrl_ltpi1_tach1_default: ltpi1-tach1-default { ++ function = "TACH1"; ++ groups = "TACH1"; ++ }; ++ ++ pinctrl_ltpi1_tach2_default: ltpi1-tach2-default { ++ function = "TACH2"; ++ groups = "TACH2"; ++ }; ++ ++ pinctrl_ltpi1_tach3_default: ltpi1-tach3-default { ++ function = "TACH3"; ++ groups = "TACH3"; ++ }; ++ ++ pinctrl_ltpi1_tach4_default: ltpi1-tach4-default { ++ function = "TACH4"; ++ groups = "TACH4"; ++ }; ++ ++ pinctrl_ltpi1_tach5_default: ltpi1-tach5-default { ++ function = "TACH5"; ++ groups = "TACH5"; ++ }; ++ ++ pinctrl_ltpi1_tach6_default: ltpi1-tach6-default { ++ function = "TACH6"; ++ groups = "TACH6"; ++ }; ++ ++ pinctrl_ltpi1_tach7_default: ltpi1-tach7-default { ++ function = "TACH7"; ++ groups = "TACH7"; ++ }; ++ ++ pinctrl_ltpi1_tach8_default: ltpi1-tach8-default { ++ function = "TACH8"; ++ groups = "TACH8"; ++ }; ++ ++ pinctrl_ltpi1_tach9_default: ltpi1-tach9-default { ++ function = "TACH9"; ++ groups = "TACH9"; ++ }; ++ ++ pinctrl_ltpi1_tach10_default: ltpi1-tach10-default { ++ function = "TACH10"; ++ groups = "TACH10"; ++ }; ++ ++ pinctrl_ltpi1_tach11_default: ltpi1-tach11-default { ++ function = "TACH11"; ++ groups = "TACH11"; ++ }; ++ ++ pinctrl_ltpi1_tach12_default: ltpi1-tach12-default { ++ function = "TACH12"; ++ groups = "TACH12"; ++ }; ++ ++ pinctrl_ltpi1_tach13_default: ltpi1-tach13-default { ++ function = "TACH13"; ++ groups = "TACH13"; ++ }; ++ ++ pinctrl_ltpi1_tach14_default: ltpi1-tach14-default { ++ function = "TACH14"; ++ groups = "TACH14"; ++ }; ++ ++ pinctrl_ltpi1_tach15_default: ltpi1-tach15-default { ++ function = "TACH15"; ++ groups = "TACH15"; ++ }; ++ ++ pinctrl_ltpi1_jtagm_default: ltpi1-jtagm-default { ++ function = "JTAGM"; ++ groups = "JTAGM"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c0_default: ltpi1-hvi3c0-default { ++ function = "I3C0"; ++ groups = "HVI3C0"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c1_default: ltpi1-hvi3c1-default { ++ function = "I3C1"; ++ groups = "HVI3C1"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c2_default: ltpi1-hvi3c2-default { ++ function = "I3C2"; ++ groups = "HVI3C2"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c3_default: ltpi1-hvi3c3-default { ++ function = "I3C3"; ++ groups = "HVI3C3"; ++ }; ++ ++ pinctrl_ltpi1_i3c4_default: ltpi1-i3c4-default { ++ function = "I3C4"; ++ groups = "I3C4"; ++ }; ++ ++ pinctrl_ltpi1_i3c5_default: ltpi1-i3c5-default { ++ function = "I3C5"; ++ groups = "I3C5"; ++ }; ++ ++ pinctrl_ltpi1_i3c6_default: ltpi1-i3c6-default { ++ function = "I3C6"; ++ groups = "I3C6"; ++ }; ++ ++ pinctrl_ltpi1_i3c7_default: ltpi1-i3c7-default { ++ function = "I3C7"; ++ groups = "I3C7"; ++ }; ++ ++ pinctrl_ltpi1_i3c8_default: ltpi1-i3c8-default { ++ function = "I3C8"; ++ groups = "I3C8"; ++ }; ++ ++ pinctrl_ltpi1_i3c9_default: ltpi1-i3c9-default { ++ function = "I3C9"; ++ groups = "I3C9"; ++ }; ++ ++ pinctrl_ltpi1_i3c10_default: ltpi1-i3c10-default { ++ function = "I3C10"; ++ groups = "I3C10"; ++ }; ++ ++ pinctrl_ltpi1_i3c11_default: ltpi1-i3c11-default { ++ function = "I3C11"; ++ groups = "I3C11"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c12_default: ltpi1-hvi3c12-default { ++ function = "I3C12"; ++ groups = "HVI3C12"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c13_default: ltpi1-hvi3c13-default { ++ function = "I3C13"; ++ groups = "HVI3C13"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c14_default: ltpi1-hvi3c14-default { ++ function = "I3C14"; ++ groups = "HVI3C14"; ++ }; ++ ++ pinctrl_ltpi1_hvi3c15_default: ltpi1-hvi3c15-default { ++ function = "I3C15"; ++ groups = "HVI3C15"; ++ }; ++ ++ pinctrl_ltpi1_adc0_default: ltpi1-adc0-default { ++ function = "ADC0"; ++ groups = "ADC0"; ++ }; ++ ++ pinctrl_ltpi1_adc1_default: ltpi1-adc1-default { ++ function = "ADC1"; ++ groups = "ADC1"; ++ }; ++ ++ pinctrl_ltpi1_adc2_default: ltpi1-adc2-default { ++ function = "ADC2"; ++ groups = "ADC2"; ++ }; ++ ++ pinctrl_ltpi1_adc3_default: ltpi1-adc3-default { ++ function = "ADC3"; ++ groups = "ADC3"; ++ }; ++ ++ pinctrl_ltpi1_adc4_default: ltpi1-adc4-default { ++ function = "ADC4"; ++ groups = "ADC4"; ++ }; ++ ++ pinctrl_ltpi1_adc5_default: ltpi1-adc5-default { ++ function = "ADC5"; ++ groups = "ADC5"; ++ }; ++ ++ pinctrl_ltpi1_adc6_default: ltpi1-adc6-default { ++ function = "ADC6"; ++ groups = "ADC6"; ++ }; ++ ++ pinctrl_ltpi1_adc7_default: ltpi1-adc7-default { ++ function = "ADC7"; ++ groups = "ADC7"; ++ }; ++ ++ pinctrl_ltpi1_adc8_default: ltpi1-adc8-default { ++ function = "ADC8"; ++ groups = "ADC8"; ++ }; ++ ++ pinctrl_ltpi1_adc9_default: ltpi1-adc9-default { ++ function = "ADC9"; ++ groups = "ADC9"; ++ }; ++ ++ pinctrl_ltpi1_adc10_default: ltpi1-adc10-default { ++ function = "ADC10"; ++ groups = "ADC10"; ++ }; ++ ++ pinctrl_ltpi1_adc11_default: ltpi1-adc11-default { ++ function = "ADC11"; ++ groups = "ADC11"; ++ }; ++ ++ pinctrl_ltpi1_adc12_default: ltpi1-adc12-default { ++ function = "ADC12"; ++ groups = "ADC12"; ++ }; ++ ++ pinctrl_ltpi1_adc13_default: ltpi1-adc13-default { ++ function = "ADC13"; ++ groups = "ADC13"; ++ }; ++ ++ pinctrl_ltpi1_adc14_default: ltpi1-adc14-default { ++ function = "ADC14"; ++ groups = "ADC14"; ++ }; ++ ++ pinctrl_ltpi1_adc15_default: ltpi1-adc15-default { ++ function = "ADC15"; ++ groups = "ADC15"; ++ }; ++ ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-ltpi1.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-ltpi1.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-ltpi1.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-ltpi1.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,660 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++#include ++#include ++ ++/ { ++ aliases { ++ i3c200 = <pi1_i3c0; ++ i3c201 = <pi1_i3c1; ++ i3c202 = <pi1_i3c2; ++ i3c203 = <pi1_i3c3; ++ i3c204 = <pi1_i3c4; ++ i3c205 = <pi1_i3c5; ++ i3c206 = <pi1_i3c6; ++ i3c207 = <pi1_i3c7; ++ i3c208 = <pi1_i3c8; ++ i3c209 = <pi1_i3c9; ++ i3c210 = <pi1_i3c10; ++ i3c211 = <pi1_i3c11; ++ i3c212 = <pi1_i3c12; ++ i3c213 = <pi1_i3c13; ++ i3c214 = <pi1_i3c14; ++ i3c215 = <pi1_i3c15; ++ }; ++ ++ ltpi1_bus: ltpi1_bus@50000000 { ++ compatible = "simple-bus"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ ltpi1_syscon: syscon@50c02000 { ++ compatible = "aspeed,ast1700-scu", "syscon", "simple-mfd"; ++ reg = <0x0 0x50c02000 0x0 0x1000>; ++ ranges = <0x0 0x0 0x0 0x50c02000 0x0 0x1000>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ #clock-cells = <1>; ++ #reset-cells = <1>; ++ ++ ltpi1_rst: reset-controller@200 { ++ reg = <0x0 0x200 0x0 0x40>; ++ }; ++ ++ ltpi1_clk: clock-controller@240 { ++ reg = <0x0 0x240 0x0 0x1c0>; ++ }; ++ ++ ltpi1_pinctrl: pinctrl@400 { ++ compatible = "aspeed,ast1700-pinctrl"; ++ reg = <0x0 0x400 0x0 0x100>; ++ }; ++ }; ++ ++ ltpi1_spi2: spi@50030000 { ++ reg = <0x0 0x50030000 0x0 0x1f0>, <0x2 0x80000000 0x0 0x04000000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ clocks = <<pi1_syscon AST1700_CLK_AHB>; ++ resets = <<pi1_syscon AST1700_RESET_SPI2>; ++ num-cs = <2>; ++ ltpi-base = <0x0 0x54000000>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_pwm_tach: pwm-tach-controller@500c0000 { ++ compatible = "aspeed,ast2700-pwm-tach"; ++ reg = <0x0 0x500c0000 0 0x100>; ++ clocks = <<pi1_syscon AST1700_CLK_AHB>; ++ resets = <<pi1_syscon AST1700_RESET_PWM>; ++ #pwm-cells = <3>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_adc0: adc@50c00000 { ++ compatible = "aspeed,ast2700-adc0"; ++ reg = <0x0 0x50c00000 0 0x100>; ++ clocks = <<pi1_syscon AST1700_CLK_AHB>; ++ resets = <<pi1_syscon AST1700_RESET_ADC>; ++ interrupts-extended = <<pi1_intc1_0 24>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <<pi1_syscon>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_adc1: adc@50c00100 { ++ compatible = "aspeed,ast2700-adc1"; ++ reg = <0x0 0x50c00100 0x0 0x100>; ++ clocks = <<pi1_syscon AST1700_CLK_AHB>; ++ resets = <<pi1_syscon AST1700_RESET_ADC>; ++ interrupts-extended = <<pi1_intc1_0 24>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <<pi1_syscon>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_jtag: jtag@50c09000 { ++ compatible = "aspeed,ast2700-jtag"; ++ reg= <0x0 0x50c09000 0x0 0x40>; ++ clocks = <<pi1_syscon AST1700_CLK_AHB>; ++ resets = <<pi1_syscon AST1700_RESET_JTAG1>; ++ interrupts-extended = <<pi1_intc1_0 23>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c: bus@50c0f000 { ++ compatible = "simple-bus"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x50c0f000 0x1100>; ++ }; ++ ++ ltpi1_intc1: interrupt-controller@50c18000 { ++ compatible = "simple-mfd"; ++ reg = <0 0x50c18000 0 0x400>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges = <0x0 0x0 0x0 0x50c18000 0x0 0x400>; ++ ++ ltpi1_intc1_0: interrupt-controller@100 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x0 0x100 0x0 0x10>; ++ interrupts-extended = <&intc0_11 8>; ++ }; ++ ++ ltpi1_intc1_1: interrupt-controller@110 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x0 0x110 0x0 0x10>; ++ interrupts-extended = <&intc0_11 9>; ++ }; ++ }; ++ ++ ltpi1_i3c0: i3c0@50c20000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c20000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 0>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C0CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C0>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c0_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c1: i3c1@50c21000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c21000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 1>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C1CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C1>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c1_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c2: i3c2@50c22000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c22000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 2>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C2CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C2>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c2_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c3: i3c3@50c23000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c23000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 3>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C3CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C3>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c3_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c4: i3c4@50c24000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c24000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 4>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C4CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C4>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c4_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c5: i3c5@50c25000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c25000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 5>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C5CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C5>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c5_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c6: i3c6@50c26000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c26000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 6>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C6CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C6>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c6_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c7: i3c7@50c27000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c27000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 7>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C7CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C7>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c7_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c8: i3c8@50c28000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c28000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 8>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C8CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C8>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c8_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c9: i3c9@50c29000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c29000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 9>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C9CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C9>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c9_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c10: i3c10@50c2a000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c2a000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 10>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C10CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C10>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c10_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c11: i3c11@50c2b000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c2b000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 11>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C11CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C11>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i3c11_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c12: i3c12@50c2c000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c2c000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 12>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C12CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C12>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c12_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c13: i3c13@50c2d000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c2d000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 13>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C13CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C13>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c13_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c14: i3c14@50c2e000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c2e000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 14>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C14CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C14>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c14_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i3c15: i3c15@50c2f000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x50c2f000 0x0 0x1000>; ++ interrupts-extended = <<pi1_intc1_1 15>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_I3C15CLK>; ++ resets = <<pi1_syscon AST1700_RESET_I3C15>, <<pi1_syscon AST1700_RESET_I3CDMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_hvi3c15_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_ltpi: ltpi@50c34000 { ++ compatible = "aspeed-ast1700-ltpi"; ++ reg = <0x0 0x50c34000 0x0 0x100>; ++ clocks = <<pi1_syscon AST1700_CLK_GATE_LTPICLK>, ++ <<pi1_syscon AST1700_CLK_GATE_LTPIPHYCLK>; ++ clock-names = "ahb", "phy"; ++ resets = <<pi1_syscon AST1700_RESET_LTPI>; ++ remote-controller; ++ aspeed,scu = <<pi1_syscon>; ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++ }; ++ ++ ltpi1_wdt0: watchdog@50c37000 { ++ compatible = "aspeed,ast2700-wdt"; ++ reg = <0x0 0x50c37000 0x0 0x40>; ++ }; ++ }; ++}; ++ ++#include "aspeed-ltpi1-pinctrl.dtsi" ++ ++<pi1_i2c { ++ ltpi1_i2c_global: i2c-global-regs@0 { ++ compatible = "aspeed,i2c-global", "simple-mfd", "syscon"; ++ reg = <0x0 0x100>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c0: i2c-bus@100 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ reg = <0x100 0xA0>, <0x1C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 0>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c0_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c1: i2c-bus@200 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ reg = <0x200 0xA0>, <0x2C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 1>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c1_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c2: i2c-bus@300 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x300 0xA0>, <0x3C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 2>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c2_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c3: i2c-bus@400 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x400 0xA0>, <0x4C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 3>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c3_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c4: i2c-bus@500 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x500 0xA0>, <0x5C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ interrupts-extended = <<pi1_intc1_0 4>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c4_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c5: i2c-bus@600 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x600 0xA0>, <0x6C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 5>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c5_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c6: i2c-bus@700 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x700 0xA0>, <0x7C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 6>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c6_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c7: i2c-bus@800 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x800 0xA0>, <0x8C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 7>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c7_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c8: i2c-bus@900 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x900 0xA0>, <0x9C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 8>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c8_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c9: i2c-bus@a00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xA00 0xA0>, <0xAC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 9>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c9_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c10: i2c-bus@b00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xB00 0xA0>, <0xBC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 10>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c10_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c11: i2c-bus@c00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xC00 0xA0>, <0xCC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 11>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c11_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c12: i2c-bus@d00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xD00 0xA0>, <0xDC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 12>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c12_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c13: i2c-bus@e00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xE00 0xA0>, <0xEC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 13>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c13_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c14: i2c-bus@f00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xF00 0xA0>, <0xFC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 14>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c14_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi1_i2c15: i2c-bus@1000 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x1000 0xA0>, <0x10C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi1_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi1_syscon AST1700_CLK_APB>; ++ resets = <<pi1_syscon AST1700_RESET_I2C>; ++ interrupts-extended = <<pi1_intc1_0 15>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi1_i2c15_default>; ++ status = "disabled"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-ltpi1800.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-ltpi1800.dtsi +--- a/arch/arm64/boot/dts/aspeed/aspeed-ltpi1800.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/aspeed-ltpi1800.dtsi 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,647 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++#include ++#include ++ ++/ { ++ aliases { ++ i3c100 = <pi0_i3c0; ++ i3c101 = <pi0_i3c1; ++ i3c102 = <pi0_i3c2; ++ i3c103 = <pi0_i3c3; ++ i3c104 = <pi0_i3c4; ++ i3c105 = <pi0_i3c5; ++ i3c106 = <pi0_i3c6; ++ i3c107 = <pi0_i3c7; ++ i3c108 = <pi0_i3c8; ++ i3c109 = <pi0_i3c9; ++ i3c110 = <pi0_i3c10; ++ i3c111 = <pi0_i3c11; ++ i3c112 = <pi0_i3c12; ++ i3c113 = <pi0_i3c13; ++ i3c114 = <pi0_i3c14; ++ i3c115 = <pi0_i3c15; ++ }; ++ ++ ltpi0_bus: ltpi0_bus@30000000 { ++ compatible = "simple-bus"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ ltpi0_syscon: syscon@30c02000 { ++ compatible = "aspeed,ast1800-scu", "syscon", "simple-mfd"; ++ reg = <0x0 0x30c02000 0x0 0x1000>; ++ ranges = <0x0 0x0 0x0 0x30c02000 0x0 0x1000>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ #clock-cells = <1>; ++ #reset-cells = <1>; ++ ++ ltpi0_rst: reset-controller@200 { ++ reg = <0x0 0x200 0x0 0x40>; ++ }; ++ ++ ltpi0_clk: clock-controller@240 { ++ reg = <0x0 0x240 0x0 0x1c0>; ++ }; ++ ++ ltpi0_pinctrl: pinctrl@400 { ++ compatible = "aspeed,ast1700-pinctrl"; ++ reg = <0x0 0x400 0x0 0x100>; ++ }; ++ }; ++ ++ ltpi0_pwm_tach: pwm-tach-controller@300c0000 { ++ compatible = "aspeed,ast2700-pwm-tach"; ++ reg = <0x0 0x300c0000 0 0x100>; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_PWM>; ++ #pwm-cells = <3>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_adc0: adc@30c00000 { ++ compatible = "aspeed,ast2700-adc0"; ++ reg = <0x0 0x30c00000 0 0x100>; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_ADC>; ++ interrupts-extended = <<pi0_intc1_1 1>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <<pi0_syscon>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_adc1: adc@30c00100 { ++ compatible = "aspeed,ast2700-adc1"; ++ reg = <0x0 0x30c00100 0x0 0x100>; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_ADC>; ++ interrupts-extended = <<pi0_intc1_1 1>; ++ #io-channel-cells = <1>; ++ aspeed,scu = <<pi0_syscon>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_jtag: jtag@30c09000 { ++ compatible = "aspeed,ast2700-jtag"; ++ reg= <0x0 0x30c09000 0x0 0x40>; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_JTAG>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c: bus@30c0f000 { ++ compatible = "simple-bus"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x30c0f000 0x1100>; ++ }; ++ ++ ltpi0_intc1: interrupt-controller@30c18000 { ++ compatible = "simple-mfd"; ++ reg = <0 0x30c18000 0 0x400>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges = <0x0 0x0 0x0 0x30c18000 0x0 0x400>; ++ ++ ltpi0_intc1_0: interrupt-controller@100 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x0 0x100 0x0 0x10>; ++ interrupts-extended = <&intc0_11 6>; ++ }; ++ ++ ltpi0_intc1_1: interrupt-controller@110 { ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ compatible = "aspeed,ast2700-intc-ic"; ++ reg = <0x0 0x110 0x0 0x10>; ++ interrupts-extended = <&intc0_11 7>; ++ }; ++ }; ++ ++ ltpi0_i3c0: i3c0@30c20000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c20000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 16>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C0>; ++ resets = <<pi0_syscon AST1800_RESET_I3C0>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c0_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c1: i3c1@30c21000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c21000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 17>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C1>; ++ resets = <<pi0_syscon AST1800_RESET_I3C1>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c1_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c2: i3c2@30c22000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c22000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 18>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C2>; ++ resets = <<pi0_syscon AST1800_RESET_I3C2>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c2_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c3: i3c3@30c23000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c23000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 19>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C3>; ++ resets = <<pi0_syscon AST1800_RESET_I3C3>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c3_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c4: i3c4@30c24000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c24000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 20>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C4>; ++ resets = <<pi0_syscon AST1800_RESET_I3C4>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c4_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c5: i3c5@30c25000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c25000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 21>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C5>; ++ resets = <<pi0_syscon AST1800_RESET_I3C5>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c5_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c6: i3c6@30c26000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c26000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 22>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C6>; ++ resets = <<pi0_syscon AST1800_RESET_I3C6>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c6_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c7: i3c7@30c27000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c27000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 23>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C7>; ++ resets = <<pi0_syscon AST1800_RESET_I3C7>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c7_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c8: i3c8@30c28000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c28000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 24>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C8>; ++ resets = <<pi0_syscon AST1800_RESET_I3C8>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c8_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c9: i3c9@30c29000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c29000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 25>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C9>; ++ resets = <<pi0_syscon AST1800_RESET_I3C9>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c9_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c10: i3c10@30c2a000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2a000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 26>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C10>; ++ resets = <<pi0_syscon AST1800_RESET_I3C10>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c10_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c11: i3c11@30c2b000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2b000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 27>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C11>; ++ resets = <<pi0_syscon AST1800_RESET_I3C11>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i3c11_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c12: i3c12@30c2c000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2c000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 28>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C12>; ++ resets = <<pi0_syscon AST1800_RESET_I3C12>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c12_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c13: i3c13@30c2d000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2d000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 29>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C13>; ++ resets = <<pi0_syscon AST1800_RESET_I3C13>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c13_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c14: i3c14@30c2e000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2e000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 30>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C14>; ++ resets = <<pi0_syscon AST1800_RESET_I3C14>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c14_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i3c15: i3c15@30c2f000 { ++ compatible = "aspeed-i3c-hci"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x30c2f000 0x0 0x1000>; ++ interrupts-extended = <<pi0_intc1_0 31>; ++ i3c-scl-hz = <1000000>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_I3C15>; ++ resets = <<pi0_syscon AST1800_RESET_I3C15>, <<pi0_syscon AST1800_RESET_I3C_DMA>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_hvi3c15_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_ltpi: ltpi@30c34000 { ++ compatible = "aspeed-ltpi"; ++ reg = <0x0 0x30c34000 0x0 0x100>; ++ clocks = <<pi0_syscon AST1800_CLK_GATE_LTPI>; ++ clock-names = "ltpi"; ++ resets = <<pi0_syscon AST1800_RESET_LTPI>; ++ remote-controller; ++ aspeed,scu = <<pi0_syscon>; ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++ }; ++ ++ ltpi0_wdt0: watchdog@30c37000 { ++ compatible = "aspeed,ast2700-wdt"; ++ reg = <0x0 0x30c37000 0x0 0x40>; ++ }; ++ }; ++}; ++ ++#include "aspeed-ltpi0-pinctrl.dtsi" ++ ++<pi0_i2c { ++ ltpi0_i2c_global: i2c-global-regs@0 { ++ compatible = "aspeed,i2c-global", "simple-mfd", "syscon"; ++ reg = <0x0 0x100>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c0: i2c-bus@100 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x100 0xA0>, <0x1C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C0>; ++ interrupts-extended = <<pi0_intc1_0 0>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c0_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c1: i2c-bus@200 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x200 0xA0>, <0x2C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C1>; ++ interrupts-extended = <<pi0_intc1_0 1>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c1_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c2: i2c-bus@300 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x300 0xA0>, <0x3C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C2>; ++ interrupts-extended = <<pi0_intc1_0 2>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c2_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c3: i2c-bus@400 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x400 0xA0>, <0x4C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C3>; ++ interrupts-extended = <<pi0_intc1_0 3>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c3_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c4: i2c-bus@500 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x500 0xA0>, <0x5C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C4>; ++ interrupts-extended = <<pi0_intc1_0 4>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c4_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c5: i2c-bus@600 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x600 0xA0>, <0x6C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C5>; ++ interrupts-extended = <<pi0_intc1_0 5>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c5_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c6: i2c-bus@700 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x700 0xA0>, <0x7C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C6>; ++ interrupts-extended = <<pi0_intc1_0 6>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c6_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c7: i2c-bus@800 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x800 0xA0>, <0x8C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C7>; ++ interrupts-extended = <<pi0_intc1_0 7>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c7_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c8: i2c-bus@900 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x900 0xA0>, <0x9C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C8>; ++ interrupts-extended = <<pi0_intc1_0 8>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c8_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c9: i2c-bus@a00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xA00 0xA0>, <0xAC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C9>; ++ interrupts-extended = <<pi0_intc1_0 9>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c9_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c10: i2c-bus@b00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xB00 0xA0>, <0xBC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C10>; ++ interrupts-extended = <<pi0_intc1_0 10>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c10_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c11: i2c-bus@c00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xC00 0xA0>, <0xCC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C11>; ++ interrupts-extended = <<pi0_intc1_0 11>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c11_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c12: i2c-bus@d00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xD00 0xA0>, <0xDC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C12>; ++ interrupts-extended = <<pi0_intc1_0 12>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c12_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c13: i2c-bus@e00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xE00 0xA0>, <0xEC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C13>; ++ interrupts-extended = <<pi0_intc1_0 13>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c13_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c14: i2c-bus@f00 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0xF00 0xA0>, <0xFC0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C14>; ++ interrupts-extended = <<pi0_intc1_0 14>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c14_default>; ++ status = "disabled"; ++ }; ++ ++ ltpi0_i2c15: i2c-bus@1000 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x1000 0xA0>, <0x10C0 0x40>; ++ compatible = "aspeed,ast2700-i2c"; ++ aspeed,global-regs = <<pi0_i2c_global>; ++ aspeed,enable-dma; ++ clocks = <<pi0_syscon AST1800_CLK_AHB>; ++ resets = <<pi0_syscon AST1800_RESET_I2C15>; ++ interrupts-extended = <<pi0_intc1_0 15>; ++ clock-frequency = <100000>; ++ debounce-level = <2>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_i2c15_default>; ++ status = "disabled"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-ci-host.dts b/arch/arm64/boot/dts/aspeed/ast2700-ci-host.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-ci-host.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-ci-host.dts 2026-09-09 15:51:27.102504264 +0000 +@@ -0,0 +1,135 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-evb.dts" ++ ++/ { ++ model = "AST2700-CI-HOST"; ++}; ++ ++&bmc_dev0 { ++ status = "disabled"; ++}; ++ ++&xdma0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "disabled"; ++}; ++ ++&pcie0_mmbi0 { ++ status = "disabled"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&bmc_dev1 { ++ status = "disabled"; ++}; ++ ++&xdma1 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart2 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "disabled"; ++}; ++ ++&pcie1_mmbi4 { ++ status = "disabled"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "disabled"; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++ ++&mctp0 { ++ pcie_rc; ++}; ++ ++&mctp1 { ++ pcie_rc; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-dcscm.dts b/arch/arm64/boot/dts/aspeed/ast2700-dcscm.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-dcscm.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-dcscm.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,991 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++#include ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700-DCSCM"; ++ compatible = "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1{ ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++#if 0 ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++#endif ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <&adc0 7>, <&adc1 7>; ++ }; ++}; ++ ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm9_default>; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ aspeed,battery-sensing; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ aspeed,battery-sensing; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc15_default>; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&i3c0 { ++ /* BMC_HPM_I3C_I2C_14, If AST1060 I3C_BMC_PFR_SCM_SEL(GPION4)=0 and I3C_SCM_EN(GPION5)=0 */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ /* BMC_I2C_I3C1_SCL1 */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ /* BMC_I2C_I3C2_SCL2 */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ /* I3C_DBG_SCM */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++/* AST2700 i3c4 -> AST1060 i3c2 for MCTP over I3C. */ ++&i3c4 { ++ /* I3C_PFR_BMC */ ++ initial-role = "target"; ++ pid = <0x000007ec 0x06000000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ /* I3C_MNG_BMC_SCM */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ /* I3C_SPD_SCM */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++/* Enable UART2 and UART9 for obmc-console. */ ++&uart2 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart9 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++/* Enable UART7 and UART10 for obmc-console. */ ++&uart7 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart10 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++/* UART3, UART13 and UART14 will be tunnelded to LTPI UART channels. */ ++&uart3 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart13 { ++ status = "okay"; ++}; ++ ++&uart14 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ u-boot@0 { ++ reg = <0x0 0x400000>; // 4MB ++ label = "u-boot"; ++ }; ++ u-boot-env@400000 { ++ reg = <0x400000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ kernel@420000 { ++ reg = <0x420000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ rofs@d20000 { ++ reg = <0xd20000 0x24a0000>; // 36.625MB ++ label = "rofs"; ++ }; ++ rwfs@31c0000 { ++ reg = <0x31c0000 0xE40000>; // 14.25MB ++ label = "rwfs"; ++ }; ++ pfm@4000000 { ++ reg = <0x4000000 0x20000>; // 128KB ++ label = "pfm"; ++ }; ++ reserved-1@4020000 { ++ reg = <0x4020000 0x200000>; // 128KB ++ label = "reserved-1"; ++ }; ++ rc-image@4220000 { ++ reg = <0x4220000 0x3de0000>; // 63360KB ++ label = "rc-image"; ++ }; ++ image-stg@8000000 { ++ reg = <0x8000000 0x3de0000>; // 63360KB ++ label = "img-stg"; ++ }; ++ pfr-stg@bde0000 { ++ reg = <0xbde0000 0x100000>; // 1024KB ++ label = "pfr-stg"; ++ }; ++ cpld-stg@bee0000 { ++ reg = <0xbee0000 0x400000>; // 4096KB ++ label = "cpld-stg"; ++ }; ++ afm-stg@c2e0000 { ++ reg = <0xc2e0000 0x20000>; // 128KB ++ label = "afm-stg"; ++ }; ++ afm-rc@c300000 { ++ reg = <0xc300000 0x20000>; // 128KB ++ label = "afm-rc"; ++ }; ++ reserved-2@c320000 { ++ reg = <0xc320000 0x3ce0000>; // 62336KB ++ label = "reserved-2"; ++ }; ++ }; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ spi0_pch_bios@0 { ++ reg = <0x0 0x3fe0000>; ++ label = "spi0_pch_reserved"; ++ }; ++ spi0_pch_pfm@3fe0000 { ++ reg = <0x3fe0000 0x20000>; ++ label = "spi0_pch_pfm"; ++ }; ++ spi0_pch_stg@4000000 { ++ reg = <0x4000000 0x2000000>; ++ label = "spi0_pch_stg"; ++ }; ++ spi0_pch_rc@6000000 { ++ reg = <0x6000000 0x2000000>; ++ label = "spi0_pch_rc"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ spi1_pch_reserved@0 { ++ reg = <0x0 0x7f0000>; ++ label = "spi1_pch_reserved"; ++ }; ++ ++ spi1_pch_stg@7f0000 { ++ reg = <0x7f0000 0x1400000>; ++ label = "spi1_pch_stg"; ++ }; ++ ++ spi1_pch_rc@1bf0000 { ++ reg = <0x1bf0000 0x1400000>; ++ label = "spi1_pch_rc"; ++ }; ++ ++ spi1_pch_pfm@2ff0000 { ++ reg = <0x2ff0000 0x10000>; ++ label = "spi1_pch_pfm"; ++ }; ++ ++ spi1_pch_bios@3000000 { ++ reg = <0x3000000 0x1000000>; ++ label = "spi1_pch_bios"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++}; ++ ++<pi0 { ++ status = "okay"; ++}; ++ ++<pi1 { ++ /* Do not tunnel I2C10 to the HPM */ ++ i2c-tunneling = <0x2f>; ++ status = "okay"; ++}; ++ ++/* The LTPI GPIO table is defined in Chapter 6 of the AST2700 LTPI Design Guide v10.pdf. */ ++/* line 0:BMC_GPI0(LTPI0_INL16), 1:BMC_GPO0(LTPI0_ONL16), 2:BMC_GPI1(LTPI0_INL17), 3:BMC_GPO1(LTPI0_ONL17), etc... */ ++/* BMC_GPI[63:0] = LTPI0_INL[79:16], BMC_GPO[63:0] = LTPI0_ONL[79:16] */ ++/* BMC_GPI[71:64] = LTPI0_ILL[11:4], BMC_GPO[71:64] = LTPI0_OLL[11:4] */ ++/* BMC_GPI[111:72] = LTPI0_INL[127:88],BMC_GPO[111:72] = LTPI0_ONL[127:88] */ ++<pi0_gpio { ++ status = "okay"; ++ gpio-line-names = ++ /*00-07*/ "","","","","","","","", ++ /*08-15*/ "","","","","","","","", ++ /*16-23*/ "","FM_CPU_FBRK_DEBUG_N","","FM_BMC_TRUST_N","","FM_RST_BTN_OUT_CPU0_PLD_N_OE","","FM_PWR_BTN_OUT_CPU0_N", ++ /*24-31*/ "","FM_BMC_ONCTL_N","","","","","","", ++ /*32-39*/ "","BIOS_POST_CODE_LED_0","","BIOS_POST_CODE_LED_1","","BIOS_POST_CODE_LED_2","","BIOS_POST_CODE_LED_3", ++ /*40-47*/ "FP_ID_BTN_N","BIOS_POST_CODE_LED_4","FP_RST_BTN_N","BIOS_POST_CODE_LED_5","","BIOS_POST_CODE_LED_6","","BIOS_POST_CODE_LED_7", ++ /*48-55*/ "","A_P3V_BAT_SCALED_EN","","FM_TPM_EN_PULSE","","FM_SKT0_FAULT_LED","","FM_SKT1_FAULT_LED", ++ /*56-63*/ "","","","","","RST_BMC_SMB_PCIE_MUX_N","","SURPRISE_RESET", ++ /*64-71*/ "PWRGD_S0_PWROK_CPU0","","","","","","","", ++ /*72-79*/ "","","","","","","","", ++ /*80-87*/ "","","","","","","","", ++ /*88-95*/ "","","","","","","","", ++ /*96-103*/ "","","","","","","","", ++ /*104-111*/ "","","","","","","","", ++ /*112-119*/ "","","","","","","","", ++ /*120-127*/ "","","","","","","","", ++ /*128-135*/ "","","","","","","","", ++ /*136-143*/ "","","","","","","","", ++ /*144-151*/ "","","","","","","","", ++ /*152-159*/ "","","","","","","","", ++ /*160-167*/ "","","","","","","","", ++ /*168-175*/ "","","","","","","","", ++ /*176-183*/ "","","","","","","","", ++ /*184-191*/ "","","","","","","","", ++ /*192-199*/ "","","","","","","","", ++ /*200-207*/ "","","","","","","","", ++ /*208-215*/ "","","","","","","","", ++ /*216-223*/ "","","","","","","",""; ++ ++ gpio_17 { ++ gpio-hog; ++ gpios = <17 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "FM_CPU_FBRK_DEBUG_N"; ++ }; ++ gpio_19 { ++ gpio-hog; ++ gpios = <19 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "FM_BMC_TRUST_N"; ++ }; ++ gpio_25 { ++ gpio-hog; ++ gpios = <25 GPIO_ACTIVE_HIGH>; ++ output-low; ++ line-name = "FM_BMC_ONCTL_N"; ++ }; ++ gpio_53 { ++ gpio-hog; ++ gpios = <53 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "FM_SKT0_FAULT_LED"; ++ }; ++ gpio_61 { ++ gpio-hog; ++ gpios = <61 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "RST_BMC_SMB_PCIE_MUX_N"; ++ }; ++ gpio_63 { ++ gpio-hog; ++ gpios = <63 GPIO_ACTIVE_HIGH>; ++ output-low; ++ line-name = "SURPRISE_RESET"; ++ }; ++}; ++ ++&peci0 { ++ status = "okay"; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++&gpio1 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving>; ++ pinctrl-names = "default"; ++ ++ gpio-line-names = ++ /*A0-A7*/ "","","","","","","","", ++ /*B0-B7*/ "","","","","","","","", ++ /*C0-C7*/ "","","","","","","","", ++ /*D0-D7*/ "","","","","","","","", ++ /*E0-E7*/ "","","","","FP_LED_STATUS_GREEN_CPLD_N","FP_LED_STATUS_AMBER_CPLD_N","","", ++ /*F0-F7*/ "","","","","","","","", ++ /*G0-G7*/ "","","","","","","","", ++ /*H0-H7*/ "","","","","","","","", ++ /*I0-I7*/ "","","","","","","","", ++ /*J0-J7*/ "","","","","","","","", ++ /*K0-K7*/ "","","","","","","","", ++ /*L0-L7*/ "","","","","","","","", ++ /*M0-M7*/ "","","","","","","","", ++ /*N0-N7*/ "","","","","","","","", ++ /*O0-O7*/ "","","","","","","","", ++ /*P0-P7*/ "","","","","","","","", ++ /*Q0-Q7*/ "","","","","","","","", ++ /*R0-R7*/ "","","","","","","","", ++ /*S0-S7*/ "","","","","","","","", ++ /*T0-T7*/ "","","","","","","","", ++ /*U0-U7*/ "","","","","","SCM_PHY_RST","","", ++ /*V0-V7*/ "","","","","","","","", ++ /*W0-W7*/ "","","","","","","","", ++ /*X0-X7*/ "","","","","","","","", ++ /*Y0-Y7*/ "","","","","IRQ_PMBUS1_ALERT_LVC3_N","FM_NVME_LVC3_ALERT_N","","", ++ /*Z0-Z7*/ "","","FM_NODE_ID0_N","FM_NODE_ID1_N","","PWRGD_AUX_PWRGD_PFR_CPU0","PWRGD_AUX_PWRGD_PFR_CPU1","", ++ /*AA0-AA7*/ "BMC_BOOT_DONE","","","","","","","", ++ /*AB0-AB7*/ "","","","","","","","", ++ /*AC0-AC7*/ "","","","","","","","", ++ /*AD0-AD7*/ "","","","","","","","", ++ /*AE0-AE7*/ "","","","","","","",""; ++}; ++ ++/* AST2700 A1 support SGPIO slave to 72*2 pins. */ ++/* The SGPIO slave table is defined in Chapter 7 of the AST2700 LTPI Design Guide v10.pdf. */ ++/* line 0:BMC_SGPI0(SCM_GPI0), 1:BMC_SGPO0(SCM_GPO0), 2:BMC_SGPI1(SCM_GPI1), 3:BMC_SGPO1(SCM_GPO1), etc... */ ++/* line 32:BMC_SGPI16(LTPI0_INL80), 33:BMC_SGPO16(LTPI0_ONL80), 34:LTPI0_INL81, 35:LTPI0_ONL81, etc... */ ++/* BMC_SGPI[15:0] = SCM_GPI0[15:0], BMC_SGPO[15:0] = BMC_SGPO0[15:0] */ ++/* BMC_SGPI[23:16] = LTPI0_INL[87:80], BMC_SGPO[23:16] = LTPI0_ONL[87:80] */ ++/* BMC_SGPI[31:24] = LTPI1_INL[87:80], BMC_SGPO[31:24] = LTPI1_ONL[87:80] */ ++/* BMC_SGPI[47:32] = LTPI0_INL[15:0], BMC_SGPO[47:32] = LTPI0_ONL[15:0] */ ++/* BMC_SGPI[63:48] = LTPI1_INL[15:0], BMC_SGPO[63:48] = LTPI1_ONL[15:0] */ ++/* BMC_SGPI[71:64] = SREG_GPO[7:0], BMC_SGPO[71:64] = SREG_GPI[7:0] */ ++/* The designe guide only define SCM_GPO9(FP_PWR_BTN_PFR_N) for PFR output pin. */ ++/* We use AST2700 SGPIOS line 18 (FP_PWR_BTN_PFR_N_BMC_IN) for x86-power-control power button input. */ ++&sgpios { ++ status = "okay"; ++ gpio-line-names = ++ /*00-07*/ "","","","","","","","", ++ /*08-15*/ "","","","","","","","", ++ /*16-23*/ "","","FP_PWR_BTN_PFR_N_BMC_IN","","","","","", ++ /*24-31*/ "","","","","","","","", ++ /*32-39*/ "","","","","","","","", ++ /*40-47*/ "","","","","","","","", ++ /*48-55*/ "","","","","","","","", ++ /*56-63*/ "","","","","","","","", ++ /*64-71*/ "","","","","","","","", ++ /*72-79*/ "","","","","","","","", ++ /*80-87*/ "","","","","","","","", ++ /*88-95*/ "","","","","","","","", ++ /*96-103*/ "","","","","","","","", ++ /*104-111*/ "","","","","","","","", ++ /*112-119*/ "","","","","","","","", ++ /*120-127*/ "","","","","","","","", ++ /*128-135*/ "","","","","","","","", ++ /*136-143*/ "","","","","","","",""; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++#if 0 //eSPI1 and SD are multi-functional pin, SD default on ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++#endif ++ ++&lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <2>; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "okay"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <4>; ++}; ++ ++&lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <5>; ++}; ++ ++&lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <6>; ++}; ++ ++&lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <7>; ++}; ++ ++&lpc1_ibt { ++ status = "okay"; ++}; ++ ++&lpc1_mbox { ++ status = "okay"; ++}; ++ ++&lpc1_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc1_uart_routing { ++ status = "okay"; ++}; ++ ++&video0 { ++ status = "okay"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "okay"; ++ memory-region = <&video_engine_memory1>; ++}; ++ ++&disp_intf { ++ status = "okay"; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&rsss { ++ status = "okay"; ++}; ++ ++&ecdsa { ++ status = "okay"; ++}; ++ ++&hace { ++ dummy-key0 = < ++ 0x89878915 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key1 = < ++ 0x89878902 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key2 = < ++ 0x2C914FA3 0x1056D87B 0xC13D6AE2 0x5E44AF98 ++ 0x0DB97321 0xCA378CF6 0x62EE115D 0x3FA74B90 ++ >; ++ dummy-key3 = < ++ 0x9E18D25C 0x6FB17A43 0x348DE520 0x1269FAC7 ++ 0xAC5503DE 0x2DB61F88 0xEF9AC074 0xB84E6731 ++ >; ++ status = "okay"; ++}; ++ ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&bmc_dev1 { ++ status = "okay"; ++ memory-region = <&bmc_dev1_memory>; ++}; ++ ++&xdma1 { ++ status = "okay"; ++ memory-region = <&xdma_memory1>; ++}; ++ ++&pcie_vuart2 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart3 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <12>; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <13>; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <14>; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <15>; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "okay"; ++ bt-channel = <3>; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++/* Enable i2c0~i2c4 for LTPI testing. */ ++&i2c0 { ++ /* SMB_PMBUS1_SCM */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c1 { ++ /* SMB_IPMB_SCL1 */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c2 { ++ /* SMB_CPLD_SCL2 */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c3 { ++ /* SMB_PMBUS2_SCM */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c4 { ++ /* SMB_PMBUS1_SCM */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++/* Default i2c5 is tunnelded to LTPI. */ ++/* Enable i2c5 pinctrl for testing on board LM75. */ ++&i2c5 { ++ /* SMB_TMP_BMC */ ++ status = "okay"; ++}; ++ ++/* AST2700 i2c8 -> AST1060 i2c5 for PCH mailbox emulation test. */ ++&i2c8 { ++ /* SMB_PCIE_SCM */ ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c9 { ++ /* SMB_HOST_BMC */ ++ status = "okay"; ++ eeprom@50 { ++ compatible = "atmel,24c04"; ++ reg = <0x50>; ++ pagesize = <16>; ++ }; ++}; ++ ++/* AST2700 A1 i2c10 -> AST1060 i2c0 for PFR mailbox. */ ++&i2c10 { ++ /* SMB_HSBP_BMC */ ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c11 { ++ /* BMC_HPM_I3C_I2C_13 */ ++ status = "okay"; ++}; ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ahpd0_default>; ++}; ++ ++&vhubb0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2bhpd0_default>; ++}; ++ ++&vhubc { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&uhci1 { ++ status = "okay"; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; ++ ++#if 0 ++&soc0 { ++ mbox-ssp-0 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31480000 0x0 0x200000>, <0x0 0x10001000 0x0 0x1000>; ++ mboxes = <&mbox0 0>; ++ aspeed,tx-timeout = <100>; ++ }; ++}; ++#endif ++ ++#if 1 ++&soc0 { ++ mbox-bootmcu-1 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31880000 0x0 0x100000>, <0x4 0x31980000 0x0 0x100000>; ++ mboxes = <&mbox2 1>; ++ aspeed,tx-timeout = <10000>; ++ }; ++}; ++#endif +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb-dual.dts b/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb-dual.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb-dual.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb-dual.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,218 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-dcscm_ast1700-evb.dts" ++#include "aspeed-ltpi1.dtsi" ++ ++/ { ++ model = "AST2700-DCSCM_AST1700-EVB-DUAL"; ++ ++ ltpi1-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi1_adc0 0>, <<pi1_adc0 1>, <<pi1_adc0 2>, <<pi1_adc0 3>, ++ <<pi1_adc0 4>, <<pi1_adc0 5>, <<pi1_adc0 6>, <<pi1_adc0 7>, ++ <<pi1_adc1 0>, <<pi1_adc1 1>, <<pi1_adc1 2>, <<pi1_adc1 3>, ++ <<pi1_adc1 4>, <<pi1_adc1 5>, <<pi1_adc1 6>, <<pi1_adc1 7>; ++ }; ++}; ++ ++<pi1_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi1_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi1 { ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++}; ++ ++<pi1_gpio { ++ status = "okay"; ++}; ++ ++<pi1_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "disabled"; ++}; ++ ++&i2c7 { ++ status = "disabled"; ++}; ++ ++// The following I2C buses require enabling according to the ast2700-dcscm.dts. ++// i2c8 / i2c9 / i2c10 ++ ++&i2c11 { ++ status = "disabled"; ++}; ++ ++<pi1_i2c0 { ++ status = "okay"; ++}; ++ ++<pi1_i2c1 { ++ status = "okay"; ++}; ++ ++<pi1_i2c2 { ++ status = "okay"; ++}; ++ ++<pi1_i2c3 { ++ status = "okay"; ++}; ++ ++<pi1_i2c4 { ++ status = "okay"; ++}; ++ ++<pi1_i2c5 { ++ status = "okay"; ++}; ++ ++<pi1_i2c6 { ++ status = "okay"; ++}; ++ ++<pi1_i2c7 { ++ status = "okay"; ++}; ++ ++<pi1_i2c8 { ++ status = "okay"; ++}; ++ ++<pi1_i2c9 { ++ status = "okay"; ++}; ++ ++<pi1_i2c10 { ++ status = "okay"; ++}; ++ ++<pi1_i2c11 { ++ status = "okay"; ++}; ++ ++<pi1_i2c12 { ++ status = "okay"; ++}; ++ ++<pi1_i2c13 { ++ status = "okay"; ++}; ++ ++<pi1_i2c14 { ++ status = "okay"; ++}; ++ ++<pi1_i2c15 { ++ status = "okay"; ++}; ++ ++&uart8 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb.dts b/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1700-evb.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,456 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-dcscm.dts" ++#include "aspeed-ltpi0.dtsi" ++ ++/ { ++ model = "AST2700-DCSCM_AST1700-EVB"; ++ ++ ltpi_fan0: ltpi-pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan1: ltpi-pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan2: ltpi-pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan3: ltpi-pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan4: ltpi-pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan5: ltpi-pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan6: ltpi-pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan7: ltpi-pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan8: ltpi-pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan9: ltpi-pwm-fan9 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 9 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan10: ltpi-pwm-fan10 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 10 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan11: ltpi-pwm-fan11 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 11 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan12: ltpi-pwm-fan12 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 12 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan13: ltpi-pwm-fan13 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 13 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan14: ltpi-pwm-fan14 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 14 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan15: ltpi-pwm-fan15 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 15 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi0-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi0_adc0 0>, <<pi0_adc0 1>, <<pi0_adc0 2>, <<pi0_adc0 3>, ++ <<pi0_adc0 4>, <<pi0_adc0 5>, <<pi0_adc0 6>, <<pi0_adc0 7>, ++ <<pi0_adc1 0>, <<pi0_adc1 1>, <<pi0_adc1 2>, <<pi0_adc1 3>, ++ <<pi0_adc1 4>, <<pi0_adc1 5>, <<pi0_adc1 6>, <<pi0_adc1 7>; ++ }; ++}; ++ ++<pi0_pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_pwm0_default &pinctrl_ltpi0_pwm1_default ++ &pinctrl_ltpi0_pwm2_default &pinctrl_ltpi0_pwm3_default ++ &pinctrl_ltpi0_pwm4_default &pinctrl_ltpi0_pwm5_default ++ &pinctrl_ltpi0_pwm6_default &pinctrl_ltpi0_pwm7_default ++ &pinctrl_ltpi0_pwm8_default ++ &pinctrl_ltpi0_tach0_default &pinctrl_ltpi0_tach1_default ++ &pinctrl_ltpi0_tach2_default &pinctrl_ltpi0_tach3_default ++ &pinctrl_ltpi0_tach4_default &pinctrl_ltpi0_tach5_default ++ &pinctrl_ltpi0_tach6_default &pinctrl_ltpi0_tach7_default ++ &pinctrl_ltpi0_tach8_default &pinctrl_ltpi0_tach9_default ++ &pinctrl_ltpi0_tach10_default &pinctrl_ltpi0_tach11_default ++ &pinctrl_ltpi0_tach12_default &pinctrl_ltpi0_tach13_default ++ &pinctrl_ltpi0_tach14_default &pinctrl_ltpi0_tach15_default>; ++ ltpi_fan0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ ltpi_fan1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ ltpi_fan2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ ltpi_fan3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ ltpi_fan4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ ltpi_fan5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ ltpi_fan6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ ltpi_fan7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ ltpi_fan8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ ltpi_fan9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ ltpi_fan10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ ltpi_fan11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ ltpi_fan12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ ltpi_fan13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ ltpi_fan14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ ltpi_fan15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&sgpios { ++ /delete-property/ gpio-line-names; ++}; ++ ++<pi0_gpio { ++ /delete-property/ gpio-line-names; ++ /delete-node/ gpio_17; ++ /delete-node/ gpio_19; ++ /delete-node/ gpio_25; ++ /delete-node/ gpio_53; ++ /delete-node/ gpio_61; ++ /delete-node/ gpio_63; ++}; ++ ++<pi0_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_adc0_default &pinctrl_ltpi0_adc1_default ++ &pinctrl_ltpi0_adc2_default &pinctrl_ltpi0_adc3_default ++ &pinctrl_ltpi0_adc4_default &pinctrl_ltpi0_adc5_default ++ &pinctrl_ltpi0_adc6_default &pinctrl_ltpi0_adc7_default>; ++}; ++ ++<pi0_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_ltpi0_adc8_default &pinctrl_ltpi0_adc9_default ++ &pinctrl_ltpi0_adc10_default &pinctrl_ltpi0_adc11_default ++ &pinctrl_ltpi0_adc12_default &pinctrl_ltpi0_adc13_default ++ &pinctrl_ltpi0_adc14_default &pinctrl_ltpi0_adc15_default>; ++}; ++ ++<pi0 { ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++}; ++ ++<pi0_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "disabled"; ++}; ++ ++&i2c1 { ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "disabled"; ++}; ++ ++&i2c4 { ++ status = "disabled"; ++}; ++ ++&i2c5 { ++ status = "disabled"; ++}; ++ ++<pi0_i2c0 { ++ status = "okay"; ++}; ++ ++<pi0_i2c1 { ++ status = "okay"; ++}; ++ ++<pi0_i2c2 { ++ status = "okay"; ++}; ++ ++<pi0_i2c3 { ++ status = "okay"; ++}; ++ ++<pi0_i2c4 { ++ status = "okay"; ++}; ++ ++<pi0_i2c5 { ++ status = "okay"; ++}; ++ ++<pi0_i2c6 { ++ status = "okay"; ++}; ++ ++<pi0_i2c7 { ++ status = "okay"; ++}; ++ ++<pi0_i2c8 { ++ status = "okay"; ++}; ++ ++<pi0_i2c9 { ++ status = "okay"; ++}; ++ ++<pi0_i2c10 { ++ status = "okay"; ++}; ++ ++<pi0_i2c11 { ++ status = "okay"; ++}; ++ ++<pi0_i2c12 { ++ status = "okay"; ++}; ++ ++<pi0_i2c13 { ++ status = "okay"; ++}; ++ ++<pi0_i2c14 { ++ status = "okay"; ++}; ++ ++<pi0_i2c15 { ++ status = "okay"; ++}; ++ ++&uart3 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1800-evb.dts b/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1800-evb.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1800-evb.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-dcscm_ast1800-evb.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,436 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-dcscm.dts" ++#include "aspeed-ltpi1800.dtsi" ++ ++/ { ++ model = "AST2700-DCSCM_AST1800-EVB"; ++ ++ ltpi_fan0: ltpi-pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan1: ltpi-pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan2: ltpi-pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan3: ltpi-pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan4: ltpi-pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan5: ltpi-pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan6: ltpi-pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan7: ltpi-pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan8: ltpi-pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan9: ltpi-pwm-fan9 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 9 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan10: ltpi-pwm-fan10 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 10 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan11: ltpi-pwm-fan11 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 11 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan12: ltpi-pwm-fan12 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 12 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan13: ltpi-pwm-fan13 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 13 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan14: ltpi-pwm-fan14 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 14 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan15: ltpi-pwm-fan15 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 15 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi0-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi0_adc0 0>, <<pi0_adc0 1>, <<pi0_adc0 2>, <<pi0_adc0 3>, ++ <<pi0_adc0 4>, <<pi0_adc0 5>, <<pi0_adc0 6>, <<pi0_adc0 7>, ++ <<pi0_adc1 0>, <<pi0_adc1 1>, <<pi0_adc1 2>, <<pi0_adc1 3>, ++ <<pi0_adc1 4>, <<pi0_adc1 5>, <<pi0_adc1 6>, <<pi0_adc1 7>; ++ }; ++}; ++ ++<pi0_jtag { ++ status = "okay"; ++}; ++ ++<pi0_pwm_tach { ++ status = "okay"; ++ ltpi_fan0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ ltpi_fan1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ ltpi_fan2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ ltpi_fan3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ ltpi_fan4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ ltpi_fan5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ ltpi_fan6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ ltpi_fan7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ ltpi_fan8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ ltpi_fan9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ ltpi_fan10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ ltpi_fan11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ ltpi_fan12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ ltpi_fan13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ ltpi_fan14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ ltpi_fan15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&sgpios { ++ /delete-property/ gpio-line-names; ++}; ++ ++<pi0_gpio { ++ /delete-property/ gpio-line-names; ++ /delete-node/ gpio_17; ++ /delete-node/ gpio_19; ++ /delete-node/ gpio_25; ++ /delete-node/ gpio_53; ++ /delete-node/ gpio_61; ++ /delete-node/ gpio_63; ++}; ++ ++<pi0_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi0_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi0 { ++ i2c-tunneling = <0x0>; ++}; ++ ++<pi0_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "disabled"; ++}; ++ ++&i2c1 { ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "disabled"; ++}; ++ ++&i2c4 { ++ status = "disabled"; ++}; ++ ++&i2c5 { ++ status = "disabled"; ++}; ++ ++<pi0_i2c0 { ++ status = "okay"; ++}; ++ ++<pi0_i2c1 { ++ status = "okay"; ++}; ++ ++<pi0_i2c2 { ++ status = "okay"; ++}; ++ ++<pi0_i2c3 { ++ status = "okay"; ++}; ++ ++<pi0_i2c4 { ++ status = "okay"; ++}; ++ ++<pi0_i2c5 { ++ status = "okay"; ++}; ++ ++<pi0_i2c6 { ++ status = "okay"; ++}; ++ ++<pi0_i2c7 { ++ status = "okay"; ++}; ++ ++<pi0_i2c8 { ++ status = "okay"; ++}; ++ ++<pi0_i2c9 { ++ status = "okay"; ++}; ++ ++<pi0_i2c10 { ++ status = "okay"; ++}; ++ ++<pi0_i2c11 { ++ status = "okay"; ++}; ++ ++<pi0_i2c12 { ++ status = "okay"; ++}; ++ ++<pi0_i2c13 { ++ status = "okay"; ++}; ++ ++<pi0_i2c14 { ++ status = "okay"; ++}; ++ ++<pi0_i2c15 { ++ status = "okay"; ++}; ++ ++&uart3 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-evb-256-abr.dts b/arch/arm64/boot/dts/aspeed/ast2700-evb-256-abr.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-evb-256-abr.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-evb-256-abr.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,40 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-evb.dts" ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-256-abr.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-evb-s0.dts b/arch/arm64/boot/dts/aspeed/ast2700-evb-s0.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-evb-s0.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-evb-s0.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,383 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "ast2700-evb.dts" ++#include ++ ++/ { ++ model = "AST2700 EVB Test S0 Test"; ++}; ++ ++&pcie2 { ++ status = "disabled"; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++ ++&sdio_controller { ++ status = "disabled"; ++}; ++ ++&sdhci { ++ status = "disabled"; ++}; ++ ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++#if 0 //TODO: No enough memory for espi1 MMBI ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++#endif ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&can0 { ++ status = "disabled"; ++}; ++ ++// S0 use 0x10 as its slave address ++&i2c0 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c1 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c2 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c3 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c4 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c5 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c6 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c7 { ++ clock-frequency = <1000000>; ++ debounce-level = <8>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c8 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c9 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c10 { ++ clock-frequency = <1000000>; ++ debounce-level = <8>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c11 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c12 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c13 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c14 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c15 { ++ clock-frequency = <1000000>; ++ debounce-level = <8>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06011000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06013000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06015000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06017000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06019000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601B000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601D000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601F000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-evb-s1.dts b/arch/arm64/boot/dts/aspeed/ast2700-evb-s1.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-evb-s1.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-evb-s1.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "ast2700-evb.dts" ++#include ++ ++/ { ++ model = "AST2700 EVB Test S1 Test"; ++}; ++ ++&bmc_dev0 { ++ status = "disabled"; ++}; ++ ++&xdma0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "disabled"; ++}; ++ ++&pcie0_mmbi0 { ++ status = "disabled"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&bmc_dev1 { ++ status = "disabled"; ++}; ++ ++&xdma1 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart2 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "disabled"; ++}; ++ ++&pcie1_mmbi4 { ++ status = "disabled"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&sdio_controller { ++ status = "disabled"; ++}; ++ ++&sdhci { ++ status = "disabled"; ++}; ++ ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++#if 0 // TODO: No enough memory for espi1 MMBI ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++#endif ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&can0 { ++ status = "disabled"; ++}; ++ ++// S1 use 0x12 as its slave address ++&i2c0 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c1 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c2 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c3 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c4 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c5 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c6 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c7 { ++ clock-frequency = <1000000>; ++ debounce-level = <8>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c8 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c9 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c10 { ++ clock-frequency = <1000000>; ++ debounce-level = <8>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c11 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c12 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c13 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c14 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c15 { ++ clock-frequency = <1000000>; ++ debounce-level = <8>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i3c0 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&sgpios { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII0 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii0_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii0_default>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII1 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii1_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii1_default>; ++}; ++ ++&syscon1 { ++ mac0-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++ mac1-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++}; ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ad0_default>; ++}; ++ ++&vhubb0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2bd0_default>; ++}; ++ ++&vhubb1 { ++ status = "disabled"; ++}; ++ ++&vhubc { ++ status = "disabled"; ++}; ++ ++&vhubd { ++ status = "okay"; ++}; ++ ++&ehci2 { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "disabled"; ++}; ++ ++&pcie2 { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-evb.dts b/arch/arm64/boot/dts/aspeed/ast2700-evb.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-evb.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-evb.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,1307 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++#include "aspeed-g7.dtsi" ++#include ++#include ++#include ++ ++#define DUAL_NODE 0 // 1: DUAL_NODE, 0: SINGLE_NODE ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700 EVB"; ++ compatible = "aspeed,ast2700-evb", "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ rvas_memory0: rvas0{ ++ size = <0x0 0x04000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ rvas_memory1: rvas1{ ++ size = <0x0 0x04000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++#if 0 ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++#endif ++ ++ espi0_mcyc_memory: mcyc0 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ }; ++ ++ fan0: pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan1: pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan2: pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan3: pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan4: pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan5: pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan6: pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan7: pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan8: pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++}; ++ ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default ++ &pinctrl_pwm2_default &pinctrl_pwm3_default ++ &pinctrl_pwm4_default &pinctrl_pwm5_default ++ &pinctrl_pwm6_default &pinctrl_pwm7_default ++ &pinctrl_pwm8_default ++ &pinctrl_tach0_default &pinctrl_tach1_default ++ &pinctrl_tach2_default &pinctrl_tach3_default ++ &pinctrl_tach4_default &pinctrl_tach5_default ++ &pinctrl_tach6_default &pinctrl_tach7_default ++ &pinctrl_tach8_default &pinctrl_tach9_default ++ &pinctrl_tach10_default &pinctrl_tach11_default ++ &pinctrl_tach12_default &pinctrl_tach13_default ++ &pinctrl_tach14_default &pinctrl_tach15_default>; ++ fan-0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ fan-1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ fan-2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ fan-3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ fan-4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ fan-5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ fan-6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ fan-7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ fan-8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ fan-9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ fan-10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ fan-11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ fan-12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ fan-13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ fan-14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ fan-15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&edac { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++&mctp2 { ++ status = "okay"; ++ memory-region = <&mctp2_reserved>; ++}; ++ ++&sgpiom0 { ++ status = "okay"; ++}; ++ ++&sgpiom1 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc0_default &pinctrl_adc1_default ++ &pinctrl_adc2_default &pinctrl_adc3_default ++ &pinctrl_adc4_default &pinctrl_adc5_default ++ &pinctrl_adc6_default &pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc8_default &pinctrl_adc9_default ++ &pinctrl_adc10_default &pinctrl_adc11_default ++ &pinctrl_adc12_default &pinctrl_adc13_default ++ &pinctrl_adc14_default &pinctrl_adc15_default ++ &pinctrl_adc13_bias>; ++}; ++ ++&pinctrl0 { ++ pinctrl_emmcclk_driving: emmcclk-driving { ++ pins = "AC14"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_emmccmd_driving: emmccmd-driving { ++ pins = "AE15"; ++ drive-strength = <1>; ++ }; ++ pinctrl_emmc4bit_driving: emmcdat-driving { ++ pins = "AD14", "AE14", "AF14", "AB13"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_emmc8bit_driving: emmcdat-driving { ++ pins = "AD14", "AE14", "AF14", "AB13", "AF13", "AC13", "AD13", "AE13"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c12_15_hv_voltage: i3chv-voltage { ++ pins = "W25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c12_driving: i3c12-driving { ++ pins = "W25", "Y23"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c13_driving: i3c13-driving { ++ pins = "Y24", "W21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c14_driving: i3c14-driving { ++ pins = "AA23", "AC22"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c15_driving: i3c15-driving { ++ pins = "AB22", "Y21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_rgmii1_driving: rgmii1-driving { ++ pins = "D19", "C19", "D15", "B12", "B10", "P13", ++ "C18", "C6", "C7", "D7", "N13", "C8"; ++ drive-strength = <1>; ++ }; ++ pinctrl_adc13_bias: adc13-bias { ++ pins = "W14"; ++ bias-disable; ++ }; ++}; ++ ++&gpio1 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage &pinctrl_i3c12_15_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving ++ &pinctrl_i3c12_driving &pinctrl_i3c13_driving ++ &pinctrl_i3c14_driving &pinctrl_i3c15_driving>; ++ pinctrl-names = "default"; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++#if 0 ++&vuart0 { ++ virtual; ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ status = "okay"; ++}; ++#endif ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++#if 1 ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ tpm0: tpmdev@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <25000000>; ++ reg = <0>; ++ status = "okay"; ++ }; ++}; ++#else ++&spi2 { ++ compatible = "aspeed,ast2700-spi"; ++ pinctrl-0 = <&pinctrl_spi2_default &pinctrl_spi2_cs1_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ flash@0 { ++ status = "okay"; ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++#endif ++ ++&can0 { ++ status = "okay"; ++}; ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++#if 1 ++ bus-width = <4>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_emmc_default ++ &pinctrl_emmcclk_driving ++ &pinctrl_emmccmd_driving ++ &pinctrl_emmc4bit_driving>; ++#else ++ bus-width = <8>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_emmc_default ++ &pinctrl_emmcg8_default ++ &pinctrl_emmcclk_driving ++ &pinctrl_emmccmd_driving ++ &pinctrl_emmc8bit_driving>; ++#endif ++ ++ non-removable; ++ max-frequency = <200000000>; ++}; ++ ++&ufs_controller { ++ status = "okay"; ++}; ++ ++&ufs { ++ status = "okay"; ++ lanes-per-direction = <2>; ++ ref-clk-freq = <26000000>; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mdio1 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy1: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ /* For DDR5 board */ ++ ti,rx-internal-delay = ; ++ ti,tx-internal-delay = ; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy1>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii1_default &pinctrl_rgmii1_driving>; ++}; ++ ++#if 0 // Default to disable RC & SGMII ++#define PCIE2_RC 1 // 1: RC, 0: SGMII ++#if PCIE2_RC ++&pcie2 { ++ status = "okay"; ++}; ++#else ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++#endif ++#endif ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ memory-region = <&espi0_mcyc_memory>; ++ perif-rtc-enable; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++#if 0 // eDAF mode: Change 1 to enable MIX mode in Linux, but HW mode in SPL would be overwritten ++ flash-edaf-mode = <0x0>; ++ flash-edaf-tgt-addr = <&edaf0>; ++#endif ++}; ++ ++&rtc_over_espi0 { ++ status = "okay"; ++}; ++ ++/* Enable UART2 and UART9 for obmc-console. */ ++&uart2 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart9 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++#if DUAL_NODE ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ perif-rtc-enable; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++#if 0 // eDAF mode: Change 1 to enable MIX mode in Linux, but HW mode in SPL would be overwritten ++ flash-edaf-mode = <0x0>; ++ flash-edaf-tgt-addr = <&edaf1>; ++#endif ++}; ++ ++&rtc_over_espi1 { ++ status = "okay"; ++}; ++ ++/* Enable UART7 and UART10 for obmc-console. */ ++&uart7 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart10 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++#endif /* DUAL_NODE */ ++ ++&lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <2>; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "okay"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++#if 1 ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++#else ++&lpc0_pcc { ++ status = "okay"; ++ pcc-ports = <0x80>; ++}; ++#endif ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <4>; ++}; ++ ++&lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <5>; ++}; ++ ++&lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <6>; ++}; ++ ++&lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <7>; ++}; ++ ++&lpc1_ibt { ++ status = "okay"; ++}; ++ ++&lpc1_mbox { ++ status = "okay"; ++}; ++ ++#if 1 ++&lpc1_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++#else ++&lpc1_pcc { ++ status = "okay"; ++ pcc-ports = <0x80>; ++}; ++#endif ++ ++&lpc1_uart_routing { ++ status = "okay"; ++}; ++ ++#if 1 ++&video0 { ++ status = "okay"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "okay"; ++ memory-region = <&video_engine_memory1>; ++}; ++#else ++&rvas0{ ++ status = "okay"; ++ memory-region = <&rvas_memory0>; ++}; ++ ++&rvas1{ ++ status = "okay"; ++ memory-region = <&rvas_memory1>; ++}; ++#endif ++ ++&disp_intf { ++ status = "okay"; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&rsss { ++ status = "okay"; ++}; ++ ++&ecdsa { ++ status = "okay"; ++}; ++ ++&hace { ++ dummy-key0 = < ++ 0x89878915 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key1 = < ++ 0x89878902 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key2 = < ++ 0x2C914FA3 0x1056D87B 0xC13D6AE2 0x5E44AF98 ++ 0x0DB97321 0xCA378CF6 0x62EE115D 0x3FA74B90 ++ >; ++ dummy-key3 = < ++ 0x9E18D25C 0x6FB17A43 0x348DE520 0x1269FAC7 ++ 0xAC5503DE 0x2DB61F88 0xEF9AC074 0xB84E6731 ++ >; ++ status = "okay"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&pcie0_mmbi0 { ++ status = "okay"; ++ memory-region = <&pcie0_mmbi0_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else /* !PCIE0_EP */ ++&pcie0 { ++ status = "okay"; ++}; ++#endif /* PCIE0_EP */ ++ ++#if PCIE1_EP ++&bmc_dev1 { ++ status = "okay"; ++ memory-region = <&bmc_dev1_memory>; ++}; ++ ++&xdma1 { ++ status = "okay"; ++ memory-region = <&xdma_memory1>; ++}; ++ ++&pcie_vuart2 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart3 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <12>; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <13>; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <14>; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <15>; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "okay"; ++ bt-channel = <3>; ++}; ++ ++&pcie1_mmbi4 { ++ status = "okay"; ++ memory-region = <&pcie1_mmbi4_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else /* !PCIE1_EP */ ++&pcie1 { ++ status = "okay"; ++}; ++#endif /* PCIE1_EP */ ++ ++&sdio_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++ ++ vcc_sdhci0: regulator-vcc-sdhci0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "SDHCI0 Vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 6) GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; ++ ++ vccq_sdhci0: regulator-vccq-sdhci0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "SDHCI0 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 7) GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1>, ++ <1800000 0>; ++ }; ++}; ++ ++&sdhci { ++ status = "okay"; ++ bus-width = <4>; ++ max-frequency = <125000000>; ++ /* DDR50 bits in CAPA2 are not supported */ ++ sdhci-caps-mask = <0x6 0x0>; ++ sdhci-drive-type = /bits/ 8 <3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sd_default>; ++ vmmc-supply = <&vcc_sdhci0>; ++ vqmmc-supply = <&vccq_sdhci0>; ++ sd-uhs-sdr104; /* enable sdr104 to execute tuning */ ++}; ++ ++#if 1 ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ status = "okay"; ++}; ++ ++&i2c2 { ++ status = "okay"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&i2c4 { ++ status = "okay"; ++}; ++ ++&i2c5 { ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++}; ++ ++&i2c12 { ++ status = "okay"; ++}; ++ ++&i2c13 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&ehci0 { ++ status = "okay"; ++}; ++ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&uhci0 { ++ status = "okay"; ++}; ++ ++#endif ++ ++#if 0 ++&uphy3a { ++ status = "okay"; ++}; ++ ++&uphy3b { ++ status = "okay"; ++}; ++ ++&xhci0 { ++ status = "okay"; ++}; ++ ++&xhci1 { ++ status = "okay"; ++}; ++#endif ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ahpd0_default>; ++}; ++ ++&vhubb0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2bhpd0_default>; ++}; ++ ++&vhubc { ++ status = "okay"; ++#if 0 ++ pinctrl-0 = <&pinctrl_usb2cud_default>; ++ aspeed,uart-ports = <12>; ++#endif ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&uhci1 { ++ status = "okay"; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; ++ ++#if 0 ++&soc0 { ++ mbox-ssp-0 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31480000 0x0 0x200000>, <0x0 0x10001000 0x0 0x1000>; ++ mboxes = <&mbox0 0>; ++ aspeed,tx-timeout = <100>; ++ }; ++}; ++#endif ++ ++#if 1 ++&soc0 { ++ mbox-bootmcu-1 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31880000 0x0 0x100000>, <0x4 0x31980000 0x0 0x100000>; ++ mboxes = <&mbox2 1>; ++ aspeed,tx-timeout = <10000>; ++ }; ++}; ++#endif +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb-dual.dts b/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb-dual.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb-dual.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb-dual.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,218 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-evb_ast1700-evb.dts" ++#include "aspeed-ltpi1.dtsi" ++ ++/ { ++ model = "AST2700-EVB_AST1700-EVB-DUAL"; ++ ++ ltpi1-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi1_adc0 0>, <<pi1_adc0 1>, <<pi1_adc0 2>, <<pi1_adc0 3>, ++ <<pi1_adc0 4>, <<pi1_adc0 5>, <<pi1_adc0 6>, <<pi1_adc0 7>, ++ <<pi1_adc1 0>, <<pi1_adc1 1>, <<pi1_adc1 2>, <<pi1_adc1 3>, ++ <<pi1_adc1 4>, <<pi1_adc1 5>, <<pi1_adc1 6>, <<pi1_adc1 7>; ++ }; ++}; ++ ++<pi1_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi1_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi1 { ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++}; ++ ++<pi1_gpio { ++ status = "okay"; ++}; ++ ++<pi1_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "disabled"; ++}; ++ ++&i2c7 { ++ status = "disabled"; ++}; ++ ++// The following I2C buses require enabling according to the ast2700-dcscm.dts. ++// i2c8 / i2c9 / i2c10 ++ ++&i2c11 { ++ status = "disabled"; ++}; ++ ++<pi1_i2c0 { ++ status = "okay"; ++}; ++ ++<pi1_i2c1 { ++ status = "okay"; ++}; ++ ++<pi1_i2c2 { ++ status = "okay"; ++}; ++ ++<pi1_i2c3 { ++ status = "okay"; ++}; ++ ++<pi1_i2c4 { ++ status = "okay"; ++}; ++ ++<pi1_i2c5 { ++ status = "okay"; ++}; ++ ++<pi1_i2c6 { ++ status = "okay"; ++}; ++ ++<pi1_i2c7 { ++ status = "okay"; ++}; ++ ++<pi1_i2c8 { ++ status = "okay"; ++}; ++ ++<pi1_i2c9 { ++ status = "okay"; ++}; ++ ++<pi1_i2c10 { ++ status = "okay"; ++}; ++ ++<pi1_i2c11 { ++ status = "okay"; ++}; ++ ++<pi1_i2c12 { ++ status = "okay"; ++}; ++ ++<pi1_i2c13 { ++ status = "okay"; ++}; ++ ++<pi1_i2c14 { ++ status = "okay"; ++}; ++ ++<pi1_i2c15 { ++ status = "okay"; ++}; ++ ++&uart8 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb.dts b/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-evb_ast1700-evb.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,456 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-evb.dts" ++#include "aspeed-ltpi0.dtsi" ++ ++/ { ++ model = "AST2700-EVB_AST1700-EVB"; ++ ++ ltpi_fan0: ltpi-pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan1: ltpi-pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan2: ltpi-pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan3: ltpi-pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan4: ltpi-pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan5: ltpi-pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan6: ltpi-pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan7: ltpi-pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan8: ltpi-pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan9: ltpi-pwm-fan9 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 9 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan10: ltpi-pwm-fan10 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 10 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan11: ltpi-pwm-fan11 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 11 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan12: ltpi-pwm-fan12 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 12 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan13: ltpi-pwm-fan13 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 13 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan14: ltpi-pwm-fan14 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 14 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan15: ltpi-pwm-fan15 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 15 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi0-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi0_adc0 0>, <<pi0_adc0 1>, <<pi0_adc0 2>, <<pi0_adc0 3>, ++ <<pi0_adc0 4>, <<pi0_adc0 5>, <<pi0_adc0 6>, <<pi0_adc0 7>, ++ <<pi0_adc1 0>, <<pi0_adc1 1>, <<pi0_adc1 2>, <<pi0_adc1 3>, ++ <<pi0_adc1 4>, <<pi0_adc1 5>, <<pi0_adc1 6>, <<pi0_adc1 7>; ++ }; ++}; ++ ++<pi0_pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_pwm0_default &pinctrl_ltpi0_pwm1_default ++ &pinctrl_ltpi0_pwm2_default &pinctrl_ltpi0_pwm3_default ++ &pinctrl_ltpi0_pwm4_default &pinctrl_ltpi0_pwm5_default ++ &pinctrl_ltpi0_pwm6_default &pinctrl_ltpi0_pwm7_default ++ &pinctrl_ltpi0_pwm8_default ++ &pinctrl_ltpi0_tach0_default &pinctrl_ltpi0_tach1_default ++ &pinctrl_ltpi0_tach2_default &pinctrl_ltpi0_tach3_default ++ &pinctrl_ltpi0_tach4_default &pinctrl_ltpi0_tach5_default ++ &pinctrl_ltpi0_tach6_default &pinctrl_ltpi0_tach7_default ++ &pinctrl_ltpi0_tach8_default &pinctrl_ltpi0_tach9_default ++ &pinctrl_ltpi0_tach10_default &pinctrl_ltpi0_tach11_default ++ &pinctrl_ltpi0_tach12_default &pinctrl_ltpi0_tach13_default ++ &pinctrl_ltpi0_tach14_default &pinctrl_ltpi0_tach15_default>; ++ ltpi_fan0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ ltpi_fan1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ ltpi_fan2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ ltpi_fan3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ ltpi_fan4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ ltpi_fan5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ ltpi_fan6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ ltpi_fan7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ ltpi_fan8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ ltpi_fan9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ ltpi_fan10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ ltpi_fan11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ ltpi_fan12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ ltpi_fan13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ ltpi_fan14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ ltpi_fan15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&sgpios { ++ /delete-property/ gpio-line-names; ++}; ++ ++<pi0_gpio { ++ /delete-property/ gpio-line-names; ++ /delete-node/ gpio_17; ++ /delete-node/ gpio_19; ++ /delete-node/ gpio_25; ++ /delete-node/ gpio_53; ++ /delete-node/ gpio_61; ++ /delete-node/ gpio_63; ++}; ++ ++<pi0_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_adc0_default &pinctrl_ltpi0_adc1_default ++ &pinctrl_ltpi0_adc2_default &pinctrl_ltpi0_adc3_default ++ &pinctrl_ltpi0_adc4_default &pinctrl_ltpi0_adc5_default ++ &pinctrl_ltpi0_adc6_default &pinctrl_ltpi0_adc7_default>; ++}; ++ ++<pi0_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_ltpi0_adc8_default &pinctrl_ltpi0_adc9_default ++ &pinctrl_ltpi0_adc10_default &pinctrl_ltpi0_adc11_default ++ &pinctrl_ltpi0_adc12_default &pinctrl_ltpi0_adc13_default ++ &pinctrl_ltpi0_adc14_default &pinctrl_ltpi0_adc15_default>; ++}; ++ ++<pi0 { ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++}; ++ ++<pi0_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "disabled"; ++}; ++ ++&i2c1 { ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "disabled"; ++}; ++ ++&i2c4 { ++ status = "disabled"; ++}; ++ ++&i2c5 { ++ status = "disabled"; ++}; ++ ++<pi0_i2c0 { ++ status = "okay"; ++}; ++ ++<pi0_i2c1 { ++ status = "okay"; ++}; ++ ++<pi0_i2c2 { ++ status = "okay"; ++}; ++ ++<pi0_i2c3 { ++ status = "okay"; ++}; ++ ++<pi0_i2c4 { ++ status = "okay"; ++}; ++ ++<pi0_i2c5 { ++ status = "okay"; ++}; ++ ++<pi0_i2c6 { ++ status = "okay"; ++}; ++ ++<pi0_i2c7 { ++ status = "okay"; ++}; ++ ++<pi0_i2c8 { ++ status = "okay"; ++}; ++ ++<pi0_i2c9 { ++ status = "okay"; ++}; ++ ++<pi0_i2c10 { ++ status = "okay"; ++}; ++ ++<pi0_i2c11 { ++ status = "okay"; ++}; ++ ++<pi0_i2c12 { ++ status = "okay"; ++}; ++ ++<pi0_i2c13 { ++ status = "okay"; ++}; ++ ++<pi0_i2c14 { ++ status = "okay"; ++}; ++ ++<pi0_i2c15 { ++ status = "okay"; ++}; ++ ++&uart3 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-evbeeprom.dts b/arch/arm64/boot/dts/aspeed/ast2700-evbeeprom.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-evbeeprom.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-evbeeprom.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,1174 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++#include ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700-EVB"; ++ compatible = "aspeed,ast2700-evb", "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x02c00000>; ++ alignment = <0x0 0x00100000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1{ ++ size = <0x0 0x02c00000>; ++ alignment = <0x0 0x00100000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ xdma_memory0: xdma0 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++ }; ++ ++ xdma_memory1: xdma1 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++ }; ++ }; ++ ++ fan0: pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan1: pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan2: pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan3: pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan4: pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan5: pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan6: pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan7: pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan8: pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++}; ++ ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default ++ &pinctrl_pwm2_default &pinctrl_pwm3_default ++ &pinctrl_pwm4_default &pinctrl_pwm5_default ++ &pinctrl_pwm6_default &pinctrl_pwm7_default ++ &pinctrl_pwm8_default ++ &pinctrl_tach0_default &pinctrl_tach1_default ++ &pinctrl_tach2_default &pinctrl_tach3_default ++ &pinctrl_tach4_default &pinctrl_tach5_default ++ &pinctrl_tach6_default &pinctrl_tach7_default ++ &pinctrl_tach8_default &pinctrl_tach9_default ++ &pinctrl_tach10_default &pinctrl_tach11_default ++ &pinctrl_tach12_default &pinctrl_tach13_default ++ &pinctrl_tach14_default &pinctrl_tach15_default>; ++ fan-0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ fan-1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ fan-2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ fan-3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ fan-4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ fan-5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ fan-6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ fan-7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ fan-8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ fan-9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ fan-10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ fan-11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ fan-12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ fan-13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ fan-14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ fan-15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++&mctp2 { ++ status = "okay"; ++ memory-region = <&mctp2_reserved>; ++}; ++ ++&sgpiom0 { ++ status = "okay"; ++}; ++ ++&sgpiom1 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc0_default &pinctrl_adc1_default ++ &pinctrl_adc2_default &pinctrl_adc3_default ++ &pinctrl_adc4_default &pinctrl_adc5_default ++ &pinctrl_adc6_default &pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc8_default &pinctrl_adc9_default ++ &pinctrl_adc10_default &pinctrl_adc11_default ++ &pinctrl_adc12_default &pinctrl_adc13_default ++ &pinctrl_adc14_default &pinctrl_adc15_default>; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c12_15_hv_voltage: i3chv-voltage { ++ pins = "W25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c12_driving: i3c12-driving { ++ pins = "W25", "Y23"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c13_driving: i3c13-driving { ++ pins = "Y24", "W21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c14_driving: i3c14-driving { ++ pins = "AA23", "AC22"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c15_driving: i3c15-driving { ++ pins = "AB22", "Y21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_rgmii1_driving: rgmii1-driving { ++ pins = "D19", "C19", "D15", "B12", "B10", "P13", ++ "C18", "C6", "C7", "D7", "N13", "C8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&gpio0 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage &pinctrl_i3c12_15_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving ++ &pinctrl_i3c12_driving &pinctrl_i3c13_driving ++ &pinctrl_i3c14_driving &pinctrl_i3c15_driving>; ++ pinctrl-names = "default"; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++#if 1 ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ spi-aspeed-full-duplex; ++ ++ tpm0: tpmdev@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <34000000>; ++ reg = <0>; ++ status = "okay"; ++ }; ++}; ++#else ++&spi2 { ++ compatible = "aspeed,ast2700-spi"; ++ pinctrl-0 = <&pinctrl_spi2_default &pinctrl_spi2_cs1_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ flash@0 { ++ status = "okay"; ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++#endif ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++#if 1 ++ bus-width = <4>; ++#else ++ bus-width = <8>; ++ pinctrl-0 = <&pinctrl_emmc_default ++ &pinctrl_emmcg8_default>; ++#endif ++ non-removable; ++ max-frequency = <200000000>; ++}; ++ ++&ufs_controller { ++ status = "okay"; ++}; ++ ++&ufs { ++ status = "okay"; ++ lanes-per-direction = <2>; ++ ref-clk-freq = <26000000>; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mdio1 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy1: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rgmii"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rgmii"; ++ phy-handle = <ðphy1>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii1_default &pinctrl_rgmii1_driving>; ++}; ++ ++#if 0 ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++ ++&pcie2 { ++ status = "okay"; ++}; ++#endif ++ ++&syscon1 { ++ mac0-clk-delay = <0x33 0x30 ++ 0x10 0x10 ++ 0x10 0x10>; ++ mac1-clk-delay = <0x31 0x31 ++ 0x10 0x10 ++ 0x10 0x10>; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <2>; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "okay"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <4>; ++}; ++ ++&lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <5>; ++}; ++ ++&lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <6>; ++}; ++ ++&lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <7>; ++}; ++ ++&lpc1_ibt { ++ status = "okay"; ++}; ++ ++&lpc1_mbox { ++ status = "okay"; ++}; ++ ++&lpc1_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc1_uart_routing { ++ status = "okay"; ++}; ++ ++&video0 { ++ status = "okay"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "okay"; ++ memory-region = <&video_engine_memory1>; ++}; ++ ++&disp_intf { ++ status = "okay"; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&rsss { ++ status = "okay"; ++}; ++ ++&ecdsa { ++ status = "okay"; ++}; ++ ++&hace { ++ status = "okay"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&pcie0_mmbi0 { ++ status = "okay"; ++ memory-region = <&pcie0_mmbi0_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else ++&pcie0 { ++ status = "okay"; ++}; ++#endif ++ ++#if PCIE1_EP ++&bmc_dev1 { ++ status = "okay"; ++ memory-region = <&bmc_dev1_memory>; ++}; ++ ++&xdma1 { ++ status = "okay"; ++ memory-region = <&xdma_memory1>; ++}; ++ ++&pcie_vuart2 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart3 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <12>; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <13>; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <14>; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <15>; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "okay"; ++ bt-channel = <3>; ++}; ++ ++&pcie1_mmbi4 { ++ status = "okay"; ++ memory-region = <&pcie1_mmbi4_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else ++&pcie1 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&i3c0 { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++#endif ++ ++&sdio_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++ ++ vcc_sdhci0: regulator-vcc-sdhci0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "SDHCI0 Vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio0 ASPEED_GPIO(G, 6) GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; ++ ++ vccq_sdhci0: regulator-vccq-sdhci0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "SDHCI0 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio0 ASPEED_GPIO(G, 7) GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1>, ++ <1800000 0>; ++ }; ++ ++}; ++ ++&sdhci { ++ status = "okay"; ++ bus-width = <4>; ++ max-frequency = <100000000>; ++ /* DDR50 bits in CAPA2 are not supported */ ++ sdhci-caps-mask = <0x6 0x0>; ++ sdhci-drive-type = /bits/ 8 <3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sd_default>; ++ vmmc-supply = <&vcc_sdhci0>; ++ vqmmc-supply = <&vccq_sdhci0>; ++ sd-uhs-sdr104; /* enable sdr104 to execute tuning */ ++}; ++ ++&i2c0 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c1 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c2 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c3 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c4 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c5 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c6 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c7 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c8 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c9 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c10 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c11 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c12 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c13 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c14 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++&i2c15 { ++ status = "okay"; ++ clock-frequency = <1000000>; ++ multi-master; ++}; ++ ++#if 0 ++&ehci0 { ++ status = "okay"; ++}; ++ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&uhci0 { ++ status = "okay"; ++}; ++ ++#endif ++ ++#if 0 ++&uphy3a { ++ status = "okay"; ++}; ++ ++&uphy3b { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&xhci0 { ++ status = "okay"; ++}; ++ ++&xhci1 { ++ status = "okay"; ++}; ++#endif ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ahpd0_default>; ++}; ++ ++&vhubb1 { ++ status = "okay"; ++}; ++ ++&vhubc { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&uhci1 { ++ status = "okay"; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-fpga.dts b/arch/arm64/boot/dts/aspeed/ast2700-fpga.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-fpga.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-fpga.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,1127 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++#include ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 1 // 1: EP, 0: RC ++#define PCIE2_RC 1 // 1: RC, 0: SGMII ++ ++/ { ++ model = "AST2700-FPGA"; ++ compatible = "aspeed,ast2700-evb", "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x02c00000>; ++ alignment = <0x0 0x00100000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1{ ++ size = <0x0 0x02c00000>; ++ alignment = <0x0 0x00100000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ }; ++ ++ fan0: pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan1: pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan2: pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan3: pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan4: pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan5: pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan6: pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan7: pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan8: pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++}; ++ ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default ++ &pinctrl_pwm2_default &pinctrl_pwm3_default ++ &pinctrl_pwm4_default &pinctrl_pwm5_default ++ &pinctrl_pwm6_default &pinctrl_pwm7_default ++ &pinctrl_pwm8_default ++ &pinctrl_tach0_default &pinctrl_tach1_default ++ &pinctrl_tach2_default &pinctrl_tach3_default ++ &pinctrl_tach4_default &pinctrl_tach5_default ++ &pinctrl_tach6_default &pinctrl_tach7_default ++ &pinctrl_tach8_default &pinctrl_tach9_default ++ &pinctrl_tach10_default &pinctrl_tach11_default ++ &pinctrl_tach12_default &pinctrl_tach13_default ++ &pinctrl_tach14_default &pinctrl_tach15_default>; ++ fan-0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ fan-1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ fan-2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ fan-3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ fan-4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ fan-5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ fan-6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ fan-7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ fan-8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ fan-9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ fan-10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ fan-11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ fan-12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ fan-13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ fan-14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ fan-15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++&mctp2 { ++ status = "okay"; ++ memory-region = <&mctp2_reserved>; ++}; ++ ++&sgpiom0 { ++ status = "okay"; ++}; ++ ++&sgpiom1 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc0_default &pinctrl_adc1_default ++ &pinctrl_adc2_default &pinctrl_adc3_default ++ &pinctrl_adc4_default &pinctrl_adc5_default ++ &pinctrl_adc6_default &pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc8_default &pinctrl_adc9_default ++ &pinctrl_adc10_default &pinctrl_adc11_default ++ &pinctrl_adc12_default &pinctrl_adc13_default ++ &pinctrl_adc14_default &pinctrl_adc15_default>; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c12_15_hv_voltage: i3chv-voltage { ++ pins = "W25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c12_driving: i3c12-driving { ++ pins = "W25", "Y23"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c13_driving: i3c13-driving { ++ pins = "Y24", "W21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c14_driving: i3c14-driving { ++ pins = "AA23", "AC22"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c15_driving: i3c15-driving { ++ pins = "AB22", "Y21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_rgmii1_driving: rgmii1-driving { ++ pins = "D19", "C19", "D15", "B12", "B10", "P13", ++ "C18", "C6", "C7", "D7", "N13", "C8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&gpio0 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage &pinctrl_i3c12_15_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving ++ &pinctrl_i3c12_driving &pinctrl_i3c13_driving ++ &pinctrl_i3c14_driving &pinctrl_i3c15_driving>; ++ pinctrl-names = "default"; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++#if 1 ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ spi-aspeed-full-duplex; ++ ++ tpm0: tpmdev@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <25000000>; ++ reg = <0>; ++ status = "okay"; ++ }; ++}; ++#else ++&spi2 { ++ compatible = "aspeed,ast2700-spi"; ++ pinctrl-0 = <&pinctrl_spi2_default &pinctrl_spi2_cs1_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ flash@0 { ++ status = "okay"; ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++#endif ++ ++&can0 { ++ status = "okay"; ++}; ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++#if 1 ++ bus-width = <4>; ++#else ++ bus-width = <8>; ++ pinctrl-0 = <&pinctrl_emmc_default ++ &pinctrl_emmcg8_default>; ++#endif ++ non-removable; ++ max-frequency = <200000000>; ++}; ++ ++&ufs_controller { ++ status = "okay"; ++}; ++ ++&ufs { ++ status = "okay"; ++ lanes-per-direction = <2>; ++ ref-clk-freq = <26000000>; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mdio1 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy1: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ /* For DDR5 board */ ++ ti,rx-internal-delay = ; ++ ti,tx-internal-delay = ; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy1>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii1_default &pinctrl_rgmii1_driving>; ++}; ++ ++#if PCIE2_RC ++&pcie2 { ++ status = "okay"; ++}; ++#else ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++#endif ++ ++&syscon1 { ++ mac0-clk-delay = <0x18 0x16 ++ 0x10 0x10 ++ 0x10 0x10>; ++ mac1-clk-delay = <0x18 0x17 ++ 0x10 0x10 ++ 0x10 0x10>; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <2>; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "okay"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <4>; ++}; ++ ++&lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <5>; ++}; ++ ++&lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <6>; ++}; ++ ++&lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <7>; ++}; ++ ++&lpc1_ibt { ++ status = "okay"; ++}; ++ ++&lpc1_mbox { ++ status = "okay"; ++}; ++ ++&lpc1_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc1_uart_routing { ++ status = "okay"; ++}; ++ ++&video0 { ++ status = "okay"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "okay"; ++ memory-region = <&video_engine_memory1>; ++}; ++ ++&disp_intf { ++ status = "okay"; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&rsss { ++ status = "okay"; ++}; ++ ++&ecdsa { ++ status = "okay"; ++}; ++ ++&hace { ++ status = "okay"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&pcie0_mmbi0 { ++ status = "okay"; ++ memory-region = <&pcie0_mmbi0_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else ++&pcie0 { ++ status = "okay"; ++}; ++#endif ++ ++#if PCIE1_EP ++&bmc_dev1 { ++ status = "okay"; ++ memory-region = <&bmc_dev1_memory>; ++}; ++ ++&xdma1 { ++ status = "okay"; ++ memory-region = <&xdma_memory1>; ++}; ++ ++&pcie_vuart2 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart3 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <12>; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <13>; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <14>; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <15>; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "okay"; ++ bt-channel = <3>; ++}; ++ ++&pcie1_mmbi4 { ++ status = "okay"; ++ memory-region = <&pcie1_mmbi4_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else ++&pcie1 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&i3c0 { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++#endif ++ ++&sdio_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++ ++ vcc_sdhci0: regulator-vcc-sdhci0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "SDHCI0 Vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio0 ASPEED_GPIO(G, 6) GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; ++ ++ vccq_sdhci0: regulator-vccq-sdhci0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "SDHCI0 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio0 ASPEED_GPIO(G, 7) GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1>, ++ <1800000 0>; ++ }; ++ ++}; ++ ++&sdhci { ++ status = "okay"; ++ bus-width = <4>; ++ max-frequency = <100000000>; ++ /* DDR50 bits in CAPA2 are not supported */ ++ sdhci-caps-mask = <0x6 0x0>; ++ sdhci-drive-type = /bits/ 8 <3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sd_default>; ++ vmmc-supply = <&vcc_sdhci0>; ++ vqmmc-supply = <&vccq_sdhci0>; ++ sd-uhs-sdr104; /* enable sdr104 to execute tuning */ ++}; ++ ++#if 1 ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ status = "okay"; ++}; ++ ++&i2c2 { ++ status = "okay"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&i2c4 { ++ status = "okay"; ++}; ++ ++&i2c5 { ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++}; ++ ++&i2c12 { ++ status = "okay"; ++}; ++ ++&i2c13 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&ehci0 { ++ status = "okay"; ++}; ++ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&uhci0 { ++ status = "okay"; ++ memory-region = <&uhci0_reserved>; ++}; ++ ++#endif ++ ++#if 0 ++&uphy3a { ++ status = "okay"; ++}; ++ ++&uphy3b { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&xhci0 { ++ status = "okay"; ++}; ++ ++&xhci1 { ++ status = "okay"; ++}; ++#endif ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ahpd0_default>; ++}; ++ ++&vhubb1 { ++ status = "okay"; ++}; ++ ++#if 0 ++&vhubc { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++#endif ++ ++&uhci1 { ++ status = "okay"; ++ memory-region = <&uhci1_reserved>; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-ncsi.dts b/arch/arm64/boot/dts/aspeed/ast2700-ncsi.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-ncsi.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-ncsi.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,48 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-evb.dts" ++ ++/ { ++ model = "AST2700-NCSI"; ++}; ++ ++&sgpios { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII0 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii0_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii0_default>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII1 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii1_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii1_default>; ++}; ++ ++&syscon1 { ++ mac0-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++ mac1-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-raw.dts b/arch/arm64/boot/dts/aspeed/ast2700-raw.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-raw.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-raw.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,220 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700-evb.dts" ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi2 { ++ status = "disabled"; ++}; ++ ++&emmc_controller { ++ status = "disabled"; ++}; ++ ++&emmc { ++ status = "disabled"; ++}; ++ ++&ufs_controller { ++ status = "disabled"; ++}; ++ ++&ufs { ++ status = "disabled"; ++}; ++ ++&video0 { ++ status = "disabled"; ++}; ++ ++&video1 { ++ status = "disabled"; ++}; ++ ++&disp_intf { ++ status = "disabled"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "disabled"; ++}; ++ ++&xdma0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "disabled"; ++}; ++ ++&pcie0_mmbi0 { ++ status = "disabled"; ++}; ++#else ++&pcie0 { ++ status = "disabled"; ++}; ++#endif ++ ++#if PCIE1_EP ++&bmc_dev1 { ++ status = "disabled"; ++}; ++ ++&xdma1 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart2 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "disabled"; ++}; ++ ++&pcie1_mmbi4 { ++ status = "disabled"; ++}; ++#else ++&pcie1 { ++ status = "disabled"; ++}; ++#endif ++ ++&sdio_controller { ++ status = "disabled"; ++}; ++ ++&sdhci { ++ status = "disabled"; ++}; ++ +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-reserved-mem.dtsi b/arch/arm64/boot/dts/aspeed/ast2700-reserved-mem.dtsi +--- a/arch/arm64/boot/dts/aspeed/ast2700-reserved-mem.dtsi 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-reserved-mem.dtsi 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,128 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/* AST2700 reserved memories with no-map property */ ++ ++#if DUAL_NODE ++espi1_mmbi_memory: espi1-mmbi-memory@418000000 { ++ reg = <0x4 0x18000000 0x0 0x4000000>; ++ no-map; ++}; ++ ++edaf1: edaf1-memory@41c000000 { ++ reg = <0x4 0x1c000000 0x0 0x4000000>; ++ no-map; ++}; ++#endif ++ ++edaf0: eadf0-memory@428000000 { ++ reg = <0x4 0x28000000 0x0 0x4000000>; ++ no-map; ++}; ++ ++espi0_mmbi_memory: espi0-mmbi-memory@424000000 { ++ reg = <0x4 0x24000000 0x0 0x4000000>; ++ no-map; ++}; ++ ++#if PCIE0_EP ++bmc_dev0_memory: bmc-dev0-memory@423800000 { ++ compatible = "shared-dma-pool"; ++ reg = <0x4 0x23800000 0x0 0x100000>; ++ no-map; ++}; ++ ++xdma_memory0: xdma0 { ++ size = <0x0 0x00100000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++}; ++ ++pcie0_mmbi0_memory: pcie0-mmbi0-memory@423a00000 { ++ reg = <0x4 0x23a00000 0x0 0x200000>; ++ no-map; ++}; ++#endif ++ ++#if PCIE1_EP ++bmc_dev1_memory: bmc-dev1-memory@423900000 { ++ compatible = "shared-dma-pool"; ++ reg = <0x4 0x23900000 0x0 0x100000>; ++ no-map; ++}; ++ ++xdma_memory1: xdma1 { ++ size = <0x0 0x00100000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++}; ++ ++pcie1_mmbi4_memory: pcie1-mmbi4-memory@423c00000 { ++ reg = <0x4 0x23c00000 0x0 0x200000>; ++ no-map; ++}; ++#endif ++ ++pcie2_mmbi0_memory: pcie2-mmbi0-memory@423e00000 { ++ reg = <0x4 0x23e00000 0x0 0x200000>; ++ no-map; ++}; ++ ++ssp_memory: ssp-memory@42c000000 { ++ reg = <0x4 0x2c000000 0x0 0x2000000>; ++ no-map; ++}; ++ ++tsp_memory: tsp-memory@42e000000 { ++ reg = <0x4 0x2e000000 0x0 0x2000000>; ++ no-map; ++}; ++ ++ipc_ssp_share: ipc-ssp-share@431080000 { ++ reg = <0x4 0x31080000 0x0 0x800000>; ++ no-map; ++}; ++ ++ipc_bootmcu_share: ipc-bootmcu-share@431880000 { ++ reg = <0x4 0x31880000 0x0 0x2b0000>; ++ no-map; ++}; ++ ++uhci0_reserved: uhci0-reserved@431b30000 { ++ reg = <0x4 0x31b30000 0x0 0x40000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++}; ++ ++uhci1_reserved: uhci1-reserved@431b70000 { ++ reg = <0x4 0x31b70000 0x0 0x40000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++}; ++ ++vbios_base0: vbios-base0@431bb0000 { ++ reg = <0x4 0x31bb0000 0x0 0x10000>; ++ no-map; ++}; ++ ++vbios_base1: vbios-base1@431bc0000 { ++ reg = <0x4 0x31bc0000 0x0 0x10000>; ++ no-map; ++}; ++ ++mctp0_reserved: mctp0-reserved@431bd0000 { ++ reg = <0x4 0x31bd0000 0x0 0x10000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++}; ++ ++mctp1_reserved: mctp1-reserved@431be0000 { ++ reg = <0x4 0x31be0000 0x0 0x10000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++}; ++ ++mctp2_reserved: mctp2-reserved@431bf0000 { ++ reg = <0x4 0x31bf0000 0x0 0x10000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700-slt.dts b/arch/arm64/boot/dts/aspeed/ast2700-slt.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700-slt.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700-slt.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,850 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 0 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700-SLT"; ++ compatible = "aspeed,ast2700-slt", "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x04000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1{ ++ size = <0x0 0x04000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ }; ++ ++ fan3: pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan4: pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan5: pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan6: pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan7: pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++}; ++ ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm3_default ++ &pinctrl_pwm4_default &pinctrl_pwm5_default ++ &pinctrl_pwm6_default &pinctrl_pwm7_default ++ &pinctrl_tach0_default &pinctrl_tach1_default ++ &pinctrl_tach2_default &pinctrl_tach3_default ++ &pinctrl_tach4_default &pinctrl_tach5_default ++ &pinctrl_tach6_default &pinctrl_tach7_default ++ &pinctrl_tach8_default &pinctrl_tach9_default ++ &pinctrl_tach10_default &pinctrl_tach11_default ++ &pinctrl_tach12_default &pinctrl_tach13_default ++ &pinctrl_tach14_default &pinctrl_tach15_default>; ++ fan-0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ fan-1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ fan-2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ fan-3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ fan-4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ fan-5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ fan-6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ fan-7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ fan-8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ fan-9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ fan-10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ fan-11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ fan-12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ fan-13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ fan-14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ fan-15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&peci0 { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++&mctp2 { ++ status = "okay"; ++ memory-region = <&mctp2_reserved>; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc0_default &pinctrl_adc1_default ++ &pinctrl_adc2_default &pinctrl_adc3_default ++ &pinctrl_adc4_default &pinctrl_adc5_default ++ &pinctrl_adc6_default &pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc8_default &pinctrl_adc9_default ++ &pinctrl_adc10_default &pinctrl_adc11_default ++ &pinctrl_adc12_default &pinctrl_adc13_default ++ &pinctrl_adc14_default &pinctrl_adc15_default>; ++}; ++ ++&pinctrl1 { ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart2 { ++ status = "okay"; ++}; ++ ++&uart3 { ++ status = "okay"; ++}; ++ ++&uart4 { ++ status = "okay"; ++}; ++ ++&uart5 { ++ status = "okay"; ++}; ++ ++&uart6 { ++ status = "okay"; ++}; ++ ++&uart7 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default &pinctrl_spi0_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default &pinctrl_spi1_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi2 { ++ compatible = "aspeed,ast2700-spi"; ++ pinctrl-0 = <&pinctrl_spi2_default &pinctrl_spi2_cs1_default &pinctrl_spi2_quad_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ flash@0 { ++ status = "okay"; ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&can0 { ++ /delete-property/ pinctrl-0; ++ /delete-property/ pinctrl-names; ++ ++ can-internal-loopback; ++ status = "okay"; ++}; ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++#if 1 ++ bus-width = <4>; ++#else ++ bus-width = <8>; ++ pinctrl-0 = <&pinctrl_emmc_default ++ &pinctrl_emmcg8_default>; ++#endif ++ non-removable; ++ max-frequency = <200000000>; ++}; ++ ++&ufs_controller { ++ status = "okay"; ++}; ++ ++&ufs { ++ status = "okay"; ++ lanes-per-direction = <2>; ++ ref-clk-freq = <26000000>; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mdio1 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy1: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy1>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii1_default>; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++ ++&lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <2>; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "okay"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&video0 { ++ status = "okay"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "okay"; ++ memory-region = <&video_engine_memory1>; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&rsss { ++ status = "okay"; ++}; ++ ++&ecdsa { ++ status = "okay"; ++}; ++ ++&hace { ++ status = "okay"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&pcie0_mmbi0 { ++ status = "okay"; ++ memory-region = <&pcie0_mmbi0_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#endif ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ status = "okay"; ++}; ++ ++&i2c2 { ++ status = "okay"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&i2c4 { ++ status = "okay"; ++}; ++ ++&i2c5 { ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c9 { ++ status = "okay"; ++}; ++ ++&i2c10 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++}; ++ ++#if 1 ++ ++&ehci0 { ++ status = "okay"; ++}; ++ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&uhci0 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&uphy3a { ++ status = "okay"; ++}; ++ ++&uphy3b { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&xhci0 { ++ status = "okay"; ++}; ++ ++&xhci1 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&vhuba1 { ++ status = "okay"; ++}; ++ ++&vhubb1 { ++ status = "okay"; ++}; ++ ++#endif ++ ++#if 0 ++&vhuba0 { ++ status = "okay"; ++}; ++ ++&vhubb0 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&usb3ahp { ++ status = "okay"; ++}; ++ ++&usb3bhp { ++ status = "okay"; ++}; ++ ++&uphy2a { ++ status = "okay"; ++}; ++ ++&uphy2b { ++ status = "okay"; ++}; ++#endif ++ ++&vhubc { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-ci-host.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-ci-host.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-ci-host.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-ci-host.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,127 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700a1-evb.dts" ++ ++/ { ++ model = "AST2700A1-CI-HOST"; ++}; ++ ++&bmc_dev0 { ++ status = "disabled"; ++}; ++ ++&xdma0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "disabled"; ++}; ++ ++&pcie0_mmbi0 { ++ status = "disabled"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&bmc_dev1 { ++ status = "disabled"; ++}; ++ ++&xdma1 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart2 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "disabled"; ++}; ++ ++&pcie1_mmbi4 { ++ status = "disabled"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "disabled"; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,991 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++#include ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700A1-DCSCM"; ++ compatible = "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1{ ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++#if 0 ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++#endif ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <&adc0 7>, <&adc1 7>; ++ }; ++}; ++ ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm9_default>; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ aspeed,battery-sensing; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ aspeed,battery-sensing; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc15_default>; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&i3c0 { ++ /* BMC_HPM_I3C_I2C_14, If AST1060 I3C_BMC_PFR_SCM_SEL(GPION4)=0 and I3C_SCM_EN(GPION5)=0 */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ /* BMC_I2C_I3C1_SCL1 */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ /* BMC_I2C_I3C2_SCL2 */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ /* I3C_DBG_SCM */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++/* AST2700 i3c4 -> AST1060 i3c2 for MCTP over I3C. */ ++&i3c4 { ++ /* I3C_PFR_BMC */ ++ initial-role = "target"; ++ pid = <0x000007ec 0x06000000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ /* I3C_MNG_BMC_SCM */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ /* I3C_SPD_SCM */ ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++/* Enable UART2 and UART9 for obmc-console. */ ++&uart2 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart9 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++/* Enable UART7 and UART10 for obmc-console. */ ++&uart7 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart10 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++/* UART3, UART13 and UART14 will be tunnelded to LTPI UART channels. */ ++&uart3 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart13 { ++ status = "okay"; ++}; ++ ++&uart14 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ u-boot@0 { ++ reg = <0x0 0x400000>; // 4MB ++ label = "u-boot"; ++ }; ++ u-boot-env@400000 { ++ reg = <0x400000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ kernel@420000 { ++ reg = <0x420000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ rofs@d20000 { ++ reg = <0xd20000 0x24a0000>; // 36.625MB ++ label = "rofs"; ++ }; ++ rwfs@31c0000 { ++ reg = <0x31c0000 0xE40000>; // 14.25MB ++ label = "rwfs"; ++ }; ++ pfm@4000000 { ++ reg = <0x4000000 0x20000>; // 128KB ++ label = "pfm"; ++ }; ++ reserved-1@4020000 { ++ reg = <0x4020000 0x200000>; // 128KB ++ label = "reserved-1"; ++ }; ++ rc-image@4220000 { ++ reg = <0x4220000 0x3de0000>; // 63360KB ++ label = "rc-image"; ++ }; ++ image-stg@8000000 { ++ reg = <0x8000000 0x3de0000>; // 63360KB ++ label = "img-stg"; ++ }; ++ pfr-stg@bde0000 { ++ reg = <0xbde0000 0x100000>; // 1024KB ++ label = "pfr-stg"; ++ }; ++ cpld-stg@bee0000 { ++ reg = <0xbee0000 0x400000>; // 4096KB ++ label = "cpld-stg"; ++ }; ++ afm-stg@c2e0000 { ++ reg = <0xc2e0000 0x20000>; // 128KB ++ label = "afm-stg"; ++ }; ++ afm-rc@c300000 { ++ reg = <0xc300000 0x20000>; // 128KB ++ label = "afm-rc"; ++ }; ++ reserved-2@c320000 { ++ reg = <0xc320000 0x3ce0000>; // 62336KB ++ label = "reserved-2"; ++ }; ++ }; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ spi0_pch_bios@0 { ++ reg = <0x0 0x3fe0000>; ++ label = "spi0_pch_reserved"; ++ }; ++ spi0_pch_pfm@3fe0000 { ++ reg = <0x3fe0000 0x20000>; ++ label = "spi0_pch_pfm"; ++ }; ++ spi0_pch_stg@4000000 { ++ reg = <0x4000000 0x2000000>; ++ label = "spi0_pch_stg"; ++ }; ++ spi0_pch_rc@6000000 { ++ reg = <0x6000000 0x2000000>; ++ label = "spi0_pch_rc"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ spi1_pch_reserved@0 { ++ reg = <0x0 0x7f0000>; ++ label = "spi1_pch_reserved"; ++ }; ++ ++ spi1_pch_stg@7f0000 { ++ reg = <0x7f0000 0x1400000>; ++ label = "spi1_pch_stg"; ++ }; ++ ++ spi1_pch_rc@1bf0000 { ++ reg = <0x1bf0000 0x1400000>; ++ label = "spi1_pch_rc"; ++ }; ++ ++ spi1_pch_pfm@2ff0000 { ++ reg = <0x2ff0000 0x10000>; ++ label = "spi1_pch_pfm"; ++ }; ++ ++ spi1_pch_bios@3000000 { ++ reg = <0x3000000 0x1000000>; ++ label = "spi1_pch_bios"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <12500000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++}; ++ ++<pi0 { ++ status = "okay"; ++}; ++ ++<pi1 { ++ /* Do not tunnel I2C10 to the HPM */ ++ i2c-tunneling = <0x2f>; ++ status = "okay"; ++}; ++ ++/* The LTPI GPIO table is defined in Chapter 6 of the AST2700 LTPI Design Guide v10.pdf. */ ++/* line 0:BMC_GPI0(LTPI0_INL16), 1:BMC_GPO0(LTPI0_ONL16), 2:BMC_GPI1(LTPI0_INL17), 3:BMC_GPO1(LTPI0_ONL17), etc... */ ++/* BMC_GPI[63:0] = LTPI0_INL[79:16], BMC_GPO[63:0] = LTPI0_ONL[79:16] */ ++/* BMC_GPI[71:64] = LTPI0_ILL[11:4], BMC_GPO[71:64] = LTPI0_OLL[11:4] */ ++/* BMC_GPI[111:72] = LTPI0_INL[127:88],BMC_GPO[111:72] = LTPI0_ONL[127:88] */ ++<pi0_gpio { ++ status = "okay"; ++ gpio-line-names = ++ /*00-07*/ "","","","","","","","", ++ /*08-15*/ "","","","","","","","", ++ /*16-23*/ "","FM_CPU_FBRK_DEBUG_N","","FM_BMC_TRUST_N","","FM_RST_BTN_OUT_CPU0_PLD_N_OE","","FM_PWR_BTN_OUT_CPU0_N", ++ /*24-31*/ "","FM_BMC_ONCTL_N","","","","","","", ++ /*32-39*/ "","BIOS_POST_CODE_LED_0","","BIOS_POST_CODE_LED_1","","BIOS_POST_CODE_LED_2","","BIOS_POST_CODE_LED_3", ++ /*40-47*/ "FP_ID_BTN_N","BIOS_POST_CODE_LED_4","FP_RST_BTN_N","BIOS_POST_CODE_LED_5","","BIOS_POST_CODE_LED_6","","BIOS_POST_CODE_LED_7", ++ /*48-55*/ "","A_P3V_BAT_SCALED_EN","","FM_TPM_EN_PULSE","","FM_SKT0_FAULT_LED","","FM_SKT1_FAULT_LED", ++ /*56-63*/ "","","","","","RST_BMC_SMB_PCIE_MUX_N","","SURPRISE_RESET", ++ /*64-71*/ "PWRGD_S0_PWROK_CPU0","","","","","","","", ++ /*72-79*/ "","","","","","","","", ++ /*80-87*/ "","","","","","","","", ++ /*88-95*/ "","","","","","","","", ++ /*96-103*/ "","","","","","","","", ++ /*104-111*/ "","","","","","","","", ++ /*112-119*/ "","","","","","","","", ++ /*120-127*/ "","","","","","","","", ++ /*128-135*/ "","","","","","","","", ++ /*136-143*/ "","","","","","","","", ++ /*144-151*/ "","","","","","","","", ++ /*152-159*/ "","","","","","","","", ++ /*160-167*/ "","","","","","","","", ++ /*168-175*/ "","","","","","","","", ++ /*176-183*/ "","","","","","","","", ++ /*184-191*/ "","","","","","","","", ++ /*192-199*/ "","","","","","","","", ++ /*200-207*/ "","","","","","","","", ++ /*208-215*/ "","","","","","","","", ++ /*216-223*/ "","","","","","","",""; ++ ++ gpio_17 { ++ gpio-hog; ++ gpios = <17 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "FM_CPU_FBRK_DEBUG_N"; ++ }; ++ gpio_19 { ++ gpio-hog; ++ gpios = <19 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "FM_BMC_TRUST_N"; ++ }; ++ gpio_25 { ++ gpio-hog; ++ gpios = <25 GPIO_ACTIVE_HIGH>; ++ output-low; ++ line-name = "FM_BMC_ONCTL_N"; ++ }; ++ gpio_53 { ++ gpio-hog; ++ gpios = <53 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "FM_SKT0_FAULT_LED"; ++ }; ++ gpio_61 { ++ gpio-hog; ++ gpios = <61 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "RST_BMC_SMB_PCIE_MUX_N"; ++ }; ++ gpio_63 { ++ gpio-hog; ++ gpios = <63 GPIO_ACTIVE_HIGH>; ++ output-low; ++ line-name = "SURPRISE_RESET"; ++ }; ++}; ++ ++&peci0 { ++ status = "okay"; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++&gpio1 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving>; ++ pinctrl-names = "default"; ++ ++ gpio-line-names = ++ /*A0-A7*/ "","","","","","","","", ++ /*B0-B7*/ "","","","","","","","", ++ /*C0-C7*/ "","","","","","","","", ++ /*D0-D7*/ "","","","","","","","", ++ /*E0-E7*/ "","","","","FP_LED_STATUS_GREEN_CPLD_N","FP_LED_STATUS_AMBER_CPLD_N","","", ++ /*F0-F7*/ "","","","","","","","", ++ /*G0-G7*/ "","","","","","","","", ++ /*H0-H7*/ "","","","","","","","", ++ /*I0-I7*/ "","","","","","","","", ++ /*J0-J7*/ "","","","","","","","", ++ /*K0-K7*/ "","","","","","","","", ++ /*L0-L7*/ "","","","","","","","", ++ /*M0-M7*/ "","","","","","","","", ++ /*N0-N7*/ "","","","","","","","", ++ /*O0-O7*/ "","","","","","","","", ++ /*P0-P7*/ "","","","","","","","", ++ /*Q0-Q7*/ "","","","","","","","", ++ /*R0-R7*/ "","","","","","","","", ++ /*S0-S7*/ "","","","","","","","", ++ /*T0-T7*/ "","","","","","","","", ++ /*U0-U7*/ "","","","","","SCM_PHY_RST","","", ++ /*V0-V7*/ "","","","","","","","", ++ /*W0-W7*/ "","","","","","","","", ++ /*X0-X7*/ "","","","","","","","", ++ /*Y0-Y7*/ "","","","","IRQ_PMBUS1_ALERT_LVC3_N","FM_NVME_LVC3_ALERT_N","","", ++ /*Z0-Z7*/ "","","FM_NODE_ID0_N","FM_NODE_ID1_N","","PWRGD_AUX_PWRGD_PFR_CPU0","PWRGD_AUX_PWRGD_PFR_CPU1","", ++ /*AA0-AA7*/ "BMC_BOOT_DONE","","","","","","","", ++ /*AB0-AB7*/ "","","","","","","","", ++ /*AC0-AC7*/ "","","","","","","","", ++ /*AD0-AD7*/ "","","","","","","","", ++ /*AE0-AE7*/ "","","","","","","",""; ++}; ++ ++/* AST2700 A1 support SGPIO slave to 72*2 pins. */ ++/* The SGPIO slave table is defined in Chapter 7 of the AST2700 LTPI Design Guide v10.pdf. */ ++/* line 0:BMC_SGPI0(SCM_GPI0), 1:BMC_SGPO0(SCM_GPO0), 2:BMC_SGPI1(SCM_GPI1), 3:BMC_SGPO1(SCM_GPO1), etc... */ ++/* line 32:BMC_SGPI16(LTPI0_INL80), 33:BMC_SGPO16(LTPI0_ONL80), 34:LTPI0_INL81, 35:LTPI0_ONL81, etc... */ ++/* BMC_SGPI[15:0] = SCM_GPI0[15:0], BMC_SGPO[15:0] = BMC_SGPO0[15:0] */ ++/* BMC_SGPI[23:16] = LTPI0_INL[87:80], BMC_SGPO[23:16] = LTPI0_ONL[87:80] */ ++/* BMC_SGPI[31:24] = LTPI1_INL[87:80], BMC_SGPO[31:24] = LTPI1_ONL[87:80] */ ++/* BMC_SGPI[47:32] = LTPI0_INL[15:0], BMC_SGPO[47:32] = LTPI0_ONL[15:0] */ ++/* BMC_SGPI[63:48] = LTPI1_INL[15:0], BMC_SGPO[63:48] = LTPI1_ONL[15:0] */ ++/* BMC_SGPI[71:64] = SREG_GPO[7:0], BMC_SGPO[71:64] = SREG_GPI[7:0] */ ++/* The designe guide only define SCM_GPO9(FP_PWR_BTN_PFR_N) for PFR output pin. */ ++/* We use AST2700 SGPIOS line 18 (FP_PWR_BTN_PFR_N_BMC_IN) for x86-power-control power button input. */ ++&sgpios { ++ status = "okay"; ++ gpio-line-names = ++ /*00-07*/ "","","","","","","","", ++ /*08-15*/ "","","","","","","","", ++ /*16-23*/ "","","FP_PWR_BTN_PFR_N_BMC_IN","","","","","", ++ /*24-31*/ "","","","","","","","", ++ /*32-39*/ "","","","","","","","", ++ /*40-47*/ "","","","","","","","", ++ /*48-55*/ "","","","","","","","", ++ /*56-63*/ "","","","","","","","", ++ /*64-71*/ "","","","","","","","", ++ /*72-79*/ "","","","","","","","", ++ /*80-87*/ "","","","","","","","", ++ /*88-95*/ "","","","","","","","", ++ /*96-103*/ "","","","","","","","", ++ /*104-111*/ "","","","","","","","", ++ /*112-119*/ "","","","","","","","", ++ /*120-127*/ "","","","","","","","", ++ /*128-135*/ "","","","","","","","", ++ /*136-143*/ "","","","","","","",""; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++#if 0 //eSPI1 and SD are multi-functional pin, SD default on ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++#endif ++ ++&lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <2>; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "okay"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <4>; ++}; ++ ++&lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <5>; ++}; ++ ++&lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <6>; ++}; ++ ++&lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <7>; ++}; ++ ++&lpc1_ibt { ++ status = "okay"; ++}; ++ ++&lpc1_mbox { ++ status = "okay"; ++}; ++ ++&lpc1_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++ ++&lpc1_uart_routing { ++ status = "okay"; ++}; ++ ++&video0 { ++ status = "okay"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "okay"; ++ memory-region = <&video_engine_memory1>; ++}; ++ ++&disp_intf { ++ status = "okay"; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&rsss { ++ status = "okay"; ++}; ++ ++&ecdsa { ++ status = "okay"; ++}; ++ ++&hace { ++ dummy-key0 = < ++ 0x89878915 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key1 = < ++ 0x89878902 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key2 = < ++ 0x2C914FA3 0x1056D87B 0xC13D6AE2 0x5E44AF98 ++ 0x0DB97321 0xCA378CF6 0x62EE115D 0x3FA74B90 ++ >; ++ dummy-key3 = < ++ 0x9E18D25C 0x6FB17A43 0x348DE520 0x1269FAC7 ++ 0xAC5503DE 0x2DB61F88 0xEF9AC074 0xB84E6731 ++ >; ++ status = "okay"; ++}; ++ ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&bmc_dev1 { ++ status = "okay"; ++ memory-region = <&bmc_dev1_memory>; ++}; ++ ++&xdma1 { ++ status = "okay"; ++ memory-region = <&xdma_memory1>; ++}; ++ ++&pcie_vuart2 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart3 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <12>; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <13>; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <14>; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <15>; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "okay"; ++ bt-channel = <3>; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++/* Enable i2c0~i2c4 for LTPI testing. */ ++&i2c0 { ++ /* SMB_PMBUS1_SCM */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c1 { ++ /* SMB_IPMB_SCL1 */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c2 { ++ /* SMB_CPLD_SCL2 */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c3 { ++ /* SMB_PMBUS2_SCM */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&i2c4 { ++ /* SMB_PMBUS1_SCM */ ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++/* Default i2c5 is tunnelded to LTPI. */ ++/* Enable i2c5 pinctrl for testing on board LM75. */ ++&i2c5 { ++ /* SMB_TMP_BMC */ ++ status = "okay"; ++}; ++ ++/* AST2700 i2c8 -> AST1060 i2c5 for PCH mailbox emulation test. */ ++&i2c8 { ++ /* SMB_PCIE_SCM */ ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c9 { ++ /* SMB_HOST_BMC */ ++ status = "okay"; ++ eeprom@50 { ++ compatible = "atmel,24c04"; ++ reg = <0x50>; ++ pagesize = <16>; ++ }; ++}; ++ ++/* AST2700 A1 i2c10 -> AST1060 i2c0 for PFR mailbox. */ ++&i2c10 { ++ /* SMB_HSBP_BMC */ ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c11 { ++ /* BMC_HPM_I3C_I2C_13 */ ++ status = "okay"; ++}; ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ahpd0_default>; ++}; ++ ++&vhubb1 { ++ status = "okay"; ++}; ++ ++&vhubc { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&uhci1 { ++ status = "okay"; ++ memory-region = <&uhci1_reserved>; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; ++ ++#if 0 ++&soc0 { ++ mbox-ssp-0 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31480000 0x0 0x200000>, <0x0 0x10001000 0x0 0x1000>; ++ mboxes = <&mbox0 0>; ++ aspeed,tx-timeout = <100>; ++ }; ++}; ++#endif ++ ++#if 1 ++&soc0 { ++ mbox-bootmcu-1 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31880000 0x0 0x100000>, <0x4 0x31980000 0x0 0x100000>; ++ mboxes = <&mbox2 1>; ++ aspeed,tx-timeout = <10000>; ++ }; ++}; ++#endif +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb-dual.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb-dual.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb-dual.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb-dual.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,218 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700a1-dcscm_ast1700a1-evb.dts" ++#include "aspeed-ltpi1.dtsi" ++ ++/ { ++ model = "AST2700A1-DCSCM_AST1700A1-EVB-DUAL"; ++ ++ ltpi1-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi1_adc0 0>, <<pi1_adc0 1>, <<pi1_adc0 2>, <<pi1_adc0 3>, ++ <<pi1_adc0 4>, <<pi1_adc0 5>, <<pi1_adc0 6>, <<pi1_adc0 7>, ++ <<pi1_adc1 0>, <<pi1_adc1 1>, <<pi1_adc1 2>, <<pi1_adc1 3>, ++ <<pi1_adc1 4>, <<pi1_adc1 5>, <<pi1_adc1 6>, <<pi1_adc1 7>; ++ }; ++}; ++ ++<pi1_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi1_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi1 { ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++}; ++ ++<pi1_gpio { ++ status = "okay"; ++}; ++ ++<pi1_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi1_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi1_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "disabled"; ++}; ++ ++&i2c7 { ++ status = "disabled"; ++}; ++ ++// The following I2C buses require enabling according to the ast2700-dcscm.dts. ++// i2c8 / i2c9 / i2c10 ++ ++&i2c11 { ++ status = "disabled"; ++}; ++ ++<pi1_i2c0 { ++ status = "okay"; ++}; ++ ++<pi1_i2c1 { ++ status = "okay"; ++}; ++ ++<pi1_i2c2 { ++ status = "okay"; ++}; ++ ++<pi1_i2c3 { ++ status = "okay"; ++}; ++ ++<pi1_i2c4 { ++ status = "okay"; ++}; ++ ++<pi1_i2c5 { ++ status = "okay"; ++}; ++ ++<pi1_i2c6 { ++ status = "okay"; ++}; ++ ++<pi1_i2c7 { ++ status = "okay"; ++}; ++ ++<pi1_i2c8 { ++ status = "okay"; ++}; ++ ++<pi1_i2c9 { ++ status = "okay"; ++}; ++ ++<pi1_i2c10 { ++ status = "okay"; ++}; ++ ++<pi1_i2c11 { ++ status = "okay"; ++}; ++ ++<pi1_i2c12 { ++ status = "okay"; ++}; ++ ++<pi1_i2c13 { ++ status = "okay"; ++}; ++ ++<pi1_i2c14 { ++ status = "okay"; ++}; ++ ++<pi1_i2c15 { ++ status = "okay"; ++}; ++ ++&uart8 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1700a1-evb.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,456 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700a1-dcscm.dts" ++#include "aspeed-ltpi0.dtsi" ++ ++/ { ++ model = "AST2700A1-DCSCM_AST1700A1-EVB"; ++ ++ ltpi_fan0: ltpi-pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan1: ltpi-pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan2: ltpi-pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan3: ltpi-pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan4: ltpi-pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan5: ltpi-pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan6: ltpi-pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan7: ltpi-pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan8: ltpi-pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan9: ltpi-pwm-fan9 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 9 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan10: ltpi-pwm-fan10 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 10 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan11: ltpi-pwm-fan11 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 11 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan12: ltpi-pwm-fan12 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 12 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan13: ltpi-pwm-fan13 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 13 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan14: ltpi-pwm-fan14 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 14 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan15: ltpi-pwm-fan15 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 15 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi0-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi0_adc0 0>, <<pi0_adc0 1>, <<pi0_adc0 2>, <<pi0_adc0 3>, ++ <<pi0_adc0 4>, <<pi0_adc0 5>, <<pi0_adc0 6>, <<pi0_adc0 7>, ++ <<pi0_adc1 0>, <<pi0_adc1 1>, <<pi0_adc1 2>, <<pi0_adc1 3>, ++ <<pi0_adc1 4>, <<pi0_adc1 5>, <<pi0_adc1 6>, <<pi0_adc1 7>; ++ }; ++}; ++ ++<pi0_pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_pwm0_default &pinctrl_ltpi0_pwm1_default ++ &pinctrl_ltpi0_pwm2_default &pinctrl_ltpi0_pwm3_default ++ &pinctrl_ltpi0_pwm4_default &pinctrl_ltpi0_pwm5_default ++ &pinctrl_ltpi0_pwm6_default &pinctrl_ltpi0_pwm7_default ++ &pinctrl_ltpi0_pwm8_default ++ &pinctrl_ltpi0_tach0_default &pinctrl_ltpi0_tach1_default ++ &pinctrl_ltpi0_tach2_default &pinctrl_ltpi0_tach3_default ++ &pinctrl_ltpi0_tach4_default &pinctrl_ltpi0_tach5_default ++ &pinctrl_ltpi0_tach6_default &pinctrl_ltpi0_tach7_default ++ &pinctrl_ltpi0_tach8_default &pinctrl_ltpi0_tach9_default ++ &pinctrl_ltpi0_tach10_default &pinctrl_ltpi0_tach11_default ++ &pinctrl_ltpi0_tach12_default &pinctrl_ltpi0_tach13_default ++ &pinctrl_ltpi0_tach14_default &pinctrl_ltpi0_tach15_default>; ++ ltpi_fan0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ ltpi_fan1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ ltpi_fan2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ ltpi_fan3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ ltpi_fan4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ ltpi_fan5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ ltpi_fan6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ ltpi_fan7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ ltpi_fan8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ ltpi_fan9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ ltpi_fan10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ ltpi_fan11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ ltpi_fan12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ ltpi_fan13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ ltpi_fan14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ ltpi_fan15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&sgpios { ++ /delete-property/ gpio-line-names; ++}; ++ ++<pi0_gpio { ++ /delete-property/ gpio-line-names; ++ /delete-node/ gpio_17; ++ /delete-node/ gpio_19; ++ /delete-node/ gpio_25; ++ /delete-node/ gpio_53; ++ /delete-node/ gpio_61; ++ /delete-node/ gpio_63; ++}; ++ ++<pi0_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_ltpi0_adc0_default &pinctrl_ltpi0_adc1_default ++ &pinctrl_ltpi0_adc2_default &pinctrl_ltpi0_adc3_default ++ &pinctrl_ltpi0_adc4_default &pinctrl_ltpi0_adc5_default ++ &pinctrl_ltpi0_adc6_default &pinctrl_ltpi0_adc7_default>; ++}; ++ ++<pi0_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_ltpi0_adc8_default &pinctrl_ltpi0_adc9_default ++ &pinctrl_ltpi0_adc10_default &pinctrl_ltpi0_adc11_default ++ &pinctrl_ltpi0_adc12_default &pinctrl_ltpi0_adc13_default ++ &pinctrl_ltpi0_adc14_default &pinctrl_ltpi0_adc15_default>; ++}; ++ ++<pi0 { ++ i2c-tunneling = <0x0>; ++ status = "okay"; ++}; ++ ++<pi0_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "disabled"; ++}; ++ ++&i2c1 { ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "disabled"; ++}; ++ ++&i2c4 { ++ status = "disabled"; ++}; ++ ++&i2c5 { ++ status = "disabled"; ++}; ++ ++<pi0_i2c0 { ++ status = "okay"; ++}; ++ ++<pi0_i2c1 { ++ status = "okay"; ++}; ++ ++<pi0_i2c2 { ++ status = "okay"; ++}; ++ ++<pi0_i2c3 { ++ status = "okay"; ++}; ++ ++<pi0_i2c4 { ++ status = "okay"; ++}; ++ ++<pi0_i2c5 { ++ status = "okay"; ++}; ++ ++<pi0_i2c6 { ++ status = "okay"; ++}; ++ ++<pi0_i2c7 { ++ status = "okay"; ++}; ++ ++<pi0_i2c8 { ++ status = "okay"; ++}; ++ ++<pi0_i2c9 { ++ status = "okay"; ++}; ++ ++<pi0_i2c10 { ++ status = "okay"; ++}; ++ ++<pi0_i2c11 { ++ status = "okay"; ++}; ++ ++<pi0_i2c12 { ++ status = "okay"; ++}; ++ ++<pi0_i2c13 { ++ status = "okay"; ++}; ++ ++<pi0_i2c14 { ++ status = "okay"; ++}; ++ ++<pi0_i2c15 { ++ status = "okay"; ++}; ++ ++&uart3 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1800-evb.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1800-evb.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1800-evb.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-dcscm_ast1800-evb.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,436 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700a1-dcscm.dts" ++#include "aspeed-ltpi1800.dtsi" ++ ++/ { ++ model = "AST2700A1-DCSCM_AST1800-EVB"; ++ ++ ltpi_fan0: ltpi-pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan1: ltpi-pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan2: ltpi-pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan3: ltpi-pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan4: ltpi-pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan5: ltpi-pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan6: ltpi-pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan7: ltpi-pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan8: ltpi-pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan9: ltpi-pwm-fan9 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 9 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan10: ltpi-pwm-fan10 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 10 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan11: ltpi-pwm-fan11 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 11 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan12: ltpi-pwm-fan12 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 12 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan13: ltpi-pwm-fan13 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 13 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan14: ltpi-pwm-fan14 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 14 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi_fan15: ltpi-pwm-fan15 { ++ compatible = "pwm-fan"; ++ pwms = <<pi0_pwm_tach 15 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ ltpi0-iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <<pi0_adc0 0>, <<pi0_adc0 1>, <<pi0_adc0 2>, <<pi0_adc0 3>, ++ <<pi0_adc0 4>, <<pi0_adc0 5>, <<pi0_adc0 6>, <<pi0_adc0 7>, ++ <<pi0_adc1 0>, <<pi0_adc1 1>, <<pi0_adc1 2>, <<pi0_adc1 3>, ++ <<pi0_adc1 4>, <<pi0_adc1 5>, <<pi0_adc1 6>, <<pi0_adc1 7>; ++ }; ++}; ++ ++<pi0_jtag { ++ status = "okay"; ++}; ++ ++<pi0_pwm_tach { ++ status = "okay"; ++ ltpi_fan0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ ltpi_fan1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ ltpi_fan2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ ltpi_fan3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ ltpi_fan4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ ltpi_fan5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ ltpi_fan6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ ltpi_fan7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ ltpi_fan8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ ltpi_fan9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ ltpi_fan10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ ltpi_fan11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ ltpi_fan12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ ltpi_fan13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ ltpi_fan14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ ltpi_fan15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&sgpios { ++ /delete-property/ gpio-line-names; ++}; ++ ++<pi0_gpio { ++ /delete-property/ gpio-line-names; ++ /delete-node/ gpio_17; ++ /delete-node/ gpio_19; ++ /delete-node/ gpio_25; ++ /delete-node/ gpio_53; ++ /delete-node/ gpio_61; ++ /delete-node/ gpio_63; ++}; ++ ++<pi0_adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi0_adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++}; ++ ++<pi0 { ++ i2c-tunneling = <0x0>; ++}; ++ ++<pi0_i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++<pi0_i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++<pi0_i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "disabled"; ++}; ++ ++&i2c1 { ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "disabled"; ++}; ++ ++&i2c4 { ++ status = "disabled"; ++}; ++ ++&i2c5 { ++ status = "disabled"; ++}; ++ ++<pi0_i2c0 { ++ status = "okay"; ++}; ++ ++<pi0_i2c1 { ++ status = "okay"; ++}; ++ ++<pi0_i2c2 { ++ status = "okay"; ++}; ++ ++<pi0_i2c3 { ++ status = "okay"; ++}; ++ ++<pi0_i2c4 { ++ status = "okay"; ++}; ++ ++<pi0_i2c5 { ++ status = "okay"; ++}; ++ ++<pi0_i2c6 { ++ status = "okay"; ++}; ++ ++<pi0_i2c7 { ++ status = "okay"; ++}; ++ ++<pi0_i2c8 { ++ status = "okay"; ++}; ++ ++<pi0_i2c9 { ++ status = "okay"; ++}; ++ ++<pi0_i2c10 { ++ status = "okay"; ++}; ++ ++<pi0_i2c11 { ++ status = "okay"; ++}; ++ ++<pi0_i2c12 { ++ status = "okay"; ++}; ++ ++<pi0_i2c13 { ++ status = "okay"; ++}; ++ ++<pi0_i2c14 { ++ status = "okay"; ++}; ++ ++<pi0_i2c15 { ++ status = "okay"; ++}; ++ ++&uart3 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-evb-256-abr.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-evb-256-abr.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-evb-256-abr.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-evb-256-abr.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,40 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700a1-evb.dts" ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-256-abr.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s0.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s0.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s0.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s0.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,383 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "ast2700a1-evb.dts" ++#include ++ ++/ { ++ model = "AST2700A1 EVB Test S0 Test"; ++}; ++ ++&pcie2 { ++ status = "disabled"; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++ ++&sdio_controller { ++ status = "disabled"; ++}; ++ ++&sdhci { ++ status = "disabled"; ++}; ++ ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++#if 0 //TODO: No enough memory for espi1 MMBI ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++#endif ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&can0 { ++ status = "disabled"; ++}; ++ ++// S0 use 0x10 as its slave address ++&i2c0 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c1 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c2 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c3 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c4 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c5 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c6 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c7 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c8 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c9 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c10 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c11 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c12 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c13 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c14 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c15 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@10 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06011000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06013000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06015000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06017000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06019000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601B000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601D000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601F000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s1.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s1.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s1.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-evb-s1.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,485 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "ast2700a1-evb.dts" ++#include ++ ++/ { ++ model = "AST2700A1 EVB Test S1 Test"; ++}; ++ ++&bmc_dev0 { ++ status = "disabled"; ++}; ++ ++&xdma0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "disabled"; ++}; ++ ++&pcie0_mmbi0 { ++ status = "disabled"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&bmc_dev1 { ++ status = "disabled"; ++}; ++ ++&xdma1 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart2 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "disabled"; ++}; ++ ++&pcie1_mmbi4 { ++ status = "disabled"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&sdio_controller { ++ status = "disabled"; ++}; ++ ++&sdhci { ++ status = "disabled"; ++}; ++ ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++#if 0 // TODO: No enough memory for espi1 MMBI ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++#endif ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&can0 { ++ status = "disabled"; ++}; ++ ++// S1 use 0x12 as its slave address ++&i2c0 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c1 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c2 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c3 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c4 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c5 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c6 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c7 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c8 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c9 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c10 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c11 { ++ clock-frequency = <1000000>; ++ debounce-level = <6>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c12 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c13 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c14 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "buff"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i2c15 { ++ clock-frequency = <1000000>; ++ debounce-level = <4>; ++ status = "okay"; ++ multi-master; ++ mctp-controller; ++ aspeed,transfer-mode = "dma"; ++ mctp@12 { ++ compatible = "mctp-i2c-controller"; ++ reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>; ++ }; ++}; ++ ++&i3c0 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&sgpios { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII0 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii0_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii0_default>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII1 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii1_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii1_default>; ++}; ++ ++&syscon1 { ++ mac0-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++ mac1-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++}; ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ad0_default>; ++}; ++ ++&vhubb0 { ++ status = "okay"; ++}; ++ ++&vhubb1 { ++ status = "disabled"; ++}; ++ ++&vhubc { ++ status = "disabled"; ++}; ++ ++&vhubd { ++ status = "okay"; ++}; ++ ++&ehci2 { ++ status = "okay"; ++}; ++ ++&ehci3 { ++ status = "disabled"; ++}; ++ ++&pcie2 { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-evb.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-evb.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-evb.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-evb.dts 2026-09-09 15:51:27.103504249 +0000 +@@ -0,0 +1,1306 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++#include "aspeed-g7.dtsi" ++#include ++#include ++#include ++ ++#define DUAL_NODE 0 // 1: DUAL_NODE, 0: SINGLE_NODE ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 1 // 1: EP, 0: RC ++#define PCIE2_RC 1 // 1: RC, 0: SGMII ++ ++/ { ++ model = "AST2700A1 EVB"; ++ compatible = "aspeed,ast2700-evb", "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ rvas_memory0: rvas0{ ++ size = <0x0 0x04000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ rvas_memory1: rvas1{ ++ size = <0x0 0x04000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++#if 0 ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++#endif ++ ++ espi0_mcyc_memory: mcyc0 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ }; ++ ++ fan0: pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan1: pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan2: pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan3: pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan4: pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan5: pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan6: pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan7: pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan8: pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++}; ++ ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default ++ &pinctrl_pwm2_default &pinctrl_pwm3_default ++ &pinctrl_pwm4_default &pinctrl_pwm5_default ++ &pinctrl_pwm6_default &pinctrl_pwm7_default ++ &pinctrl_pwm8_default ++ &pinctrl_tach0_default &pinctrl_tach1_default ++ &pinctrl_tach2_default &pinctrl_tach3_default ++ &pinctrl_tach4_default &pinctrl_tach5_default ++ &pinctrl_tach6_default &pinctrl_tach7_default ++ &pinctrl_tach8_default &pinctrl_tach9_default ++ &pinctrl_tach10_default &pinctrl_tach11_default ++ &pinctrl_tach12_default &pinctrl_tach13_default ++ &pinctrl_tach14_default &pinctrl_tach15_default>; ++ fan-0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ fan-1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ fan-2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ fan-3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ fan-4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ fan-5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ fan-6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ fan-7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ fan-8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ fan-9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ fan-10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ fan-11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ fan-12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ fan-13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ fan-14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ fan-15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++ ++&edac { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++&mctp2 { ++ status = "okay"; ++ memory-region = <&mctp2_reserved>; ++}; ++ ++&sgpiom0 { ++ status = "okay"; ++}; ++ ++&sgpiom1 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc0_default &pinctrl_adc1_default ++ &pinctrl_adc2_default &pinctrl_adc3_default ++ &pinctrl_adc4_default &pinctrl_adc5_default ++ &pinctrl_adc6_default &pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc8_default &pinctrl_adc9_default ++ &pinctrl_adc10_default &pinctrl_adc11_default ++ &pinctrl_adc12_default &pinctrl_adc13_default ++ &pinctrl_adc14_default &pinctrl_adc15_default>; ++}; ++ ++&pinctrl0 { ++ pinctrl_emmcclk_driving: emmcclk-driving { ++ pins = "AC14"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_emmccmd_driving: emmccmd-driving { ++ pins = "AE15"; ++ drive-strength = <1>; ++ }; ++ pinctrl_emmc4bit_driving: emmcdat-driving { ++ pins = "AD14", "AE14", "AF14", "AB13"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_emmc8bit_driving: emmcdat-driving { ++ pins = "AD14", "AE14", "AF14", "AB13", "AF13", "AC13", "AD13", "AE13"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c12_15_hv_voltage: i3chv-voltage { ++ pins = "W25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c12_driving: i3c12-driving { ++ pins = "W25", "Y23"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c13_driving: i3c13-driving { ++ pins = "Y24", "W21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c14_driving: i3c14-driving { ++ pins = "AA23", "AC22"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c15_driving: i3c15-driving { ++ pins = "AB22", "Y21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_rgmii1_driving: rgmii1-driving { ++ pins = "D19", "C19", "D15", "B12", "B10", "P13", ++ "C18", "C6", "C7", "D7", "N13", "C8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&gpio1 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage &pinctrl_i3c12_15_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving ++ &pinctrl_i3c12_driving &pinctrl_i3c13_driving ++ &pinctrl_i3c14_driving &pinctrl_i3c15_driving>; ++ pinctrl-names = "default"; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "okay"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++#if 0 ++&vuart0 { ++ virtual; ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ status = "okay"; ++}; ++#endif ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++#if 1 ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ tpm0: tpmdev@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <25000000>; ++ reg = <0>; ++ status = "okay"; ++ }; ++}; ++#else ++&spi2 { ++ compatible = "aspeed,ast2700-spi"; ++ pinctrl-0 = <&pinctrl_spi2_default &pinctrl_spi2_cs1_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ flash@0 { ++ status = "okay"; ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++#endif ++ ++&can0 { ++ status = "okay"; ++}; ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++#if 1 ++ bus-width = <4>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_emmc_default ++ &pinctrl_emmcclk_driving ++ &pinctrl_emmccmd_driving ++ &pinctrl_emmc4bit_driving>; ++#else ++ bus-width = <8>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_emmc_default ++ &pinctrl_emmcg8_default ++ &pinctrl_emmcclk_driving ++ &pinctrl_emmccmd_driving ++ &pinctrl_emmc8bit_driving>; ++#endif ++ ++ non-removable; ++ max-frequency = <200000000>; ++}; ++ ++&ufs_controller { ++ status = "okay"; ++}; ++ ++&ufs { ++ status = "okay"; ++ lanes-per-direction = <2>; ++ ref-clk-freq = <26000000>; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mdio1 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy1: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ /* For DDR5 board */ ++ ti,rx-internal-delay = ; ++ ti,tx-internal-delay = ; ++ }; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy1>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii1_default &pinctrl_rgmii1_driving>; ++}; ++ ++#if 0 // Default to disable RC & SGMII ++#define PCIE2_RC 1 // 1: RC, 0: SGMII ++#if PCIE2_RC ++&pcie2 { ++ status = "okay"; ++}; ++#else ++&mdio2 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++#endif ++#endif ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ memory-region = <&espi0_mcyc_memory>; ++ perif-rtc-enable; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++#if 0 // eDAF mode: Change 1 to enable MIX mode in Linux, but HW mode in SPL would be overwritten ++ flash-edaf-mode = <0x0>; ++ flash-edaf-tgt-addr = <&edaf0>; ++#endif ++}; ++ ++&rtc_over_espi0 { ++ status = "okay"; ++}; ++ ++/* Enable UART2 and UART9 for obmc-console. */ ++&uart2 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart9 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++#if DUAL_NODE ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ perif-rtc-enable; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++ flash-dma-mode; ++#if 0 // eDAF mode: Change 1 to enable MIX mode in Linux, but HW mode in SPL would be overwritten ++ flash-edaf-mode = <0x0>; ++ flash-edaf-tgt-addr = <&edaf1>; ++#endif ++}; ++ ++&rtc_over_espi1 { ++ status = "okay"; ++}; ++ ++/* Enable UART7 and UART10 for obmc-console. */ ++&uart7 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++ ++&uart10 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ status = "okay"; ++}; ++#endif /* DUAL_NODE */ ++ ++&lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <2>; ++}; ++ ++&lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "okay"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++#if 1 ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++#else ++&lpc0_pcc { ++ status = "okay"; ++ pcc-ports = <0x80>; ++}; ++#endif ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <4>; ++}; ++ ++&lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <5>; ++}; ++ ++&lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <6>; ++}; ++ ++&lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <7>; ++}; ++ ++&lpc1_ibt { ++ status = "okay"; ++}; ++ ++&lpc1_mbox { ++ status = "okay"; ++}; ++ ++#if 1 ++&lpc1_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++#else ++&lpc1_pcc { ++ status = "okay"; ++ pcc-ports = <0x80>; ++}; ++#endif ++ ++&lpc1_uart_routing { ++ status = "okay"; ++}; ++ ++#if 1 ++&video0 { ++ status = "okay"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "okay"; ++ memory-region = <&video_engine_memory1>; ++}; ++#else ++&rvas0{ ++ status = "okay"; ++ memory-region = <&rvas_memory0>; ++}; ++ ++&rvas1{ ++ status = "okay"; ++ memory-region = <&rvas_memory1>; ++}; ++#endif ++ ++&disp_intf { ++ status = "okay"; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&rsss { ++ status = "okay"; ++}; ++ ++&ecdsa { ++ status = "okay"; ++}; ++ ++&hace { ++ dummy-key0 = < ++ 0x89878915 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key1 = < ++ 0x89878902 0x1DA242FA 0x1A7885CE 0x5BD7FA39 ++ 0xC53265FD 0xCC346B25 0x230370EE 0xD0570629 ++ >; ++ dummy-key2 = < ++ 0x2C914FA3 0x1056D87B 0xC13D6AE2 0x5E44AF98 ++ 0x0DB97321 0xCA378CF6 0x62EE115D 0x3FA74B90 ++ >; ++ dummy-key3 = < ++ 0x9E18D25C 0x6FB17A43 0x348DE520 0x1269FAC7 ++ 0xAC5503DE 0x2DB61F88 0xEF9AC074 0xB84E6731 ++ >; ++ status = "okay"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&pcie0_mmbi0 { ++ status = "okay"; ++ memory-region = <&pcie0_mmbi0_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else /* !PCIE0_EP */ ++&pcie0 { ++ status = "okay"; ++}; ++#endif /* PCIE0_EP */ ++ ++#if PCIE1_EP ++&bmc_dev1 { ++ status = "okay"; ++ memory-region = <&bmc_dev1_memory>; ++}; ++ ++&xdma1 { ++ status = "okay"; ++ memory-region = <&xdma_memory1>; ++}; ++ ++&pcie_vuart2 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart3 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <12>; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <13>; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <14>; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <15>; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "okay"; ++ bt-channel = <3>; ++}; ++ ++&pcie1_mmbi4 { ++ status = "okay"; ++ memory-region = <&pcie1_mmbi4_memory>; ++ ++ noi = <7>; ++ aspeed,mmbi-interface = "mctp-netdev", /* ch0 */ ++ "ioctl", /* ch1 */ ++ "ioctl", /* ch2 */ ++ "ioctl", /* ch3 */ ++ "ioctl", /* ch4 */ ++ "ioctl", /* ch5 */ ++ "ioctl"; /* ch6 */ ++}; ++#else /* !PCIE1_EP */ ++&pcie1 { ++ status = "okay"; ++}; ++#endif /* PCIE1_EP */ ++ ++&sdio_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++ ++ vcc_sdhci0: regulator-vcc-sdhci0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "SDHCI0 Vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 6) GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; ++ ++ vccq_sdhci0: regulator-vccq-sdhci0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "SDHCI0 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 7) GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1>, ++ <1800000 0>; ++ }; ++}; ++ ++&sdhci { ++ status = "okay"; ++ bus-width = <4>; ++ max-frequency = <125000000>; ++ /* DDR50 bits in CAPA2 are not supported */ ++ sdhci-caps-mask = <0x6 0x0>; ++ sdhci-drive-type = /bits/ 8 <3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sd_default>; ++ vmmc-supply = <&vcc_sdhci0>; ++ vqmmc-supply = <&vccq_sdhci0>; ++ sd-uhs-sdr104; /* enable sdr104 to execute tuning */ ++}; ++ ++#if 1 ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ status = "okay"; ++}; ++ ++&i2c2 { ++ status = "okay"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&i2c4 { ++ status = "okay"; ++}; ++ ++&i2c5 { ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++}; ++ ++&i2c12 { ++ status = "okay"; ++}; ++ ++&i2c13 { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&ehci0 { ++ status = "okay"; ++}; ++ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&uhci0 { ++ status = "okay"; ++ memory-region = <&uhci0_reserved>; ++}; ++ ++#endif ++ ++#if 0 ++&uphy3a { ++ status = "okay"; ++}; ++ ++&uphy3b { ++ status = "okay"; ++}; ++#endif ++ ++#if 0 ++&xhci0 { ++ status = "okay"; ++}; ++ ++&xhci1 { ++ status = "okay"; ++}; ++#endif ++ ++&vhuba0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2ahpd0_default>; ++}; ++ ++&vhubb1 { ++ status = "okay"; ++}; ++ ++&vhubc { ++ status = "okay"; ++#if 0 ++ pinctrl-0 = <&pinctrl_usb2cud_default>; ++ aspeed,uart-ports = <12>; ++#endif ++}; ++ ++&ehci3 { ++ status = "okay"; ++}; ++ ++&uhci1 { ++ status = "okay"; ++ memory-region = <&uhci1_reserved>; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; ++ ++#if 0 ++&soc0 { ++ mbox-ssp-0 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31480000 0x0 0x200000>, <0x0 0x10001000 0x0 0x1000>; ++ mboxes = <&mbox0 0>; ++ aspeed,tx-timeout = <100>; ++ }; ++}; ++#endif ++ ++#if 1 ++&soc0 { ++ mbox-bootmcu-1 { ++ compatible = "aspeed,aspeed-mbox"; ++ reg = <0x4 0x31880000 0x0 0x100000>, <0x4 0x31980000 0x0 0x100000>; ++ mboxes = <&mbox2 1>; ++ aspeed,tx-timeout = <10000>; ++ }; ++}; ++#endif +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-ncsi.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-ncsi.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-ncsi.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-ncsi.dts 2026-09-09 15:51:27.104504234 +0000 +@@ -0,0 +1,48 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700a1-evb.dts" ++ ++/ { ++ model = "AST2700A1-NCSI"; ++}; ++ ++&sgpios { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII0 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii0_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii0_default>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rmii"; ++ use-ncsi; ++ ++ pinctrl-names = "default"; ++ /* If you want to use RMII1 RCLKO as internal clock for RMII, ++ * add &pinctrl_rmii1_rclko_default in pinctrl-0. ++ */ ++ pinctrl-0 = <&pinctrl_rmii1_default>; ++}; ++ ++&syscon1 { ++ mac0-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++ mac1-clk-delay = <0 0 ++ 0 0 ++ 0 0>; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/ast2700a1-raw.dts b/arch/arm64/boot/dts/aspeed/ast2700a1-raw.dts +--- a/arch/arm64/boot/dts/aspeed/ast2700a1-raw.dts 1970-01-01 00:00:00.000000000 +0000 ++++ b/arch/arm64/boot/dts/aspeed/ast2700a1-raw.dts 2026-09-09 15:51:27.104504234 +0000 +@@ -0,0 +1,220 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "ast2700a1-evb.dts" ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi0 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi0:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "spi1:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "spi1:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi2 { ++ status = "disabled"; ++}; ++ ++&emmc_controller { ++ status = "disabled"; ++}; ++ ++&emmc { ++ status = "disabled"; ++}; ++ ++&ufs_controller { ++ status = "disabled"; ++}; ++ ++&ufs { ++ status = "disabled"; ++}; ++ ++&video0 { ++ status = "disabled"; ++}; ++ ++&video1 { ++ status = "disabled"; ++}; ++ ++&disp_intf { ++ status = "disabled"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "disabled"; ++}; ++ ++&xdma0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart0 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "disabled"; ++}; ++ ++&pcie0_mmbi0 { ++ status = "disabled"; ++}; ++#else ++&pcie0 { ++ status = "disabled"; ++}; ++#endif ++ ++#if PCIE1_EP ++&bmc_dev1 { ++ status = "disabled"; ++}; ++ ++&xdma1 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart2 { ++ status = "disabled"; ++}; ++ ++&pcie_vuart3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "disabled"; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "disabled"; ++}; ++ ++&pcie1_mmbi4 { ++ status = "disabled"; ++}; ++#else ++&pcie1 { ++ status = "disabled"; ++}; ++#endif ++ ++&sdio_controller { ++ status = "disabled"; ++}; ++ ++&sdhci { ++ status = "disabled"; ++}; ++ +diff --git a/crypto/aspeed_crypt.c b/crypto/aspeed_crypt.c +--- a/crypto/aspeed_crypt.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/crypto/aspeed_crypt.c 2026-09-09 15:51:44.713238147 +0000 +@@ -0,0 +1,264 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2025 Aspeed Technology Inc. ++ */ ++ ++#define ASPEED_CRYPT_NAME "aspeed-crypt" ++#define pr_fmt(fmt) ASPEED_CRYPT_NAME ": " fmt ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "internal.h" ++ ++enum aspeed_crypt_type { ++ ASPEED_SKCIPHER_TYPE, ++ ASPEED_AHASH_TYPE, ++ ASPEED_AKCIPHER_TYPE, ++ ASPEED_SIG_TYPE, ++}; ++ ++struct aspeed_crypt_drv { ++ char *algo; ++ char *drv_name; ++}; ++ ++struct aspeed_crypt_test { ++ char *test_item; ++ enum aspeed_crypt_type type; ++ int num; ++ struct aspeed_crypt_drv *algo; ++}; ++ ++struct aspeed_crypt_drv aspeed_des_binding[] = { ++ { "ecb(des)", "aspeed-ecb-des" }, ++ { "cbc(des)", "aspeed-cbc-des" }, ++ { "ctr(des)", "aspeed-ctr-des" }, ++}; ++ ++struct aspeed_crypt_drv aspeed_tdes_binding[] = { ++ { "ecb(des3_ede)", "aspeed-ecb-tdes" }, ++ { "cbc(des3_ede)", "aspeed-cbc-tdes" }, ++ { "ctr(des3_ede)", "aspeed-ctr-tdes" }, ++}; ++ ++struct aspeed_crypt_drv aspeed_sha3_binding[] = { ++ { "sha3-224", "aspeed-sha3-224" }, ++ { "sha3-256", "aspeed-sha3-256" }, ++ { "sha3-384", "aspeed-sha3-384" }, ++ { "sha3-512", "aspeed-sha3-512" }, ++}; ++ ++struct aspeed_crypt_drv aspeed_rsa_binding[] = { ++ { "rsa", "aspeed-rsa" }, ++}; ++ ++struct aspeed_crypt_drv aspeed_ecdsa_binding[] = { ++ { "ecdsa-nist-p256", "aspeed-ecdsa-nist-p256" }, ++ { "ecdsa-nist-p384", "aspeed-ecdsa-nist-p384" }, ++}; ++ ++static bool aspeed_detect_skcipher_drv(char *drv_name) ++{ ++ struct crypto_skcipher *tfm = NULL; ++ bool found = false; ++ ++ tfm = crypto_alloc_skcipher(drv_name, 0, 0); ++ if (!IS_ERR(tfm)) { ++ found = true; ++ crypto_free_skcipher(tfm); ++ } ++ ++ return found; ++} ++ ++static bool aspeed_detect_ahash_drv(char *drv_name) ++{ ++ struct crypto_ahash *tfm = NULL; ++ bool found = false; ++ ++ tfm = crypto_alloc_ahash(drv_name, 0, 0); ++ if (!IS_ERR(tfm)) { ++ found = true; ++ crypto_free_ahash(tfm); ++ } ++ ++ return found; ++} ++ ++static bool aspeed_detect_akcipher_drv(char *drv_name) ++{ ++ struct crypto_akcipher *tfm = NULL; ++ bool found = false; ++ ++ tfm = crypto_alloc_akcipher(drv_name, 0, 0); ++ if (!IS_ERR(tfm)) { ++ found = true; ++ crypto_free_akcipher(tfm); ++ } ++ ++ return found; ++} ++ ++static bool aspeed_detect_sig_drv(char *drv_name) ++{ ++ struct crypto_sig *tfm = NULL; ++ bool found = false; ++ ++ tfm = crypto_alloc_sig(drv_name, 0, 0); ++ if (!IS_ERR(tfm)) { ++ found = true; ++ crypto_free_sig(tfm); ++ } ++ ++ return found; ++} ++ ++static bool aspeed_detect_drv(enum aspeed_crypt_type type, char *drv_name) ++{ ++ switch (type) { ++ case ASPEED_SKCIPHER_TYPE: ++ return aspeed_detect_skcipher_drv(drv_name); ++ case ASPEED_AHASH_TYPE: ++ return aspeed_detect_ahash_drv(drv_name); ++ case ASPEED_AKCIPHER_TYPE: ++ return aspeed_detect_akcipher_drv(drv_name); ++ case ASPEED_SIG_TYPE: ++ return aspeed_detect_sig_drv(drv_name); ++ default: ++ return false; ++ } ++} ++ ++static const struct aspeed_crypt_test * ++aspeed_crypt_get_alg(const char *algo_name) ++{ ++ int i = 0; ++ static const struct aspeed_crypt_test algo_ts[] = { ++ { "des", ASPEED_SKCIPHER_TYPE, ARRAY_SIZE(aspeed_des_binding), ++ aspeed_des_binding }, ++ { "des3", ASPEED_SKCIPHER_TYPE, ARRAY_SIZE(aspeed_tdes_binding), ++ aspeed_tdes_binding }, ++ { "sha-3", ASPEED_AHASH_TYPE, ARRAY_SIZE(aspeed_sha3_binding), ++ aspeed_sha3_binding }, ++ { "rsa", ASPEED_AKCIPHER_TYPE, ARRAY_SIZE(aspeed_rsa_binding), ++ aspeed_rsa_binding }, ++ { "ecdsa", ASPEED_SIG_TYPE, ARRAY_SIZE(aspeed_ecdsa_binding), ++ aspeed_ecdsa_binding }, ++ }; ++ ++ for (i = 0; i < ARRAY_SIZE(algo_ts); i++) { ++ if (strlen(algo_ts[i].test_item) == strlen(algo_name) && ++ strncmp(algo_ts[i].test_item, algo_name, ++ strlen(algo_name)) == 0) ++ return &algo_ts[i]; ++ } ++ ++ return NULL; ++} ++ ++static __maybe_unused int aspeed_crypt_run_tests(char *alg_name) ++{ ++ const struct aspeed_crypt_test *ts; ++ int ret = 0; ++ int i = 0; ++ ++ ts = aspeed_crypt_get_alg(alg_name); ++ if (!ts) { ++ pr_err("No test suite found for algorithm: %s", alg_name); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < ts->num && ret == 0; i++) { ++ if (aspeed_detect_drv(ts->type, ts->algo[i].drv_name)) ++ ret = alg_test(ts->algo[i].drv_name, ts->algo[i].algo, ++ 0, 0); ++ else ++ ret = -ENOENT; ++ ++ pr_info("alg: %s tests %s\n", ts->algo[i].algo, ++ !ret ? "passed" : "failed"); ++ } ++ ++ return ret; ++} ++ ++#if defined(CONFIG_CRYPTO_MANAGER_DISABLE_TESTS) && \ ++ !defined(CONFIG_CRYPTO_DEV_ASPEED_ACRY) ++static ssize_t aspeed_crypt_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ pr_err("Crypto tests are disabled\n"); ++ return -EOPNOTSUPP; ++} ++#else ++static ssize_t aspeed_crypt_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ u8 kbuf[16] = { 0 }; ++ ++ if (count > sizeof(kbuf) - 1) ++ return -EINVAL; ++ ++ if (copy_from_user(kbuf, ubuf, min(count, sizeof(kbuf)))) ++ return -EFAULT; ++ strim(kbuf); ++ ++ if (!aspeed_crypt_run_tests(kbuf)) ++ pr_err("alg: %s tests passed\n", kbuf); ++ else ++ pr_err("alg: %s tests failed\n", kbuf); ++ ++ return min(count, sizeof(kbuf)); ++} ++#endif ++ ++static const struct file_operations fops = { ++ .owner = THIS_MODULE, ++ .write = aspeed_crypt_write, ++}; ++ ++static dev_t dev; ++static struct cdev cdev; ++static struct class *class; ++static int __init acrypt_mod_init(void) ++{ ++ int ret; ++ ++ ret = alloc_chrdev_region(&dev, 0, 1, ASPEED_CRYPT_NAME); ++ if (ret < 0) ++ return ret; ++ ++ cdev_init(&cdev, &fops); ++ ret = cdev_add(&cdev, dev, 1); ++ if (ret) ++ goto del_region; ++ ++ class = class_create(ASPEED_CRYPT_NAME); ++ if (IS_ERR(class)) { ++ ret = PTR_ERR(class); ++ goto del_cdev; ++ } ++ ++ device_create(class, NULL, dev, NULL, ASPEED_CRYPT_NAME); ++ return 0; ++del_cdev: ++ cdev_del(&cdev); ++del_region: ++ unregister_chrdev_region(dev, 1); ++ return ret; ++} ++ ++static void __exit acrypt_mod_fini(void) ++{ ++ device_destroy(class, dev); ++ class_destroy(class); ++ cdev_del(&cdev); ++ unregister_chrdev_region(dev, 1); ++} ++ ++subsys_initcall(acrypt_mod_init); ++module_exit(acrypt_mod_fini); +diff --git a/drivers/bus/aspeed-ltpi.c b/drivers/bus/aspeed-ltpi.c +--- a/drivers/bus/aspeed-ltpi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/bus/aspeed-ltpi.c 2026-09-09 15:51:44.538240791 +0000 +@@ -0,0 +1,1195 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright ASPEED Technology ++ ++#include "linux/dev_printk.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "aspeed-ltpi.h" ++ ++#define LTPI_AUTO_CAP_LOW 0x24 ++#define LTPI_I2C_IO_FRAME_EN GENMASK(29, 24) ++#define LTPI_AUTO_CAP_HIGH 0x28 ++#define LTPI_UART_IO_FRAME_EN GENMASK(14, 13) ++ ++#define LTPI_LINK_CONTROLL 0x80 ++#define LTPI_AUTO_CONFIG BIT(10) ++ ++#define LTPI_INTR_STATUS 0x100 ++#define LTPI_INTR_EN 0x104 ++#define LTPI_INTR_EN_OP_LINK_LOST BIT(4) ++#define LTPI_LINK_MANAGE_ST 0x108 ++#define LTPI_LINK_PARTNER_FLAG BIT(24) ++ ++#define LTPI_MANUAL_CAP_LOW 0x118 ++#define LTPI_MANUAL_CAP_HIGH 0x11c ++ ++#define LTPI_I2C_TIMING_0 0x134 ++#define LTPI_I2C_TIMING_1 0x138 ++ ++#define LTPI_I2C_100K_0 0x3535352f ++#define LTPI_I2C_100K_1 0x09353535 ++ ++#define LTPI_I2C_400K_0 0x06060d06 ++#define LTPI_I2C_400K_1 0x090d0a06 ++ ++#define SCU_IO_PINS_TRAP1 0x10 ++#define SCU_IO_PINS_TRAP1_CLEAR 0x14 ++#define SCU_IO_PINS_TRAP_LTPI GENMASK(2, 0) ++#define SCU_IO_OTP_TRAP1 0xa00 ++#define SCU_IO_OTP_TRAP1_CLEAR 0xa04 ++#define SCU_IO_OTP_TRAP2 0xa20 ++#define SCU_IO_OTP_TRAP2_CLEAR 0xa24 ++ ++/* SCU registers for PLL control */ ++#define SCU1_HPLL_1 0x300 ++#define SCU1_HPLL_2 0x304 ++#define SCU1_APLL_1 0x310 ++#define SCU1_APLL_2 0x314 ++#define SCU1_DPLL_1 0x320 ++#define SCU1_DPLL_2 0x324 ++#define SCU1_L0PLL_1 0x340 ++#define SCU1_L0PLL_2 0x344 ++#define SCU1_L1PLL_1 0x350 ++#define SCU1_L1PLL_2 0x354 ++ ++#define PLL_REG1_RESET BIT(25) ++#define PLL_REG1_BYPASS BIT(24) ++#define PLL_REG1_DIS BIT(23) ++#define PLL_REG1_P GENMASK(22, 19) ++#define PLL_REG1_N GENMASK(18, 13) ++#define PLL_REG1_M GENMASK(12, 0) ++ ++#define PLL_REG2_LOCK BIT(31) ++#define PLL_REG2_BWADJ GENMASK(11, 0) ++ ++#define MAX_I2C_IN_LTPI 6 ++#define MAX_UART_IN_LTPI 2 ++ ++#define ADVERTISE_TIMEOUT_US 105000 /* 105 ms */ ++ ++enum chip_version { ++ AST2700, ++ AST1700, ++}; ++ ++struct ltpi_clk_info { ++ s16 freq; /* clock frequency in MHz*/ ++ s16 clk_sel; /* clock selection */ ++}; ++ ++static const struct ltpi_clk_info ltpi_clk_lookup_sdr[13] = { ++ { 25, REG_LTPI_PLL_25M }, ++ { 50, REG_LTPI_PLL_LPLL }, ++ { 75, REG_LTPI_PLL_LPLL }, ++ { 100, REG_LTPI_PLL_LPLL }, ++ { 150, REG_LTPI_PLL_LPLL }, ++ { 200, REG_LTPI_PLL_LPLL }, ++ { 250, REG_LTPI_PLL_LPLL }, ++ { 300, REG_LTPI_PLL_LPLL }, ++ { 400, REG_LTPI_PLL_LPLL }, ++ { 600, REG_LTPI_PLL_LPLL }, ++ { -1, -1 }, ++ { -1, -1 }, ++ { 500, REG_LTPI_PLL_LPLL } ++}; ++ ++static const struct ltpi_clk_info ltpi_clk_lookup_ddr[13] = { ++ { 50, REG_LTPI_PLL_LPLL }, ++ { 100, REG_LTPI_PLL_LPLL }, ++ { 150, REG_LTPI_PLL_LPLL }, ++ { 200, REG_LTPI_PLL_LPLL }, ++ { 300, REG_LTPI_PLL_LPLL }, ++ { 400, REG_LTPI_PLL_LPLL }, ++ { 500, REG_LTPI_PLL_LPLL }, ++ { 600, REG_LTPI_PLL_LPLL }, ++ { 800, REG_LTPI_PLL_LPLL }, ++ { 1200, REG_LTPI_PLL_LPLL }, ++ { -1, -1 }, ++ { -1, -1 }, ++ { 1000, REG_LTPI_PLL_LPLL } ++}; ++ ++#define MHZ(x) ((x) * 1000000) ++#define NUM_PLL_PARAM 13 ++#define REG_N_M_P(n, m, p) ((((n) - 1) << 13) | ((m) - 1) | (((p) - 1) << 19)) ++#define REG_BWADJ(bwadj) ((bwadj) - 1) ++ ++struct pll_param { ++ int freq; ++ u32 n_m_p; ++ u16 bwadj; ++}; ++ ++static const struct pll_param pll_param_lookup[NUM_PLL_PARAM] = { ++ { .freq = MHZ(50), .n_m_p = REG_N_M_P(1, 32, 16), .bwadj = REG_BWADJ(16) }, ++ { .freq = MHZ(75), .n_m_p = REG_N_M_P(1, 48, 16), .bwadj = REG_BWADJ(24) }, ++ { .freq = MHZ(100), .n_m_p = REG_N_M_P(1, 56, 14), .bwadj = REG_BWADJ(28) }, ++ { .freq = MHZ(150), .n_m_p = REG_N_M_P(1, 60, 10), .bwadj = REG_BWADJ(30) }, ++ { .freq = MHZ(200), .n_m_p = REG_N_M_P(1, 48, 6), .bwadj = REG_BWADJ(24) }, ++ { .freq = MHZ(250), .n_m_p = REG_N_M_P(1, 60, 6), .bwadj = REG_BWADJ(30) }, ++ { .freq = MHZ(300), .n_m_p = REG_N_M_P(1, 48, 4), .bwadj = REG_BWADJ(24) }, ++ { .freq = MHZ(400), .n_m_p = REG_N_M_P(1, 32, 2), .bwadj = REG_BWADJ(16) }, ++ { .freq = MHZ(500), .n_m_p = REG_N_M_P(1, 40, 2), .bwadj = REG_BWADJ(20) }, ++ { .freq = MHZ(600), .n_m_p = REG_N_M_P(1, 48, 2), .bwadj = REG_BWADJ(24) }, ++ { .freq = MHZ(800), .n_m_p = REG_N_M_P(1, 32, 1), .bwadj = REG_BWADJ(16) }, ++ { .freq = MHZ(1000), .n_m_p = REG_N_M_P(1, 40, 1), .bwadj = REG_BWADJ(20) }, ++ { .freq = MHZ(1200), .n_m_p = REG_N_M_P(1, 48, 1), .bwadj = REG_BWADJ(24) }, ++}; ++ ++struct pll_info { ++ u32 reg_offset0; ++ u32 reg_offset1; ++}; ++ ++static const struct pll_info scu_pll_info[] = { ++ [0] = { .reg_offset0 = SCU1_L0PLL_1, .reg_offset1 = SCU1_L0PLL_2 }, ++ [1] = { .reg_offset0 = SCU1_L1PLL_1, .reg_offset1 = SCU1_L1PLL_2 }, ++}; ++ ++struct aspeed_ltpi_priv { ++ struct device *dev; ++ void __iomem *regs; ++ void __iomem *phy_regs; ++ void __iomem *top_regs; ++ struct clk *ltpi_clk; ++ struct clk *ltpi_phyclk; ++ struct reset_control *ltpi_rst; ++ struct regmap *scu; ++ u32 version; ++ u32 i2c_tunneling; ++ u32 i2c_timing_0; ++ u32 i2c_timing_1; ++ u32 uart_tunneling; ++ int index; ++ ++ /* Training parameters */ ++ u16 phy_speed_cap; ++ bool otp_ddr_dis; ++ int disable_auto_downshift; ++ int crc_format; ++ int io_driving; ++ int clk_inverse; ++ int link_speed_frm_rx_cnt; ++ int ad_timeout; ++ u64 op_timeout; ++ u64 t_link_detect; ++}; ++ ++static int ltpi_get_link_partner(struct aspeed_ltpi_priv *ltpi) ++{ ++ u32 reg = readl(ltpi->regs + LTPI_LINK_MNG_ST); ++ ++ return FIELD_GET(REG_LTPI_LINK_PARTNER_FLAG, reg); ++} ++ ++static void ltpi_dump(struct aspeed_ltpi_priv *ltpi) ++{ ++ int i; ++ ++ dev_warn(ltpi->dev, "\nDumping LTPI registers:\n"); ++ for (i = 0; i < 0x250; i += 0x10) { ++ dev_warn(ltpi->dev, "control[%08x]: %08x %08x %08x %08x\n", i, ++ readl(ltpi->regs + i), readl(ltpi->regs + i + 4), ++ readl(ltpi->regs + i + 8), readl(ltpi->regs + i + 12)); ++ } ++ ++ for (i = 0; i < 0x30; i += 0x10) { ++ dev_warn(ltpi->dev, "PHY[%08x]: %08x %08x %08x %08x\n", i, ++ readl(ltpi->phy_regs + i), ++ readl(ltpi->phy_regs + i + 4), ++ readl(ltpi->phy_regs + i + 8), ++ readl(ltpi->phy_regs + i + 12)); ++ } ++} ++ ++static irqreturn_t aspeed_ltpi_irq_handler(int irq, void *dev_id) ++{ ++ struct aspeed_ltpi_priv *priv = dev_id; ++ u32 status = readl(priv->regs + LTPI_INTR_STATUS); ++ ++ if (status & LTPI_INTR_EN_OP_LINK_LOST) { ++ writel(0, priv->regs + LTPI_INTR_EN); ++ writel(status, priv->regs + LTPI_INTR_STATUS); ++ if (ltpi_get_link_partner(priv)) ++ panic("LTPI link lost!\n"); ++ /* Will not return */ ++ else ++ dev_err(priv->dev, "LTPI link lost!\n"); ++ } ++ ++ writel(status, priv->regs + LTPI_INTR_STATUS); ++ ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_ltpi_init_mux(struct aspeed_ltpi_priv *priv) ++{ ++ u32 reg, i2c_en, uart_en, i; ++ ++ reg = readl(priv->regs + LTPI_AUTO_CAP_LOW); ++ ++ i2c_en = FIELD_GET(LTPI_I2C_IO_FRAME_EN, reg); ++ i2c_en &= priv->i2c_tunneling; ++ ++ reg &= ~LTPI_I2C_IO_FRAME_EN; ++ reg |= FIELD_PREP(LTPI_I2C_IO_FRAME_EN, i2c_en); ++ writel(reg, priv->regs + LTPI_MANUAL_CAP_LOW); ++ ++ reg = readl(priv->regs + LTPI_AUTO_CAP_HIGH); ++ ++ uart_en = FIELD_GET(LTPI_UART_IO_FRAME_EN, reg); ++ uart_en &= priv->uart_tunneling; ++ ++ reg &= ~LTPI_UART_IO_FRAME_EN; ++ reg |= FIELD_PREP(LTPI_UART_IO_FRAME_EN, uart_en); ++ ++ writel(reg, priv->regs + LTPI_MANUAL_CAP_HIGH); ++ ++ /* Apply LTPI manual configuration */ ++ reg = readl(priv->regs + LTPI_LINK_CONTROLL); ++ reg &= ~LTPI_AUTO_CONFIG; ++ writel(reg, priv->regs + LTPI_LINK_CONTROLL); ++ ++ /* Set the AST1700 i2c ac-timing */ ++ if (priv->version == AST1700) { ++ /* Apply i2c timing with i2c tunneling setting */ ++ for (i = 0; i < MAX_I2C_IN_LTPI; i++) { ++ if ((priv->i2c_tunneling >> i) & 0x1) { ++ writel(priv->i2c_timing_0, ++ priv->regs + LTPI_I2C_TIMING_0 + ++ (0x8 * i)); ++ writel(priv->i2c_timing_1, ++ priv->regs + LTPI_I2C_TIMING_1 + ++ (0x8 * i)); ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int clz16(u16 x) ++{ ++ int n = 0; ++ ++ if (x == 0) ++ return 16; ++ ++ if (x <= 0x00ff) { ++ n += 8; ++ x <<= 8; ++ } ++ if (x <= 0x0fff) { ++ n += 4; ++ x <<= 4; ++ } ++ if (x <= 0x3fff) { ++ n += 2; ++ x <<= 2; ++ } ++ if (x <= 0x7fff) ++ n += 1; ++ ++ return n; ++} ++ ++static u16 find_max_speed(u16 cap) ++{ ++ return 15 - clz16(cap & ~LTPI_SP_CAP_DDR); ++} ++ ++static void ltpi_phy_unlock(struct aspeed_ltpi_priv *ltpi) ++{ ++ writel(LTPI_PROT_KEY_UNLOCK, ltpi->phy_regs + LTPI_PROT_KEY); ++} ++ ++static void ltpi_enable_rx_bias(struct aspeed_ltpi_priv *ltpi) ++{ ++ u32 val = readl(ltpi->top_regs + LTPI_LVDS_RX_CTRL); ++ ++ val |= (REG_LTPI_LVDS_RX1_BIAS_EN | REG_LTPI_LVDS_RX0_BIAS_EN); ++ writel(val, ltpi->top_regs + LTPI_LVDS_RX_CTRL); ++ udelay(1); ++} ++ ++static int ltpi_phy_get_mode(struct aspeed_ltpi_priv *ltpi) ++{ ++ u32 reg = readl(ltpi->phy_regs + LTPI_PHY_CTRL); ++ ++ return FIELD_GET(REG_LTPI_PHY_MODE, reg); ++} ++ ++static int ltpi_phy_set_mode(struct aspeed_ltpi_priv *ltpi, int mode) ++{ ++ u32 reg; ++ ++ if (mode < 0 || mode > LTPI_PHY_MODE_CDR_HI_SP) { ++ dev_err(ltpi->dev, "%s: invalid mode %d\n", __func__, mode); ++ return -1; ++ } ++ ++ reg = readl(ltpi->phy_regs + LTPI_PHY_CTRL); ++ reg &= ~REG_LTPI_PHY_MODE; ++ reg |= mode; ++ writel(reg, ltpi->phy_regs + LTPI_PHY_CTRL); ++ ++ return 0; ++} ++ ++static int ltpi_phy_set_clksel(struct aspeed_ltpi_priv *ltpi, int clksel, ++ bool is_op_clk) ++{ ++ u32 reg; ++ ++#define RX_CLK_INVERSE BIT(1) ++#define TX_CLK_INVERSE BIT(0) ++ ++ reg = readl(ltpi->phy_regs + LTPI_PLL_CTRL); ++ reg &= ~(REG_LTPI_PLL_SELECT | REG_LTPI_PLL_SET | ++ REG_LTPI_RX_PHY_CLK_INV | REG_LTPI_TX_PHY_CLK_INV); ++ reg |= FIELD_PREP(REG_LTPI_PLL_SELECT, clksel); ++ ++ if (ltpi->clk_inverse & RX_CLK_INVERSE) ++ reg |= REG_LTPI_RX_PHY_CLK_INV; ++ ++ if (ltpi->clk_inverse & TX_CLK_INVERSE) ++ reg |= REG_LTPI_TX_PHY_CLK_INV; ++ ++ if (is_op_clk) ++ reg |= REG_LTPI_PLL_SET; ++ ++ writel(reg, ltpi->phy_regs + LTPI_PLL_CTRL); ++ ++ return 0; ++} ++ ++static void ltpi_set_crc_format(struct aspeed_ltpi_priv *ltpi, int crc_fmt) ++{ ++ u32 val = readl(ltpi->regs + LTPI_CRC_OPTION); ++ ++ val &= ~(REG_LTPI_SW_CRC_OUT_ML_FIRST | REG_LTPI_SW_CRC_IN_LSB_FIRST); ++ if (crc_fmt) ++ val |= REG_LTPI_SW_CRC_OUT_ML_FIRST | ++ REG_LTPI_SW_CRC_IN_LSB_FIRST; ++ ++ writel(val, ltpi->regs + LTPI_CRC_OPTION); ++} ++ ++static int ltpi_reset(struct aspeed_ltpi_priv *ltpi) ++{ ++ /* Using reset controller */ ++ reset_control_assert(ltpi->ltpi_rst); ++ udelay(1); ++ /* Assuming clk gate is handled by clock framework or enabled */ ++ /* U-Boot does ungate here */ ++ if (ltpi->ltpi_clk) ++ clk_prepare_enable(ltpi->ltpi_clk); ++ ++ reset_control_deassert(ltpi->ltpi_rst); ++ ++ ltpi_phy_unlock(ltpi); ++ ++ return 0; ++} ++ ++static u32 ltpi_get_link_mng_state(struct aspeed_ltpi_priv *ltpi) ++{ ++ return FIELD_GET(REG_LTPI_LINK_MNG_ST, ++ readl(ltpi->regs + LTPI_LINK_MNG_ST)); ++} ++ ++static int ltpi_poll_link_mng_state(struct aspeed_ltpi_priv *ltpi, ++ u32 expected, u32 unexpected, ++ int timeout_us) ++{ ++ u32 state; ++ int ret; ++ ++ ret = readl_poll_timeout(ltpi->regs + LTPI_LINK_MNG_ST, state, ++ (FIELD_GET(REG_LTPI_LINK_MNG_ST, state) == expected) || ++ (FIELD_GET(REG_LTPI_LINK_MNG_ST, state) == unexpected), ++ 100, timeout_us); ++ ++ if (FIELD_GET(REG_LTPI_LINK_MNG_ST, state) == unexpected) ++ return -ENOLINK; ++ ++ if (ret) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static int ltpi_wait_state_pll_set(struct aspeed_ltpi_priv *ltpi, ++ int timeout_us) ++{ ++ return ltpi_poll_link_mng_state(ltpi, LTPI_LINK_MNG_ST_WAIT_PLL_SET, -1, ++ timeout_us); ++} ++ ++static int ltpi_wait_state_op(struct aspeed_ltpi_priv *ltpi) ++{ ++ u32 val = readl(ltpi->regs + LTPI_LINK_ST); ++ ++ val |= (REG_LTPI_CON_ACC_TO_ERR | REG_LTPI_FRM_CRC_ERR | ++ REG_LTPI_LINK_LOST_ERR); ++ writel(val, ltpi->regs + LTPI_LINK_ST); ++ ++ return ltpi_poll_link_mng_state(ltpi, LTPI_LINK_MNG_ST_OP, ++ LTPI_LINK_MNG_ST_DETECT_ALIGN, ++ ltpi->ad_timeout); ++} ++ ++static int ltpi_set_lvds_io_driving(struct aspeed_ltpi_priv *ltpi, int driving) ++{ ++ u32 val; ++ ++ val = readl(ltpi->top_regs + LTPI_SW_FORCE_EN); ++ val |= REG_LTPI_SW_FORCE_LVDS_TX_DS_EN; ++ writel(val, ltpi->top_regs + LTPI_SW_FORCE_EN); ++ ++ val = readl(ltpi->top_regs + LTPI_LVDS_TX_CTRL); ++ val &= ~(REG_LTPI_LVDS_TX1_DS1 | REG_LTPI_LVDS_TX1_DS0 | ++ REG_LTPI_LVDS_TX0_DS1 | REG_LTPI_LVDS_TX0_DS0); ++ ++ /* tx1: clk, tx0: data */ ++ if (driving & BIT(1)) ++ val |= (REG_LTPI_LVDS_TX1_DS1 | REG_LTPI_LVDS_TX0_DS1); ++ ++ if (driving & BIT(0)) ++ val |= (REG_LTPI_LVDS_TX1_DS0 | REG_LTPI_LVDS_TX0_DS0); ++ ++ writel(val, ltpi->top_regs + LTPI_LVDS_TX_CTRL); ++ ++ return 0; ++} ++ ++static int ltpi_set_local_speed_cap(struct aspeed_ltpi_priv *ltpi, ++ u32 speed_cap) ++{ ++ u32 reg; ++ ++ /* only set bits that Aspeed SOC supported */ ++ speed_cap &= LTPI_SP_CAP_ASPEED_SUPPORTED; ++ if (ltpi->otp_ddr_dis) ++ speed_cap &= ~LTPI_SP_CAP_DDR; ++ ++ reg = readl(ltpi->regs + LTPI_CAP_LOCAL); ++ reg &= ~REG_LTPI_SP_CAP_LOCAL; ++ reg |= FIELD_PREP(REG_LTPI_SP_CAP_LOCAL, speed_cap); ++ writel(reg, ltpi->regs + LTPI_CAP_LOCAL); ++ ++ return 0; ++} ++ ++static void ltpi_do_link_training(struct aspeed_ltpi_priv *ltpi) ++{ ++ u32 val; ++ ++ /* Reset the PHY to PHY_MODE_OFF */ ++ ltpi_reset(ltpi); ++ ++ ltpi_enable_rx_bias(ltpi); ++ ltpi_set_local_speed_cap(ltpi, ltpi->phy_speed_cap); ++ ltpi_set_lvds_io_driving(ltpi, ltpi->io_driving); ++ ltpi_set_crc_format(ltpi, ltpi->crc_format); ++ ++ /* ++ * Configure the LINK_SPEED frame count to be received before ++ * entering AD. This configuraiton only effects the SCM LTPI. ++ */ ++ val = readl(ltpi->regs + LTPI_LINK_MANAGE_CTRL0); ++ val &= ~REG_LTPI_RX_LINK_SP_FRM_NUM; ++ val |= FIELD_PREP(REG_LTPI_RX_LINK_SP_FRM_NUM, ++ ltpi->link_speed_frm_rx_cnt); ++ writel(val, ltpi->regs + LTPI_LINK_MANAGE_CTRL0); ++ ++ /* ++ * ad_timeout in us = ad_timeout in clock cycles * period of 25MHz ++ * ad_timeout in clock cycles ++ * = ad_timeout in us / period of 25MHz ++ * = (ad_timeout in us * 1000)ns / 40ns = ad_timeout * 25 ++ */ ++ writel(ltpi->ad_timeout * 25, ltpi->regs + LTPI_LINK_MANAGE_CTRL1); ++ ++ /* Set the clock source to the base frequency 25MHz */ ++ ltpi_phy_set_clksel(ltpi, REG_LTPI_PLL_25M, false); ++ ++ /* To make the remote side back to the link lost state */ ++ mdelay(ADVERTISE_TIMEOUT_US / 1000); ++ ++ ltpi_phy_set_mode(ltpi, LTPI_PHY_MODE_SDR); ++} ++ ++static int scu_get_pll_freq(struct aspeed_ltpi_priv *ltpi, int pll_id) ++{ ++ const struct pll_info *info; ++ u32 reg; ++ int m, n, p; ++ ++ if (pll_id > 1) ++ return -1; ++ ++ info = &scu_pll_info[pll_id]; ++ regmap_read(ltpi->scu, info->reg_offset0, ®); ++ m = FIELD_GET(PLL_REG1_M, reg); ++ n = FIELD_GET(PLL_REG1_N, reg); ++ p = FIELD_GET(PLL_REG1_P, reg); ++ ++ return (25000000 * (m + 1) / (n + 1) / (p + 1)); ++} ++ ++static int scu_set_pll_freq(struct aspeed_ltpi_priv *ltpi, int pll_id, int freq) ++{ ++ const struct pll_info *info; ++ const struct pll_param *param; ++ int curr_freq, i; ++ bool match = false; ++ ++ if (pll_id > 1) ++ return -EINVAL; ++ ++ curr_freq = scu_get_pll_freq(ltpi, pll_id); ++ if (curr_freq == freq) ++ return 0; ++ ++ for (i = 0; i < NUM_PLL_PARAM; i++) { ++ if (freq == pll_param_lookup[i].freq) { ++ match = true; ++ break; ++ } ++ } ++ ++ if (!match) ++ return -EINVAL; ++ ++ param = &pll_param_lookup[i]; ++ info = &scu_pll_info[pll_id]; ++ ++ dev_info(ltpi->dev, "Setting PLL frequency to %d MHz\n", ++ freq / 1000000); ++ regmap_update_bits(ltpi->scu, info->reg_offset0, PLL_REG1_RESET, ++ PLL_REG1_RESET); ++ regmap_update_bits(ltpi->scu, info->reg_offset0, ++ PLL_REG1_P | PLL_REG1_N | PLL_REG1_M, param->n_m_p); ++ regmap_update_bits(ltpi->scu, info->reg_offset1, PLL_REG2_BWADJ, ++ param->bwadj); ++ ++ udelay(5); ++ regmap_update_bits(ltpi->scu, info->reg_offset0, PLL_REG1_RESET, 0); ++ udelay(20); ++ ++ return 0; ++} ++ ++static int ltpi_set_operational_clk(struct aspeed_ltpi_priv *ltpi, ++ u16 speed_cap) ++{ ++ const struct ltpi_clk_info *info; ++ int target_speed, clksel, phy_mode, pll_id; ++ ++ pll_id = ltpi->index; /* 0 or 1 maps to L0PLL or L1PLL in array */ ++ ++ /* find max attainable speed */ ++ target_speed = find_max_speed(speed_cap); ++ ++ /* set phy mode "OFF" */ ++ ltpi_phy_set_mode(ltpi, LTPI_PHY_MODE_OFF); ++ ++ if (speed_cap & LTPI_SP_CAP_DDR) { ++ phy_mode = LTPI_PHY_MODE_DDR; ++ info = ltpi_clk_lookup_ddr; ++ } else { ++ phy_mode = LTPI_PHY_MODE_SDR; ++ info = ltpi_clk_lookup_sdr; ++ } ++ clksel = info[target_speed].clk_sel; ++ ltpi_phy_set_clksel(ltpi, clksel, true); ++ if (clksel == REG_LTPI_PLL_LPLL) ++ scu_set_pll_freq(ltpi, pll_id, ++ info[target_speed].freq * 1000000); ++ ++ /* Start TX with the operational frequency */ ++ ltpi_phy_set_mode(ltpi, phy_mode); ++ ++ return target_speed; ++} ++ ++static int ltpi_scm_init(struct aspeed_ltpi_priv *ltpi) ++{ ++ int ret, target_speed; ++ u32 reg, state; ++ ++ dev_info(ltpi->dev, "Starting LTPI initialization\n"); ++ /* Check whether LTPI is initialized */ ++ state = ltpi_get_link_mng_state(ltpi); ++ if (state == LTPI_LINK_MNG_ST_OP) { ++ dev_info(ltpi->dev, "LTPI already operational PHY mode: %d\n", ++ ltpi_phy_get_mode(ltpi)); ++ return 0; ++ } ++ ++ ltpi->t_link_detect = ktime_get_ns(); ++ ++ /* LTPI initialization is required, start link training phase */ ++ do { ++ dev_info(ltpi->dev, "Starting LTPI link training\n"); ++ ltpi_do_link_training(ltpi); ++ do { ++ ret = ltpi_wait_state_pll_set(ltpi, 20000); ++ if (ret == 0) ++ break; ++ ++ if (ltpi->op_timeout && ++ (ktime_get_ns() - ltpi->t_link_detect > ++ ltpi->op_timeout)) { ++ dev_err(ltpi->dev, ++ "Timeout while waiting for PLL set state\n"); ++ ret = -ETIMEDOUT; ++ goto ltpi_scm_exit; ++ } ++ } while (1); ++ ++ /* read intersection of the speed capabilities */ ++ reg = FIELD_GET(REG_LTPI_SP_INTERSETION, ++ readl(ltpi->regs + LTPI_LINK_MNG_ST)); ++ if (reg == 0) { ++ dev_err(ltpi->dev, "No common speed\n"); ++ ret = -ENOLINK; ++ goto ltpi_scm_exit; ++ } ++ ++ target_speed = ltpi_set_operational_clk(ltpi, reg); ++ ++ /* poll link state 0x7 */ ++ ret = ltpi_wait_state_op(ltpi); ++ if (ret == 0) { ++ /* Start OEM TX & RX if the link partner is AST1700 */ ++ if (readl(ltpi->regs + LTPI_LINK_MNG_ST) & ++ REG_LTPI_LINK_PARTNER_FLAG) { ++ u32 val = readl(ltpi->regs + ++ LTPI_OEM_BUS_SETTING); ++ val |= (REG_LTPI_OEM_RX_START_TRIG | ++ REG_LTPI_OEM_TX_START_TRIG); ++ writel(val, ltpi->regs + LTPI_OEM_BUS_SETTING); ++ } ++ break; ++ } ++ ++ if (ltpi->op_timeout && ++ (ktime_get_ns() - ltpi->t_link_detect > ltpi->op_timeout)) { ++ dev_err(ltpi->dev, ++ "Timeout while waiting for operational state\n"); ++ ret = -ETIMEDOUT; ++ goto ltpi_scm_exit; ++ } ++ ++ if (!ltpi->disable_auto_downshift) ++ /* clear the bit to specify the current speed doesn't work */ ++ ltpi->phy_speed_cap &= ~BIT(target_speed); ++ ++ /* the lowest speed 25M should always be supported */ ++ if ((ltpi->phy_speed_cap & LTPI_SP_CAP_25M) == 0) ++ ltpi->phy_speed_cap |= LTPI_SP_CAP_25M; ++ ++ dev_warn(ltpi->dev, ++ "Failed to enter operational state, restarting link training\n"); ++ ltpi_dump(ltpi); ++ } while (1); ++ ++ dev_info(ltpi->dev, "LTPI Link trained successfully\n"); ++ return 0; ++ ++ltpi_scm_exit: ++ dev_err(ltpi->dev, "Exiting initialization\n"); ++ ltpi_dump(ltpi); ++ ltpi_reset(ltpi); ++ return ret; ++} ++ ++/* Sysfs attribute show/store functions */ ++static ssize_t ad_timeout_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", priv->ad_timeout); ++} ++ ++static ssize_t ad_timeout_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->ad_timeout = val; ++ return count; ++} ++ ++static ssize_t io_driving_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "0x%x\n", priv->io_driving); ++} ++ ++static ssize_t io_driving_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->io_driving = val; ++ return count; ++} ++ ++static ssize_t clk_inverse_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "0x%x\n", priv->clk_inverse); ++} ++ ++static ssize_t clk_inverse_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->clk_inverse = val; ++ return count; ++} ++ ++static ssize_t link_speed_frm_rx_cnt_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", priv->link_speed_frm_rx_cnt); ++} ++ ++static ssize_t link_speed_frm_rx_cnt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->link_speed_frm_rx_cnt = val; ++ return count; ++} ++ ++static ssize_t disable_auto_downshift_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", priv->disable_auto_downshift); ++} ++ ++static ssize_t disable_auto_downshift_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->disable_auto_downshift = val ? 1 : 0; ++ return count; ++} ++ ++static ssize_t crc_format_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", priv->crc_format); ++} ++ ++static ssize_t crc_format_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->crc_format = val ? 1 : 0; ++ return count; ++} ++ ++static ssize_t phy_speed_cap_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "0x%x\n", priv->phy_speed_cap); ++} ++ ++static ssize_t phy_speed_cap_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->phy_speed_cap = val; ++ return count; ++} ++ ++static ssize_t op_timeout_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%llu\n", priv->op_timeout); ++} ++ ++static ssize_t op_timeout_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ unsigned long long val; ++ ++ if (kstrtoull(buf, 0, &val)) ++ return -EINVAL; ++ ++ priv->op_timeout = val; ++ return count; ++} ++ ++static ssize_t rescan_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ u32 val, reg; ++ ++ if (kstrtouint(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val == 1 && priv->version == AST2700) { ++ dev_info(priv->dev, "Triggering LTPI rescan\n"); ++ if (ltpi_scm_init(priv)) { ++ dev_err(priv->dev, "LTPI rescan failed\n"); ++ return -EIO; ++ } ++ writel(LTPI_INTR_EN_OP_LINK_LOST, ++ priv->regs + LTPI_INTR_STATUS); ++ writel(LTPI_INTR_EN_OP_LINK_LOST, priv->regs + LTPI_INTR_EN); ++ aspeed_ltpi_init_mux(priv); ++ if (ltpi_get_link_partner(priv)) { ++ reg = FIELD_PREP(REG_LTPI_AHB_ADDR_MAP0, 0x5) | ++ FIELD_PREP(REG_LTPI_AHB_ADDR_MAP1, 0xa0); ++ } else { ++ reg = 0; ++ writel(0, priv->regs + LTPI_DATA_CH_CFG0); ++ } ++ writel(reg, priv->regs + LTPI_AHB_CTRL0); ++ } ++ ++ return count; ++} ++ ++static ssize_t link_status_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_ltpi_priv *priv = dev_get_drvdata(dev); ++ u32 state = ltpi_get_link_mng_state(priv); ++ char state_str[128]; ++ int speed; ++ u32 phy_mode, clk_select; ++ char modes[9][8] = { "OFF", "SDR", "DDR", "NA", "CDR_LO", ++ "NA", "NA", "NA", "CDR_HI" }; ++ ++ if (state != LTPI_LINK_MNG_ST_OP) { ++ sprintf(state_str, "LTPI: not linked\n"); ++ } else { ++ phy_mode = FIELD_GET(REG_LTPI_PHY_MODE, readl(priv->phy_regs + LTPI_PHY_CTRL)); ++ clk_select = FIELD_GET(REG_LTPI_PLL_SELECT, readl(priv->phy_regs + LTPI_PLL_CTRL)); ++ if (clk_select == REG_LTPI_PLL_LPLL) ++ speed = scu_get_pll_freq(priv, priv->index) / 1000000; ++ else ++ speed = 25; ++ ++ sprintf(state_str, ++ "LTPI%d:\n" ++ " link partner : %s\n" ++ " link mode : %s\n" ++ " link bandwidth : %dMbps\n", ++ priv->index, ++ ltpi_get_link_partner(priv) ? "ast1700" : "fpga", ++ &modes[phy_mode][0], speed); ++ } ++ ++ return sprintf(buf, "%s\n", state_str); ++} ++ ++/* Device attributes */ ++static DEVICE_ATTR_RW(ad_timeout); ++static DEVICE_ATTR_RW(io_driving); ++static DEVICE_ATTR_RW(clk_inverse); ++static DEVICE_ATTR_RW(link_speed_frm_rx_cnt); ++static DEVICE_ATTR_RW(disable_auto_downshift); ++static DEVICE_ATTR_RW(crc_format); ++static DEVICE_ATTR_RW(phy_speed_cap); ++static DEVICE_ATTR_RW(op_timeout); ++static DEVICE_ATTR_WO(rescan); ++static DEVICE_ATTR_RO(link_status); ++ ++static struct attribute *aspeed_ltpi_attrs[] = { ++ &dev_attr_ad_timeout.attr, ++ &dev_attr_io_driving.attr, ++ &dev_attr_clk_inverse.attr, ++ &dev_attr_link_speed_frm_rx_cnt.attr, ++ &dev_attr_disable_auto_downshift.attr, ++ &dev_attr_crc_format.attr, ++ &dev_attr_phy_speed_cap.attr, ++ &dev_attr_op_timeout.attr, ++ &dev_attr_rescan.attr, ++ &dev_attr_link_status.attr, ++ NULL, ++}; ++ ++static const struct attribute_group aspeed_ltpi_attr_group = { ++ .attrs = aspeed_ltpi_attrs, ++}; ++ ++static int aspeed_ltpi_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct of_dev_auxdata *lookup = dev_get_platdata(dev); ++ struct device_node *np = dev->of_node; ++ const struct of_device_id *match; ++ struct aspeed_ltpi_priv *priv; ++ int irq, ret; ++ struct resource *res; ++ ++ match = of_match_device(dev->driver->of_match_table, dev); ++ ++ if (match) { ++ if (of_property_match_string(np, "compatible", ++ match->compatible) < 0) ++ return -ENODEV; ++ } else { ++ return -ENODEV; ++ } ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->dev = dev; ++ priv->regs = devm_platform_ioremap_resource_byname(pdev, "base"); ++ if (IS_ERR(priv->regs)) ++ priv->regs = ++ devm_platform_ioremap_resource(pdev, 0); // Fallback ++ if (IS_ERR(priv->regs)) ++ return PTR_ERR(priv->regs); ++ ++ priv->phy_regs = devm_platform_ioremap_resource_byname(pdev, "phy"); ++ if (IS_ERR(priv->phy_regs)) ++ priv->phy_regs = ++ priv->regs + 0x200; // Fallback unsafe if size small ++ ++ priv->top_regs = devm_platform_ioremap_resource_byname(pdev, "top"); ++ if (IS_ERR(priv->top_regs)) ++ priv->top_regs = ++ priv->regs + 0x800; // Fallback unsafe if size small ++ ++ /* Identify index based on physical address for PLL control */ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (res && (res->start & 0x1000)) ++ priv->index = 1; ++ else ++ priv->index = 0; ++ ++ priv->version = (enum chip_version)device_get_match_data(dev); ++ ++ priv->ltpi_clk = devm_clk_get(&pdev->dev, "ltpi"); ++ if (IS_ERR(priv->ltpi_clk)) { ++ priv->ltpi_clk = devm_clk_get(&pdev->dev, "ahb"); ++ if (IS_ERR(priv->ltpi_clk)) ++ return PTR_ERR(priv->ltpi_clk); ++ ++ clk_prepare_enable(priv->ltpi_clk); ++ ++ priv->ltpi_phyclk = devm_clk_get(&pdev->dev, "phy"); ++ if (IS_ERR(priv->ltpi_phyclk)) ++ return PTR_ERR(priv->ltpi_phyclk); ++ ++ clk_prepare_enable(priv->ltpi_phyclk); ++ } else { ++ priv->ltpi_phyclk = NULL; ++ } ++ ++ priv->ltpi_rst = ++ devm_reset_control_get_optional_shared(&pdev->dev, NULL); ++ if (IS_ERR(priv->ltpi_rst)) ++ return PTR_ERR(priv->ltpi_rst); ++ ++ reset_control_deassert(priv->ltpi_rst); ++ ++ priv->i2c_tunneling = GENMASK(MAX_I2C_IN_LTPI - 1, 0); ++ if (!of_property_read_u32(np, "i2c-tunneling", &ret)) ++ priv->i2c_tunneling = ret; ++ ++ priv->i2c_timing_0 = LTPI_I2C_100K_0; ++ priv->i2c_timing_1 = LTPI_I2C_100K_1; ++ if (!of_property_read_u32(np, "i2c-tunneling-timing", &ret)) { ++ if (ret == 400) { ++ priv->i2c_timing_0 = LTPI_I2C_400K_0; ++ priv->i2c_timing_1 = LTPI_I2C_400K_1; ++ } ++ } ++ priv->uart_tunneling = GENMASK(MAX_UART_IN_LTPI - 1, 0); ++ if (!of_property_read_u32(np, "uart-tunneling", &ret)) ++ priv->uart_tunneling = ret; ++ ++ priv->scu = syscon_regmap_lookup_by_phandle(np, "aspeed,scu"); ++ if (IS_ERR(priv->scu)) { ++ dev_err(&pdev->dev, "failed to get SCU regmap\n"); ++ return PTR_ERR(priv->scu); ++ } ++ if (of_get_property(np, "remote-controller", NULL)) { ++ u32 reg; ++ ++ /* Clear all the pins/otp strap but LTPI related settings for AST1700 */ ++ regmap_read(priv->scu, SCU_IO_PINS_TRAP1, ®); ++ reg &= ~SCU_IO_PINS_TRAP_LTPI; ++ regmap_write(priv->scu, SCU_IO_PINS_TRAP1_CLEAR, reg); ++ ++ regmap_read(priv->scu, SCU_IO_OTP_TRAP1, ®); ++ regmap_write(priv->scu, SCU_IO_OTP_TRAP1_CLEAR, reg); ++ ++ regmap_read(priv->scu, SCU_IO_OTP_TRAP2, ®); ++ regmap_write(priv->scu, SCU_IO_OTP_TRAP2_CLEAR, reg); ++ } else { ++ irq = platform_get_irq(pdev, 0); ++ ret = devm_request_irq(priv->dev, irq, aspeed_ltpi_irq_handler, ++ 0, dev_name(priv->dev), priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to request irq\n"); ++ reset_control_assert(priv->ltpi_rst); ++ clk_disable_unprepare(priv->ltpi_phyclk); ++ clk_disable_unprepare(priv->ltpi_clk); ++ return ret; ++ } ++ ++ writel(LTPI_INTR_EN_OP_LINK_LOST, ++ priv->regs + LTPI_INTR_STATUS); ++ writel(LTPI_INTR_EN_OP_LINK_LOST, priv->regs + LTPI_INTR_EN); ++ } ++ ++ /* Initialize training parameters with defaults */ ++ priv->ad_timeout = ADVERTISE_TIMEOUT_US; ++ priv->io_driving = 0x2; ++ priv->clk_inverse = 0x0; ++ priv->link_speed_frm_rx_cnt = 0; ++ priv->disable_auto_downshift = 0; ++ priv->crc_format = 0; ++ priv->otp_ddr_dis = false; ++ priv->op_timeout = 2000000000; /* 2 seconds timeout by default */ ++ priv->phy_speed_cap = LTPI_SP_CAP_ASPEED_SUPPORTED; ++ ++ aspeed_ltpi_init_mux(priv); ++ ++ platform_set_drvdata(pdev, priv); ++ ++ /* Create sysfs attributes */ ++ ret = sysfs_create_group(&dev->kobj, &aspeed_ltpi_attr_group); ++ if (ret) { ++ dev_err(dev, "Failed to create sysfs group\n"); ++ reset_control_assert(priv->ltpi_rst); ++ clk_disable_unprepare(priv->ltpi_phyclk); ++ clk_disable_unprepare(priv->ltpi_clk); ++ return ret; ++ } ++ ++ if (np) ++ of_platform_populate(np, NULL, lookup, priv->dev); ++ ++ return 0; ++} ++ ++static void aspeed_ltpi_remove(struct platform_device *pdev) ++{ ++ struct aspeed_ltpi_priv *priv; ++ ++ priv = platform_get_drvdata(pdev); ++ ++ /* Remove sysfs attributes */ ++ sysfs_remove_group(&pdev->dev.kobj, &aspeed_ltpi_attr_group); ++ ++ reset_control_assert(priv->ltpi_rst); ++ clk_disable_unprepare(priv->ltpi_phyclk); ++ clk_disable_unprepare(priv->ltpi_clk); ++} ++ ++static const struct of_device_id aspeed_ltpi_of_match[] = { ++ { ++ .compatible = "aspeed-ltpi", ++ .data = (const void *)AST2700, ++ }, ++ { ++ .compatible = "aspeed-ast1700-ltpi", ++ .data = (const void *)AST1700, ++ }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_ltpi_of_match); ++ ++static struct platform_driver aspeed_ltpi_driver = { ++ .probe = aspeed_ltpi_probe, ++ .remove = aspeed_ltpi_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_ltpi_of_match, ++ }, ++}; ++ ++module_platform_driver(aspeed_ltpi_driver); ++ ++MODULE_DESCRIPTION("LVDS Tunneling Protocol and Interface Bus Driver"); ++MODULE_AUTHOR("Dylan Hung "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/bus/aspeed-ltpi.h b/drivers/bus/aspeed-ltpi.h +--- a/drivers/bus/aspeed-ltpi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/bus/aspeed-ltpi.h 2026-09-09 15:51:44.532240882 +0000 +@@ -0,0 +1,275 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2024 ASPEED Technology Inc. ++ */ ++#ifndef __ASPEED_LTPI_H__ ++#define __ASPEED_LTPI_H__ ++ ++#include ++ ++/* OCP_DC-SCM_2.0_LTPI_ver_1.0, Table 21 LTPI speed capability encoding */ ++#define LTPI_SP_CAP_25M BIT(0) ++#define LTPI_SP_CAP_50M BIT(1) ++#define LTPI_SP_CAP_75M BIT(2) ++#define LTPI_SP_CAP_100M BIT(3) ++#define LTPI_SP_CAP_150M BIT(4) ++#define LTPI_SP_CAP_200M BIT(5) ++#define LTPI_SP_CAP_250M BIT(6) ++#define LTPI_SP_CAP_300M BIT(7) ++#define LTPI_SP_CAP_400M BIT(8) ++#define LTPI_SP_CAP_600M BIT(9) ++#define LTPI_SP_CAP_800M BIT(10) ++#define LTPI_SP_CAP_1G BIT(11) ++#define LTPI_SP_CAP_500M BIT(12) /* Aspeed only */ ++/* --gap-- */ ++#define LTPI_SP_CAP_DDR BIT(15) ++ ++#define LTPI_SP_CAP_ASPEED_SUPPORTED \ ++ (LTPI_SP_CAP_25M | LTPI_SP_CAP_50M | LTPI_SP_CAP_75M | \ ++ LTPI_SP_CAP_100M | LTPI_SP_CAP_150M | LTPI_SP_CAP_200M | \ ++ LTPI_SP_CAP_250M | LTPI_SP_CAP_300M | LTPI_SP_CAP_400M | \ ++ LTPI_SP_CAP_500M | LTPI_SP_CAP_DDR) ++ ++/* control registers */ ++#define LTPI_LINK_ST 0x000 ++#define REG_LTPI_LINK_ST_LOCAL GENMASK(19, 16) ++#define REG_LTPI_LINK_ST_REMOTE GENMASK(15, 12) ++#define REG_LTPI_LINK_SPEED GENMASK(11, 8) ++#define REG_LTPI_LINK_DDR_MODE BIT(7) ++#define REG_LTPI_LINK_ST_RESERVED BIT(6) ++#define REG_LTPI_CON_ACC_TO_ERR BIT(5) ++#define REG_LTPI_LINK_SP_TO_ERR BIT(4) ++#define REG_LTPI_UNKNOWN_COMMA_ERR BIT(3) ++#define REG_LTPI_FRM_CRC_ERR BIT(2) ++#define REG_LTPI_LINK_LOST_ERR BIT(1) ++#define REG_LTPI_LINK_ALIGN BIT(0) ++ ++#define LTPI_CAP_LOCAL 0x004 ++#define REG_LTPI_SP_CAP_LOCAL GENMASK(23, 8) ++#define REG_LTPI_MAJOR_VER_LOCAL GENMASK(7, 4) ++#define REG_LTPI_MIN_VER_LOCAL GENMASK(3, 0) ++#define LTPI_CAP_REMOTE 0x008 ++#define REG_LTPI_SP_CAP_REMOTE GENMASK(23, 8) ++#define REG_LTPI_MAJOR_VER_REMOTE GENMASK(7, 4) ++#define REG_LTPI_MIN_VER_REMOTE GENMASK(3, 0) ++#define LTPI_PF_ID_LOCAL 0x00C ++#define LTPI_PF_ID_REMOTE 0x010 ++#define LTPI_AD_CAP_LOW_LOCAL 0x014 ++#define LTPI_AD_CAP_HIGH_LOCAL 0x018 ++#define LTPI_AD_CAP_LOW_REMOTE 0x01C ++#define LTPI_AD_CAP_HIGH_REMOTE 0x020 ++#define LTPI_DEFAULT_CON_LOW 0x024 ++#define LTPI_DEFAULT_CON_HIGH 0x028 ++#define LTPI_LINK_ALIGN_ERR_CNT 0x02C ++#define LTPI_LINK_LOST_ERR_CNT 0x030 ++#define LTPI_CRC_ERR_CNT 0x034 ++#define LTPI_UNKNOWN_COMMA_ERR_CNT 0x038 ++#define LTPI_LINK_SP_TO_ERR_CNT 0x03C ++#define LTPI_CON_ACC_TO_ERR_CNT 0x040 ++#define LTPI_LINK_TRAIN_RX_FRM_CNT_LOW 0x044 ++#define REG_LTPI_CON_ACC_FRM_RX_CNT GENMASK(31, 24) ++#define REG_LTPI_LINK_SP_FRM_RX_CNT GENMASK(23, 16) ++#define REG_LTPI_LINK_DET_FRM_RX_CNT GENMASK(15, 0) ++#define LTPI_LINK_TRAIN_RX_FRM_CNT_HIGH 0x048 ++#define LTPI_LINK_TRAIN_TX_FRM_CNT_LOW 0x04C ++#define REG_LTPI_CON_ACC_FRM_TX_CNT GENMASK(31, 24) ++#define REG_LTPI_LINK_SP_FRM_TX_CNT GENMASK(23, 16) ++#define REG_LTPI_LINK_DET_FRM_TX_CNT GENMASK(15, 0) ++#define LTPI_LINK_TRAIN_TX_FRM_CNT_HIGH 0x050 ++#define LTPI_OP_RX_FRM_CNT 0x054 ++#define LTPI_OP_TX_FRM_CNT 0x058 ++#define LTPI_LINK_LOST_CNT 0x05c ++#define REG_LTPI_AD_ALIGN_TO_CNT GENMASK(27, 24) ++#define REG_LTPI_LINK_LOST_OP_CNT GENMASK(23, 16) ++#define REG_LTPI_LINK_LOST_CON_OR_ACC_CNT GENMASK(15, 8) ++#define REG_LTPI_LINK_LOST_AD_CNT GENMASK(7, 0) ++#define LTPI_LINK_CONTROL 0x080 ++#define REG_LTPI_TRIG_CON_ST BIT(11) ++#define REG_LTPI_AUTO_CON_ST BIT(10) ++#define REG_LTPI_DATA_CH_RST BIT(9) ++#define REG_LTPI_I2C_RST GENMASK(8, 2) ++#define REG_LTPI_LINK_RETRAIN_REQ BIT(1) ++#define REG_LTPI_LINK_SW_RST BIT(0) ++ ++#define LTPI_INTR 0x100 ++#define REG_LTPI_LINK_HALT BIT(24) ++#define REG_LTPI_DATA_CH_DROP_FRAME BIT(23) ++#define REG_LTPI_DATA_CH_INVALID_ACCESS BIT(22) ++#define REG_LTPI_I2C5_TO_ERR BIT(21) ++#define REG_LTPI_I2C4_TO_ERR BIT(20) ++#define REG_LTPI_I2C3_TO_ERR BIT(19) ++#define REG_LTPI_I2C2_TO_ERR BIT(18) ++#define REG_LTPI_I2C1_TO_ERR BIT(17) ++#define REG_LTPI_I2C0_TO_ERR BIT(16) ++#define REG_LTPI_AD_ALIGN_TO BIT(12) ++#define REG_LTPI_HW_RETRY_LINK_DET_STUCK_TO_ERR BIT(11) ++#define REG_LTPI_HW_RETRY_TO_ERR BIT(10) ++#define REG_LTPI_OP_LINK_LOST_ERR BIT(4) ++#define REG_LTPI_CON_OR_ACC_LINK_LOST_ERR BIT(3) ++#define REG_LTPI_AD_LINK_LOST_ERR BIT(2) ++#define REG_LTPI_CON_READY BIT(1) ++#define REG_LTPI_AD_READY BIT(0) ++#define LTPI_INTR_EN 0x104 ++#define REG_LTPI_LINK_HALT_INTR_EN BIT(24) ++#define REG_LTPI_DATA_CH_DROP_FRAME_EN BIT(23) ++#define REG_LTPI_DATA_CH_INVALID_ACCESS_EN BIT(22) ++#define REG_LTPI_I2C5_TO_ERR_EN BIT(21) ++#define REG_LTPI_I2C4_TO_ERR_EN BIT(20) ++#define REG_LTPI_I2C3_TO_ERR_EN BIT(19) ++#define REG_LTPI_I2C2_TO_ERR_EN BIT(18) ++#define REG_LTPI_I2C1_TO_ERR_EN BIT(17) ++#define REG_LTPI_I2C0_TO_ERR_EN BIT(16) ++#define REG_LTPI_AD_ALIGN_TO_EN BIT(12) ++#define REG_LTPI_HW_RETRY_LINK_DET_STUCK_TO_ERR_EN BIT(11) ++#define REG_LTPI_HW_RETRY_TO_ERR_EN BIT(10) ++#define REG_LTPI_CON_ACC_TO_ERR_EN BIT(9) ++#define REG_LTPI_LINK_SP_TO_ERR_EN BIT(8) ++#define REG_LTPI_UNKNOWN_COMMA_ERR_EN BIT(7) ++#define REG_LTPI_FRM_CRC_ERR_EN BIT(6) ++#define REG_LTPI_LINK_LOST_ERR_EN BIT(5) ++#define REG_LTPI_OP_LINK_LOST_ERR_EN BIT(4) ++#define REG_LTPI_CON_OR_ACC_LINK_LOST_ERR_EN BIT(3) ++#define REG_LTPI_AD_LINK_LOST_ERR_EN BIT(2) ++#define REG_LTPI_CON_READY_EN BIT(1) ++#define REG_LTPI_AD_READY_EN BIT(0) ++#define LTPI_LINK_MNG_ST 0x108 ++#define REG_LTPI_LINK_PARTNER_FLAG BIT(24) ++#define REG_LTPI_LINK_PARTNER_FPGA 0b0 ++#define REG_LTPI_LINK_PARTNER_1700 0b1 ++#define REG_LTPI_SP_INTERSETION GENMASK(23, 8) ++#define REG_LTPI_LINK_MNG_ST GENMASK(3, 0) ++#define LTPI_LINK_MNG_ST_DETECT_ALIGN 0 ++#define LTPI_LINK_MNG_ST_DETECT 1 ++#define LTPI_LINK_MNG_ST_SPEED 2 ++#define LTPI_LINK_MNG_ST_WAIT_PLL_SET 3 ++#define LTPI_LINK_MNG_ST_ADV_ALIGN 4 ++#define LTPI_LINK_MNG_ST_ADV 5 ++#define LTPI_LINK_MNG_ST_CONFIG_ACC 6 ++#define LTPI_LINK_MNG_ST_OP 7 ++#define LTPI_LINK_MANAGE_CTRL0 0x10C ++#define REG_LTPI_LINK_PARTNER_CHKNUM GENMASK(19, 16) ++#define REG_LTPI_CON_FRM_NOCHK BIT(8) ++#define REG_LTPI_TX_LINK_SP_FRM_NUM GENMASK(7, 4) ++#define REG_LTPI_RX_LINK_SP_FRM_NUM GENMASK(3, 0) ++#define LTPI_LINK_MANAGE_CTRL1 0x110 ++#define LTPI_LINK_MANAGE_CTRL2 0x114 ++#define LTPI_CON_CAP_LOW 0x118 ++#define LTPI_CON_CAP_HIGH 0x11C ++#define LTPI_MAC_CTRL 0x120 ++#define LTPI_AHB_CTRL0 0x124 ++#define REG_LTPI_AHB_ADDR_MAP1 GENMASK(25, 16) ++#define REG_LTPI_AHB_ADDR_MAP0 GENMASK(9, 0) ++#define LTPI_AHB_CTRL1 0x128 ++#define REG_LTPI_AHB_ADDR_MAP3 GENMASK(25, 16) ++#define REG_LTPI_AHB_ADDR_MAP2 GENMASK(9, 0) ++#define LTPI_CRC_OPTION 0x12c ++#define REG_LTPI_SW_CRC_OUT_ML_FIRST BIT(2) ++#define REG_LTPI_SW_CRC_IN_LSB_FIRST BIT(1) ++#define REG_LTPI_CRC_SW_FORCE BIT(0) ++#define LTPI_I2C 0x130 ++#define REG_LTPI_I2C_SLV_MST_SWITCH_SW_EN BIT(8) ++#define REG_LTPI_I2C_SLV_EN GENMASK(5, 0) ++ ++#define LTPI_OEM_BUS_SETTING 0x188 ++#define REG_LTPI_OEM_RX_START_TRIG BIT(1) ++#define REG_LTPI_OEM_TX_START_TRIG BIT(0) ++ ++#define LTPI_OEM_DBG0 0x190 ++#define REG_LTPI_OEM_RX_INIT_DONE BIT(9) ++#define REG_LTPI_OEM_TX_INIT_DONE BIT(8) ++ ++#define LTPI_DATA_CH_CFG0 0x1D8 ++#define REG_LTPI_DATA_CH_TAG_CHK_EN BIT(2) ++#define REG_LTPI_DATA_CH_ADDR_CHK_EN BIT(1) ++#define REG_LTPI_DATA_CH_WAIT_ACK_TO_EN BIT(0) ++ ++/* LTPI PHY control registers */ ++#define LTPI_PHY_CTRL 0x000 ++#define REG_LTPI_PHY_MODE GENMASK(3, 0) ++#define LTPI_PHY_MODE_CDR_HI_SP 0b1000 ++#define LTPI_PHY_MODE_CDR_LO_SP 0b0100 ++#define LTPI_PHY_MODE_DDR 0b0010 ++#define LTPI_PHY_MODE_SDR 0b0001 ++#define LTPI_PHY_MODE_OFF 0b0000 ++#define LTPI_PLL_CTRL 0x004 ++#define REG_LTPI_RX_PHY_CLK_INV BIT(9) ++#define REG_LTPI_TX_PHY_CLK_INV BIT(8) ++#define REG_LTPI_PLL_SET BIT(4) ++#define REG_LTPI_RX_PLL_DIV2 BIT(3) ++#define REG_LTPI_PLL_SELECT GENMASK(2, 0) ++#define REG_LTPI_PLL_25M 0 ++/* AST2700A0 */ ++#define REG_LTPI_PLL_50M 1 ++#define REG_LTPI_PLL_100M 2 ++#define REG_LTPI_PLL_200M 3 ++#define REG_LTPI_PLL_250M 4 ++#define REG_LTPI_PLL_400M 5 ++#define REG_LTPI_PLL_800M 6 ++#define REG_LTPI_PLL_1G 7 ++/* AST2700A1 */ ++#define REG_LTPI_PLL_LPLL 7 ++ ++#define LTPI_PHY_ALIGN_CTRL 0x008 ++#define LTPI_DLL_CTRL 0x00C ++#define REG_LTPI_SW_DLL_RST BIT(26) ++#define REG_LTPI_FORCE_DLL_RST BIT(25) ++#define REG_LTPI_DLL_PD BIT(24) ++#define REG_LTPI_DLL_TSTCTRL GENMASK(23, 16) ++#define REG_LTPI_DLL_RST_TIMEOUT_A0 GENMASK(15, 0) ++#define REG_LTPI_DLL_CLK_2X BIT(8) ++#define REG_LTPI_DLL_RST_TIMEOUT GENMASK(7, 0) ++#define LTPI_PHY_HI_SP_CDR_CTRL 0x010 ++#define REG_LTPI_SW_HI_SP_CDR_EN BIT(10) ++#define REG_LTPI_FORCE_HI_SP_CDR_EN BIT(9) ++#define REG_LTPI_HI_SP_CDR_EN_TIMEOUT_EN BIT(8) ++#define LTPI_HI_SP_CDR_EN_TIMEOUT GENMASK(7, 0) ++ ++#define LTPI_PROT_KEY 0x34 ++#define LTPI_PROT_KEY_UNLOCK 0x1728aacc ++ ++/* LVDS TOP registers */ ++#define LTPI_LVDS_TX_CTRL 0x000 ++#define REG_LTPI_LVDS_TX1_DS1 BIT(22) ++#define REG_LTPI_LVDS_TX1_DS0 BIT(21) ++#define REG_LTPI_LVDS_TX1_IPREE BIT(20) ++#define REG_LTPI_LVDS_TX1_IPREE_EN BIT(19) ++#define REG_LTPI_LVDS_TX1_PD BIT(18) ++#define REG_LTPI_LVDS_TX1_PU BIT(17) ++#define REG_LTPI_LVDS_TX1_OE BIT(16) ++#define REG_LTPI_LVDS_TX0_DS1 BIT(6) ++#define REG_LTPI_LVDS_TX0_DS0 BIT(5) ++#define REG_LTPI_LVDS_TX0_IPREE BIT(4) ++#define REG_LTPI_LVDS_TX0_IPREE_EN BIT(3) ++#define REG_LTPI_LVDS_TX0_PD BIT(2) ++#define REG_LTPI_LVDS_TX0_PU BIT(1) ++#define REG_LTPI_LVDS_TX0_OE BIT(0) ++#define LTPI_LVDS_RX_CTRL 0x004 ++#define REG_LTPI_LVDS_RX1_BIAS_EN BIT(18) ++#define REG_LTPI_LVDS_RX1_ST BIT(17) ++#define REG_LTPI_LVDS_RX1_IE BIT(16) ++#define REG_LTPI_LVDS_RX0_BIAS_EN BIT(2) ++#define REG_LTPI_LVDS_RX0_ST BIT(1) ++#define REG_LTPI_LVDS_RX0_IE BIT(0) ++#define LTPI_SW_RST 0x008 ++#define REG_LTPI_ALL_SW_RST BIT(31) ++#define REG_LTPI1_SW_RST BIT(17) ++#define REG_LTPI0_SW_RST BIT(16) ++#define REG_LTPI_DLL_CTRL_SW_RST BIT(9) ++#define REG_LTPI_REF_SW_RST BIT(8) ++#define REG_LTPI1_RX_PHY_SW_RST BIT(7) ++#define REG_LTPI1_TX_PHY_SW_RST BIT(6) ++#define REG_LTPI1_RX_MAC_SW_RST BIT(5) ++#define REG_LTPI1_TX_MAC_SW_RST BIT(4) ++#define REG_LTPI0_RX_PHY_SW_RST BIT(3) ++#define REG_LTPI0_TX_PHY_SW_RST BIT(2) ++#define REG_LTPI0_RX_MAC_SW_RST BIT(1) ++#define REG_LTPI0_TX_MAC_SW_RST BIT(0) ++#define LTPI_STRAP_VAL 0x00c ++#define REG_LTPI_STRAP_2LTPI_EN BIT(1) ++#define REG_LTPI_STRAP_1700_EN BIT(0) ++#define LTPI_SW_FORCE_EN 0x010 ++#define LTPI_SW_FORCE_VAL 0x014 ++#define REG_LTPI_SW_FORCE_LVDS_TX_DS_EN BIT(1) ++#define REG_LTPI_SW_FORCE_1700_EN BIT(0) ++ ++#endif /* __ASPEED_LTPI_H__ */ +diff --git a/drivers/char/hw_random/aspeed-rng.c b/drivers/char/hw_random/aspeed-rng.c +--- a/drivers/char/hw_random/aspeed-rng.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/aspeed-rng.c 2026-09-09 15:51:44.684238585 +0000 +@@ -0,0 +1,134 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) ASPEED Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TRNG_CTL 0x00 ++#define TRNG_EN 0x0 ++#define TRNG_MODE 0x04 ++#define TRNG_RDY 0x1f ++#define TRNG_ODATA 0x04 ++ ++struct aspeed_trng { ++ u32 ver; ++ void __iomem *base; ++ struct hwrng rng; ++ unsigned int present: 1; ++ ktime_t period; ++ struct hrtimer timer; ++ struct completion completion; ++}; ++ ++static int aspeed_trng_read(struct hwrng *rng, void *buf, size_t max, ++ bool wait) ++{ ++ struct aspeed_trng *priv = container_of(rng, struct aspeed_trng, rng); ++ u32 *data = buf; ++ size_t read = 0; ++ int timeout = max / 4 + 1; ++ ++ while (read < max) { ++ if (!(readl(priv->base + TRNG_CTL) & (1 << TRNG_RDY))) { ++ if (wait) { ++ if (timeout-- == 0) ++ return read; ++ } else { ++ return 0; ++ } ++ } else { ++ *data = readl(priv->base + TRNG_ODATA); ++ data++; ++ read += 4; ++ } ++ } ++ ++ return read; ++} ++ ++static void aspeed_trng_enable(struct aspeed_trng *priv) ++{ ++ u32 ctl; ++ ++ ctl = readl(priv->base + TRNG_CTL); ++ ctl = ctl & ~(1 << TRNG_EN); /* enable rng */ ++ ctl = ctl | (3 << TRNG_MODE); /* select mode */ ++ ++ writel(ctl, priv->base + TRNG_CTL); ++} ++ ++static void aspeed_trng_disable(struct aspeed_trng *priv) ++{ ++ writel(1, priv->base + TRNG_CTL); ++} ++ ++static int aspeed_trng_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct aspeed_trng *priv; ++ struct resource *res; ++ int ret; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ priv->base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(priv->base)) ++ return PTR_ERR(priv->base); ++ ++ priv->rng.name = pdev->name; ++ priv->rng.quality = 900; ++ priv->rng.read = aspeed_trng_read; ++ ++ aspeed_trng_enable(priv); ++ ++ ret = devm_hwrng_register(&pdev->dev, &priv->rng); ++ if (ret) ++ return ret; ++ ++ platform_set_drvdata(pdev, priv); ++ ++ dev_info(dev, "Aspeed Hardware RNG successfully registered\n"); ++ ++ return 0; ++} ++ ++static void aspeed_trng_remove(struct platform_device *pdev) ++{ ++ struct aspeed_trng *priv = platform_get_drvdata(pdev); ++ ++ aspeed_trng_disable(priv); ++} ++ ++static const struct of_device_id aspeed_trng_dt_ids[] = { ++ { .compatible = "aspeed,ast2600-trng" }, ++ { .compatible = "aspeed,ast2700-trng" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aspeed_trng_dt_ids); ++ ++static struct platform_driver aspeed_trng_driver = { ++ .probe = aspeed_trng_probe, ++ .remove = aspeed_trng_remove, ++ .driver = { ++ .name = "aspeed-trng", ++ .of_match_table = aspeed_trng_dt_ids, ++ }, ++}; ++ ++module_platform_driver(aspeed_trng_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Neal Liu "); ++MODULE_DESCRIPTION("Aspeed true random number generator driver"); +diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c +--- a/drivers/char/ipmi/kcs_bmc_aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/char/ipmi/kcs_bmc_aspeed.c 2026-09-09 15:51:44.691238480 +0000 +@@ -1,13 +1,14 @@ + // SPDX-License-Identifier: GPL-2.0 + /* + * Copyright (c) 2015-2018, Intel Corporation. ++ * Copyright (c) 2023, Aspeed Technology Inc. + */ +- + #define pr_fmt(fmt) "aspeed-kcs-bmc: " fmt + + #include + #include + #include ++#include + #include + #include + #include +@@ -23,10 +24,9 @@ + + #include "kcs_bmc_device.h" + ++#define DEVICE_NAME "aspeed-kcs-bmc" + +-#define DEVICE_NAME "ast-kcs-bmc" +- +-#define KCS_CHANNEL_MAX 4 ++static DEFINE_IDA(aspeed_kcs_bmc_ida); + + /* + * Field class descriptions +@@ -38,87 +38,74 @@ + * SELyIRQX SerIRQ polarity for LPC channel y (low: 0, high: 1) + * IRQXEy Assert the SerIRQ specified in IDyIRQX for LPC channel y + */ +- +-#define LPC_TYIRQX_LOW 0b00 +-#define LPC_TYIRQX_HIGH 0b01 +-#define LPC_TYIRQX_RSVD 0b10 +-#define LPC_TYIRQX_RISING 0b11 +- +-#define LPC_HICR0 0x000 +-#define LPC_HICR0_LPC3E BIT(7) +-#define LPC_HICR0_LPC2E BIT(6) +-#define LPC_HICR0_LPC1E BIT(5) +-#define LPC_HICR2 0x008 +-#define LPC_HICR2_IBFIE3 BIT(3) +-#define LPC_HICR2_IBFIE2 BIT(2) +-#define LPC_HICR2_IBFIE1 BIT(1) +-#define LPC_HICR4 0x010 +-#define LPC_HICR4_LADR12AS BIT(7) +-#define LPC_HICR4_KCSENBL BIT(2) +-#define LPC_SIRQCR0 0x070 ++#define HICR0 0x000 ++#define HICR0_LPC3E BIT(7) ++#define HICR0_LPC2E BIT(6) ++#define HICR0_LPC1E BIT(5) ++#define HICR2 0x008 ++#define HICR2_IBFIE3 BIT(3) ++#define HICR2_IBFIE2 BIT(2) ++#define HICR2_IBFIE1 BIT(1) ++#define HICR4 0x010 ++#define HICR4_LADR12AS BIT(7) ++#define HICR4_KCSENBL BIT(2) ++#define LADR3H 0x014 ++#define LADR3L 0x018 ++#define LADR12H 0x01C ++#define LADR12L 0x020 ++#define IDR1 0x024 ++#define IDR2 0x028 ++#define IDR3 0x02C ++#define ODR1 0x030 ++#define ODR2 0x034 ++#define ODR3 0x038 ++#define STR1 0x03C ++#define STR2 0x040 ++#define STR3 0x044 ++#define SIRQCR0 0x070 + /* IRQ{12,1}E1 are deprecated as of AST2600 A3 but necessary for prior chips */ +-#define LPC_SIRQCR0_IRQ12E1 BIT(1) +-#define LPC_SIRQCR0_IRQ1E1 BIT(0) +-#define LPC_HICR5 0x080 +-#define LPC_HICR5_ID3IRQX_MASK GENMASK(23, 20) +-#define LPC_HICR5_ID3IRQX_SHIFT 20 +-#define LPC_HICR5_ID2IRQX_MASK GENMASK(19, 16) +-#define LPC_HICR5_ID2IRQX_SHIFT 16 +-#define LPC_HICR5_SEL3IRQX BIT(15) +-#define LPC_HICR5_IRQXE3 BIT(14) +-#define LPC_HICR5_SEL2IRQX BIT(13) +-#define LPC_HICR5_IRQXE2 BIT(12) +-#define LPC_LADR3H 0x014 +-#define LPC_LADR3L 0x018 +-#define LPC_LADR12H 0x01C +-#define LPC_LADR12L 0x020 +-#define LPC_IDR1 0x024 +-#define LPC_IDR2 0x028 +-#define LPC_IDR3 0x02C +-#define LPC_ODR1 0x030 +-#define LPC_ODR2 0x034 +-#define LPC_ODR3 0x038 +-#define LPC_STR1 0x03C +-#define LPC_STR2 0x040 +-#define LPC_STR3 0x044 +-#define LPC_HICRB 0x100 +-#define LPC_HICRB_EN16LADR2 BIT(5) +-#define LPC_HICRB_EN16LADR1 BIT(4) +-#define LPC_HICRB_IBFIE4 BIT(1) +-#define LPC_HICRB_LPC4E BIT(0) +-#define LPC_HICRC 0x104 +-#define LPC_HICRC_ID4IRQX_MASK GENMASK(7, 4) +-#define LPC_HICRC_ID4IRQX_SHIFT 4 +-#define LPC_HICRC_TY4IRQX_MASK GENMASK(3, 2) +-#define LPC_HICRC_TY4IRQX_SHIFT 2 +-#define LPC_HICRC_OBF4_AUTO_CLR BIT(1) +-#define LPC_HICRC_IRQXE4 BIT(0) +-#define LPC_LADR4 0x110 +-#define LPC_IDR4 0x114 +-#define LPC_ODR4 0x118 +-#define LPC_STR4 0x11C +-#define LPC_LSADR12 0x120 +-#define LPC_LSADR12_LSADR2_MASK GENMASK(31, 16) +-#define LPC_LSADR12_LSADR2_SHIFT 16 +-#define LPC_LSADR12_LSADR1_MASK GENMASK(15, 0) +-#define LPC_LSADR12_LSADR1_SHIFT 0 +- +-#define OBE_POLL_PERIOD (HZ / 2) +- +-enum aspeed_kcs_irq_mode { +- aspeed_kcs_irq_none, +- aspeed_kcs_irq_serirq, +-}; ++#define SIRQCR0_IRQ12E1 BIT(1) ++#define SIRQCR0_IRQ1E1 BIT(0) ++#define HICR5 0x080 ++#define HICR5_ID3IRQX GENMASK(23, 20) ++#define HICR5_ID2IRQX GENMASK(19, 16) ++#define HICR5_SEL3IRQX BIT(15) ++#define HICR5_IRQXE3 BIT(14) ++#define HICR5_SEL2IRQX BIT(13) ++#define HICR5_IRQXE2 BIT(12) ++#define HICRB 0x100 ++#define HICRB_EN16LADR2 BIT(5) ++#define HICRB_EN16LADR1 BIT(4) ++#define HICRB_IBFIE4 BIT(1) ++#define HICRB_LPC4E BIT(0) ++#define HICRC 0x104 ++#define HICRC_ID4IRQX GENMASK(7, 4) ++#define HICRC_SEL4IRQX BIT(2) ++#define HICRC_OBF4_AUTO_CLR BIT(1) ++#define HICRC_IRQXE4 BIT(0) ++#define LADR4 0x110 ++#define IDR4 0x114 ++#define ODR4 0x118 ++#define STR4 0x11C ++#define LSADR12 0x120 ++#define LSADR12_LSADR2 GENMASK(31, 16) ++#define LSADR12_LSADR1 GENMASK(15, 0) ++ ++#define KCS_HW_INSTANCE_NUM 4 ++#define KCS_OBE_POLL_PERIOD (HZ / 2) + + struct aspeed_kcs_bmc { + struct kcs_bmc_device kcs_bmc; +- + struct regmap *map; ++ int irq; ++ ++ u32 io_addr; ++ u32 hw_inst; + + struct { +- enum aspeed_kcs_irq_mode mode; +- int id; +- } upstream_irq; ++ u32 id; ++ u32 type; ++ } sirq; + + struct { + spinlock_t lock; +@@ -127,6 +114,13 @@ + } obe; + }; + ++static const struct kcs_ioreg aspeed_kcs_ioregs[KCS_HW_INSTANCE_NUM] = { ++ { .idr = IDR1, .odr = ODR1, .str = STR1 }, ++ { .idr = IDR2, .odr = ODR2, .str = STR2 }, ++ { .idr = IDR3, .odr = ODR3, .str = STR3 }, ++ { .idr = IDR4, .odr = ODR4, .str = STR4 }, ++}; ++ + static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc_device *kcs_bmc) + { + return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc); +@@ -134,11 +128,11 @@ + + static u8 aspeed_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg) + { +- struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc); ++ struct aspeed_kcs_bmc *kcs_aspeed = to_aspeed_kcs_bmc(kcs_bmc); + u32 val = 0; + int rc; + +- rc = regmap_read(priv->map, reg, &val); ++ rc = regmap_read(kcs_aspeed->map, reg, &val); + WARN(rc != 0, "regmap_read() failed: %d\n", rc); + + return rc == 0 ? (u8) val : 0; +@@ -146,50 +140,50 @@ + + static void aspeed_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data) + { +- struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc); ++ struct aspeed_kcs_bmc *kcs_aspeed = to_aspeed_kcs_bmc(kcs_bmc); + int rc; + +- rc = regmap_write(priv->map, reg, data); ++ rc = regmap_write(kcs_aspeed->map, reg, data); + WARN(rc != 0, "regmap_write() failed: %d\n", rc); + + /* Trigger the upstream IRQ on ODR writes, if enabled */ + + switch (reg) { +- case LPC_ODR1: +- case LPC_ODR2: +- case LPC_ODR3: +- case LPC_ODR4: ++ case ODR1: ++ case ODR2: ++ case ODR3: ++ case ODR4: + break; + default: + return; + } + +- if (priv->upstream_irq.mode != aspeed_kcs_irq_serirq) ++ if (kcs_aspeed->sirq.type == IRQ_TYPE_NONE) + return; + +- switch (kcs_bmc->channel) { +- case 1: +- switch (priv->upstream_irq.id) { ++ switch (kcs_aspeed->hw_inst) { ++ case 0: ++ switch (kcs_aspeed->sirq.id) { + case 12: +- regmap_update_bits(priv->map, LPC_SIRQCR0, LPC_SIRQCR0_IRQ12E1, +- LPC_SIRQCR0_IRQ12E1); ++ regmap_update_bits(kcs_aspeed->map, SIRQCR0, SIRQCR0_IRQ12E1, ++ SIRQCR0_IRQ12E1); + break; + case 1: +- regmap_update_bits(priv->map, LPC_SIRQCR0, LPC_SIRQCR0_IRQ1E1, +- LPC_SIRQCR0_IRQ1E1); ++ regmap_update_bits(kcs_aspeed->map, SIRQCR0, SIRQCR0_IRQ1E1, ++ SIRQCR0_IRQ1E1); + break; + default: + break; + } + break; ++ case 1: ++ regmap_update_bits(kcs_aspeed->map, HICR5, HICR5_IRQXE2, HICR5_IRQXE2); ++ break; + case 2: +- regmap_update_bits(priv->map, LPC_HICR5, LPC_HICR5_IRQXE2, LPC_HICR5_IRQXE2); ++ regmap_update_bits(kcs_aspeed->map, HICR5, HICR5_IRQXE3, HICR5_IRQXE3); + break; + case 3: +- regmap_update_bits(priv->map, LPC_HICR5, LPC_HICR5_IRQXE3, LPC_HICR5_IRQXE3); +- break; +- case 4: +- regmap_update_bits(priv->map, LPC_HICRC, LPC_HICRC_IRQXE4, LPC_HICRC_IRQXE4); ++ regmap_update_bits(kcs_aspeed->map, HICRC, HICRC_IRQXE4, HICRC_IRQXE4); + break; + default: + break; +@@ -198,86 +192,116 @@ + + static void aspeed_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 val) + { +- struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc); ++ struct aspeed_kcs_bmc *kcs_aspeed = to_aspeed_kcs_bmc(kcs_bmc); + int rc; + +- rc = regmap_update_bits(priv->map, reg, mask, val); ++ rc = regmap_update_bits(kcs_aspeed->map, reg, mask, val); + WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc); + } + ++static void aspeed_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state) ++{ ++ struct aspeed_kcs_bmc *kcs_aspeed = to_aspeed_kcs_bmc(kcs_bmc); ++ int rc; ++ u8 str; ++ ++ /* We don't have an OBE IRQ, emulate it */ ++ if (mask & KCS_BMC_EVENT_TYPE_OBE) { ++ if (KCS_BMC_EVENT_TYPE_OBE & state) { ++ /* ++ * Given we don't have an OBE IRQ, delay by polling briefly to see if we can ++ * observe such an event before returning to the caller. This is not ++ * incorrect because OBF may have already become clear before enabling the ++ * IRQ if we had one, under which circumstance no event will be propagated ++ * anyway. ++ * ++ * The onus is on the client to perform a race-free check that it hasn't ++ * missed the event. ++ */ ++ rc = read_poll_timeout_atomic(aspeed_kcs_inb, str, ++ !(str & KCS_BMC_STR_OBF), 1, 100, false, ++ &kcs_aspeed->kcs_bmc, kcs_aspeed->kcs_bmc.ioreg.str); ++ /* Time for the slow path? */ ++ if (rc == -ETIMEDOUT) ++ mod_timer(&kcs_aspeed->obe.timer, jiffies + KCS_OBE_POLL_PERIOD); ++ } else { ++ del_timer(&kcs_aspeed->obe.timer); ++ } ++ } ++ ++ if (mask & KCS_BMC_EVENT_TYPE_IBF) { ++ const bool enable = !!(state & KCS_BMC_EVENT_TYPE_IBF); ++ ++ switch (kcs_aspeed->hw_inst) { ++ case 0: ++ regmap_update_bits(kcs_aspeed->map, HICR2, HICR2_IBFIE1, ++ enable * HICR2_IBFIE1); ++ return; ++ case 1: ++ regmap_update_bits(kcs_aspeed->map, HICR2, HICR2_IBFIE2, ++ enable * HICR2_IBFIE2); ++ return; ++ case 2: ++ regmap_update_bits(kcs_aspeed->map, HICR2, HICR2_IBFIE3, ++ enable * HICR2_IBFIE3); ++ return; ++ case 3: ++ regmap_update_bits(kcs_aspeed->map, HICRB, HICRB_IBFIE4, ++ enable * HICRB_IBFIE4); ++ return; ++ default: ++ pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel); ++ return; ++ } ++ } ++} ++ ++static const struct kcs_bmc_device_ops aspeed_kcs_ops = { ++ .io_inputb = aspeed_kcs_inb, ++ .io_outputb = aspeed_kcs_outb, ++ .io_updateb = aspeed_kcs_updateb, ++ .irq_mask_update = aspeed_kcs_irq_mask_update, ++}; ++ + /* +- * We note D for Data, and C for Cmd/Status, default rules are ++ * Follow IPMI v2.0, given a KCS IO base X, ++ * the Data and Cmd/Status IO addresses are X and X+1. + * +- * 1. Only the D address is given: +- * A. KCS1/KCS2 (D/C: X/X+4) +- * D/C: CA0h/CA4h +- * D/C: CA8h/CACh +- * B. KCS3 (D/C: XX2/XX3h) +- * D/C: CA2h/CA3h +- * C. KCS4 (D/C: X/X+1) +- * D/C: CA4h/CA5h +- * +- * 2. Both the D/C addresses are given: +- * A. KCS1/KCS2/KCS4 (D/C: X/Y) +- * D/C: CA0h/CA1h +- * D/C: CA8h/CA9h +- * D/C: CA4h/CA5h +- * B. KCS3 (D/C: XX2/XX3h) +- * D/C: CA2h/CA3h ++ * Note that the IO base of KCS channel 3/7/11/... must ends with 2 ++ * e.g. CA2h for KCS#3 + */ +-static int aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u32 addrs[2], int nr_addrs) ++static int aspeed_kcs_config_io_address(struct aspeed_kcs_bmc *kcs_aspeed) + { +- struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc); ++ u32 io_addr; + +- if (WARN_ON(nr_addrs < 1 || nr_addrs > 2)) +- return -EINVAL; ++ io_addr = kcs_aspeed->io_addr; + +- switch (priv->kcs_bmc.channel) { ++ switch (kcs_aspeed->hw_inst) { ++ case 0: ++ regmap_update_bits(kcs_aspeed->map, HICR4, HICR4_LADR12AS, 0); ++ regmap_write(kcs_aspeed->map, LADR12H, io_addr >> 8); ++ regmap_write(kcs_aspeed->map, LADR12L, io_addr & 0xFF); ++ regmap_update_bits(kcs_aspeed->map, LSADR12, LSADR12_LSADR1, ++ FIELD_PREP(LSADR12_LSADR1, io_addr + 1)); ++ regmap_update_bits(kcs_aspeed->map, HICRB, HICRB_EN16LADR1, ++ HICRB_EN16LADR1); ++ break; + case 1: +- regmap_update_bits(priv->map, LPC_HICR4, LPC_HICR4_LADR12AS, 0); +- regmap_write(priv->map, LPC_LADR12H, addrs[0] >> 8); +- regmap_write(priv->map, LPC_LADR12L, addrs[0] & 0xFF); +- if (nr_addrs == 2) { +- regmap_update_bits(priv->map, LPC_LSADR12, LPC_LSADR12_LSADR1_MASK, +- addrs[1] << LPC_LSADR12_LSADR1_SHIFT); +- +- regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_EN16LADR1, +- LPC_HICRB_EN16LADR1); +- } ++ regmap_update_bits(kcs_aspeed->map, HICR4, HICR4_LADR12AS, HICR4_LADR12AS); ++ regmap_write(kcs_aspeed->map, LADR12H, io_addr >> 8); ++ regmap_write(kcs_aspeed->map, LADR12L, io_addr & 0xFF); ++ regmap_update_bits(kcs_aspeed->map, LSADR12, LSADR12_LSADR2, ++ FIELD_PREP(LSADR12_LSADR2, io_addr + 1)); ++ regmap_update_bits(kcs_aspeed->map, HICRB, HICRB_EN16LADR2, ++ HICRB_EN16LADR2); + break; +- + case 2: +- regmap_update_bits(priv->map, LPC_HICR4, LPC_HICR4_LADR12AS, LPC_HICR4_LADR12AS); +- regmap_write(priv->map, LPC_LADR12H, addrs[0] >> 8); +- regmap_write(priv->map, LPC_LADR12L, addrs[0] & 0xFF); +- if (nr_addrs == 2) { +- regmap_update_bits(priv->map, LPC_LSADR12, LPC_LSADR12_LSADR2_MASK, +- addrs[1] << LPC_LSADR12_LSADR2_SHIFT); +- +- regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_EN16LADR2, +- LPC_HICRB_EN16LADR2); +- } ++ regmap_write(kcs_aspeed->map, LADR3H, io_addr >> 8); ++ regmap_write(kcs_aspeed->map, LADR3L, io_addr & 0xFF); + break; +- + case 3: +- if (nr_addrs == 2) { +- dev_err(priv->kcs_bmc.dev, +- "Channel 3 only supports inferred status IO address\n"); +- return -EINVAL; +- } +- +- regmap_write(priv->map, LPC_LADR3H, addrs[0] >> 8); +- regmap_write(priv->map, LPC_LADR3L, addrs[0] & 0xFF); ++ regmap_write(kcs_aspeed->map, LADR4, ((io_addr + 1) << 16) | io_addr); + break; +- +- case 4: +- if (nr_addrs == 1) +- regmap_write(priv->map, LPC_LADR4, ((addrs[0] + 1) << 16) | addrs[0]); +- else +- regmap_write(priv->map, LPC_LADR4, (addrs[1] << 16) | addrs[0]); +- +- break; +- + default: + return -EINVAL; + } +@@ -285,398 +309,283 @@ + return 0; + } + +-static inline int aspeed_kcs_map_serirq_type(u32 dt_type) +-{ +- switch (dt_type) { +- case IRQ_TYPE_EDGE_RISING: +- return LPC_TYIRQX_RISING; +- case IRQ_TYPE_LEVEL_HIGH: +- return LPC_TYIRQX_HIGH; +- case IRQ_TYPE_LEVEL_LOW: +- return LPC_TYIRQX_LOW; +- default: +- return -EINVAL; +- } +-} +- +-static int aspeed_kcs_config_upstream_irq(struct aspeed_kcs_bmc *priv, u32 id, u32 dt_type) ++static int aspeed_kcs_config_upstream_serirq(struct aspeed_kcs_bmc *kcs_aspeed) + { +- unsigned int mask, val, hw_type; +- int ret; ++ unsigned int mask, val; ++ u32 sirq_id, sirq_type; + +- if (id > 15) +- return -EINVAL; +- +- ret = aspeed_kcs_map_serirq_type(dt_type); +- if (ret < 0) +- return ret; +- hw_type = ret; ++ if (kcs_aspeed->sirq.type == IRQ_TYPE_NONE) ++ return 0; + +- priv->upstream_irq.mode = aspeed_kcs_irq_serirq; +- priv->upstream_irq.id = id; ++ sirq_id = kcs_aspeed->sirq.id; ++ sirq_type = kcs_aspeed->sirq.type; + +- switch (priv->kcs_bmc.channel) { +- case 1: ++ switch (kcs_aspeed->hw_inst) { ++ case 0: + /* Needs IRQxE1 rather than (ID1IRQX, SEL1IRQX, IRQXE1) before AST2600 A3 */ + break; ++ case 1: ++ mask = HICR5_SEL2IRQX | HICR5_ID2IRQX; ++ val = FIELD_PREP(HICR5_ID2IRQX, sirq_id); ++ val |= (sirq_type == IRQ_TYPE_LEVEL_HIGH) ? HICR5_SEL2IRQX : 0; ++ regmap_update_bits(kcs_aspeed->map, HICR5, mask, val); ++ break; + case 2: +- if (!(hw_type == LPC_TYIRQX_LOW || hw_type == LPC_TYIRQX_HIGH)) +- return -EINVAL; +- +- mask = LPC_HICR5_SEL2IRQX | LPC_HICR5_ID2IRQX_MASK; +- val = (id << LPC_HICR5_ID2IRQX_SHIFT); +- val |= (hw_type == LPC_TYIRQX_HIGH) ? LPC_HICR5_SEL2IRQX : 0; +- regmap_update_bits(priv->map, LPC_HICR5, mask, val); +- ++ mask = HICR5_SEL3IRQX | HICR5_ID3IRQX; ++ val = FIELD_PREP(HICR5_ID3IRQX, sirq_id); ++ val |= (sirq_type == IRQ_TYPE_LEVEL_HIGH) ? HICR5_SEL3IRQX : 0; ++ regmap_update_bits(kcs_aspeed->map, HICR5, mask, val); + break; + case 3: +- if (!(hw_type == LPC_TYIRQX_LOW || hw_type == LPC_TYIRQX_HIGH)) +- return -EINVAL; +- +- mask = LPC_HICR5_SEL3IRQX | LPC_HICR5_ID3IRQX_MASK; +- val = (id << LPC_HICR5_ID3IRQX_SHIFT); +- val |= (hw_type == LPC_TYIRQX_HIGH) ? LPC_HICR5_SEL3IRQX : 0; +- regmap_update_bits(priv->map, LPC_HICR5, mask, val); +- +- break; +- case 4: +- mask = LPC_HICRC_ID4IRQX_MASK | LPC_HICRC_TY4IRQX_MASK | LPC_HICRC_OBF4_AUTO_CLR; +- val = (id << LPC_HICRC_ID4IRQX_SHIFT) | (hw_type << LPC_HICRC_TY4IRQX_SHIFT); +- regmap_update_bits(priv->map, LPC_HICRC, mask, val); ++ mask = HICRC_ID4IRQX | HICRC_SEL4IRQX | HICRC_OBF4_AUTO_CLR; ++ val = FIELD_PREP(HICRC_ID4IRQX, sirq_id); ++ val |= (sirq_type == IRQ_TYPE_LEVEL_HIGH) ? HICRC_SEL4IRQX : 0; ++ regmap_update_bits(kcs_aspeed->map, HICRC, mask, val); + break; + default: +- dev_warn(priv->kcs_bmc.dev, ++ dev_warn(kcs_aspeed->kcs_bmc.dev, + "SerIRQ configuration not supported on KCS channel %d\n", +- priv->kcs_bmc.channel); ++ kcs_aspeed->kcs_bmc.channel); + return -EINVAL; + } + + return 0; + } + +-static void aspeed_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable) ++static int aspeed_kcs_enable_channel(struct aspeed_kcs_bmc *kcs_aspeed, bool enable) + { +- struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc); +- +- switch (kcs_bmc->channel) { ++ switch (kcs_aspeed->hw_inst) { ++ case 0: ++ regmap_update_bits(kcs_aspeed->map, HICR0, HICR0_LPC1E, enable * HICR0_LPC1E); ++ break; + case 1: +- regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC1E, enable * LPC_HICR0_LPC1E); +- return; ++ regmap_update_bits(kcs_aspeed->map, HICR0, HICR0_LPC2E, enable * HICR0_LPC2E); ++ break; + case 2: +- regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC2E, enable * LPC_HICR0_LPC2E); +- return; ++ regmap_update_bits(kcs_aspeed->map, HICR0, HICR0_LPC3E, enable * HICR0_LPC3E); ++ regmap_update_bits(kcs_aspeed->map, HICR4, HICR4_KCSENBL, enable * HICR4_KCSENBL); ++ break; + case 3: +- regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC3E, enable * LPC_HICR0_LPC3E); +- regmap_update_bits(priv->map, LPC_HICR4, +- LPC_HICR4_KCSENBL, enable * LPC_HICR4_KCSENBL); +- return; +- case 4: +- regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_LPC4E, enable * LPC_HICRB_LPC4E); +- return; ++ regmap_update_bits(kcs_aspeed->map, HICRB, HICRB_LPC4E, enable * HICRB_LPC4E); ++ break; + default: +- pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel); +- return; ++ return -EINVAL; + } ++ ++ return 0; + } + + static void aspeed_kcs_check_obe(struct timer_list *timer) + { +- struct aspeed_kcs_bmc *priv = container_of(timer, struct aspeed_kcs_bmc, obe.timer); ++ struct aspeed_kcs_bmc *kcs_aspeed = container_of(timer, struct aspeed_kcs_bmc, obe.timer); + unsigned long flags; + u8 str; + +- spin_lock_irqsave(&priv->obe.lock, flags); +- if (priv->obe.remove) { +- spin_unlock_irqrestore(&priv->obe.lock, flags); ++ spin_lock_irqsave(&kcs_aspeed->obe.lock, flags); ++ if (kcs_aspeed->obe.remove) { ++ spin_unlock_irqrestore(&kcs_aspeed->obe.lock, flags); + return; + } + +- str = aspeed_kcs_inb(&priv->kcs_bmc, priv->kcs_bmc.ioreg.str); ++ str = aspeed_kcs_inb(&kcs_aspeed->kcs_bmc, kcs_aspeed->kcs_bmc.ioreg.str); + if (str & KCS_BMC_STR_OBF) { +- mod_timer(timer, jiffies + OBE_POLL_PERIOD); +- spin_unlock_irqrestore(&priv->obe.lock, flags); ++ mod_timer(timer, jiffies + KCS_OBE_POLL_PERIOD); ++ spin_unlock_irqrestore(&kcs_aspeed->obe.lock, flags); + return; + } +- spin_unlock_irqrestore(&priv->obe.lock, flags); +- +- kcs_bmc_handle_event(&priv->kcs_bmc); +-} +- +-static void aspeed_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state) +-{ +- struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc); +- int rc; +- u8 str; +- +- /* We don't have an OBE IRQ, emulate it */ +- if (mask & KCS_BMC_EVENT_TYPE_OBE) { +- if (KCS_BMC_EVENT_TYPE_OBE & state) { +- /* +- * Given we don't have an OBE IRQ, delay by polling briefly to see if we can +- * observe such an event before returning to the caller. This is not +- * incorrect because OBF may have already become clear before enabling the +- * IRQ if we had one, under which circumstance no event will be propagated +- * anyway. +- * +- * The onus is on the client to perform a race-free check that it hasn't +- * missed the event. +- */ +- rc = read_poll_timeout_atomic(aspeed_kcs_inb, str, +- !(str & KCS_BMC_STR_OBF), 1, 100, false, +- &priv->kcs_bmc, priv->kcs_bmc.ioreg.str); +- /* Time for the slow path? */ +- if (rc == -ETIMEDOUT) +- mod_timer(&priv->obe.timer, jiffies + OBE_POLL_PERIOD); +- } else { +- del_timer(&priv->obe.timer); +- } +- } ++ spin_unlock_irqrestore(&kcs_aspeed->obe.lock, flags); + +- if (mask & KCS_BMC_EVENT_TYPE_IBF) { +- const bool enable = !!(state & KCS_BMC_EVENT_TYPE_IBF); +- +- switch (kcs_bmc->channel) { +- case 1: +- regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE1, +- enable * LPC_HICR2_IBFIE1); +- return; +- case 2: +- regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE2, +- enable * LPC_HICR2_IBFIE2); +- return; +- case 3: +- regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE3, +- enable * LPC_HICR2_IBFIE3); +- return; +- case 4: +- regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIE4, +- enable * LPC_HICRB_IBFIE4); +- return; +- default: +- pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel); +- return; +- } +- } ++ kcs_bmc_handle_event(&kcs_aspeed->kcs_bmc); + } + +-static const struct kcs_bmc_device_ops aspeed_kcs_ops = { +- .irq_mask_update = aspeed_kcs_irq_mask_update, +- .io_inputb = aspeed_kcs_inb, +- .io_outputb = aspeed_kcs_outb, +- .io_updateb = aspeed_kcs_updateb, +-}; +- +-static irqreturn_t aspeed_kcs_irq(int irq, void *arg) ++static irqreturn_t aspeed_kcs_isr(int irq, void *arg) + { + struct kcs_bmc_device *kcs_bmc = arg; + + return kcs_bmc_handle_event(kcs_bmc); + } + +-static int aspeed_kcs_config_downstream_irq(struct kcs_bmc_device *kcs_bmc, +- struct platform_device *pdev) ++static int aspeed_kcs_probe(struct platform_device *pdev) + { +- struct device *dev = &pdev->dev; +- int irq; ++ struct aspeed_kcs_bmc *kcs_aspeed; ++ struct kcs_bmc_device *kcs_bmc; ++ struct device *dev; ++ const __be32 *reg; ++ int i, rc, chan; + +- irq = platform_get_irq(pdev, 0); +- if (irq < 0) +- return irq; +- +- return devm_request_irq(dev, irq, aspeed_kcs_irq, IRQF_SHARED, +- dev_name(dev), kcs_bmc); +-} ++ dev = &pdev->dev; + +-static const struct kcs_ioreg ast_kcs_bmc_ioregs[KCS_CHANNEL_MAX] = { +- { .idr = LPC_IDR1, .odr = LPC_ODR1, .str = LPC_STR1 }, +- { .idr = LPC_IDR2, .odr = LPC_ODR2, .str = LPC_STR2 }, +- { .idr = LPC_IDR3, .odr = LPC_ODR3, .str = LPC_STR3 }, +- { .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 }, +-}; ++ kcs_aspeed = devm_kzalloc(dev, sizeof(*kcs_aspeed), GFP_KERNEL); ++ if (!kcs_aspeed) ++ return -ENOMEM; + +-static int aspeed_kcs_of_get_channel(struct platform_device *pdev) +-{ +- struct device_node *np; +- struct kcs_ioreg ioreg; +- const __be32 *reg; +- int i; ++ kcs_bmc = &kcs_aspeed->kcs_bmc; ++ kcs_bmc->ops = &aspeed_kcs_ops; ++ kcs_bmc->dev = dev; + +- np = pdev->dev.of_node; ++ kcs_aspeed->map = syscon_node_to_regmap(pdev->dev.parent->of_node); ++ if (IS_ERR(kcs_aspeed->map)) { ++ dev_err(&pdev->dev, "cannot get regmap\n"); ++ return -ENODEV; ++ } + +- /* Don't translate addresses, we want offsets for the regmaps */ +- reg = of_get_address(np, 0, NULL, NULL); +- if (!reg) +- return -EINVAL; +- ioreg.idr = be32_to_cpup(reg); ++ kcs_aspeed->irq = platform_get_irq(pdev, 0); ++ if (kcs_aspeed->irq < 0) { ++ dev_err(dev, "cannot get IRQ number\n"); ++ return kcs_aspeed->irq; ++ } + +- reg = of_get_address(np, 1, NULL, NULL); +- if (!reg) +- return -EINVAL; +- ioreg.odr = be32_to_cpup(reg); ++ reg = of_get_address(dev->of_node, 0, NULL, NULL); ++ if (!reg) { ++ dev_err(dev, "cannot get IDR\n"); ++ return -ENODEV; ++ } + +- reg = of_get_address(np, 2, NULL, NULL); +- if (!reg) +- return -EINVAL; +- ioreg.str = be32_to_cpup(reg); ++ kcs_bmc->ioreg.idr = be32_to_cpup(reg); + +- for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) { +- if (!memcmp(&ast_kcs_bmc_ioregs[i], &ioreg, sizeof(ioreg))) +- return i + 1; ++ reg = of_get_address(dev->of_node, 1, NULL, NULL); ++ if (!reg) { ++ dev_err(dev, "cannot get ODR\n"); ++ return -ENODEV; + } +- return -EINVAL; +-} + +-static int +-aspeed_kcs_of_get_io_address(struct platform_device *pdev, u32 addrs[2]) +-{ +- int rc; ++ kcs_bmc->ioreg.odr = be32_to_cpup(reg); + +- rc = of_property_read_variable_u32_array(pdev->dev.of_node, +- "aspeed,lpc-io-reg", +- addrs, 1, 2); +- if (rc < 0) { +- dev_err(&pdev->dev, "No valid 'aspeed,lpc-io-reg' configured\n"); +- return rc; ++ reg = of_get_address(dev->of_node, 2, NULL, NULL); ++ if (!reg) { ++ dev_err(dev, "cannot get STR\n"); ++ return -ENODEV; + } + +- if (addrs[0] > 0xffff) { +- dev_err(&pdev->dev, "Invalid data address in 'aspeed,lpc-io-reg'\n"); +- return -EINVAL; ++ kcs_bmc->ioreg.str = be32_to_cpup(reg); ++ ++ for (i = 0; i < KCS_HW_INSTANCE_NUM; ++i) { ++ if (aspeed_kcs_ioregs[i].idr == kcs_bmc->ioreg.idr && ++ aspeed_kcs_ioregs[i].odr == kcs_bmc->ioreg.odr && ++ aspeed_kcs_ioregs[i].str == kcs_bmc->ioreg.str) { ++ kcs_aspeed->hw_inst = i; ++ break; ++ } + } + +- if (rc == 2 && addrs[1] > 0xffff) { +- dev_err(&pdev->dev, "Invalid status address in 'aspeed,lpc-io-reg'\n"); ++ if (i >= KCS_HW_INSTANCE_NUM) { ++ dev_err(dev, "invalid IDR/ODR/STR register\n"); + return -EINVAL; + } + +- return rc; +-} +- +-static int aspeed_kcs_probe(struct platform_device *pdev) +-{ +- struct kcs_bmc_device *kcs_bmc; +- struct aspeed_kcs_bmc *priv; +- struct device_node *np; +- bool have_upstream_irq; +- u32 upstream_irq[2]; +- int rc, channel; +- int nr_addrs; +- u32 addrs[2]; +- +- np = pdev->dev.of_node->parent; +- if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") && +- !of_device_is_compatible(np, "aspeed,ast2500-lpc-v2") && +- !of_device_is_compatible(np, "aspeed,ast2600-lpc-v2")) { +- dev_err(&pdev->dev, "unsupported LPC device binding\n"); ++ rc = of_property_read_u32(dev->of_node, "kcs-io-addr", &kcs_aspeed->io_addr); ++ if (rc || kcs_aspeed->io_addr > (USHRT_MAX - 1)) { ++ dev_err(dev, "invalid IO address\n"); + return -ENODEV; + } + +- channel = aspeed_kcs_of_get_channel(pdev); +- if (channel < 0) +- return channel; +- +- nr_addrs = aspeed_kcs_of_get_io_address(pdev, addrs); +- if (nr_addrs < 0) +- return nr_addrs; +- +- np = pdev->dev.of_node; +- rc = of_property_read_u32_array(np, "aspeed,lpc-interrupts", upstream_irq, 2); +- if (rc && rc != -EINVAL) +- return -EINVAL; +- +- have_upstream_irq = !rc; ++ rc = of_property_read_u32(dev->of_node, "kcs-channel", &chan); ++ if (rc) { ++ chan = ida_alloc(&aspeed_kcs_bmc_ida, GFP_KERNEL); ++ if (chan < 0) { ++ dev_err(dev, "cannot allocate ID for KCS channel\n"); ++ return chan; ++ } ++ } + +- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; ++ kcs_bmc->channel = chan; + +- kcs_bmc = &priv->kcs_bmc; +- kcs_bmc->dev = &pdev->dev; +- kcs_bmc->channel = channel; +- kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1]; +- kcs_bmc->ops = &aspeed_kcs_ops; ++ rc = of_property_read_u32_array(dev->of_node, "kcs-upstream-serirq", (u32 *)&kcs_aspeed->sirq, 2); ++ if (rc) { ++ kcs_aspeed->sirq.type = IRQ_TYPE_NONE; ++ } else { ++ if (kcs_aspeed->sirq.id > 15) { ++ dev_err(dev, "invalid SerIRQ number, expected sirq <= 15\n"); ++ return -EINVAL; ++ } + +- priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node); +- if (IS_ERR(priv->map)) { +- dev_err(&pdev->dev, "Couldn't get regmap\n"); +- return -ENODEV; ++ if (kcs_aspeed->sirq.type != IRQ_TYPE_LEVEL_HIGH && ++ kcs_aspeed->sirq.type != IRQ_TYPE_LEVEL_LOW) { ++ dev_err(dev, "invalid SerIRQ type, expected IRQ_TYPE_LEVEL_HIGH/LOW only\n"); ++ return -EINVAL; ++ } + } + +- spin_lock_init(&priv->obe.lock); +- priv->obe.remove = false; +- timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0); ++ timer_setup(&kcs_aspeed->obe.timer, aspeed_kcs_check_obe, 0); ++ spin_lock_init(&kcs_aspeed->obe.lock); ++ kcs_aspeed->obe.remove = false; + +- rc = aspeed_kcs_set_address(kcs_bmc, addrs, nr_addrs); ++ aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0); ++ ++ rc = aspeed_kcs_config_io_address(kcs_aspeed); + if (rc) + return rc; + +- /* Host to BMC IRQ */ +- rc = aspeed_kcs_config_downstream_irq(kcs_bmc, pdev); ++ rc = aspeed_kcs_config_upstream_serirq(kcs_aspeed); + if (rc) + return rc; + +- /* BMC to Host IRQ */ +- if (have_upstream_irq) { +- rc = aspeed_kcs_config_upstream_irq(priv, upstream_irq[0], upstream_irq[1]); +- if (rc < 0) +- return rc; +- } else { +- priv->upstream_irq.mode = aspeed_kcs_irq_none; ++ rc = devm_request_irq(dev, kcs_aspeed->irq, aspeed_kcs_isr, IRQF_SHARED, ++ dev_name(dev), kcs_bmc); ++ if (rc) { ++ dev_err(dev, "cannot request IRQ\n"); ++ return rc; + } + +- platform_set_drvdata(pdev, priv); ++ platform_set_drvdata(pdev, kcs_aspeed); + +- aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0); +- aspeed_kcs_enable_channel(kcs_bmc, true); ++ rc = aspeed_kcs_enable_channel(kcs_aspeed, true); ++ if (rc) { ++ dev_err(dev, "cannot enable channel %d: %d\n", ++ kcs_bmc->channel, rc); ++ return rc; ++ } + +- rc = kcs_bmc_add_device(&priv->kcs_bmc); ++ rc = kcs_bmc_add_device(kcs_bmc); + if (rc) { +- dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc); ++ dev_warn(dev, "cannot register channel %d: %d\n", ++ kcs_bmc->channel, rc); + return rc; + } + +- dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", +- kcs_bmc->channel, addrs[0]); ++ dev_info(dev, "Initialised channel %d at IO address 0x%x\n", ++ kcs_bmc->channel, kcs_aspeed->io_addr); + + return 0; + } + + static void aspeed_kcs_remove(struct platform_device *pdev) + { +- struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev); +- struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc; ++ struct aspeed_kcs_bmc *kcs_aspeed = platform_get_drvdata(pdev); ++ struct kcs_bmc_device *kcs_bmc = &kcs_aspeed->kcs_bmc; + + kcs_bmc_remove_device(kcs_bmc); + +- aspeed_kcs_enable_channel(kcs_bmc, false); ++ aspeed_kcs_enable_channel(kcs_aspeed, false); + aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0); + + /* Make sure it's proper dead */ +- spin_lock_irq(&priv->obe.lock); +- priv->obe.remove = true; +- spin_unlock_irq(&priv->obe.lock); +- del_timer_sync(&priv->obe.timer); ++ spin_lock_irq(&kcs_aspeed->obe.lock); ++ kcs_aspeed->obe.remove = true; ++ spin_unlock_irq(&kcs_aspeed->obe.lock); ++ del_timer_sync(&kcs_aspeed->obe.timer); + } + +-static const struct of_device_id ast_kcs_bmc_match[] = { +- { .compatible = "aspeed,ast2400-kcs-bmc-v2" }, +- { .compatible = "aspeed,ast2500-kcs-bmc-v2" }, ++static const struct of_device_id aspeed_kcs_bmc_match[] = { ++ { .compatible = "aspeed,ast2400-kcs-bmc" }, ++ { .compatible = "aspeed,ast2500-kcs-bmc" }, + { .compatible = "aspeed,ast2600-kcs-bmc" }, + { } + }; +-MODULE_DEVICE_TABLE(of, ast_kcs_bmc_match); ++MODULE_DEVICE_TABLE(of, aspeed_kcs_bmc_match); + +-static struct platform_driver ast_kcs_bmc_driver = { ++static struct platform_driver aspeed_kcs_bmc_driver = { + .driver = { +- .name = DEVICE_NAME, +- .of_match_table = ast_kcs_bmc_match, ++ .name = DEVICE_NAME, ++ .of_match_table = aspeed_kcs_bmc_match, + }, +- .probe = aspeed_kcs_probe, +- .remove_new = aspeed_kcs_remove, ++ .probe = aspeed_kcs_probe, ++ .remove = aspeed_kcs_remove, + }; +-module_platform_driver(ast_kcs_bmc_driver); ++module_platform_driver(aspeed_kcs_bmc_driver); + + MODULE_LICENSE("GPL v2"); + MODULE_AUTHOR("Haiyue Wang "); + MODULE_AUTHOR("Andrew Jeffery "); ++MODULE_AUTHOR("Chia-Wei Wang "); + MODULE_DESCRIPTION("Aspeed device interface to the KCS BMC device"); +diff --git a/drivers/clk/clk-aspeed.c b/drivers/clk/clk-aspeed.c +--- a/drivers/clk/clk-aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/clk/clk-aspeed.c 2026-09-09 15:51:44.114247198 +0000 +@@ -54,15 +54,15 @@ + [ASPEED_CLK_GATE_DCLK] = { 5, -1, "dclk-gate", NULL, CLK_IS_CRITICAL }, /* DAC */ + [ASPEED_CLK_GATE_REFCLK] = { 6, -1, "refclk-gate", "clkin", CLK_IS_CRITICAL }, + [ASPEED_CLK_GATE_USBPORT2CLK] = { 7, 3, "usb-port2-gate", NULL, 0 }, /* USB2.0 Host port 2 */ +- [ASPEED_CLK_GATE_LCLK] = { 8, 5, "lclk-gate", NULL, 0 }, /* LPC */ ++ [ASPEED_CLK_GATE_LCLK] = { 8, 5, "lclk-gate", NULL, CLK_IS_CRITICAL }, /* LPC */ + [ASPEED_CLK_GATE_USBUHCICLK] = { 9, 15, "usb-uhci-gate", NULL, 0 }, /* USB1.1 (requires port 2 enabled) */ + [ASPEED_CLK_GATE_D1CLK] = { 10, 13, "d1clk-gate", NULL, 0 }, /* GFX CRT */ + [ASPEED_CLK_GATE_YCLK] = { 13, 4, "yclk-gate", NULL, 0 }, /* HAC */ + [ASPEED_CLK_GATE_USBPORT1CLK] = { 14, 14, "usb-port1-gate", NULL, 0 }, /* USB2 hub/USB2 host port 1/USB1.1 dev */ +- [ASPEED_CLK_GATE_UART1CLK] = { 15, -1, "uart1clk-gate", "uart", 0 }, /* UART1 */ +- [ASPEED_CLK_GATE_UART2CLK] = { 16, -1, "uart2clk-gate", "uart", 0 }, /* UART2 */ ++ [ASPEED_CLK_GATE_UART1CLK] = { 15, -1, "uart1clk-gate", "uart", CLK_IS_CRITICAL }, /* UART1 */ ++ [ASPEED_CLK_GATE_UART2CLK] = { 16, -1, "uart2clk-gate", "uart", CLK_IS_CRITICAL }, /* UART2 */ + [ASPEED_CLK_GATE_UART5CLK] = { 17, -1, "uart5clk-gate", "uart", 0 }, /* UART5 */ +- [ASPEED_CLK_GATE_ESPICLK] = { 19, -1, "espiclk-gate", NULL, 0 }, /* eSPI */ ++ [ASPEED_CLK_GATE_ESPICLK] = { 19, -1, "espiclk-gate", NULL, CLK_IS_CRITICAL }, /* eSPI */ + [ASPEED_CLK_GATE_MAC1CLK] = { 20, 11, "mac1clk-gate", "mac", 0 }, /* MAC1 */ + [ASPEED_CLK_GATE_MAC2CLK] = { 21, 12, "mac2clk-gate", "mac", 0 }, /* MAC2 */ + [ASPEED_CLK_GATE_RSACLK] = { 24, -1, "rsaclk-gate", NULL, 0 }, /* RSA */ +@@ -278,6 +278,7 @@ + [ASPEED_RESET_PECI] = 10, + [ASPEED_RESET_I2C] = 2, + [ASPEED_RESET_AHB] = 1, ++ [ASPEED_RESET_VIDEO] = 6, + + /* + * SCUD4 resets start at an offset to separate them from +diff --git a/drivers/clk/clk-ast1700.c b/drivers/clk/clk-ast1700.c +--- a/drivers/clk/clk-ast1700.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/clk/clk-ast1700.c 2026-09-09 15:51:44.119247123 +0000 +@@ -0,0 +1,809 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright ASPEED Technology ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#define AST1700_CLK_25MHZ 25000000 ++#define AST1700_CLK_24MHZ 24000000 ++#define AST1700_CLK_192MHZ 192000000 ++/* IO Die */ ++#define AST1700_CLK_STOP 0x240 ++#define AST1700_CLK_STOP2 0x260 ++#define AST1700_CLK_SEL1 0x280 ++#define AST1700_CLK_SEL2 0x284 ++#define UXCLK_MASK GENMASK(1, 0) ++#define HUXCLK_MASK GENMASK(4, 3) ++#define AST1700_HPLL_PARAM 0x300 ++#define AST1700_APLL_PARAM 0x310 ++#define AST1700_DPLL_PARAM 0x320 ++#define AST1700_UXCLK_CTRL 0x330 ++#define AST1700_HUXCLK_CTRL 0x334 ++ ++#define CREATE_CLK_NAME(id, suffix) kasprintf(GFP_KERNEL, "ast1700_%d-%s", id, suffix) ++ ++static DEFINE_IDA(ast1700_clk_ida); ++ ++/* Globally visible clocks */ ++static DEFINE_SPINLOCK(ast1700_clk_lock); ++ ++/* Division of RGMII Clock */ ++static const struct clk_div_table ast1700_rgmii_div_table[] = { ++ { 0x0, 4 }, ++ { 0x1, 4 }, ++ { 0x2, 6 }, ++ { 0x3, 8 }, ++ { 0x4, 10 }, ++ { 0x5, 12 }, ++ { 0x6, 14 }, ++ { 0x7, 16 }, ++ { 0 } ++}; ++ ++/* Division of RMII Clock */ ++static const struct clk_div_table ast1700_rmii_div_table[] = { ++ { 0x0, 8 }, ++ { 0x1, 8 }, ++ { 0x2, 12 }, ++ { 0x3, 16 }, ++ { 0x4, 20 }, ++ { 0x5, 24 }, ++ { 0x6, 28 }, ++ { 0x7, 32 }, ++ { 0 } ++}; ++ ++/* Division of HCLK/SDIO/MAC/apll_divn CLK */ ++static const struct clk_div_table ast1700_clk_div_table[] = { ++ { 0x0, 2 }, ++ { 0x1, 2 }, ++ { 0x2, 3 }, ++ { 0x3, 4 }, ++ { 0x4, 5 }, ++ { 0x5, 6 }, ++ { 0x6, 7 }, ++ { 0x7, 8 }, ++ { 0 } ++}; ++ ++/* Division of PCLK/EMMC CLK */ ++static const struct clk_div_table ast1700_clk_div_table2[] = { ++ { 0x0, 2 }, ++ { 0x1, 4 }, ++ { 0x2, 6 }, ++ { 0x3, 8 }, ++ { 0x4, 10 }, ++ { 0x5, 12 }, ++ { 0x6, 14 }, ++ { 0x7, 16 }, ++ { 0 } ++}; ++ ++static struct clk_hw *AST1700_calc_uclk(int id, u32 val) ++{ ++ unsigned int mult, div; ++ ++ /* UARTCLK = UXCLK * R / (N * 2) */ ++ u32 r = val & 0xff; ++ u32 n = (val >> 8) & 0x3ff; ++ ++ mult = r; ++ div = n * 2; ++ ++ return clk_hw_register_fixed_factor(NULL, CREATE_CLK_NAME(id, "uartxclk"), ++ CREATE_CLK_NAME(id, "uxclk"), 0, mult, div); ++}; ++ ++static struct clk_hw *AST1700_calc_huclk(int id, u32 val) ++{ ++ unsigned int mult, div; ++ ++ /* UARTCLK = UXCLK * R / (N * 2) */ ++ u32 r = val & 0xff; ++ u32 n = (val >> 8) & 0x3ff; ++ ++ mult = r; ++ div = n * 2; ++ ++ return clk_hw_register_fixed_factor(NULL, CREATE_CLK_NAME(id, "huartxclk"), ++ CREATE_CLK_NAME(id, "huxclk"), 0, mult, div); ++}; ++ ++static struct clk_hw *AST1700_calc_pll(const char *name, const char *parent_name, u32 val) ++{ ++ unsigned int mult, div; ++ ++ if (val & BIT(24)) { ++ /* Pass through mode */ ++ mult = 1; ++ div = 1; ++ } else { ++ /* F = 25Mhz * [(M + 1) / (n + 1)] / (p + 1) */ ++ u32 m = val & 0x1fff; ++ u32 n = (val >> 13) & 0x3f; ++ u32 p = (val >> 19) & 0xf; ++ ++ mult = (m + 1) / (n + 1); ++ div = (p + 1); ++ } ++ return clk_hw_register_fixed_factor(NULL, name, parent_name, 0, mult, div); ++} ++ ++static int AST1700_clk_is_enabled(struct clk_hw *hw) ++{ ++ struct clk_gate *gate = to_clk_gate(hw); ++ u32 clk = BIT(gate->bit_idx); ++ u32 reg; ++ ++ reg = readl(gate->reg); ++ ++ return !(reg & clk); ++} ++ ++static int AST1700_clk_enable(struct clk_hw *hw) ++{ ++ struct clk_gate *gate = to_clk_gate(hw); ++ u32 clk = BIT(gate->bit_idx); ++ ++ if (readl(gate->reg) & clk) ++ writel(clk, gate->reg + 0x04); ++ ++ return 0; ++} ++ ++static void AST1700_clk_disable(struct clk_hw *hw) ++{ ++ struct clk_gate *gate = to_clk_gate(hw); ++ u32 clk = BIT(gate->bit_idx); ++ ++ /* Clock is set to enable, so use write to set register */ ++ writel(clk, gate->reg); ++} ++ ++static const struct clk_ops AST1700_clk_gate_ops = { ++ .enable = AST1700_clk_enable, ++ .disable = AST1700_clk_disable, ++ .is_enabled = AST1700_clk_is_enabled, ++}; ++ ++static struct clk_hw *AST1700_clk_hw_register_gate(struct device *dev, const char *name, ++ const char *parent_name, unsigned long flags, ++ void __iomem *reg, u8 clock_idx, ++ u8 clk_gate_flags, spinlock_t *lock) ++{ ++ struct clk_gate *gate; ++ struct clk_hw *hw; ++ struct clk_init_data init; ++ int ret = -EINVAL; ++ ++ gate = kzalloc(sizeof(*gate), GFP_KERNEL); ++ if (!gate) ++ return ERR_PTR(-ENOMEM); ++ ++ init.name = name; ++ init.ops = &AST1700_clk_gate_ops; ++ init.flags = flags; ++ init.parent_names = parent_name ? &parent_name : NULL; ++ init.num_parents = parent_name ? 1 : 0; ++ ++ gate->reg = reg; ++ gate->bit_idx = clock_idx; ++ gate->flags = clk_gate_flags; ++ gate->lock = lock; ++ gate->hw.init = &init; ++ ++ hw = &gate->hw; ++ ret = clk_hw_register(dev, hw); ++ if (ret) { ++ kfree(gate); ++ hw = ERR_PTR(ret); ++ } ++ ++ return hw; ++} ++ ++struct ast1700_reset { ++ void __iomem *base; ++ struct reset_controller_dev rcdev; ++}; ++ ++#define to_rc_data(p) container_of(p, struct ast1700_reset, rcdev) ++ ++static int ast1700_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) ++{ ++ struct ast1700_reset *rc = to_rc_data(rcdev); ++ u32 rst = BIT(id % 32); ++ u32 reg = id >= 32 ? 0x220 : 0x200; ++ ++ writel(rst, rc->base + reg); ++ return 0; ++} ++ ++static int ast1700_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) ++{ ++ struct ast1700_reset *rc = to_rc_data(rcdev); ++ u32 rst = BIT(id % 32); ++ u32 reg = id >= 32 ? 0x220 : 0x200; ++ ++ /* Use set to clear register */ ++ writel(rst, rc->base + reg + 0x04); ++ return 0; ++} ++ ++static int ast1700_reset_status(struct reset_controller_dev *rcdev, unsigned long id) ++{ ++ struct ast1700_reset *rc = to_rc_data(rcdev); ++ u32 rst = BIT(id % 32); ++ u32 reg = id >= 32 ? 0x220 : 0x200; ++ ++ return (readl(rc->base + reg) & rst); ++} ++ ++static const struct reset_control_ops ast1700_reset_ops = { ++ .assert = ast1700_reset_assert, ++ .deassert = ast1700_reset_deassert, ++ .status = ast1700_reset_status, ++}; ++ ++static const char *const sdclk_sel0[] = { ++ "ast1700_0-hpll_divn", ++ "ast1700_0-apll_divn", ++}; ++ ++static const char *const sdclk_sel1[] = { ++ "ast1700_1-hpll_divn", ++ "ast1700_1-apll_divn", ++}; ++ ++static const char *const uartclk_sel0[] = { ++ "ast1700_0-uartxclk", ++ "ast1700_0-huartxclk", ++}; ++ ++static const char *const uartclk_sel1[] = { ++ "ast1700_1-uartxclk", ++ "ast1700_1-huartxclk", ++}; ++ ++static const char *const uxclk_sel0[] = { ++ "ast1700_0-apll_div4", ++ "ast1700_0-apll_div2", ++ "ast1700_0-apll", ++ "ast1700_0-hpll", ++}; ++ ++static const char *const uxclk_sel1[] = { ++ "ast1700_1-apll_div4", ++ "ast1700_1-apll_div2", ++ "ast1700_1-apll", ++ "ast1700_1-hpll", ++}; ++ ++static int AST1700_clk_init(struct device_node *ast1700_node) ++{ ++ struct clk_hw_onecell_data *clk_data; ++ struct ast1700_reset *reset; ++ u32 uart_clk_source = 0; ++ void __iomem *clk_base; ++ struct clk_hw **clks; ++ struct clk_hw *hw; ++ u32 val; ++ int ret; ++ ++ int id = ida_simple_get(&ast1700_clk_ida, 0, 0, GFP_KERNEL); ++ ++ clk_base = of_iomap(ast1700_node, 0); ++ WARN_ON(!clk_base); ++ ++ clk_data = kzalloc(struct_size(clk_data, hws, AST1700_NUM_CLKS), GFP_KERNEL); ++ if (!clk_data) ++ return -ENOMEM; ++ ++ clk_data->num = AST1700_NUM_CLKS; ++ clks = clk_data->hws; ++ ++ reset = kzalloc(sizeof(*reset), GFP_KERNEL); ++ if (!reset) ++ return -ENOMEM; ++ ++ reset->base = clk_base; ++ ++ reset->rcdev.owner = THIS_MODULE; ++ reset->rcdev.nr_resets = AST1700_RESET_NUMS; ++ reset->rcdev.ops = &ast1700_reset_ops; ++ reset->rcdev.of_node = ast1700_node; ++ ++ ret = reset_controller_register(&reset->rcdev); ++ if (ret) { ++ pr_err("soc1 failed to register reset controller\n"); ++ return ret; ++ } ++ /* ++ * Ast1700 A0 workaround: ++ * I3C reset should assert all of the I3C controllers simultaneously. ++ * Otherwise, it may lead to failure in accessing I3C registers. ++ */ ++ if (!(readl(clk_base) & BIT(16))) { ++ for (int i = AST1700_RESET_I3C0; i <= AST1700_RESET_I3C15; i++) ++ ast1700_reset_assert(&reset->rcdev, i); ++ } ++ ++ hw = clk_hw_register_fixed_rate(NULL, CREATE_CLK_NAME(id, "clkin"), NULL, 0, AST1700_CLK_25MHZ); ++ if (IS_ERR(hw)) ++ return PTR_ERR(hw); ++ clks[AST1700_CLKIN] = hw; ++ ++ /* HPLL 1000Mhz */ ++ val = readl(clk_base + AST1700_HPLL_PARAM); ++ clks[AST1700_CLK_HPLL] = AST1700_calc_pll(CREATE_CLK_NAME(id, "hpll"), CREATE_CLK_NAME(id, "clkin"), val); ++ ++ /* HPLL 800Mhz */ ++ val = readl(clk_base + AST1700_APLL_PARAM); ++ clks[AST1700_CLK_APLL] = AST1700_calc_pll(CREATE_CLK_NAME(id, "apll"), CREATE_CLK_NAME(id, "clkin"), val); ++ ++ clks[AST1700_CLK_APLL_DIV2] = ++ clk_hw_register_fixed_factor(NULL, CREATE_CLK_NAME(id, "apll_div2"), CREATE_CLK_NAME(id, "apll"), 0, 1, 2); ++ ++ clks[AST1700_CLK_APLL_DIV4] = ++ clk_hw_register_fixed_factor(NULL, CREATE_CLK_NAME(id, "apll_div4"), CREATE_CLK_NAME(id, "apll"), 0, 1, 4); ++ ++ val = readl(clk_base + AST1700_DPLL_PARAM); ++ clks[AST1700_CLK_DPLL] = AST1700_calc_pll(CREATE_CLK_NAME(id, "dpll"), CREATE_CLK_NAME(id, "clkin"), val); ++ ++ /* uxclk mux selection */ ++ clks[AST1700_CLK_UXCLK] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uxclk"), ++ (id == 0) ? uxclk_sel0 : uxclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uxclk_sel0) : ARRAY_SIZE(uxclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 0, 2, 0, &ast1700_clk_lock); ++ ++ val = readl(clk_base + AST1700_UXCLK_CTRL); ++ clks[AST1700_CLK_UARTX] = AST1700_calc_uclk(id, val); ++ ++ /* huxclk mux selection */ ++ clks[AST1700_CLK_HUXCLK] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "huxclk"), ++ (id == 0) ? uxclk_sel0 : uxclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uxclk_sel0) : ARRAY_SIZE(uxclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 3, 2, 0, &ast1700_clk_lock); ++ ++ val = readl(clk_base + AST1700_HUXCLK_CTRL); ++ clks[AST1700_CLK_HUARTX] = AST1700_calc_huclk(id, val); ++ ++ /* AHB CLK = 200Mhz */ ++ clks[AST1700_CLK_AHB] = ++ clk_hw_register_divider_table(NULL, CREATE_CLK_NAME(id, "ahb"), ++ CREATE_CLK_NAME(id, "hpll"), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 20, 3, 0, ast1700_clk_div_table, &ast1700_clk_lock); ++ ++ /* APB CLK = 100Mhz */ ++ clks[AST1700_CLK_APB] = ++ clk_hw_register_divider_table(NULL, CREATE_CLK_NAME(id, "apb"), ++ CREATE_CLK_NAME(id, "hpll"), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 18, 3, 0, ast1700_clk_div_table2, &ast1700_clk_lock); ++ ++ //rmii ++ clks[AST1700_CLK_RMII] = ++ clk_hw_register_divider_table(NULL, CREATE_CLK_NAME(id, "rmii"), ++ CREATE_CLK_NAME(id, "hpll"), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 21, 3, 0, ast1700_rmii_div_table, &ast1700_clk_lock); ++ ++ //rgmii ++ clks[AST1700_CLK_RGMII] = ++ clk_hw_register_divider_table(NULL, CREATE_CLK_NAME(id, "rgmii"), ++ CREATE_CLK_NAME(id, "hpll"), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 25, 3, 0, ast1700_rgmii_div_table, &ast1700_clk_lock); ++ ++ //mac hclk ++ clks[AST1700_CLK_MACHCLK] = ++ clk_hw_register_divider_table(NULL, CREATE_CLK_NAME(id, "machclk"), ++ CREATE_CLK_NAME(id, "hpll"), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 29, 3, 0, ast1700_clk_div_table, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_LCLK0] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "lclk0-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP, ++ 0, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_LCLK0] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "lclk1-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP, ++ 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_ESPI0CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "espi0clk-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP, ++ 2, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_ESPI1CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "espi1clk-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP, ++ 3, 0, &ast1700_clk_lock); ++ ++ //sd pll divn ++ clks[AST1700_CLK_HPLL_DIVN] = ++ clk_hw_register_divider_table(NULL, CREATE_CLK_NAME(id, "hpll_divn"), ++ CREATE_CLK_NAME(id, "hpll"), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 20, 3, 0, ast1700_clk_div_table, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_APLL_DIVN] = ++ clk_hw_register_divider_table(NULL, CREATE_CLK_NAME(id, "apll_divn"), ++ CREATE_CLK_NAME(id, "apll"), ++ 0, clk_base + AST1700_CLK_SEL2, ++ 8, 3, 0, ast1700_clk_div_table, &ast1700_clk_lock); ++ ++ //sd clk ++ clks[AST1700_CLK_SDCLK] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "sdclk"), ++ (id == 0) ? sdclk_sel0 : sdclk_sel1, ++ (id == 0) ? ARRAY_SIZE(sdclk_sel0) : ARRAY_SIZE(sdclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 13, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_SDCLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "sdclk-gate"), ++ CREATE_CLK_NAME(id, "sdclk"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 4, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_REFCLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "io-refclk-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP, ++ 6, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_LPCHCLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "lpchclk-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP, ++ 7, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_MAC0CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "mac0clk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP, ++ 8, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_MAC1CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "mac1clk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP, ++ 9, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_MAC2CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "mac2clk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP, ++ 10, 0, &ast1700_clk_lock); ++ ++ of_property_read_u32(ast1700_node, "uart-clk-source", &uart_clk_source); ++ ++ if (uart_clk_source) { ++ val = readl(clk_base + AST1700_CLK_SEL1) & ~GENMASK(12, 0); ++ uart_clk_source &= GENMASK(12, 0); ++ writel(val | uart_clk_source, clk_base + AST1700_CLK_SEL1); ++ } ++ ++ //UART0 ++ clks[AST1700_CLK_UART0] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart0clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 0, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART0CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart0clk-gate"), ++ CREATE_CLK_NAME(id, "uart0clk"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 11, 0, &ast1700_clk_lock); ++ ++ //UART1 ++ clks[AST1700_CLK_UART1] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart1clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 1, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART1CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart1clk-gate"), ++ CREATE_CLK_NAME(id, "uart1clk"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 12, 0, &ast1700_clk_lock); ++ ++ //UART2 ++ clks[AST1700_CLK_UART2] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart2clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 2, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART2CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart2clk-gate"), ++ CREATE_CLK_NAME(id, "uart2clk"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 13, 0, &ast1700_clk_lock); ++ ++ //UART3 ++ clks[AST1700_CLK_UART3] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart3clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 3, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART3CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart3clk-gate"), ++ CREATE_CLK_NAME(id, "uart3clk"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 14, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C0CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c0clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 16, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C1CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c1clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 17, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C2CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c2clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 18, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C3CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c3clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 19, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C4CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c4clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 20, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C5CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c5clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 21, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C6CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c6clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 22, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C7CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c7clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 23, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C8CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c8clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 24, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C9CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c9clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 25, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C10CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c10clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 26, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C11CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c11clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 27, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C12CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c12clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 28, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C13CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c13clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 29, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C14CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c14clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 30, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_I3C15CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "i3c15clk-gate"), ++ CREATE_CLK_NAME(id, "ahb"), ++ 0, clk_base + AST1700_CLK_STOP, ++ 31, 0, &ast1700_clk_lock); ++ ++ /*clk stop 2 */ ++ //UART5 ++ clks[AST1700_CLK_UART5] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart5clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 5, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART5CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart5clk-gate"), ++ CREATE_CLK_NAME(id, "uart5clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 0, 0, &ast1700_clk_lock); ++ ++ //UART6 ++ clks[AST1700_CLK_UART6] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart6clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 6, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART6CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart6clk-gate"), ++ CREATE_CLK_NAME(id, "uart6clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 1, 0, &ast1700_clk_lock); ++ ++ //UART7 ++ clks[AST1700_CLK_UART7] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart7clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 7, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART7CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart7clk-gate"), ++ CREATE_CLK_NAME(id, "uart7clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 2, 0, &ast1700_clk_lock); ++ ++ //UART8 ++ clks[AST1700_CLK_UART8] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart8clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 8, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART8CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart8clk-gate"), ++ CREATE_CLK_NAME(id, "uart8clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 3, 0, &ast1700_clk_lock); ++ ++ //UART9 ++ clks[AST1700_CLK_UART9] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart9clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 9, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART9CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart9clk-gate"), ++ CREATE_CLK_NAME(id, "uart9clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 4, 0, &ast1700_clk_lock); ++ ++ //UART10 ++ clks[AST1700_CLK_UART10] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart10clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 10, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART10CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart10clk-gate"), ++ CREATE_CLK_NAME(id, "uart10clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 5, 0, &ast1700_clk_lock); ++ ++ //UART11 ++ clks[AST1700_CLK_UART11] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart11clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 11, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART11CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart11clk-gate"), ++ CREATE_CLK_NAME(id, "uart11clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 6, 0, &ast1700_clk_lock); ++ ++ //uart12: call bmc uart ++ clks[AST1700_CLK_UART12] = ++ clk_hw_register_mux(NULL, CREATE_CLK_NAME(id, "uart12clk"), ++ (id == 0) ? uartclk_sel0 : uartclk_sel1, ++ (id == 0) ? ARRAY_SIZE(uartclk_sel0) : ARRAY_SIZE(uartclk_sel1), ++ 0, clk_base + AST1700_CLK_SEL1, ++ 12, 1, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_UART12CLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "uart12clk-gate"), ++ CREATE_CLK_NAME(id, "uart12clk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 7, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_FSICLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "fsiclk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP2, ++ 8, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_LTPIPHYCLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "ltpiphyclk-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP2, ++ 9, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_LTPICLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "ltpiclk-gate"), NULL, ++ CLK_IS_CRITICAL, clk_base + AST1700_CLK_STOP2, ++ 10, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_VGALCLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "vgalclk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP2, ++ 11, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_USBUARTCLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "usbuartclk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP2, ++ 12, 0, &ast1700_clk_lock); ++ ++ clk_hw_register_fixed_factor(NULL, CREATE_CLK_NAME(id, "canclk"), CREATE_CLK_NAME(id, "apll"), 0, 1, 10); ++ ++ clks[AST1700_CLK_GATE_CANCLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "canclk-gate"), ++ CREATE_CLK_NAME(id, "canclk"), ++ 0, clk_base + AST1700_CLK_STOP2, ++ 13, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_PCICLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "pciclk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP2, ++ 14, 0, &ast1700_clk_lock); ++ ++ clks[AST1700_CLK_GATE_SLICLK] = ++ AST1700_clk_hw_register_gate(NULL, CREATE_CLK_NAME(id, "sliclk-gate"), NULL, ++ 0, clk_base + AST1700_CLK_STOP2, ++ 15, 0, &ast1700_clk_lock); ++ ++ of_clk_add_hw_provider(ast1700_node, of_clk_hw_onecell_get, clk_data); ++ ++ return 0; ++}; ++ ++CLK_OF_DECLARE_DRIVER(ast1700, "aspeed,ast1700-scu", AST1700_clk_init); ++ +diff --git a/drivers/clk/clk-ast2600.c b/drivers/clk/clk-ast2600.c +--- a/drivers/clk/clk-ast2600.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/clk/clk-ast2600.c 2026-09-09 15:51:44.126247017 +0000 +@@ -19,7 +19,7 @@ + * This includes the gates (configured from aspeed_g6_gates), plus the + * explicitly-configured clocks (ASPEED_CLK_HPLL and up). + */ +-#define ASPEED_G6_NUM_CLKS 73 ++#define ASPEED_G6_NUM_CLKS 76 + + #define ASPEED_G6_SILICON_REV 0x014 + #define CHIP_REVISION_ID GENMASK(23, 16) +@@ -59,9 +59,13 @@ + + #define ASPEED_G6_STRAP1 0x500 + ++#define ASPEED_UARTCLK_FROM_UXCLK 0x338 ++ + #define ASPEED_MAC12_CLK_DLY 0x340 + #define ASPEED_MAC34_CLK_DLY 0x350 + ++#define ASPEED_G6_MAC34_DRIVING_CTRL 0x458 ++ + /* Globally visible clocks */ + static DEFINE_SPINLOCK(aspeed_g6_clk_lock); + +@@ -72,6 +76,45 @@ + /* AST2600 revision: A0, A1, A2, etc */ + static u8 soc_rev; + ++struct mac_delay_config { ++ u32 tx_delay_1000; ++ u32 rx_delay_1000; ++ u32 tx_delay_100; ++ u32 rx_delay_100; ++ u32 tx_delay_10; ++ u32 rx_delay_10; ++}; ++ ++union mac_delay_1g { ++ u32 w; ++ struct { ++ unsigned int tx_delay_1 : 6; /* bit[5:0] */ ++ unsigned int tx_delay_2 : 6; /* bit[11:6] */ ++ unsigned int rx_delay_1 : 6; /* bit[17:12] */ ++ unsigned int rx_delay_2 : 6; /* bit[23:18] */ ++ unsigned int rx_clk_inv_1 : 1; /* bit[24] */ ++ unsigned int rx_clk_inv_2 : 1; /* bit[25] */ ++ unsigned int rmii_tx_data_at_falling_1 : 1; /* bit[26] */ ++ unsigned int rmii_tx_data_at_falling_2 : 1; /* bit[27] */ ++ unsigned int rgmiick_pad_dir : 1; /* bit[28] */ ++ unsigned int rmii_50m_oe_1 : 1; /* bit[29] */ ++ unsigned int rmii_50m_oe_2 : 1; /* bit[30] */ ++ unsigned int rgmii_125m_o_sel : 1; /* bit[31] */ ++ } b; ++}; ++ ++union mac_delay_100_10 { ++ u32 w; ++ struct { ++ unsigned int tx_delay_1 : 6; /* bit[5:0] */ ++ unsigned int tx_delay_2 : 6; /* bit[11:6] */ ++ unsigned int rx_delay_1 : 6; /* bit[17:12] */ ++ unsigned int rx_delay_2 : 6; /* bit[23:18] */ ++ unsigned int rx_clk_inv_1 : 1; /* bit[24] */ ++ unsigned int rx_clk_inv_2 : 1; /* bit[25] */ ++ unsigned int reserved_0 : 6; /* bit[31:26] */ ++ } b; ++}; + /* + * The majority of the clocks in the system are gates paired with a reset + * controller that holds the IP in reset; this is represented by the @reset_idx +@@ -99,14 +142,14 @@ + * ref0 and ref1 are essential for the SoC to operate + * mpll is required if SDRAM is used + */ +-static const struct aspeed_gate_data aspeed_g6_gates[] = { ++static struct aspeed_gate_data aspeed_g6_gates[] = { + /* clk rst name parent flags */ + [ASPEED_CLK_GATE_MCLK] = { 0, -1, "mclk-gate", "mpll", CLK_IS_CRITICAL }, /* SDRAM */ + [ASPEED_CLK_GATE_ECLK] = { 1, 6, "eclk-gate", "eclk", 0 }, /* Video Engine */ + [ASPEED_CLK_GATE_GCLK] = { 2, 7, "gclk-gate", NULL, 0 }, /* 2D engine */ + /* vclk parent - dclk/d1clk/hclk/mclk */ + [ASPEED_CLK_GATE_VCLK] = { 3, -1, "vclk-gate", NULL, 0 }, /* Video Capture */ +- [ASPEED_CLK_GATE_BCLK] = { 4, 8, "bclk-gate", "bclk", 0 }, /* PCIe/PCI */ ++ [ASPEED_CLK_GATE_BCLK] = { 4, 8, "bclk-gate", "bclk", CLK_IS_CRITICAL }, /* PCIe/PCI */ + /* From dpll */ + [ASPEED_CLK_GATE_DCLK] = { 5, -1, "dclk-gate", NULL, CLK_IS_CRITICAL }, /* DAC */ + [ASPEED_CLK_GATE_REF0CLK] = { 6, -1, "ref0clk-gate", "clkin", CLK_IS_CRITICAL }, +@@ -128,8 +171,8 @@ + /* Reserved 26 */ + [ASPEED_CLK_GATE_EMMCCLK] = { 27, 16, "emmcclk-gate", NULL, 0 }, /* For card clk */ + /* Reserved 28/29/30 */ +- [ASPEED_CLK_GATE_LCLK] = { 32, 32, "lclk-gate", NULL, 0 }, /* LPC */ +- [ASPEED_CLK_GATE_ESPICLK] = { 33, -1, "espiclk-gate", NULL, 0 }, /* eSPI */ ++ [ASPEED_CLK_GATE_LCLK] = { 32, 32, "lclk-gate", NULL, CLK_IS_CRITICAL }, /* LPC */ ++ [ASPEED_CLK_GATE_ESPICLK] = { 33, -1, "espiclk-gate", NULL, CLK_IS_CRITICAL }, /* eSPI */ + [ASPEED_CLK_GATE_REF1CLK] = { 34, -1, "ref1clk-gate", "clkin", CLK_IS_CRITICAL }, + /* Reserved 35 */ + [ASPEED_CLK_GATE_SDCLK] = { 36, 56, "sdclk-gate", NULL, 0 }, /* SDIO/SD */ +@@ -143,20 +186,20 @@ + [ASPEED_CLK_GATE_I3C4CLK] = { 44, 44, "i3c4clk-gate", "i3cclk", 0 }, /* I3C4 */ + [ASPEED_CLK_GATE_I3C5CLK] = { 45, 45, "i3c5clk-gate", "i3cclk", 0 }, /* I3C5 */ + /* Reserved: 46 & 47 */ +- [ASPEED_CLK_GATE_UART1CLK] = { 48, -1, "uart1clk-gate", "uart", 0 }, /* UART1 */ +- [ASPEED_CLK_GATE_UART2CLK] = { 49, -1, "uart2clk-gate", "uart", 0 }, /* UART2 */ +- [ASPEED_CLK_GATE_UART3CLK] = { 50, -1, "uart3clk-gate", "uart", 0 }, /* UART3 */ +- [ASPEED_CLK_GATE_UART4CLK] = { 51, -1, "uart4clk-gate", "uart", 0 }, /* UART4 */ ++ [ASPEED_CLK_GATE_UART1CLK] = { 48, -1, "uart1clk-gate", "uxclk", CLK_IS_CRITICAL }, /* UART1 */ ++ [ASPEED_CLK_GATE_UART2CLK] = { 49, -1, "uart2clk-gate", "uxclk", CLK_IS_CRITICAL }, /* UART2 */ ++ [ASPEED_CLK_GATE_UART3CLK] = { 50, -1, "uart3clk-gate", "uxclk", 0 }, /* UART3 */ ++ [ASPEED_CLK_GATE_UART4CLK] = { 51, -1, "uart4clk-gate", "uxclk", 0 }, /* UART4 */ + [ASPEED_CLK_GATE_MAC3CLK] = { 52, 52, "mac3clk-gate", "mac34", 0 }, /* MAC3 */ + [ASPEED_CLK_GATE_MAC4CLK] = { 53, 53, "mac4clk-gate", "mac34", 0 }, /* MAC4 */ +- [ASPEED_CLK_GATE_UART6CLK] = { 54, -1, "uart6clk-gate", "uartx", 0 }, /* UART6 */ +- [ASPEED_CLK_GATE_UART7CLK] = { 55, -1, "uart7clk-gate", "uartx", 0 }, /* UART7 */ +- [ASPEED_CLK_GATE_UART8CLK] = { 56, -1, "uart8clk-gate", "uartx", 0 }, /* UART8 */ +- [ASPEED_CLK_GATE_UART9CLK] = { 57, -1, "uart9clk-gate", "uartx", 0 }, /* UART9 */ +- [ASPEED_CLK_GATE_UART10CLK] = { 58, -1, "uart10clk-gate", "uartx", 0 }, /* UART10 */ +- [ASPEED_CLK_GATE_UART11CLK] = { 59, -1, "uart11clk-gate", "uartx", 0 }, /* UART11 */ +- [ASPEED_CLK_GATE_UART12CLK] = { 60, -1, "uart12clk-gate", "uartx", 0 }, /* UART12 */ +- [ASPEED_CLK_GATE_UART13CLK] = { 61, -1, "uart13clk-gate", "uartx", 0 }, /* UART13 */ ++ [ASPEED_CLK_GATE_UART6CLK] = { 54, -1, "uart6clk-gate", "uxclk", 0 }, /* UART6 */ ++ [ASPEED_CLK_GATE_UART7CLK] = { 55, -1, "uart7clk-gate", "uxclk", 0 }, /* UART7 */ ++ [ASPEED_CLK_GATE_UART8CLK] = { 56, -1, "uart8clk-gate", "uxclk", 0 }, /* UART8 */ ++ [ASPEED_CLK_GATE_UART9CLK] = { 57, -1, "uart9clk-gate", "uxclk", 0 }, /* UART9 */ ++ [ASPEED_CLK_GATE_UART10CLK] = { 58, -1, "uart10clk-gate", "uxclk", 0 }, /* UART10 */ ++ [ASPEED_CLK_GATE_UART11CLK] = { 59, -1, "uart11clk-gate", "uxclk", CLK_IS_CRITICAL }, /* UART11 */ ++ [ASPEED_CLK_GATE_UART12CLK] = { 60, -1, "uart12clk-gate", "uxclk", 0 }, /* UART12 */ ++ [ASPEED_CLK_GATE_UART13CLK] = { 61, -1, "uart13clk-gate", "uxclk", 0 }, /* UART13 */ + [ASPEED_CLK_GATE_FSICLK] = { 62, 59, "fsiclk-gate", "fsiclk", 0 }, /* FSI */ + }; + +@@ -184,6 +227,18 @@ + { 0 } + }; + ++static const struct clk_div_table ast2600_sd_div_table[] = { ++ { 0x0, 2 }, ++ { 0x1, 4 }, ++ { 0x2, 6 }, ++ { 0x3, 8 }, ++ { 0x4, 10 }, ++ { 0x5, 12 }, ++ { 0x6, 14 }, ++ { 0x7, 1 }, ++ { 0 } ++}; ++ + static const struct clk_div_table ast2600_mac_div_table[] = { + { 0x0, 4 }, + { 0x1, 4 }, +@@ -384,9 +439,14 @@ + struct aspeed_reset *ar = to_aspeed_reset(rcdev); + u32 rst = get_bit(id); + u32 reg = id >= 32 ? ASPEED_G6_RESET_CTRL2 : ASPEED_G6_RESET_CTRL; ++ u32 val; ++ int ret; + + /* Use set to clear register */ +- return regmap_write(ar->map, reg + 0x04, rst); ++ ret = regmap_write(ar->map, reg + 0x04, rst); ++ /* Add dummy read to ensure the write transfer is finished */ ++ regmap_read(ar->map, reg + 4, &val); ++ return ret; + } + + static int aspeed_g6_reset_assert(struct reset_controller_dev *rcdev, +@@ -458,11 +518,6 @@ + return hw; + } + +-static const char *const emmc_extclk_parent_names[] = { +- "emmc_extclk_hpll_in", +- "mpll", +-}; +- + static const char * const vclk_parent_names[] = { + "dpll", + "d1pll", +@@ -484,7 +539,7 @@ + struct aspeed_reset *ar; + struct regmap *map; + struct clk_hw *hw; +- u32 val, rate; ++ u32 val; + int i, ret; + + map = syscon_node_to_regmap(dev->of_node); +@@ -510,70 +565,50 @@ + return ret; + } + +- /* UART clock div13 setting */ +- regmap_read(map, ASPEED_G6_MISC_CTRL, &val); +- if (val & UART_DIV13_EN) +- rate = 24000000 / 13; +- else +- rate = 24000000; +- hw = clk_hw_register_fixed_rate(dev, "uart", NULL, 0, rate); +- if (IS_ERR(hw)) +- return PTR_ERR(hw); +- aspeed_g6_clk_data->hws[ASPEED_CLK_UART] = hw; +- +- /* UART6~13 clock div13 setting */ +- regmap_read(map, 0x80, &val); +- if (val & BIT(31)) +- rate = 24000000 / 13; +- else +- rate = 24000000; +- hw = clk_hw_register_fixed_rate(dev, "uartx", NULL, 0, rate); +- if (IS_ERR(hw)) +- return PTR_ERR(hw); +- aspeed_g6_clk_data->hws[ASPEED_CLK_UARTX] = hw; ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION1, GENMASK(14, 11), BIT(11)); + +- /* EMMC ext clock */ +- hw = clk_hw_register_fixed_factor(dev, "emmc_extclk_hpll_in", "hpll", +- 0, 1, 2); ++ /* EMMC ext clock divider */ ++ hw = clk_hw_register_gate(dev, "emmc_extclk_gate", "mpll", 0, ++ scu_g6_base + ASPEED_G6_CLK_SELECTION1, 15, 0, ++ &aspeed_g6_clk_lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); + +- hw = clk_hw_register_mux(dev, "emmc_extclk_mux", +- emmc_extclk_parent_names, +- ARRAY_SIZE(emmc_extclk_parent_names), 0, +- scu_g6_base + ASPEED_G6_CLK_SELECTION1, 11, 1, +- 0, &aspeed_g6_clk_lock); +- if (IS_ERR(hw)) +- return PTR_ERR(hw); +- +- hw = clk_hw_register_gate(dev, "emmc_extclk_gate", "emmc_extclk_mux", +- 0, scu_g6_base + ASPEED_G6_CLK_SELECTION1, +- 15, 0, &aspeed_g6_clk_lock); +- if (IS_ERR(hw)) +- return PTR_ERR(hw); +- +- hw = clk_hw_register_divider_table(dev, "emmc_extclk", +- "emmc_extclk_gate", 0, +- scu_g6_base + +- ASPEED_G6_CLK_SELECTION1, 12, +- 3, 0, ast2600_emmc_extclk_div_table, ++ //ast2600 emmc clk should under 200Mhz ++ hw = clk_hw_register_divider_table(dev, "emmc_extclk", "emmc_extclk_gate", 0, ++ scu_g6_base + ASPEED_G6_CLK_SELECTION1, 12, 3, 0, ++ ast2600_emmc_extclk_div_table, + &aspeed_g6_clk_lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); + aspeed_g6_clk_data->hws[ASPEED_CLK_EMMC] = hw; + +- /* SD/SDIO clock divider and gate */ +- hw = clk_hw_register_gate(dev, "sd_extclk_gate", "hpll", 0, +- scu_g6_base + ASPEED_G6_CLK_SELECTION4, 31, 0, +- &aspeed_g6_clk_lock); +- if (IS_ERR(hw)) +- return PTR_ERR(hw); ++ clk_hw_register_fixed_rate(NULL, "hclk", NULL, 0, 200000000); ++ ++ regmap_read(map, 0x310, &val); ++ if (val & BIT(8)) { ++ /* SD/SDIO clock divider and gate */ ++ hw = clk_hw_register_gate(dev, "sd_extclk_gate", "apll", 0, ++ scu_g6_base + ASPEED_G6_CLK_SELECTION4, 31, 0, ++ &aspeed_g6_clk_lock); ++ if (IS_ERR(hw)) ++ return PTR_ERR(hw); ++ } else { ++ /* SD/SDIO clock divider and gate */ ++ hw = clk_hw_register_gate(dev, "sd_extclk_gate", "hclk", 0, ++ scu_g6_base + ASPEED_G6_CLK_SELECTION4, 31, 0, ++ &aspeed_g6_clk_lock); ++ if (IS_ERR(hw)) ++ return PTR_ERR(hw); ++ } ++ + hw = clk_hw_register_divider_table(dev, "sd_extclk", "sd_extclk_gate", +- 0, scu_g6_base + ASPEED_G6_CLK_SELECTION4, 28, 3, 0, +- ast2600_div_table, +- &aspeed_g6_clk_lock); ++ 0, scu_g6_base + ASPEED_G6_CLK_SELECTION4, 28, 3, 0, ++ ast2600_sd_div_table, ++ &aspeed_g6_clk_lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); ++ + aspeed_g6_clk_data->hws[ASPEED_CLK_SDIO] = hw; + + /* MAC1/2 RMII 50MHz RCLK */ +@@ -645,8 +680,8 @@ + return PTR_ERR(hw); + aspeed_g6_clk_data->hws[ASPEED_CLK_LHCLK] = hw; + +- /* gfx d1clk : use dp clk */ +- regmap_update_bits(map, ASPEED_G6_CLK_SELECTION1, GENMASK(10, 8), BIT(10)); ++ /* gfx d1clk : use usb phy */ ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION1, GENMASK(10, 8), BIT(9)); + /* SoC Display clock selection */ + hw = clk_hw_register_mux(dev, "d1clk", d1clk_parent_names, + ARRAY_SIZE(d1clk_parent_names), 0, +@@ -677,6 +712,8 @@ + return PTR_ERR(hw); + aspeed_g6_clk_data->hws[ASPEED_CLK_VCLK] = hw; + ++ //vclk : force disable dynmamic slow down and fix vclk = eclk / 2 ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION1, GENMASK(31, 28), 0); + /* Video Engine clock divider */ + hw = clk_hw_register_divider_table(dev, "eclk", NULL, 0, + scu_g6_base + ASPEED_G6_CLK_SELECTION1, 28, 3, 0, +@@ -686,6 +723,26 @@ + return PTR_ERR(hw); + aspeed_g6_clk_data->hws[ASPEED_CLK_ECLK] = hw; + ++ /* uartx parent assign*/ ++ for (i = 0; i < 13; i++) { ++ if (i < 6 && i != 4) { ++ regmap_read(map, 0x310, &val); ++ if (val & BIT(i)) ++ aspeed_g6_gates[ASPEED_CLK_GATE_UART1CLK + i].parent_name = "huxclk"; ++ else ++ aspeed_g6_gates[ASPEED_CLK_GATE_UART1CLK + i].parent_name = "uxclk"; ++ } ++ if (i == 4) ++ aspeed_g6_gates[ASPEED_CLK_GATE_UART1CLK + i].parent_name = "uart5"; ++ if (i > 5 && i != 4) { ++ regmap_read(map, 0x314, &val); ++ if (val & BIT(i)) ++ aspeed_g6_gates[ASPEED_CLK_GATE_UART1CLK + i].parent_name = "huxclk"; ++ else ++ aspeed_g6_gates[ASPEED_CLK_GATE_UART1CLK + i].parent_name = "uxclk"; ++ } ++ } ++ + for (i = 0; i < ARRAY_SIZE(aspeed_g6_gates); i++) { + const struct aspeed_gate_data *gd = &aspeed_g6_gates[i]; + u32 gate_flags; +@@ -749,7 +806,8 @@ + static void __init aspeed_g6_cc(struct regmap *map) + { + struct clk_hw *hw; +- u32 val, div, divbits, axi_div, ahb_div; ++ u32 val, freq, div, divbits, axi_div, ahb_div; ++ u32 mult; + + clk_hw_register_fixed_rate(NULL, "clkin", NULL, 0, 25000000); + +@@ -814,6 +872,55 @@ + hw = clk_hw_register_fixed_rate(NULL, "usb-phy-40m", NULL, 0, 40000000); + aspeed_g6_clk_data->hws[ASPEED_CLK_USBPHY_40M] = hw; + ++ /* uart5 clock selection */ ++ regmap_read(map, ASPEED_G6_MISC_CTRL, &val); ++ if (val & UART_DIV13_EN) ++ div = 13; ++ else ++ div = 1; ++ regmap_read(map, ASPEED_G6_CLK_SELECTION2, &val); ++ if (val & BIT(14)) ++ freq = 192000000; ++ else ++ freq = 24000000; ++ freq = freq / div; ++ ++ aspeed_g6_clk_data->hws[ASPEED_CLK_UART5] = clk_hw_register_fixed_rate(NULL, "uart5", NULL, 0, freq); ++ ++ /* UART1~13 clock div13 setting except uart5 */ ++ regmap_read(map, ASPEED_G6_CLK_SELECTION5, &val); ++ ++ switch (val & 0x3) { ++ case 0: ++ aspeed_g6_clk_data->hws[ASPEED_CLK_UARTX] = clk_hw_register_fixed_factor(NULL, "uartx", "apll", 0, 1, 4); ++ break; ++ case 1: ++ aspeed_g6_clk_data->hws[ASPEED_CLK_UARTX] = clk_hw_register_fixed_factor(NULL, "uartx", "apll", 0, 1, 2); ++ break; ++ case 2: ++ aspeed_g6_clk_data->hws[ASPEED_CLK_UARTX] = clk_hw_register_fixed_factor(NULL, "uartx", "apll", 0, 1, 1); ++ break; ++ case 3: ++ aspeed_g6_clk_data->hws[ASPEED_CLK_UARTX] = clk_hw_register_fixed_factor(NULL, "uartx", "ahb", 0, 1, 1); ++ break; ++ } ++ ++ /* uxclk */ ++ regmap_read(map, ASPEED_UARTCLK_FROM_UXCLK, &val); ++ div = ((val >> 8) & 0x3ff) * 2; ++ mult = val & 0xff; ++ ++ hw = clk_hw_register_fixed_factor(NULL, "uxclk", "uartx", 0, mult, div); ++ aspeed_g6_clk_data->hws[ASPEED_CLK_UXCLK] = hw; ++ ++ /* huxclk */ ++ regmap_read(map, 0x33c, &val); ++ div = ((val >> 8) & 0x3ff) * 2; ++ mult = val & 0xff; ++ ++ hw = clk_hw_register_fixed_factor(NULL, "huxclk", "uartx", 0, mult, div); ++ aspeed_g6_clk_data->hws[ASPEED_CLK_HUXCLK] = hw; ++ + /* i3c clock: source from apll, divide by 8 */ + regmap_update_bits(map, ASPEED_G6_CLK_SELECTION5, + I3C_CLK_SELECTION | APLL_DIV_SELECTION, +@@ -829,6 +936,7 @@ + static void __init aspeed_g6_cc_init(struct device_node *np) + { + struct regmap *map; ++ u32 uart_clk_source = 0; + int ret; + int i; + +@@ -863,6 +971,32 @@ + return; + } + ++ of_property_read_u32(np, "uart-clk-source", &uart_clk_source); ++ ++ if (uart_clk_source) { ++ if (uart_clk_source & GENMASK(5, 0)) ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION4, GENMASK(5, 0), uart_clk_source & GENMASK(5, 0)); ++ ++ if (uart_clk_source & GENMASK(12, 6)) ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION5, GENMASK(12, 6), uart_clk_source & GENMASK(12, 6)); ++ } ++ ++ /* fixed settings for RGMII/RMII clock generator */ ++ /* MAC1/2 RGMII 125MHz = EPLL / 8 */ ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION2, GENMASK(23, 20), ++ (0x7 << 20)); ++ ++ /* MAC3/4 RMII 50MHz = HCLK / 4 */ ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION4, GENMASK(18, 16), ++ (0x3 << 16)); ++ ++ /* MAC3/4 default pad driving strength */ ++ regmap_write(map, ASPEED_G6_MAC34_DRIVING_CTRL, 0x0000000f); ++ ++ /* A0/A1 need change to RSA clock = HPLL/3, A2/A3 have been set at Rom Code */ ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION1, BIT(19), BIT(19)); ++ regmap_update_bits(map, ASPEED_G6_CLK_SELECTION1, GENMASK(27, 26), (2 << 26)); ++ + aspeed_g6_cc(map); + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, aspeed_g6_clk_data); + if (ret) +diff --git a/drivers/clk/clk-ast2700.c b/drivers/clk/clk-ast2700.c +--- a/drivers/clk/clk-ast2700.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/clk/clk-ast2700.c 2026-09-09 15:51:44.107247304 +0000 +@@ -0,0 +1,1254 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2024 ASPEED Technology Inc. ++ * Author: Ryan Chen ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#define REVISION_ID GENMASK(23, 16) ++ ++/* SOC0 */ ++#define SCU0_HWSTRAP1 0x010 ++#define SCU0_CLK_STOP 0x240 ++#define SCU0_CLK_SEL1 0x280 ++#define SCU0_CLK_SEL2 0x284 ++#define GET_USB_REFCLK_DIV(x) ((GENMASK(23, 20) & (x)) >> 20) ++#define UART_DIV13_EN BIT(30) ++#define SCU0_HPLL_PARAM 0x300 ++#define SCU0_DPLL_PARAM 0x308 ++#define SCU0_MPLL_PARAM 0x310 ++#define SCU0_D0CLK_PARAM 0x320 ++#define SCU0_D1CLK_PARAM 0x330 ++#define SCU0_CRT0CLK_PARAM 0x340 ++#define SCU0_CRT1CLK_PARAM 0x350 ++#define SCU0_MPHYCLK_PARAM 0x360 ++ ++/* SOC1 */ ++#define SCU1_CLK_STOP 0x240 ++#define AST2755_SCU1_CLK_STOP 0x248 ++#define SCU1_CLK_STOP2 0x260 ++#define SCU1_CLK_SEL1 0x280 ++#define SCU1_CLK_SEL2 0x284 ++#define SCU1_CLK_I3C_DIV_MASK GENMASK(25, 23) ++#define SCU1_CLK_I3C_DIV(n) ((n) - 1) ++#define UXCLK_MASK GENMASK(1, 0) ++#define HUXCLK_MASK GENMASK(4, 3) ++#define SCU1_HPLL_PARAM 0x300 ++#define SCU1_APLL_PARAM 0x310 ++#define SCU1_DPLL_PARAM 0x320 ++#define SCU1_UXCLK_CTRL 0x330 ++#define SCU1_HUXCLK_CTRL 0x334 ++#define SCU1_MAC12_CLK_DLY 0x390 ++ ++struct mac_delay_config { ++ u32 tx_delay_1000; ++ u32 rx_delay_1000; ++ u32 tx_delay_100; ++ u32 rx_delay_100; ++ u32 tx_delay_10; ++ u32 rx_delay_10; ++}; ++ ++enum ast2700_clk_type { ++ CLK_MUX, ++ CLK_PLL, ++ CLK_HPLL, ++ CLK_GATE, ++ CLK_MISC, ++ CLK_FIXED, ++ CLK_FIXED_DISPLAY, ++ CLK_DIVIDER, ++ CLK_UART_PLL, ++ CLK_FIXED_FACTOR, ++ CLK_GATE_ASPEED, ++}; ++ ++struct ast2700_clk_fixed_factor_data { ++ unsigned int mult; ++ unsigned int div; ++ int parent_id; ++}; ++ ++struct ast2700_clk_gate_data { ++ int parent_id; ++ u32 flags; ++ u32 reg; ++ u8 bit; ++}; ++ ++struct ast2700_clk_mux_data { ++ const struct clk_hw **parent_hws; ++ const unsigned int *parent_ids; ++ unsigned int num_parents; ++ u8 bit_shift; ++ u8 bit_width; ++ u32 reg; ++}; ++ ++struct ast2700_clk_div_data { ++ const struct clk_div_table *div_table; ++ unsigned int parent_id; ++ u8 bit_shift; ++ u8 bit_width; ++ u32 reg; ++}; ++ ++struct ast2700_clk_pll_data { ++ unsigned int parent_id; ++ u32 reg; ++}; ++ ++struct ast2700_clk_fixed_rate_data { ++ unsigned long fixed_rate; ++}; ++ ++struct ast2700_clk_display_fixed_data { ++ u32 reg; ++}; ++ ++struct ast2700_clk_info { ++ u32 id; ++ const char *name; ++ u32 reg; ++ u32 type; ++ union { ++ struct ast2700_clk_fixed_factor_data factor; ++ struct ast2700_clk_fixed_rate_data rate; ++ struct ast2700_clk_display_fixed_data display_rate; ++ struct ast2700_clk_gate_data gate; ++ struct ast2700_clk_div_data div; ++ struct ast2700_clk_pll_data pll; ++ struct ast2700_clk_mux_data mux; ++ } data; ++}; ++ ++struct ast2700_clk_data { ++ const struct ast2700_clk_info *clk_info; ++ unsigned int nr_clks; ++ const int scu; ++}; ++ ++struct ast2700_clk_ctrl { ++ const struct ast2700_clk_data *clk_data; ++ struct device *dev; ++ void __iomem *base; ++ spinlock_t lock; /* clk lock */ ++}; ++ ++static const struct clk_div_table ast2700_rgmii_div_table[] = { ++ { 0x0, 4 }, ++ { 0x1, 4 }, ++ { 0x2, 6 }, ++ { 0x3, 8 }, ++ { 0x4, 10 }, ++ { 0x5, 12 }, ++ { 0x6, 14 }, ++ { 0x7, 16 }, ++ { 0 } ++}; ++ ++static const struct clk_div_table ast2700_rmii_div_table[] = { ++ { 0x0, 8 }, ++ { 0x1, 8 }, ++ { 0x2, 12 }, ++ { 0x3, 16 }, ++ { 0x4, 20 }, ++ { 0x5, 24 }, ++ { 0x6, 28 }, ++ { 0x7, 32 }, ++ { 0 } ++}; ++ ++static const struct clk_div_table ast2700_clk_div_table[] = { ++ { 0x0, 2 }, ++ { 0x1, 2 }, ++ { 0x2, 3 }, ++ { 0x3, 4 }, ++ { 0x4, 5 }, ++ { 0x5, 6 }, ++ { 0x6, 7 }, ++ { 0x7, 8 }, ++ { 0 } ++}; ++ ++static const struct clk_div_table ast2700_clk_div_table2[] = { ++ { 0x0, 2 }, ++ { 0x1, 4 }, ++ { 0x2, 6 }, ++ { 0x3, 8 }, ++ { 0x4, 10 }, ++ { 0x5, 12 }, ++ { 0x6, 14 }, ++ { 0x7, 16 }, ++ { 0 } ++}; ++ ++static const struct clk_div_table ast2700_hclk_div_table[] = { ++ { 0x0, 6 }, ++ { 0x1, 5 }, ++ { 0x2, 4 }, ++ { 0x3, 7 }, ++ { 0 } ++}; ++ ++static const struct clk_div_table ast2700_clk_uart_div_table[] = { ++ { 0x0, 1 }, ++ { 0x1, 13 }, ++ { 0 } ++}; ++ ++/* soc 0 */ ++static const unsigned int psp_parent_ids[] = { ++ SCU0_CLK_MPLL, ++ SCU0_CLK_HPLL, ++ SCU0_CLK_HPLL, ++ SCU0_CLK_HPLL, ++ SCU0_CLK_MPLL_DIV2, ++ SCU0_CLK_HPLL_DIV2, ++ SCU0_CLK_HPLL, ++ SCU0_CLK_HPLL ++}; ++ ++static const struct clk_hw *psp_parent_hws[ARRAY_SIZE(psp_parent_ids)]; ++ ++static const unsigned int hclk_parent_ids[] = { ++ SCU0_CLK_HPLL, ++ SCU0_CLK_MPLL ++}; ++ ++static const struct clk_hw *hclk_parent_hws[ARRAY_SIZE(hclk_parent_ids)]; ++ ++static const unsigned int emmc_parent_ids[] = { ++ SCU0_CLK_MPLL_DIV4, ++ SCU0_CLK_HPLL_DIV4 ++}; ++ ++static const struct clk_hw *emmc_parent_hws[ARRAY_SIZE(emmc_parent_ids)]; ++ ++static const unsigned int mphy_parent_ids[] = { ++ SCU0_CLK_MPLL, ++ SCU0_CLK_HPLL, ++ SCU0_CLK_DPLL, ++ SCU0_CLK_192M ++}; ++ ++static const struct clk_hw *mphy_parent_hws[ARRAY_SIZE(mphy_parent_ids)]; ++ ++static const unsigned int u2phy_parent_ids[] = { ++ SCU0_CLK_MPLL, ++ SCU0_CLK_HPLL ++}; ++ ++static const struct clk_hw *u2phy_parent_hws[ARRAY_SIZE(u2phy_parent_ids)]; ++ ++static const unsigned int uart_parent_ids[] = { ++ SCU0_CLK_24M, ++ SCU0_CLK_192M ++}; ++ ++static const struct clk_hw *uart_parent_hws[ARRAY_SIZE(uart_parent_ids)]; ++ ++/* soc 1 */ ++static const unsigned int uartx_parent_ids[] = { ++ SCU1_CLK_UARTX, ++ SCU1_CLK_HUARTX ++}; ++ ++static const struct clk_hw *uartx_parent_hws[ARRAY_SIZE(uartx_parent_ids)]; ++ ++static const unsigned int uxclk_parent_ids[] = { ++ SCU1_CLK_APLL_DIV4, ++ SCU1_CLK_APLL_DIV2, ++ SCU1_CLK_APLL, ++ SCU1_CLK_HPLL ++}; ++ ++static const struct clk_hw *uxclk_parent_hws[ARRAY_SIZE(uxclk_parent_ids)]; ++ ++static const unsigned int sdclk_parent_ids[] = { ++ SCU1_CLK_HPLL, ++ SCU1_CLK_APLL ++}; ++ ++static const struct clk_hw *sdclk_parent_hws[ARRAY_SIZE(sdclk_parent_ids)]; ++ ++static const unsigned int peciclk_parent_ids[] = { ++ SCU1_CLKIN, ++ SCU1_CLK_HPLL_DIV4 ++}; ++ ++static const struct clk_hw *peciclk_parent_hws[ARRAY_SIZE(peciclk_parent_ids)]; ++ ++#define FIXED_CLK(_id, _name, _rate) \ ++ { \ ++ .id = _id, \ ++ .type = CLK_FIXED, \ ++ .name = _name, \ ++ .data = { .rate = { .fixed_rate = _rate, } }, \ ++ } ++ ++#define FIXED_DISPLAY_CLK(_id, _name, _reg) \ ++ { \ ++ .id = _id, \ ++ .type = CLK_FIXED_DISPLAY, \ ++ .name = _name, \ ++ .data = { .display_rate = { .reg = _reg } }, \ ++ } ++ ++#define PLL_CLK(_id, _type, _name, _parent_id, _reg) \ ++ { \ ++ .id = _id, \ ++ .type = _type, \ ++ .name = _name, \ ++ .data = { .pll = { \ ++ .parent_id = _parent_id, \ ++ .reg = _reg, \ ++ } }, \ ++ } ++ ++#define MUX_CLK(_id, _name, _parent_ids, _num_parents, _parent_hws, _reg, _shift, _width) \ ++ { \ ++ .id = _id, \ ++ .type = CLK_MUX, \ ++ .name = _name, \ ++ .data = { \ ++ .mux = { \ ++ .parent_ids = _parent_ids, \ ++ .parent_hws = _parent_hws, \ ++ .num_parents = _num_parents, \ ++ .reg = (_reg), \ ++ .bit_shift = _shift, \ ++ .bit_width = _width, \ ++ }, \ ++ }, \ ++ } ++ ++#define DIVIDER_CLK(_id, _name, _parent_id, _reg, _shift, _width, _div_table) \ ++ { \ ++ .id = _id, \ ++ .type = CLK_DIVIDER, \ ++ .name = _name, \ ++ .data = { \ ++ .div = { \ ++ .parent_id = _parent_id, \ ++ .reg = _reg, \ ++ .bit_shift = _shift, \ ++ .bit_width = _width, \ ++ .div_table = _div_table, \ ++ }, \ ++ }, \ ++ } ++ ++#define FIXED_FACTOR_CLK(_id, _name, _parent_id, _mult, _div) \ ++ { \ ++ .id = _id, \ ++ .type = CLK_FIXED_FACTOR, \ ++ .name = _name, \ ++ .data = { .factor = { .parent_id = _parent_id, .mult = _mult, .div = _div, } }, \ ++ } ++ ++#define GATE_CLK(_id, _type, _name, _parent_id, _reg, _bit, _flags) \ ++ { \ ++ .id = _id, \ ++ .type = _type, \ ++ .name = _name, \ ++ .data = { \ ++ .gate = { \ ++ .parent_id = _parent_id, \ ++ .reg = _reg, \ ++ .bit = _bit, \ ++ .flags = _flags, \ ++ }, \ ++ }, \ ++ } ++ ++static const struct ast2700_clk_info ast2700_scu0_clk_info[] __initconst = { ++ FIXED_CLK(SCU0_CLKIN, "soc0-clkin", 25 * HZ_PER_MHZ), ++ FIXED_CLK(SCU0_CLK_24M, "soc0-clk24Mhz", 24 * HZ_PER_MHZ), ++ FIXED_CLK(SCU0_CLK_192M, "soc0-clk192Mhz", 192 * HZ_PER_MHZ), ++ FIXED_CLK(SCU0_CLK_U2PHY_CLK12M, "u2phy_clk12m", 12 * HZ_PER_MHZ), ++ FIXED_DISPLAY_CLK(SCU0_CLK_D0, "d0clk", SCU0_D0CLK_PARAM), ++ FIXED_DISPLAY_CLK(SCU0_CLK_D1, "d1clk", SCU0_D1CLK_PARAM), ++ FIXED_DISPLAY_CLK(SCU0_CLK_CRT0, "crt0clk", SCU0_CRT0CLK_PARAM), ++ FIXED_DISPLAY_CLK(SCU0_CLK_CRT1, "crt1clk", SCU0_CRT1CLK_PARAM), ++ PLL_CLK(SCU0_CLK_HPLL, CLK_HPLL, "soc0-hpll", SCU0_CLKIN, SCU0_HPLL_PARAM), ++ PLL_CLK(SCU0_CLK_DPLL, CLK_PLL, "soc0-dpll", SCU0_CLKIN, SCU0_DPLL_PARAM), ++ PLL_CLK(SCU0_CLK_MPLL, CLK_PLL, "soc0-mpll", SCU0_CLKIN, SCU0_MPLL_PARAM), ++ FIXED_FACTOR_CLK(SCU0_CLK_HPLL_DIV2, "soc0-hpll_div2", SCU0_CLK_HPLL, 1, 2), ++ FIXED_FACTOR_CLK(SCU0_CLK_HPLL_DIV4, "soc0-hpll_div4", SCU0_CLK_HPLL, 1, 4), ++ FIXED_FACTOR_CLK(SCU0_CLK_MPLL_DIV2, "soc0-mpll_div2", SCU0_CLK_MPLL, 1, 2), ++ FIXED_FACTOR_CLK(SCU0_CLK_MPLL_DIV4, "soc0-mpll_div4", SCU0_CLK_MPLL, 1, 4), ++ FIXED_FACTOR_CLK(SCU0_CLK_MPLL_DIV8, "soc0-mpll_div8", SCU0_CLK_MPLL, 1, 8), ++ FIXED_FACTOR_CLK(SCU0_CLK_AXI1, "axi1clk", SCU0_CLK_MPLL, 1, 4), ++ MUX_CLK(SCU0_CLK_PSP, "pspclk", psp_parent_ids, ARRAY_SIZE(psp_parent_ids), ++ psp_parent_hws, SCU0_HWSTRAP1, 2, 3), ++ FIXED_FACTOR_CLK(SCU0_CLK_AXI0, "axi0clk", SCU0_CLK_PSP, 1, 2), ++ MUX_CLK(SCU0_CLK_AHBMUX, "soc0-ahbmux", hclk_parent_ids, ARRAY_SIZE(hclk_parent_ids), ++ hclk_parent_hws, SCU0_HWSTRAP1, 7, 1), ++ MUX_CLK(SCU0_CLK_EMMCMUX, "emmcsrc-mux", emmc_parent_ids, ARRAY_SIZE(emmc_parent_ids), ++ emmc_parent_hws, SCU0_CLK_SEL1, 11, 1), ++ MUX_CLK(SCU0_CLK_MPHYSRC, "mphysrc", mphy_parent_ids, ARRAY_SIZE(mphy_parent_ids), ++ mphy_parent_hws, SCU0_CLK_SEL2, 18, 2), ++ MUX_CLK(SCU0_CLK_U2PHY_REFCLKSRC, "u2phy_refclksrc", u2phy_parent_ids, ++ ARRAY_SIZE(u2phy_parent_ids), u2phy_parent_hws, SCU0_CLK_SEL2, 23, 1), ++ MUX_CLK(SCU0_CLK_UART, "soc0-uartclk", uart_parent_ids, ARRAY_SIZE(uart_parent_ids), ++ uart_parent_hws, SCU0_CLK_SEL2, 14, 1), ++ PLL_CLK(SCU0_CLK_MPHY, CLK_MISC, "mphyclk", SCU0_CLK_MPHYSRC, SCU0_MPHYCLK_PARAM), ++ PLL_CLK(SCU0_CLK_U2PHY_REFCLK, CLK_MISC, "u2phy_refclk", SCU0_CLK_U2PHY_REFCLKSRC, ++ SCU0_CLK_SEL2), ++ DIVIDER_CLK(SCU0_CLK_AHB, "soc0-ahb", SCU0_CLK_AHBMUX, ++ SCU0_HWSTRAP1, 5, 2, ast2700_hclk_div_table), ++ DIVIDER_CLK(SCU0_CLK_EMMC, "emmcclk", SCU0_CLK_EMMCMUX, ++ SCU0_CLK_SEL1, 12, 3, ast2700_clk_div_table2), ++ DIVIDER_CLK(SCU0_CLK_APB, "soc0-apb", SCU0_CLK_AXI0, ++ SCU0_CLK_SEL1, 23, 3, ast2700_clk_div_table2), ++ DIVIDER_CLK(SCU0_CLK_HPLL_DIV_AHB, "soc0-hpll-ahb", SCU0_CLK_HPLL, ++ SCU0_HWSTRAP1, 5, 2, ast2700_hclk_div_table), ++ DIVIDER_CLK(SCU0_CLK_MPLL_DIV_AHB, "soc0-mpll-ahb", SCU0_CLK_MPLL, ++ SCU0_HWSTRAP1, 5, 2, ast2700_hclk_div_table), ++ DIVIDER_CLK(SCU0_CLK_UART4, "uart4clk", SCU0_CLK_UART, ++ SCU0_CLK_SEL2, 30, 1, ast2700_clk_uart_div_table), ++ GATE_CLK(SCU0_CLK_GATE_MCLK, CLK_GATE_ASPEED, "mclk-gate", SCU0_CLK_MPLL, ++ SCU0_CLK_STOP, 0, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_ECLK, CLK_GATE_ASPEED, "eclk-gate", -1, SCU0_CLK_STOP, 1, 0), ++ GATE_CLK(SCU0_CLK_GATE_2DCLK, CLK_GATE_ASPEED, "gclk-gate", -1, SCU0_CLK_STOP, 2, 0), ++ GATE_CLK(SCU0_CLK_GATE_VCLK, CLK_GATE_ASPEED, "vclk-gate", -1, SCU0_CLK_STOP, 3, 0), ++ GATE_CLK(SCU0_CLK_GATE_BCLK, CLK_GATE_ASPEED, "bclk-gate", -1, ++ SCU0_CLK_STOP, 4, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_VGA0CLK, CLK_GATE_ASPEED, "vga0clk-gate", -1, ++ SCU0_CLK_STOP, 5, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_REFCLK, CLK_GATE_ASPEED, "soc0-refclk-gate", SCU0_CLKIN, ++ SCU0_CLK_STOP, 6, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_PORTBUSB2CLK, CLK_GATE_ASPEED, "portb-usb2clk-gate", -1, ++ SCU0_CLK_STOP, 7, 0), ++ GATE_CLK(SCU0_CLK_GATE_UHCICLK, CLK_GATE_ASPEED, "uhciclk-gate", -1, SCU0_CLK_STOP, 9, 0), ++ GATE_CLK(SCU0_CLK_GATE_VGA1CLK, CLK_GATE_ASPEED, "vga1clk-gate", -1, ++ SCU0_CLK_STOP, 10, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_DDRPHYCLK, CLK_GATE_ASPEED, "ddrphy-gate", -1, ++ SCU0_CLK_STOP, 11, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_E2M0CLK, CLK_GATE_ASPEED, "e2m0clk-gate", -1, ++ SCU0_CLK_STOP, 12, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_HACCLK, CLK_GATE_ASPEED, "hacclk-gate", -1, SCU0_CLK_STOP, 13, 0), ++ GATE_CLK(SCU0_CLK_GATE_PORTAUSB2CLK, CLK_GATE_ASPEED, "porta-usb2clk-gate", -1, ++ SCU0_CLK_STOP, 14, 0), ++ GATE_CLK(SCU0_CLK_GATE_UART4CLK, CLK_GATE_ASPEED, "uart4clk-gate", SCU0_CLK_UART4, ++ SCU0_CLK_STOP, 15, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_SLICLK, CLK_GATE_ASPEED, "soc0-sliclk-gate", -1, ++ SCU0_CLK_STOP, 16, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_DACCLK, CLK_GATE_ASPEED, "dacclk-gate", -1, ++ SCU0_CLK_STOP, 17, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_DP, CLK_GATE_ASPEED, "dpclk-gate", -1, ++ SCU0_CLK_STOP, 18, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_E2M1CLK, CLK_GATE_ASPEED, "e2m1clk-gate", -1, ++ SCU0_CLK_STOP, 19, CLK_IS_CRITICAL), ++ GATE_CLK(SCU0_CLK_GATE_CRT0CLK, CLK_GATE_ASPEED, "crt0clk-gate", -1, ++ SCU0_CLK_STOP, 20, 0), ++ GATE_CLK(SCU0_CLK_GATE_CRT1CLK, CLK_GATE_ASPEED, "crt1clk-gate", -1, ++ SCU0_CLK_STOP, 21, 0), ++ GATE_CLK(SCU0_CLK_GATE_ECDSACLK, CLK_GATE_ASPEED, "eccclk-gate", -1, ++ SCU0_CLK_STOP, 23, 0), ++ GATE_CLK(SCU0_CLK_GATE_RSACLK, CLK_GATE_ASPEED, "rsaclk-gate", -1, ++ SCU0_CLK_STOP, 24, 0), ++ GATE_CLK(SCU0_CLK_GATE_RVAS0CLK, CLK_GATE_ASPEED, "rvas0clk-gate", -1, ++ SCU0_CLK_STOP, 25, 0), ++ GATE_CLK(SCU0_CLK_GATE_UFSCLK, CLK_GATE_ASPEED, "ufsclk-gate", -1, ++ SCU0_CLK_STOP, 26, 0), ++ GATE_CLK(SCU0_CLK_GATE_EMMCCLK, CLK_GATE_ASPEED, "emmcclk-gate", SCU0_CLK_EMMC, ++ SCU0_CLK_STOP, 27, 0), ++ GATE_CLK(SCU0_CLK_GATE_RVAS1CLK, CLK_GATE_ASPEED, "rvas1clk-gate", -1, ++ SCU0_CLK_STOP, 28, 0), ++}; ++ ++static const struct ast2700_clk_info ast2700_scu1_clk_info[] __initconst = { ++ FIXED_CLK(SCU1_CLKIN, "soc1-clkin", 25 * HZ_PER_MHZ), ++ PLL_CLK(SCU1_CLK_HPLL, CLK_PLL, "soc1-hpll", SCU1_CLKIN, SCU1_HPLL_PARAM), ++ PLL_CLK(SCU1_CLK_APLL, CLK_PLL, "soc1-apll", SCU1_CLKIN, SCU1_APLL_PARAM), ++ PLL_CLK(SCU1_CLK_DPLL, CLK_PLL, "soc1-dpll", SCU1_CLKIN, SCU1_DPLL_PARAM), ++ FIXED_FACTOR_CLK(SCU1_CLK_HPLL_DIV4, "soc1-hpll_div4", SCU1_CLK_HPLL, 1, 4), ++ FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV2, "soc1-apll_div2", SCU1_CLK_APLL, 1, 2), ++ FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV4, "soc1-apll_div4", SCU1_CLK_APLL, 1, 4), ++ FIXED_FACTOR_CLK(SCU1_CLK_CAN, "canclk", SCU1_CLK_APLL, 1, 10), ++ DIVIDER_CLK(SCU1_CLK_APB, "soc1-apb", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 18, 3, ast2700_clk_div_table2), ++ DIVIDER_CLK(SCU1_CLK_RMII, "rmii", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 21, 3, ast2700_rmii_div_table), ++ DIVIDER_CLK(SCU1_CLK_RGMII, "rgmii", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 25, 3, ast2700_rgmii_div_table), ++ DIVIDER_CLK(SCU1_CLK_MACHCLK, "machclk", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 29, 3, ast2700_clk_div_table), ++ DIVIDER_CLK(SCU1_CLK_APLL_DIVN, "soc1-apll_divn", ++ SCU1_CLK_APLL, SCU1_CLK_SEL2, 8, 3, ast2700_clk_div_table), ++ DIVIDER_CLK(SCU1_CLK_AHB, "soc1-ahb", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL2, 20, 3, ast2700_clk_div_table), ++ DIVIDER_CLK(SCU1_CLK_I3C, "soc1-i3c", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL2, 23, 3, ast2700_clk_div_table), ++ MUX_CLK(SCU1_CLK_SDMUX, "sdclk-mux", sdclk_parent_ids, ARRAY_SIZE(sdclk_parent_ids), ++ sdclk_parent_hws, SCU1_CLK_SEL1, 13, 1), ++ MUX_CLK(SCU1_CLK_UXCLK, "uxclk", uxclk_parent_ids, ARRAY_SIZE(uxclk_parent_ids), ++ uxclk_parent_hws, SCU1_CLK_SEL2, 0, 2), ++ MUX_CLK(SCU1_CLK_HUXCLK, "huxclk", uxclk_parent_ids, ARRAY_SIZE(uxclk_parent_ids), ++ uxclk_parent_hws, SCU1_CLK_SEL2, 3, 2), ++ MUX_CLK(SCU1_CLK_PECI, "peciclk", peciclk_parent_ids, ARRAY_SIZE(peciclk_parent_ids), ++ peciclk_parent_hws, SCU1_CLK_SEL2, 16, 1), ++ DIVIDER_CLK(SCU1_CLK_SDCLK, "sdclk", SCU1_CLK_SDMUX, ++ SCU1_CLK_SEL1, 14, 3, ast2700_clk_div_table), ++ PLL_CLK(SCU1_CLK_UARTX, CLK_UART_PLL, "uartxclk", SCU1_CLK_UXCLK, SCU1_UXCLK_CTRL), ++ PLL_CLK(SCU1_CLK_HUARTX, CLK_UART_PLL, "huartxclk", SCU1_CLK_HUXCLK, SCU1_HUXCLK_CTRL), ++ MUX_CLK(SCU1_CLK_UART0, "uart0clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 0, 1), ++ MUX_CLK(SCU1_CLK_UART1, "uart1clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 1, 1), ++ MUX_CLK(SCU1_CLK_UART2, "uart2clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 2, 1), ++ MUX_CLK(SCU1_CLK_UART3, "uart3clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 3, 1), ++ MUX_CLK(SCU1_CLK_UART5, "uart5clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 5, 1), ++ MUX_CLK(SCU1_CLK_UART6, "uart6clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 6, 1), ++ MUX_CLK(SCU1_CLK_UART7, "uart7clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 7, 1), ++ MUX_CLK(SCU1_CLK_UART8, "uart8clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 8, 1), ++ MUX_CLK(SCU1_CLK_UART9, "uart9clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 9, 1), ++ MUX_CLK(SCU1_CLK_UART10, "uart10clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 10, 1), ++ MUX_CLK(SCU1_CLK_UART11, "uart11clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 11, 1), ++ MUX_CLK(SCU1_CLK_UART12, "uart12clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 12, 1), ++ FIXED_FACTOR_CLK(SCU1_CLK_UART13, "uart13clk", SCU1_CLK_HUARTX, 1, 1), ++ FIXED_FACTOR_CLK(SCU1_CLK_UART14, "uart14clk", SCU1_CLK_HUARTX, 1, 1), ++ GATE_CLK(SCU1_CLK_MAC0RCLK, CLK_GATE, "mac0rclk-gate", SCU1_CLK_RMII, ++ SCU1_MAC12_CLK_DLY, 29, 0), ++ GATE_CLK(SCU1_CLK_MAC1RCLK, CLK_GATE, "mac1rclk-gate", SCU1_CLK_RMII, ++ SCU1_MAC12_CLK_DLY, 30, 0), ++ GATE_CLK(SCU1_CLK_GATE_LCLK0, CLK_GATE_ASPEED, "lclk0-gate", -1, ++ SCU1_CLK_STOP, 0, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_LCLK1, CLK_GATE_ASPEED, "lclk1-gate", -1, ++ SCU1_CLK_STOP, 1, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_ESPI0CLK, CLK_GATE_ASPEED, "espi0clk-gate", -1, ++ SCU1_CLK_STOP, 2, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_ESPI1CLK, CLK_GATE_ASPEED, "espi1clk-gate", -1, ++ SCU1_CLK_STOP, 3, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_SDCLK, CLK_GATE_ASPEED, "sdclk-gate", SCU1_CLK_SDCLK, ++ SCU1_CLK_STOP, 4, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_IPEREFCLK, CLK_GATE_ASPEED, "soc1-iperefclk-gate", -1, ++ SCU1_CLK_STOP, 5, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_REFCLK, CLK_GATE_ASPEED, "soc1-refclk-gate", -1, ++ SCU1_CLK_STOP, 6, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_LPCHCLK, CLK_GATE_ASPEED, "lpchclk-gate", -1, ++ SCU1_CLK_STOP, 7, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_MAC0CLK, CLK_GATE_ASPEED, "mac0clk-gate", -1, ++ SCU1_CLK_STOP, 8, 0), ++ GATE_CLK(SCU1_CLK_GATE_MAC1CLK, CLK_GATE_ASPEED, "mac1clk-gate", -1, ++ SCU1_CLK_STOP, 9, 0), ++ GATE_CLK(SCU1_CLK_GATE_MAC2CLK, CLK_GATE_ASPEED, "mac2clk-gate", -1, ++ SCU1_CLK_STOP, 10, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART0CLK, CLK_GATE_ASPEED, "uart0clk-gate", SCU1_CLK_UART0, ++ SCU1_CLK_STOP, 11, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART1CLK, CLK_GATE_ASPEED, "uart1clk-gate", SCU1_CLK_UART1, ++ SCU1_CLK_STOP, 12, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART2CLK, CLK_GATE_ASPEED, "uart2clk-gate", SCU1_CLK_UART2, ++ SCU1_CLK_STOP, 13, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART3CLK, CLK_GATE_ASPEED, "uart3clk-gate", SCU1_CLK_UART3, ++ SCU1_CLK_STOP, 14, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_I2CCLK, CLK_GATE_ASPEED, "i2cclk-gate", -1, SCU1_CLK_STOP, 15, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C0CLK, CLK_GATE_ASPEED, "i3c0clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 16, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C1CLK, CLK_GATE_ASPEED, "i3c1clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 17, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C2CLK, CLK_GATE_ASPEED, "i3c2clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 18, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C3CLK, CLK_GATE_ASPEED, "i3c3clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 19, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C4CLK, CLK_GATE_ASPEED, "i3c4clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 20, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C5CLK, CLK_GATE_ASPEED, "i3c5clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 21, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C6CLK, CLK_GATE_ASPEED, "i3c6clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 22, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C7CLK, CLK_GATE_ASPEED, "i3c7clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 23, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C8CLK, CLK_GATE_ASPEED, "i3c8clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 24, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C9CLK, CLK_GATE_ASPEED, "i3c9clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 25, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C10CLK, CLK_GATE_ASPEED, "i3c10clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 26, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C11CLK, CLK_GATE_ASPEED, "i3c11clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 27, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C12CLK, CLK_GATE_ASPEED, "i3c12clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 28, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C13CLK, CLK_GATE_ASPEED, "i3c13clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 29, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C14CLK, CLK_GATE_ASPEED, "i3c14clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 30, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C15CLK, CLK_GATE_ASPEED, "i3c15clk-gate", SCU1_CLK_I3C, ++ SCU1_CLK_STOP, 31, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART5CLK, CLK_GATE_ASPEED, "uart5clk-gate", SCU1_CLK_UART5, ++ SCU1_CLK_STOP2, 0, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART6CLK, CLK_GATE_ASPEED, "uart6clk-gate", SCU1_CLK_UART6, ++ SCU1_CLK_STOP2, 1, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART7CLK, CLK_GATE_ASPEED, "uart7clk-gate", SCU1_CLK_UART7, ++ SCU1_CLK_STOP2, 2, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART8CLK, CLK_GATE_ASPEED, "uart8clk-gate", SCU1_CLK_UART8, ++ SCU1_CLK_STOP2, 3, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART9CLK, CLK_GATE_ASPEED, "uart9clk-gate", SCU1_CLK_UART9, ++ SCU1_CLK_STOP2, 4, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART10CLK, CLK_GATE_ASPEED, "uart10clk-gate", SCU1_CLK_UART10, ++ SCU1_CLK_STOP2, 5, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART11CLK, CLK_GATE_ASPEED, "uart11clk-gate", SCU1_CLK_UART11, ++ SCU1_CLK_STOP2, 6, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART12CLK, CLK_GATE_ASPEED, "uart12clk-gate", SCU1_CLK_UART12, ++ SCU1_CLK_STOP2, 7, 0), ++ GATE_CLK(SCU1_CLK_GATE_FSICLK, CLK_GATE_ASPEED, "fsiclk-gate", -1, SCU1_CLK_STOP2, 8, 0), ++ GATE_CLK(SCU1_CLK_GATE_LTPIPHYCLK, CLK_GATE_ASPEED, "ltpiphyclk-gate", -1, ++ SCU1_CLK_STOP2, 9, 0), ++ GATE_CLK(SCU1_CLK_GATE_LTPICLK, CLK_GATE_ASPEED, "ltpiclk-gate", -1, ++ SCU1_CLK_STOP2, 10, 0), ++ GATE_CLK(SCU1_CLK_GATE_VGALCLK, CLK_GATE_ASPEED, "vgalclk-gate", -1, ++ SCU1_CLK_STOP2, 11, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UHCICLK, CLK_GATE_ASPEED, "usbuartclk-gate", -1, ++ SCU1_CLK_STOP2, 12, 0), ++ GATE_CLK(SCU1_CLK_GATE_CANCLK, CLK_GATE_ASPEED, "canclk-gate", SCU1_CLK_CAN, ++ SCU1_CLK_STOP2, 13, 0), ++ GATE_CLK(SCU1_CLK_GATE_PCICLK, CLK_GATE_ASPEED, "pciclk-gate", -1, ++ SCU1_CLK_STOP2, 14, 0), ++ GATE_CLK(SCU1_CLK_GATE_SLICLK, CLK_GATE_ASPEED, "soc1-sliclk-gate", -1, ++ SCU1_CLK_STOP2, 15, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_E2MCLK, CLK_GATE_ASPEED, "soc1-e2m-gate", -1, ++ SCU1_CLK_STOP2, 16, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_PORTCUSB2CLK, CLK_GATE_ASPEED, "portcusb2-gate", -1, ++ SCU1_CLK_STOP2, 17, 0), ++ GATE_CLK(SCU1_CLK_GATE_PORTDUSB2CLK, CLK_GATE_ASPEED, "portdusb2-gate", -1, ++ SCU1_CLK_STOP2, 18, 0), ++ GATE_CLK(SCU1_CLK_GATE_LTPI1TXCLK, CLK_GATE_ASPEED, "ltp1tx-gate", -1, ++ SCU1_CLK_STOP2, 19, 0), ++}; ++ ++static const struct ast2700_clk_info ast2755_scu1_clk_info[] __initconst = { ++ FIXED_CLK(SCU1_CLKIN, "soc1-clkin", 25 * HZ_PER_MHZ), ++ PLL_CLK(SCU1_CLK_HPLL, CLK_PLL, "soc1-hpll", SCU1_CLKIN, SCU1_HPLL_PARAM), ++ PLL_CLK(SCU1_CLK_APLL, CLK_PLL, "soc1-apll", SCU1_CLKIN, SCU1_APLL_PARAM), ++ PLL_CLK(SCU1_CLK_DPLL, CLK_PLL, "soc1-dpll", SCU1_CLKIN, SCU1_DPLL_PARAM), ++ FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV2, "soc1-apll_div2", SCU1_CLK_APLL, 1, 2), ++ FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV4, "soc1-apll_div4", SCU1_CLK_APLL, 1, 4), ++ FIXED_FACTOR_CLK(SCU1_CLK_CAN, "canclk", SCU1_CLK_APLL, 1, 10), ++ DIVIDER_CLK(SCU1_CLK_APB, "soc1-apb", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 18, 3, ast2700_clk_div_table2), ++ DIVIDER_CLK(SCU1_CLK_RMII, "rmii", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 21, 3, ast2700_rmii_div_table), ++ DIVIDER_CLK(SCU1_CLK_RGMII, "rgmii", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 25, 3, ast2700_rgmii_div_table), ++ DIVIDER_CLK(SCU1_CLK_MACHCLK, "machclk", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL1, 29, 3, ast2700_clk_div_table), ++ DIVIDER_CLK(SCU1_CLK_APLL_DIVN, "soc1-apll_divn", ++ SCU1_CLK_APLL, SCU1_CLK_SEL2, 8, 3, ast2700_clk_div_table), ++ DIVIDER_CLK(SCU1_CLK_AHB, "soc1-ahb", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL2, 20, 3, ast2700_clk_div_table), ++ DIVIDER_CLK(SCU1_CLK_I3C, "soc1-i3c", SCU1_CLK_HPLL, ++ SCU1_CLK_SEL2, 23, 3, ast2700_clk_div_table), ++ MUX_CLK(SCU1_CLK_SDMUX, "sdclk-mux", sdclk_parent_ids, ARRAY_SIZE(sdclk_parent_ids), ++ sdclk_parent_hws, SCU1_CLK_SEL1, 13, 1), ++ MUX_CLK(SCU1_CLK_UXCLK, "uxclk", uxclk_parent_ids, ARRAY_SIZE(uxclk_parent_ids), ++ uxclk_parent_hws, SCU1_CLK_SEL2, 0, 2), ++ MUX_CLK(SCU1_CLK_HUXCLK, "huxclk", uxclk_parent_ids, ARRAY_SIZE(uxclk_parent_ids), ++ uxclk_parent_hws, SCU1_CLK_SEL2, 3, 2), ++ DIVIDER_CLK(SCU1_CLK_SDCLK, "sdclk", SCU1_CLK_SDMUX, ++ SCU1_CLK_SEL1, 14, 3, ast2700_clk_div_table), ++ PLL_CLK(SCU1_CLK_UARTX, CLK_UART_PLL, "uartxclk", SCU1_CLK_UXCLK, SCU1_UXCLK_CTRL), ++ PLL_CLK(SCU1_CLK_HUARTX, CLK_UART_PLL, "huartxclk", SCU1_CLK_HUXCLK, SCU1_HUXCLK_CTRL), ++ MUX_CLK(SCU1_CLK_UART0, "uart0clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 0, 1), ++ MUX_CLK(SCU1_CLK_UART1, "uart1clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 1, 1), ++ MUX_CLK(SCU1_CLK_UART2, "uart2clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 2, 1), ++ MUX_CLK(SCU1_CLK_UART3, "uart3clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 3, 1), ++ MUX_CLK(SCU1_CLK_UART5, "uart5clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 5, 1), ++ MUX_CLK(SCU1_CLK_UART6, "uart6clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 6, 1), ++ MUX_CLK(SCU1_CLK_UART7, "uart7clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 7, 1), ++ MUX_CLK(SCU1_CLK_UART8, "uart8clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 8, 1), ++ MUX_CLK(SCU1_CLK_UART9, "uart9clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 9, 1), ++ MUX_CLK(SCU1_CLK_UART10, "uart10clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 10, 1), ++ MUX_CLK(SCU1_CLK_UART11, "uart11clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 11, 1), ++ MUX_CLK(SCU1_CLK_UART12, "uart12clk", uartx_parent_ids, ARRAY_SIZE(uartx_parent_ids), ++ uartx_parent_hws, SCU1_CLK_SEL1, 12, 1), ++ FIXED_FACTOR_CLK(SCU1_CLK_UART13, "uart13clk", SCU1_CLK_HUARTX, 1, 1), ++ FIXED_FACTOR_CLK(SCU1_CLK_UART14, "uart14clk", SCU1_CLK_HUARTX, 1, 1), ++ GATE_CLK(SCU1_CLK_MAC0RCLK, CLK_GATE, "mac0rclk-gate", SCU1_CLK_RMII, ++ SCU1_MAC12_CLK_DLY, 29, 0), ++ GATE_CLK(SCU1_CLK_MAC1RCLK, CLK_GATE, "mac1rclk-gate", SCU1_CLK_RMII, ++ SCU1_MAC12_CLK_DLY, 30, 0), ++ GATE_CLK(SCU1_CLK_GATE_LCLK0, CLK_GATE_ASPEED, "lclk0-gate", -1, ++ SCU1_CLK_STOP, 0, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_LCLK1, CLK_GATE_ASPEED, "lclk1-gate", -1, ++ AST2755_SCU1_CLK_STOP, 1, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_ESPI0CLK, CLK_GATE_ASPEED, "espi0clk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 2, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_ESPI1CLK, CLK_GATE_ASPEED, "espi1clk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 3, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_SDCLK, CLK_GATE_ASPEED, "sdclk-gate", SCU1_CLK_SDCLK, ++ AST2755_SCU1_CLK_STOP, 4, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_IPEREFCLK, CLK_GATE_ASPEED, "soc1-iperefclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 5, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_REFCLK, CLK_GATE_ASPEED, "soc1-refclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 6, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_LPCHCLK, CLK_GATE_ASPEED, "lpchclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 7, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_MAC0CLK, CLK_GATE_ASPEED, "mac0clk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 8, 0), ++ GATE_CLK(SCU1_CLK_GATE_MAC1CLK, CLK_GATE_ASPEED, "mac1clk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 9, 0), ++ GATE_CLK(SCU1_CLK_GATE_MAC2CLK, CLK_GATE_ASPEED, "mac2clk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 10, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART0CLK, CLK_GATE_ASPEED, "uart0clk-gate", SCU1_CLK_UART0, ++ AST2755_SCU1_CLK_STOP, 11, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART1CLK, CLK_GATE_ASPEED, "uart1clk-gate", SCU1_CLK_UART1, ++ AST2755_SCU1_CLK_STOP, 12, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART2CLK, CLK_GATE_ASPEED, "uart2clk-gate", SCU1_CLK_UART2, ++ AST2755_SCU1_CLK_STOP, 13, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART3CLK, CLK_GATE_ASPEED, "uart3clk-gate", SCU1_CLK_UART3, ++ AST2755_SCU1_CLK_STOP, 14, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_I2CCLK, CLK_GATE_ASPEED, "i2cclk-gate", -1, SCU1_CLK_STOP, 15, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C0CLK, CLK_GATE_ASPEED, "i3c0clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 16, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C1CLK, CLK_GATE_ASPEED, "i3c1clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 17, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C2CLK, CLK_GATE_ASPEED, "i3c2clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 18, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C3CLK, CLK_GATE_ASPEED, "i3c3clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 19, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C4CLK, CLK_GATE_ASPEED, "i3c4clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 20, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C5CLK, CLK_GATE_ASPEED, "i3c5clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 21, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C6CLK, CLK_GATE_ASPEED, "i3c6clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 22, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C7CLK, CLK_GATE_ASPEED, "i3c7clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 23, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C8CLK, CLK_GATE_ASPEED, "i3c8clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 24, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C9CLK, CLK_GATE_ASPEED, "i3c9clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 25, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C10CLK, CLK_GATE_ASPEED, "i3c10clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 26, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C11CLK, CLK_GATE_ASPEED, "i3c11clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 27, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C12CLK, CLK_GATE_ASPEED, "i3c12clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 28, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C13CLK, CLK_GATE_ASPEED, "i3c13clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 29, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C14CLK, CLK_GATE_ASPEED, "i3c14clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 30, 0), ++ GATE_CLK(SCU1_CLK_GATE_I3C15CLK, CLK_GATE_ASPEED, "i3c15clk-gate", SCU1_CLK_I3C, ++ AST2755_SCU1_CLK_STOP, 31, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART5CLK, CLK_GATE_ASPEED, "uart5clk-gate", SCU1_CLK_UART5, ++ AST2755_SCU1_CLK_STOP, 0, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART6CLK, CLK_GATE_ASPEED, "uart6clk-gate", SCU1_CLK_UART6, ++ AST2755_SCU1_CLK_STOP, 1, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART7CLK, CLK_GATE_ASPEED, "uart7clk-gate", SCU1_CLK_UART7, ++ AST2755_SCU1_CLK_STOP, 2, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART8CLK, CLK_GATE_ASPEED, "uart8clk-gate", SCU1_CLK_UART8, ++ AST2755_SCU1_CLK_STOP, 3, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UART9CLK, CLK_GATE_ASPEED, "uart9clk-gate", SCU1_CLK_UART9, ++ AST2755_SCU1_CLK_STOP, 4, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART10CLK, CLK_GATE_ASPEED, "uart10clk-gate", SCU1_CLK_UART10, ++ AST2755_SCU1_CLK_STOP, 5, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART11CLK, CLK_GATE_ASPEED, "uart11clk-gate", SCU1_CLK_UART11, ++ AST2755_SCU1_CLK_STOP, 6, 0), ++ GATE_CLK(SCU1_CLK_GATE_UART12CLK, CLK_GATE_ASPEED, "uart12clk-gate", SCU1_CLK_UART12, ++ AST2755_SCU1_CLK_STOP, 7, 0), ++ GATE_CLK(SCU1_CLK_GATE_FSICLK, CLK_GATE_ASPEED, "fsiclk-gate", -1, SCU1_CLK_STOP2, 8, 0), ++ GATE_CLK(SCU1_CLK_GATE_LTPIPHYCLK, CLK_GATE_ASPEED, "ltpiphyclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 9, 0), ++ GATE_CLK(SCU1_CLK_GATE_LTPICLK, CLK_GATE_ASPEED, "ltpiclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 10, 0), ++ GATE_CLK(SCU1_CLK_GATE_VGALCLK, CLK_GATE_ASPEED, "vgalclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 11, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_UHCICLK, CLK_GATE_ASPEED, "usbuartclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 12, 0), ++ GATE_CLK(SCU1_CLK_GATE_CANCLK, CLK_GATE_ASPEED, "canclk-gate", SCU1_CLK_CAN, ++ AST2755_SCU1_CLK_STOP, 13, 0), ++ GATE_CLK(SCU1_CLK_GATE_PCICLK, CLK_GATE_ASPEED, "pciclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 14, 0), ++ GATE_CLK(SCU1_CLK_GATE_SLICLK, CLK_GATE_ASPEED, "soc1-sliclk-gate", -1, ++ AST2755_SCU1_CLK_STOP, 15, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_E2MCLK, CLK_GATE_ASPEED, "soc1-e2m-gate", -1, ++ AST2755_SCU1_CLK_STOP, 16, CLK_IS_CRITICAL), ++ GATE_CLK(SCU1_CLK_GATE_PORTCUSB2CLK, CLK_GATE_ASPEED, "portcusb2-gate", -1, ++ AST2755_SCU1_CLK_STOP, 17, 0), ++ GATE_CLK(SCU1_CLK_GATE_PORTDUSB2CLK, CLK_GATE_ASPEED, "portdusb2-gate", -1, ++ AST2755_SCU1_CLK_STOP, 18, 0), ++ GATE_CLK(SCU1_CLK_GATE_LTPI1TXCLK, CLK_GATE_ASPEED, "ltp1tx-gate", -1, ++ AST2755_SCU1_CLK_STOP, 19, 0), ++}; ++ ++static struct clk_hw *ast2700_clk_hw_register_fixed_display(void __iomem *reg, const char *name, ++ struct ast2700_clk_ctrl *clk_ctrl) ++{ ++ unsigned int mult, div, r, n; ++ u32 xdclk; ++ u32 val; ++ ++ val = readl(clk_ctrl->base + SCU0_CLK_SEL2); ++ if (val & BIT(29)) ++ xdclk = 800 * HZ_PER_MHZ; ++ else ++ xdclk = 1000 * HZ_PER_MHZ; ++ ++ val = readl(reg); ++ r = val & GENMASK(15, 0); ++ n = (val >> 16) & GENMASK(15, 0); ++ mult = r; ++ div = 2 * n; ++ ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, 0, (xdclk * mult) / div); ++} ++ ++static struct clk_hw *ast2700_clk_hw_register_hpll(void __iomem *reg, ++ const char *name, const struct clk_hw *parent_hw, ++ struct ast2700_clk_ctrl *clk_ctrl) ++{ ++ unsigned int mult, div; ++ u32 val; ++ ++ val = readl(clk_ctrl->base + SCU0_HWSTRAP1); ++ if ((readl(clk_ctrl->base) & REVISION_ID) && (val & BIT(3))) { ++ switch ((val & GENMASK(4, 2)) >> 2) { ++ case 2: ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, ++ 0, 1800 * HZ_PER_MHZ); ++ case 3: ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, ++ 0, 1700 * HZ_PER_MHZ); ++ case 6: ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, ++ 0, 1200 * HZ_PER_MHZ); ++ case 7: ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, ++ 0, 800 * HZ_PER_MHZ); ++ default: ++ return ERR_PTR(-EINVAL); ++ } ++ } else if ((val & GENMASK(3, 2)) != 0) { ++ switch ((val & GENMASK(3, 2)) >> 2) { ++ case 1: ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, ++ 0, 1900 * HZ_PER_MHZ); ++ case 2: ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, ++ 0, 1800 * HZ_PER_MHZ); ++ case 3: ++ return devm_clk_hw_register_fixed_rate(clk_ctrl->dev, name, NULL, ++ 0, 1700 * HZ_PER_MHZ); ++ default: ++ return ERR_PTR(-EINVAL); ++ } ++ } else { ++ val = readl(reg); ++ ++ if (val & BIT(24)) { ++ /* Pass through mode */ ++ mult = 1; ++ div = 1; ++ } else { ++ u32 m = val & 0x1fff; ++ u32 n = (val >> 13) & 0x3f; ++ u32 p = (val >> 19) & 0xf; ++ ++ mult = (m + 1) / (2 * (n + 1)); ++ div = p + 1; ++ } ++ } ++ ++ return devm_clk_hw_register_fixed_factor_parent_hw(clk_ctrl->dev, name, ++ parent_hw, 0, mult, div); ++} ++ ++static struct clk_hw *ast2700_clk_hw_register_pll(int clk_idx, void __iomem *reg, ++ const char *name, const struct clk_hw *parent_hw, ++ struct ast2700_clk_ctrl *clk_ctrl) ++{ ++ int scu = clk_ctrl->clk_data->scu; ++ unsigned int mult, div; ++ u32 val = readl(reg); ++ ++ if (val & BIT(24)) { ++ /* Pass through mode */ ++ mult = 1; ++ div = 1; ++ } else { ++ u32 m = val & 0x1fff; ++ u32 n = (val >> 13) & 0x3f; ++ u32 p = (val >> 19) & 0xf; ++ ++ if (scu) { ++ mult = (m + 1) / (n + 1); ++ div = p + 1; ++ } else { ++ if (clk_idx == SCU0_CLK_MPLL) { ++ mult = m / (n + 1); ++ div = p + 1; ++ } else { ++ mult = (m + 1) / (2 * (n + 1)); ++ div = p + 1; ++ } ++ } ++ } ++ ++ return devm_clk_hw_register_fixed_factor_parent_hw(clk_ctrl->dev, name, ++ parent_hw, 0, mult, div); ++} ++ ++static struct clk_hw *ast2700_clk_hw_register_uartpll(void __iomem *reg, const char *name, ++ const struct clk_hw *parent_hw, ++ struct ast2700_clk_ctrl *clk_ctrl) ++{ ++ unsigned int mult, div; ++ u32 val = readl(reg); ++ u32 r = val & 0xff; ++ u32 n = (val >> 8) & 0x3ff; ++ ++ mult = r; ++ div = n * 2; ++ ++ return devm_clk_hw_register_fixed_factor_parent_hw(clk_ctrl->dev, name, ++ parent_hw, 0, mult, div); ++} ++ ++static struct clk_hw *ast2700_clk_hw_register_misc(int clk_idx, void __iomem *reg, ++ const char *name, const struct clk_hw *parent_hw, ++ struct ast2700_clk_ctrl *clk_ctrl) ++{ ++ u32 div = 0; ++ ++ if (clk_idx == SCU0_CLK_MPHY) { ++ div = readl(reg) + 1; ++ } else if (clk_idx == SCU0_CLK_U2PHY_REFCLK) { ++ if (readl(clk_ctrl->base) & REVISION_ID) ++ div = (GET_USB_REFCLK_DIV(readl(reg)) + 1) << 4; ++ else ++ div = (GET_USB_REFCLK_DIV(readl(reg)) + 1) << 1; ++ } else { ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return devm_clk_hw_register_fixed_factor_parent_hw(clk_ctrl->dev, name, ++ parent_hw, 0, 1, div); ++} ++ ++static int ast2700_clk_is_enabled(struct clk_hw *hw) ++{ ++ struct clk_gate *gate = to_clk_gate(hw); ++ u32 clk = BIT(gate->bit_idx); ++ u32 reg; ++ ++ reg = readl(gate->reg); ++ ++ return !(reg & clk); ++} ++ ++static int ast2700_clk_enable(struct clk_hw *hw) ++{ ++ struct clk_gate *gate = to_clk_gate(hw); ++ u32 clk = BIT(gate->bit_idx); ++ ++ if (readl(gate->reg) & clk) ++ writel(clk, gate->reg + 0x04); ++ ++ return 0; ++} ++ ++static void ast2700_clk_disable(struct clk_hw *hw) ++{ ++ struct clk_gate *gate = to_clk_gate(hw); ++ u32 clk = BIT(gate->bit_idx); ++ ++ /* Clock is set to enable, so use write to set register */ ++ writel(clk, gate->reg); ++} ++ ++static const struct clk_ops ast2700_clk_gate_ops = { ++ .enable = ast2700_clk_enable, ++ .disable = ast2700_clk_disable, ++ .is_enabled = ast2700_clk_is_enabled, ++}; ++ ++static struct clk_hw *ast2700_clk_hw_register_gate(struct device *dev, const char *name, ++ const struct clk_hw *parent_hw, ++ void __iomem *reg, u8 clock_idx, ++ unsigned long flags, spinlock_t *lock) ++{ ++ struct clk_init_data init; ++ struct clk_gate *gate; ++ struct clk_hw *hw; ++ int ret = -EINVAL; ++ ++ gate = kzalloc(sizeof(*gate), GFP_KERNEL); ++ if (!gate) ++ return ERR_PTR(-ENOMEM); ++ ++ init.name = name; ++ init.ops = &ast2700_clk_gate_ops; ++ init.flags = flags; ++ init.parent_names = NULL; ++ init.parent_hws = parent_hw ? &parent_hw : NULL; ++ init.parent_data = NULL; ++ init.num_parents = parent_hw ? 1 : 0; ++ ++ gate->reg = reg; ++ gate->bit_idx = clock_idx; ++ gate->flags = 0; ++ gate->lock = lock; ++ gate->hw.init = &init; ++ ++ hw = &gate->hw; ++ ret = clk_hw_register(dev, hw); ++ if (ret) { ++ kfree(gate); ++ hw = ERR_PTR(ret); ++ } ++ ++ return hw; ++} ++ ++static void ast2700_soc1_configure_i3c_clk(struct ast2700_clk_ctrl *clk_ctrl) ++{ ++ if (readl(clk_ctrl->base) & REVISION_ID) { ++ u32 val; ++ ++ /* I3C 250MHz = HPLL/4 */ ++ val = readl(clk_ctrl->base + SCU1_CLK_SEL2) & ~SCU1_CLK_I3C_DIV_MASK; ++ val |= FIELD_PREP(SCU1_CLK_I3C_DIV_MASK, SCU1_CLK_I3C_DIV(4)); ++ writel(val, clk_ctrl->base + SCU1_CLK_SEL2); ++ } ++} ++ ++static inline const struct clk_hw *get_parent_hw_or_null(struct clk_hw **hws, int idx) ++{ ++ if (idx < 0) ++ return NULL; ++ else ++ return hws[idx]; ++} ++ ++static int ast2700_soc_clk_probe(struct platform_device *pdev) ++{ ++ const struct ast2700_clk_data *clk_data; ++ struct clk_hw_onecell_data *clk_hw_data; ++ struct ast2700_clk_ctrl *clk_ctrl; ++ struct device *dev = &pdev->dev; ++ u32 uart_clk_source = 0; ++ void __iomem *clk_base; ++ struct clk_hw **hws; ++ char *reset_name; ++ int ret; ++ int i; ++ ++ clk_ctrl = devm_kzalloc(dev, sizeof(*clk_ctrl), GFP_KERNEL); ++ if (!clk_ctrl) ++ return -ENOMEM; ++ clk_ctrl->dev = dev; ++ dev_set_drvdata(&pdev->dev, clk_ctrl); ++ ++ spin_lock_init(&clk_ctrl->lock); ++ ++ clk_base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(clk_base)) ++ return PTR_ERR(clk_base); ++ ++ clk_ctrl->base = clk_base; ++ ++ clk_data = (struct ast2700_clk_data *)device_get_match_data(dev); ++ if (!clk_data) ++ return -ENODEV; ++ ++ clk_ctrl->clk_data = clk_data; ++ reset_name = devm_kasprintf(dev, GFP_KERNEL, "reset%d", clk_data->scu); ++ ++ clk_hw_data = devm_kzalloc(dev, struct_size(clk_hw_data, hws, clk_data->nr_clks), ++ GFP_KERNEL); ++ if (!clk_hw_data) ++ return -ENOMEM; ++ ++ clk_hw_data->num = clk_data->nr_clks; ++ hws = clk_hw_data->hws; ++ ++ if (clk_data->scu) { ++ of_property_read_u32(dev->of_node, "uart-clk-source", &uart_clk_source); ++ if (uart_clk_source) { ++ u32 val = readl(clk_base + SCU1_CLK_SEL1) & ~GENMASK(12, 0); ++ ++ uart_clk_source &= GENMASK(12, 0); ++ writel(val | uart_clk_source, clk_base + SCU1_CLK_SEL1); ++ } ++ ++ ast2700_soc1_configure_i3c_clk(clk_ctrl); ++ } ++ ++ for (i = 0; i < clk_data->nr_clks; i++) { ++ const struct ast2700_clk_info *clk = &clk_data->clk_info[i]; ++ const struct clk_hw *phw = NULL; ++ unsigned int id = clk->id; ++ void __iomem *reg = NULL; ++ ++ if (id >= clk_hw_data->num || hws[id]) { ++ dev_err(dev, "clk id %u invalid for %s\n", id, clk->name); ++ return -EINVAL; ++ } ++ ++ if (clk->type == CLK_FIXED) { ++ const struct ast2700_clk_fixed_rate_data *fixed_rate = &clk->data.rate; ++ ++ hws[id] = devm_clk_hw_register_fixed_rate(dev, clk->name, NULL, 0, ++ fixed_rate->fixed_rate); ++ } else if (clk->type == CLK_FIXED_FACTOR) { ++ const struct ast2700_clk_fixed_factor_data *factor = &clk->data.factor; ++ ++ phw = hws[factor->parent_id]; ++ hws[id] = devm_clk_hw_register_fixed_factor_parent_hw(dev, clk->name, ++ phw, 0, factor->mult, ++ factor->div); ++ } else if (clk->type == CLK_FIXED_DISPLAY) { ++ reg = clk_ctrl->base + clk->data.display_rate.reg; ++ ++ hws[id] = ast2700_clk_hw_register_fixed_display(reg, clk->name, clk_ctrl); ++ } else if (clk->type == CLK_HPLL) { ++ const struct ast2700_clk_pll_data *pll = &clk->data.pll; ++ ++ reg = clk_ctrl->base + pll->reg; ++ phw = hws[pll->parent_id]; ++ hws[id] = ast2700_clk_hw_register_hpll(reg, clk->name, phw, clk_ctrl); ++ } else if (clk->type == CLK_PLL) { ++ const struct ast2700_clk_pll_data *pll = &clk->data.pll; ++ ++ reg = clk_ctrl->base + pll->reg; ++ phw = hws[pll->parent_id]; ++ hws[id] = ast2700_clk_hw_register_pll(id, reg, clk->name, phw, clk_ctrl); ++ } else if (clk->type == CLK_UART_PLL) { ++ const struct ast2700_clk_pll_data *pll = &clk->data.pll; ++ ++ reg = clk_ctrl->base + pll->reg; ++ phw = hws[pll->parent_id]; ++ hws[id] = ast2700_clk_hw_register_uartpll(reg, clk->name, phw, clk_ctrl); ++ } else if (clk->type == CLK_MUX) { ++ const struct ast2700_clk_mux_data *mux = &clk->data.mux; ++ ++ reg = clk_ctrl->base + mux->reg; ++ for (int j = 0; j < mux->num_parents; j++) { ++ unsigned int pid = mux->parent_ids[j]; ++ ++ mux->parent_hws[j] = hws[pid]; ++ } ++ ++ hws[id] = devm_clk_hw_register_mux_parent_hws(dev, clk->name, ++ mux->parent_hws, ++ mux->num_parents, 0, ++ reg, mux->bit_shift, ++ mux->bit_width, 0, ++ &clk_ctrl->lock); ++ } else if (clk->type == CLK_MISC) { ++ const struct ast2700_clk_pll_data *pll = &clk->data.pll; ++ ++ reg = clk_ctrl->base + pll->reg; ++ phw = hws[pll->parent_id]; ++ hws[id] = ast2700_clk_hw_register_misc(id, reg, clk->name, phw, clk_ctrl); ++ } else if (clk->type == CLK_DIVIDER) { ++ const struct ast2700_clk_div_data *divider = &clk->data.div; ++ ++ reg = clk_ctrl->base + divider->reg; ++ phw = hws[divider->parent_id]; ++ hws[id] = clk_hw_register_divider_table_parent_hw(dev, clk->name, ++ phw, ++ 0, reg, ++ divider->bit_shift, ++ divider->bit_width, 0, ++ divider->div_table, ++ &clk_ctrl->lock); ++ } else if (clk->type == CLK_GATE_ASPEED) { ++ const struct ast2700_clk_gate_data *gate = &clk->data.gate; ++ ++ phw = get_parent_hw_or_null(hws, gate->parent_id); ++ reg = clk_ctrl->base + gate->reg; ++ hws[id] = ast2700_clk_hw_register_gate(dev, clk->name, phw, reg, gate->bit, ++ gate->flags, &clk_ctrl->lock); ++ } else { ++ const struct ast2700_clk_gate_data *gate = &clk->data.gate; ++ ++ phw = get_parent_hw_or_null(hws, gate->parent_id); ++ reg = clk_ctrl->base + gate->reg; ++ hws[id] = devm_clk_hw_register_gate_parent_hw(dev, clk->name, phw, ++ gate->flags, reg, gate->bit, ++ 0, &clk_ctrl->lock); ++ } ++ ++ if (IS_ERR(hws[id])) ++ return PTR_ERR(hws[id]); ++ } ++ ++ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_hw_data); ++ if (ret) ++ return ret; ++ ++ return aspeed_reset_controller_register(dev, clk_base, reset_name); ++} ++ ++static const struct ast2700_clk_data ast2700_clk0_data = { ++ .scu = 0, ++ .clk_info = ast2700_scu0_clk_info, ++ .nr_clks = ARRAY_SIZE(ast2700_scu0_clk_info), ++}; ++ ++static const struct ast2700_clk_data ast2700_clk1_data = { ++ .scu = 1, ++ .clk_info = ast2700_scu1_clk_info, ++ .nr_clks = ARRAY_SIZE(ast2700_scu1_clk_info), ++}; ++ ++static const struct ast2700_clk_data ast2755_clk1_data = { ++ .scu = 2, ++ .clk_info = ast2755_scu1_clk_info, ++ .nr_clks = ARRAY_SIZE(ast2755_scu1_clk_info), ++}; ++ ++static const struct of_device_id ast2700_scu_match[] = { ++ { .compatible = "aspeed,ast2700-scu0", .data = &ast2700_clk0_data }, ++ { .compatible = "aspeed,ast2700-scu1", .data = &ast2700_clk1_data }, ++ { .compatible = "aspeed,ast2755-scu1", .data = &ast2755_clk1_data }, ++ { /* sentinel */ } ++}; ++ ++MODULE_DEVICE_TABLE(of, ast2700_scu_match); ++ ++static struct platform_driver ast2700_scu_driver = { ++ .probe = ast2700_soc_clk_probe, ++ .driver = { ++ .name = "clk-ast2700", ++ .of_match_table = ast2700_scu_match, ++ }, ++}; ++ ++static int __init clk_ast2700_init(void) ++{ ++ return platform_driver_register(&ast2700_scu_driver); ++} ++arch_initcall(clk_ast2700_init); +diff --git a/drivers/crypto/aspeed/Kconfig b/drivers/crypto/aspeed/Kconfig +--- a/drivers/crypto/aspeed/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/crypto/aspeed/Kconfig 2026-09-09 15:51:44.737237784 +0000 +@@ -1,26 +1,32 @@ + config CRYPTO_DEV_ASPEED +- tristate "Support for Aspeed cryptographic engine driver" ++ bool "Support for Aspeed cryptographic engine driver" + depends on ARCH_ASPEED || COMPILE_TEST + select CRYPTO_ENGINE + help +- Hash and Crypto Engine (HACE) is designed to accelerate the +- throughput of hash data digest, encryption and decryption. ++ Say Y here to get to see options for Aspeed hardware crypto devices + +- Select y here to have support for the cryptographic driver +- available on Aspeed SoC. ++if CRYPTO_DEV_ASPEED + + config CRYPTO_DEV_ASPEED_DEBUG + bool "Enable Aspeed crypto debug messages" +- depends on CRYPTO_DEV_ASPEED + help + Print Aspeed crypto debugging messages if you use this + option to ask for those messages. + Avoid enabling this option for production build to + minimize driver timing. + ++config CRYPTO_DEV_ASPEED_HACE ++ tristate "Enable Aspeed Hash & Crypto Engine (HACE) Engine" ++ help ++ Hash and Crypto Engine (HACE) is designed to accelerate the ++ throughput of hash data digest, encryption and decryption. ++ ++ Select y here to have support for the cryptographic driver ++ available on Aspeed SoC. ++ + config CRYPTO_DEV_ASPEED_HACE_HASH + bool "Enable Aspeed Hash & Crypto Engine (HACE) hash" +- depends on CRYPTO_DEV_ASPEED ++ depends on CRYPTO_DEV_ASPEED_HACE + select CRYPTO_SHA1 + select CRYPTO_SHA256 + select CRYPTO_SHA512 +@@ -33,25 +39,55 @@ + + config CRYPTO_DEV_ASPEED_HACE_CRYPTO + bool "Enable Aspeed Hash & Crypto Engine (HACE) crypto" +- depends on CRYPTO_DEV_ASPEED ++ depends on CRYPTO_DEV_ASPEED_HACE + select CRYPTO_AES + select CRYPTO_DES + select CRYPTO_ECB + select CRYPTO_CBC ++ select CRYPTO_GCM + select CRYPTO_CTR + help + Select here to enable Aspeed Hash & Crypto Engine (HACE) + crypto driver. + Supports AES/DES symmetric-key encryption and decryption +- with ECB/CBC/CTR options. ++ with ECB/CBC/CFB/OFB/CTR options. + + config CRYPTO_DEV_ASPEED_ACRY +- bool "Enable Aspeed ACRY RSA Engine" +- depends on CRYPTO_DEV_ASPEED +- select CRYPTO_ENGINE ++ tristate "Enable Aspeed ACRY RSA Engine" ++ depends on MACH_ASPEED_G6 + select CRYPTO_RSA + help + Select here to enable Aspeed ECC/RSA Engine (ACRY) + RSA driver. + Supports 256 bits to 4096 bits RSA encryption/decryption + and signature/verification. ++ ++config CRYPTO_DEV_ASPEED_RSSS ++ tristate "Enable Aspeed RSSS Engine" ++ depends on ARCH_ASPEED ++ select CRYPTO_RSA ++ select CRYPTO_SHA3 ++ select CRYPTO_SM3 ++ select CRYPTO_SM4 ++ help ++ Select here to enable Aspeed RSSS Engine driver. ++ Supports RSA 512 to 4096 bits encryption/decryption and ++ signature/verification, SHA3-224/256/384/512 and XOF of ++ SHAKE 128/256, SM3 Hash crypto, SM4 ECB/CBC/CFB/OFB/CTR ++ cryptographic algorithms. ++ It's a new hardware design from ast2700 for simply SRAM ++ layout. ++ ++config CRYPTO_DEV_ASPEED_ECDSA ++ tristate "Enable Aspeed ECDSA Engine" ++ depends on ARCH_ASPEED ++ select CRYPTO_ECC ++ select CRYPTO_ECDSA ++ select CRYPTO_AKCIPHER ++ help ++ Select here to enable Aspeed ECC Engine for ECDSA driver. ++ Supports ECDSA (Elliptic Curve Digital Signature Algorithm) ++ using curves P-256, P-384. ++ Only signature verification is implemented. ++ ++endif #CRYPTO_DEV_ASPEED +diff --git a/drivers/crypto/aspeed/Makefile b/drivers/crypto/aspeed/Makefile +--- a/drivers/crypto/aspeed/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/crypto/aspeed/Makefile 2026-09-09 15:51:44.737237784 +0000 +@@ -1,11 +1,14 @@ + hace-hash-$(CONFIG_CRYPTO_DEV_ASPEED_HACE_HASH) := aspeed-hace-hash.o +-hace-crypto-$(CONFIG_CRYPTO_DEV_ASPEED_HACE_CRYPTO) := aspeed-hace-crypto.o ++hace-crypto-$(CONFIG_CRYPTO_DEV_ASPEED_HACE_CRYPTO) := aspeed-hace-crypto.o aspeed-hace-gcm.o + +-obj-$(CONFIG_CRYPTO_DEV_ASPEED) += aspeed_crypto.o ++obj-$(CONFIG_CRYPTO_DEV_ASPEED_HACE) += aspeed_crypto.o + aspeed_crypto-objs := aspeed-hace.o \ + $(hace-hash-y) \ + $(hace-crypto-y) + +-aspeed_acry-$(CONFIG_CRYPTO_DEV_ASPEED_ACRY) += aspeed-acry.o ++obj-$(CONFIG_CRYPTO_DEV_ASPEED_ACRY) += aspeed-acry.o + +-obj-$(CONFIG_CRYPTO_DEV_ASPEED) += $(aspeed_acry-y) ++obj-$(CONFIG_CRYPTO_DEV_ASPEED_RSSS) += aspeed_rsss.o ++aspeed_rsss-objs := aspeed-rsss.o aspeed-rsss-rsa.o aspeed-rsss-hash.o ++ ++obj-$(CONFIG_CRYPTO_DEV_ASPEED_ECDSA) += aspeed-ecdsa.o +diff --git a/drivers/crypto/aspeed/aspeed-acry.c b/drivers/crypto/aspeed/aspeed-acry.c +--- a/drivers/crypto/aspeed/aspeed-acry.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-acry.c 2026-09-09 15:51:44.737237784 +0000 +@@ -789,6 +789,7 @@ + err_engine_rsa_start: + crypto_engine_exit(acry_dev->crypt_engine_rsa); + clk_exit: ++ clk_disable_unprepare(acry_dev->clk); + + return rc; + } +@@ -800,13 +801,14 @@ + aspeed_acry_unregister(acry_dev); + crypto_engine_exit(acry_dev->crypt_engine_rsa); + tasklet_kill(&acry_dev->done_task); ++ clk_disable_unprepare(acry_dev->clk); + } + + MODULE_DEVICE_TABLE(of, aspeed_acry_of_matches); + + static struct platform_driver aspeed_acry_driver = { + .probe = aspeed_acry_probe, +- .remove_new = aspeed_acry_remove, ++ .remove = aspeed_acry_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_acry_of_matches, +diff --git a/drivers/crypto/aspeed/aspeed-ecdsa.c b/drivers/crypto/aspeed/aspeed-ecdsa.c +--- a/drivers/crypto/aspeed/aspeed-ecdsa.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-ecdsa.c 2026-09-09 15:51:44.737237784 +0000 +@@ -0,0 +1,779 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "aspeed-ecdsa.h" ++ ++//#define ASPEED_ECDSA_IRQ_MODE ++ ++static int aspeed_ecdsa_self_test(struct aspeed_ecdsa_dev *ecdsa_dev) ++{ ++ u32 val; ++ ++ ast_write(ecdsa_dev, ECC_EN, ASPEED_ECC_CTRL_REG); ++ val = ast_read(ecdsa_dev, ASPEED_ECC_CTRL_REG); ++ if (val != ECC_EN) ++ return -EIO; ++ ++ ast_write(ecdsa_dev, 0x0, ASPEED_ECC_CTRL_REG); ++ val = ast_read(ecdsa_dev, ASPEED_ECC_CTRL_REG); ++ if (val) ++ return -EIO; ++ ++ return 0; ++} ++ ++static inline struct akcipher_request * ++ akcipher_request_cast(struct crypto_async_request *req) ++{ ++ return container_of(req, struct akcipher_request, base); ++} ++ ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_DEBUG ++static void hexdump(const char *name, unsigned char *buf, unsigned int len) ++{ ++ pr_info("%s\n", name); ++ print_hex_dump(KERN_CONT, "", DUMP_PREFIX_OFFSET, ++ 16, 1, ++ buf, len, false); ++} ++#else ++static void hexdump(const char *name, unsigned char *buf, unsigned int len) ++{ ++ /* empty */ ++} ++#endif ++ ++static void buff_reverse(u8 *dst, u8 *src, int len) ++{ ++ for (int i = 0; i < len; i++) ++ dst[len - i - 1] = src[i]; ++} ++ ++static bool aspeed_ecdsa_need_fallback(struct aspeed_ecc_ctx *ctx, int d_len) ++{ ++ int curve_id = ctx->curve_id; ++ ++ switch (curve_id) { ++ case ECC_CURVE_NIST_P256: ++ if (d_len != SHA256_DIGEST_SIZE) ++ return true; ++ break; ++ case ECC_CURVE_NIST_P384: ++ if (d_len != SHA384_DIGEST_SIZE) ++ return true; ++ break; ++ } ++ ++ return false; ++} ++ ++#ifndef ASPEED_ECDSA_IRQ_MODE ++static int aspeed_ecdsa_wait_complete(struct aspeed_ecdsa_dev *ecdsa_dev) ++{ ++ struct aspeed_engine_ecdsa *ecdsa_engine = &ecdsa_dev->ecdsa_engine; ++ u32 sts; ++ int ret; ++ ++ ret = readl_poll_timeout(ecdsa_dev->regs + ASPEED_ECC_STS_REG, sts, ++ ((sts & ECC_IDLE) == ECC_IDLE), ++ ASPEED_ECC_POLLING_TIME, ++ ASPEED_ECC_TIMEOUT * 10); ++ if (ret) { ++ dev_err(ecdsa_dev->dev, "ECC engine wrong status\n"); ++ return -EIO; ++ } ++ ++ sts = ast_read(ecdsa_dev, ASPEED_ECC_STS_REG) & ECC_VERIFY_PASS; ++ if (sts == ECC_VERIFY_PASS) { ++ ecdsa_engine->results = 0; ++ AST_DBG(ecdsa_dev, "Verify PASS !\n"); ++ ++ } else { ++ ecdsa_engine->results = -EKEYREJECTED; ++ AST_DBG(ecdsa_dev, "Verify FAILED !\n"); ++ } ++ ++ /* Stop ECDSA engine */ ++ if (ecdsa_engine->flags & CRYPTO_FLAGS_BUSY) ++ tasklet_schedule(&ecdsa_engine->done_task); ++ else ++ dev_err(ecdsa_dev->dev, "ECDSA no active requests.\n"); ++ ++ return ecdsa_engine->results; ++} ++#endif ++ ++static int aspeed_hw_trigger(struct aspeed_ecdsa_dev *ecdsa_dev) ++{ ++ AST_DBG(ecdsa_dev, "\n"); ++ ++ ast_write(ecdsa_dev, 0x1, ASPEED_ECC_ECDSA_VERIFY); ++ ++ ast_write(ecdsa_dev, ECC_EN, ASPEED_ECC_CMD_REG); ++ ast_write(ecdsa_dev, 0x0, ASPEED_ECC_CMD_REG); ++ ++#ifdef ASPEED_ECDSA_IRQ_MODE ++ return 0; ++#else ++ return aspeed_ecdsa_wait_complete(ecdsa_dev); ++#endif ++} ++ ++static int _aspeed_ecdsa_verify(struct aspeed_ecc_ctx *ctx, const u64 *hash, ++ const u64 *r, const u64 *s) ++{ ++ int nbytes = ctx->curve->g.ndigits << ECC_DIGITS_TO_BYTES_SHIFT; ++ const struct ecc_curve *curve = ctx->curve; ++ void __iomem *base = ctx->ecdsa_dev->regs; ++ unsigned int ndigits = curve->g.ndigits; ++ u8 *data, *buf; ++ ++ /* 0 < r < n and 0 < s < n */ ++ if (vli_is_zero(r, ndigits) || vli_cmp(r, curve->n, ndigits) >= 0 || ++ vli_is_zero(s, ndigits) || vli_cmp(s, curve->n, ndigits) >= 0) ++ return -EBADMSG; ++ ++ /* hash is given */ ++ AST_DBG(ctx->ecdsa_dev, "hash : %016llx %016llx ... %016llx\n", ++ hash[ndigits - 1], hash[ndigits - 2], hash[0]); ++ ++ data = vmalloc(nbytes); ++ if (!data) ++ return -ENOMEM; ++ ++ /* Initial signature/message and trigger ecdsa verification */ ++ buf = (u8 *)r; ++ hexdump("Dump r:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)r, nbytes); ++ memcpy_toio(base + ASPEED_ECC_SIGN_R_REG, data, nbytes); ++ ++ buf = (u8 *)s; ++ hexdump("Dump s:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)s, nbytes); ++ memcpy_toio(base + ASPEED_ECC_SIGN_S_REG, data, nbytes); ++ ++ buf = (u8 *)hash; ++ hexdump("Dump m:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)hash, nbytes); ++ memcpy_toio(base + ASPEED_ECC_MESSAGE_REG, data, nbytes); ++ ++ vfree(data); ++ ++ return aspeed_hw_trigger(ctx->ecdsa_dev); ++} ++ ++static int aspeed_ecdsa_handle_queue(struct aspeed_ecdsa_dev *ecdsa_dev, ++ struct akcipher_request *req) ++{ ++ struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req); ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(tfm); ++ int ret; ++ ++ if (aspeed_ecdsa_need_fallback(ctx, req->dst_len)) { ++ AST_DBG(ctx->ecdsa_dev, "SW fallback\n"); ++ ++ akcipher_request_set_tfm(req, ctx->fallback_tfm); ++ ret = crypto_akcipher_verify(req); ++ akcipher_request_set_tfm(req, tfm); ++ ++ AST_DBG(ctx->ecdsa_dev, "SW verify...ret:0x%x\n", ret); ++ ++ return ret; ++ } ++ ++ return crypto_transfer_akcipher_request_to_engine(ecdsa_dev->crypt_engine_ecdsa, req); ++} ++ ++static int aspeed_ecdsa_trigger(struct aspeed_ecdsa_dev *ecdsa_dev) ++{ ++ struct aspeed_engine_ecdsa *ecdsa_engine = &ecdsa_dev->ecdsa_engine; ++ struct akcipher_request *req = ecdsa_engine->req; ++ struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req); ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(tfm); ++ size_t keylen = ctx->curve->g.ndigits * sizeof(u64); ++ struct ecdsa_signature_ctx sig_ctx = { ++ .curve = ctx->curve, ++ }; ++ u8 rawhash[ECC_MAX_BYTES]; ++ u64 hash[ECC_MAX_DIGITS]; ++ unsigned char *buffer; ++ ssize_t diff; ++ int ret; ++ ++ AST_DBG(ecdsa_dev, "\n"); ++ ++ if (unlikely(!ctx->pub_key_set)) ++ return -EINVAL; ++ ++ buffer = kmalloc(req->src_len + req->dst_len, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ /* Input src: signature + digest */ ++ sg_pcopy_to_buffer(req->src, sg_nents_for_len(req->src, req->src_len + req->dst_len), ++ buffer, req->src_len + req->dst_len, 0); ++ ++ ret = asn1_ber_decoder(&ecdsasignature_decoder, &sig_ctx, ++ buffer, req->src_len); ++ if (ret < 0) ++ goto error; ++ ++ /* if the hash is shorter then we will add leading zeros to fit to ndigits */ ++ diff = keylen - req->dst_len; ++ if (diff >= 0) { ++ if (diff) ++ memset(rawhash, 0, diff); ++ memcpy(&rawhash[diff], buffer + req->src_len, req->dst_len); ++ } else if (diff < 0) { ++ /* given hash is longer, we take the left-most bytes */ ++ memcpy(&rawhash, buffer + req->src_len, keylen); ++ } ++ ++ ecc_swap_digits((u64 *)rawhash, hash, ctx->curve->g.ndigits); ++ ++ ret = _aspeed_ecdsa_verify(ctx, hash, sig_ctx.r, sig_ctx.s); ++ ++error: ++ kfree(buffer); ++ ++ return ret; ++} ++ ++/* ++ * Verify an ECDSA signature. ++ */ ++static int aspeed_ecdsa_verify(struct akcipher_request *req) ++{ ++ struct crypto_akcipher *cipher = crypto_akcipher_reqtfm(req); ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(cipher); ++ struct aspeed_ecdsa_dev *ecdsa_dev = ctx->ecdsa_dev; ++ ++ AST_DBG(ecdsa_dev, "\n"); ++ ++ ctx->trigger = aspeed_ecdsa_trigger; ++ ++ return aspeed_ecdsa_handle_queue(ecdsa_dev, req); ++} ++ ++static int aspeed_ecdsa_ecc_ctx_init(struct aspeed_ecc_ctx *ctx, unsigned int curve_id) ++{ ++ void __iomem *base = ctx->ecdsa_dev->regs; ++ u8 *data, *buf; ++ u32 ctrl; ++ int nbytes; ++ ++ ctx->curve_id = curve_id; ++ ctx->curve = ecc_get_curve(curve_id); ++ if (!ctx->curve) ++ return -EINVAL; ++ ++ nbytes = ctx->curve->g.ndigits << ECC_DIGITS_TO_BYTES_SHIFT; ++ ++ switch (curve_id) { ++ case ECC_CURVE_NIST_P256: ++ AST_DBG(ctx->ecdsa_dev, "curve ECC_CURVE_NIST_P256\n"); ++ ctrl = ECDSA_256_EN; ++ break; ++ case ECC_CURVE_NIST_P384: ++ AST_DBG(ctx->ecdsa_dev, "curve ECC_CURVE_NIST_P384\n"); ++ ctrl = ECDSA_384_EN; ++ break; ++ } ++ ++ mutex_lock(&ctx->ecdsa_dev->lock); ++ ++ ast_write(ctx->ecdsa_dev, ECC_EN | ctrl, ASPEED_ECC_CTRL_REG); ++ ++ /* Initial Curve: ecc point/p/a/n */ ++ data = vmalloc(nbytes); ++ if (!data) ++ return -ENOMEM; ++ ++ buf = (u8 *)ctx->curve->g.x; ++ hexdump("Dump Gx:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)ctx->curve->g.x, nbytes); ++ memcpy_toio(base + ASPEED_ECC_PAR_GX_REG, data, nbytes); ++ ++ buf = (u8 *)ctx->curve->g.y; ++ hexdump("Dump Gy:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)ctx->curve->g.y, nbytes); ++ memcpy_toio(base + ASPEED_ECC_PAR_GY_REG, data, nbytes); ++ ++ buf = (u8 *)ctx->curve->p; ++ hexdump("Dump P:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)ctx->curve->p, nbytes); ++ memcpy_toio(base + ASPEED_ECC_PAR_P_REG, data, nbytes); ++ ++ buf = (u8 *)ctx->curve->a; ++ hexdump("Dump A:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)ctx->curve->a, nbytes); ++ memcpy_toio(base + ASPEED_ECC_PAR_A_REG, data, nbytes); ++ ++ buf = (u8 *)ctx->curve->n; ++ hexdump("Dump N:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)ctx->curve->n, nbytes); ++ memcpy_toio(base + ASPEED_ECC_PAR_N_REG, data, nbytes); ++ ++ vfree(data); ++ return 0; ++} ++ ++static void aspeed_ecdsa_ecc_ctx_deinit(struct aspeed_ecc_ctx *ctx) ++{ ++ mutex_unlock(&ctx->ecdsa_dev->lock); ++ ++ ctx->pub_key_set = false; ++} ++ ++static void aspeed_ecdsa_ecc_ctx_reset(struct aspeed_ecc_ctx *ctx) ++{ ++ ctx->pub_key = ECC_POINT_INIT(ctx->x, ctx->y, ++ ctx->curve->g.ndigits); ++} ++ ++/* ++ * Set the public key given the raw uncompressed key data from an X509 ++ * certificate. The key data contain the concatenated X and Y coordinates of ++ * the public key. ++ */ ++static int aspeed_ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, ++ unsigned int keylen) ++{ ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(tfm); ++ int nbytes = ctx->curve->g.ndigits << ECC_DIGITS_TO_BYTES_SHIFT; ++ void __iomem *base = ctx->ecdsa_dev->regs; ++ const unsigned char *d = key; ++ const u64 *digits = (const u64 *)&d[1]; ++ unsigned int ndigits; ++ u8 *data, *buf; ++ int ret; ++ ++ AST_DBG(ctx->ecdsa_dev, "\n"); ++ ++ ret = crypto_akcipher_set_pub_key(ctx->fallback_tfm, key, keylen); ++ if (ret) ++ return ret; ++ ++ aspeed_ecdsa_ecc_ctx_reset(ctx); ++ ++ if (keylen < 1 || (((keylen - 1) >> 1) % sizeof(u64)) != 0) ++ return -EINVAL; ++ /* we only accept uncompressed format indicated by '4' */ ++ if (d[0] != 4) ++ return -EINVAL; ++ ++ keylen--; ++ ndigits = (keylen >> 1) / sizeof(u64); ++ if (ndigits != ctx->curve->g.ndigits) ++ return -EINVAL; ++ ++ ecc_swap_digits(digits, ctx->pub_key.x, ndigits); ++ ecc_swap_digits(&digits[ndigits], ctx->pub_key.y, ndigits); ++ ret = ecc_is_pubkey_valid_full(ctx->curve, &ctx->pub_key); ++ ++ /* Set public key: Qx/Qy */ ++ data = vmalloc(nbytes); ++ if (!data) ++ return -ENOMEM; ++ ++ buf = (u8 *)ctx->pub_key.x; ++ hexdump("Dump Qx:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)ctx->pub_key.x, nbytes); ++ memcpy_toio(base + ASPEED_ECC_PAR_QX_REG, data, nbytes); ++ ++ buf = (u8 *)ctx->pub_key.y; ++ hexdump("Dump Qy:", buf, nbytes); ++ ++ buff_reverse(data, (u8 *)ctx->pub_key.y, nbytes); ++ memcpy_toio(base + ASPEED_ECC_PAR_QY_REG, data, nbytes); ++ ++ ctx->pub_key_set = ret == 0; ++ ++ vfree(data); ++ ++ return ret; ++} ++ ++static void aspeed_ecdsa_exit_tfm(struct crypto_akcipher *tfm) ++{ ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(tfm); ++ ++ AST_DBG(ctx->ecdsa_dev, "\n"); ++ ++ aspeed_ecdsa_ecc_ctx_deinit(ctx); ++ ++ crypto_free_akcipher(ctx->fallback_tfm); ++} ++ ++static unsigned int aspeed_ecdsa_max_size(struct crypto_akcipher *tfm) ++{ ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(tfm); ++ ++ return ctx->pub_key.ndigits << ECC_DIGITS_TO_BYTES_SHIFT; ++} ++ ++static int aspeed_ecdsa_nist_p384_init_tfm(struct crypto_akcipher *tfm) ++{ ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(tfm); ++ struct akcipher_alg *alg = crypto_akcipher_alg(tfm); ++ const char *name = crypto_tfm_alg_name(&tfm->base); ++ struct aspeed_ecdsa_alg *ecdsa_alg; ++ ++ ecdsa_alg = container_of(alg, struct aspeed_ecdsa_alg, akcipher.base); ++ ++ ctx->ecdsa_dev = ecdsa_alg->ecdsa_dev; ++ ++ AST_DBG(ctx->ecdsa_dev, "\n"); ++ ctx->fallback_tfm = crypto_alloc_akcipher(name, 0, CRYPTO_ALG_NEED_FALLBACK); ++ if (IS_ERR(ctx->fallback_tfm)) { ++ dev_err(ctx->ecdsa_dev->dev, "ERROR: Cannot allocate fallback for %s %ld\n", ++ name, PTR_ERR(ctx->fallback_tfm)); ++ return PTR_ERR(ctx->fallback_tfm); ++ } ++ ++ return aspeed_ecdsa_ecc_ctx_init(ctx, ECC_CURVE_NIST_P384); ++} ++ ++static int aspeed_ecdsa_nist_p256_init_tfm(struct crypto_akcipher *tfm) ++{ ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(tfm); ++ struct akcipher_alg *alg = crypto_akcipher_alg(tfm); ++ const char *name = crypto_tfm_alg_name(&tfm->base); ++ struct aspeed_ecdsa_alg *ecdsa_alg; ++ ++ ecdsa_alg = container_of(alg, struct aspeed_ecdsa_alg, akcipher.base); ++ ++ ctx->ecdsa_dev = ecdsa_alg->ecdsa_dev; ++ ++ AST_DBG(ctx->ecdsa_dev, "\n"); ++ ctx->fallback_tfm = crypto_alloc_akcipher(name, 0, CRYPTO_ALG_NEED_FALLBACK); ++ if (IS_ERR(ctx->fallback_tfm)) { ++ dev_err(ctx->ecdsa_dev->dev, "ERROR: Cannot allocate fallback for %s %ld\n", ++ name, PTR_ERR(ctx->fallback_tfm)); ++ return PTR_ERR(ctx->fallback_tfm); ++ } ++ ++ return aspeed_ecdsa_ecc_ctx_init(ctx, ECC_CURVE_NIST_P256); ++} ++ ++static int aspeed_ecdsa_complete(struct aspeed_ecdsa_dev *ecdsa_dev) ++{ ++ struct aspeed_engine_ecdsa *ecdsa_engine = &ecdsa_dev->ecdsa_engine; ++ struct akcipher_request *req = ecdsa_engine->req; ++ int results = ecdsa_engine->results; ++ ++ AST_DBG(ecdsa_dev, "\n"); ++ ++ ecdsa_engine->flags &= ~CRYPTO_FLAGS_BUSY; ++ ++ crypto_finalize_akcipher_request(ecdsa_dev->crypt_engine_ecdsa, req, results); ++ ++ return results; ++} ++ ++static int aspeed_ecdsa_do_request(struct crypto_engine *engine, void *areq) ++{ ++ struct akcipher_request *req = akcipher_request_cast(areq); ++ struct crypto_akcipher *cipher = crypto_akcipher_reqtfm(req); ++ struct aspeed_ecc_ctx *ctx = akcipher_tfm_ctx(cipher); ++ struct aspeed_ecdsa_dev *ecdsa_dev = ctx->ecdsa_dev; ++ struct aspeed_engine_ecdsa *ecdsa_engine; ++ ++ AST_DBG(ctx->ecdsa_dev, "\n"); ++ ++ ecdsa_engine = &ecdsa_dev->ecdsa_engine; ++ ecdsa_engine->req = req; ++ ecdsa_engine->flags |= CRYPTO_FLAGS_BUSY; ++ ecdsa_engine->resume = aspeed_ecdsa_complete; ++ ++ return ctx->trigger(ecdsa_dev); ++} ++ ++static void aspeed_ecdsa_done_task(unsigned long data) ++{ ++ struct aspeed_ecdsa_dev *ecdsa_dev = (struct aspeed_ecdsa_dev *)data; ++ struct aspeed_engine_ecdsa *ecdsa_engine = &ecdsa_dev->ecdsa_engine; ++ u32 ctrl; ++ ++ AST_DBG(ecdsa_dev, "\n"); ++ ++ /* Reset engine */ ++ ctrl = ast_read(ecdsa_dev, ASPEED_ECC_CTRL_REG); ++ ast_write(ecdsa_dev, 0, ASPEED_ECC_CTRL_REG); ++ ++ /* Memory barrier to ensure ecc ctrl is reset. */ ++ mb(); ++ ast_write(ecdsa_dev, ctrl, ASPEED_ECC_CTRL_REG); ++ ++ (void)ecdsa_engine->resume(ecdsa_dev); ++} ++ ++static struct aspeed_ecdsa_alg aspeed_ecdsa_nist_p256 = { ++ .akcipher.base = { ++ .verify = aspeed_ecdsa_verify, ++ .set_pub_key = aspeed_ecdsa_set_pub_key, ++ .max_size = aspeed_ecdsa_max_size, ++ .init = aspeed_ecdsa_nist_p256_init_tfm, ++ .exit = aspeed_ecdsa_exit_tfm, ++ .base = { ++ .cra_name = "ecdsa-nist-p256", ++ .cra_driver_name = "aspeed-ecdsa-nist-p256", ++ .cra_priority = 300, ++ .cra_module = THIS_MODULE, ++ .cra_ctxsize = sizeof(struct aspeed_ecc_ctx), ++ .cra_flags = CRYPTO_ALG_TYPE_AKCIPHER | ++ CRYPTO_ALG_KERN_DRIVER_ONLY | ++ CRYPTO_ALG_NEED_FALLBACK, ++ }, ++ }, ++ .akcipher.op = { ++ .do_one_request = aspeed_ecdsa_do_request, ++ }, ++}; ++ ++static struct aspeed_ecdsa_alg aspeed_ecdsa_nist_p384 = { ++ .akcipher.base = { ++ .verify = aspeed_ecdsa_verify, ++ .set_pub_key = aspeed_ecdsa_set_pub_key, ++ .max_size = aspeed_ecdsa_max_size, ++ .init = aspeed_ecdsa_nist_p384_init_tfm, ++ .exit = aspeed_ecdsa_exit_tfm, ++ .base = { ++ .cra_name = "ecdsa-nist-p384", ++ .cra_driver_name = "aspeed-ecdsa-nist-p384", ++ .cra_priority = 300, ++ .cra_module = THIS_MODULE, ++ .cra_ctxsize = sizeof(struct aspeed_ecc_ctx), ++ .cra_flags = CRYPTO_ALG_TYPE_AKCIPHER | ++ CRYPTO_ALG_KERN_DRIVER_ONLY | ++ CRYPTO_ALG_NEED_FALLBACK, ++ }, ++ }, ++ .akcipher.op = { ++ .do_one_request = aspeed_ecdsa_do_request, ++ }, ++}; ++ ++static int aspeed_ecdsa_register(struct aspeed_ecdsa_dev *ecdsa_dev) ++{ ++ int rc; ++ ++ aspeed_ecdsa_nist_p256.ecdsa_dev = ecdsa_dev; ++ rc = crypto_engine_register_akcipher(&aspeed_ecdsa_nist_p256.akcipher); ++ if (rc) ++ goto nist_p256_error; ++ ++ aspeed_ecdsa_nist_p384.ecdsa_dev = ecdsa_dev; ++ rc = crypto_engine_register_akcipher(&aspeed_ecdsa_nist_p384.akcipher); ++ if (rc) ++ goto nist_p384_error; ++ ++ return 0; ++ ++nist_p384_error: ++ crypto_engine_unregister_akcipher(&aspeed_ecdsa_nist_p256.akcipher); ++ ++nist_p256_error: ++ return rc; ++} ++ ++static void aspeed_ecdsa_unregister(struct aspeed_ecdsa_dev *ecdsa_dev) ++{ ++ crypto_engine_unregister_akcipher(&aspeed_ecdsa_nist_p256.akcipher); ++ crypto_engine_unregister_akcipher(&aspeed_ecdsa_nist_p384.akcipher); ++} ++ ++#ifdef ASPEED_ECDSA_IRQ_MODE ++/* ecdsa interrupt service routine. */ ++static irqreturn_t aspeed_ecdsa_irq(int irq, void *dev) ++{ ++ struct aspeed_ecdsa_dev *ecdsa_dev = (struct aspeed_ecdsa_dev *)dev; ++ struct aspeed_engine_ecdsa *ecdsa_engine = &ecdsa_dev->ecdsa_engine; ++ u32 sts; ++ ++ sts = ast_read(ecdsa_dev, ASPEED_ECC_INT_STS); ++ ast_write(ecdsa_dev, sts, ASPEED_ECC_INT_STS); ++ ++ AST_DBG(ecdsa_dev, "irq sts:0x%x\n", sts); ++ ++ sts = ast_read(ecdsa_dev, ASPEED_ECC_STS_REG) & ECC_VERIFY_PASS; ++ if (sts == ECC_VERIFY_PASS) { ++ AST_DBG(ecdsa_dev, "Verify PASS !\n"); ++ ++ ecdsa_engine->results = 0; ++ /* Stop ECDSA engine */ ++ if (ecdsa_engine->flags & CRYPTO_FLAGS_BUSY) ++ tasklet_schedule(&ecdsa_engine->done_task); ++ else ++ dev_err(ecdsa_dev->dev, "ECDSA no active requests.\n"); ++ ++ } else { ++ ecdsa_engine->results = -EKEYREJECTED; ++ AST_DBG(ecdsa_dev, "Verify FAILED !\n"); ++ } ++ ++ return IRQ_HANDLED; ++} ++#endif ++ ++static const struct of_device_id aspeed_ecdsa_of_matches[] = { ++ { .compatible = "aspeed,ast2700-ecdsa", }, ++ {}, ++}; ++ ++static int aspeed_ecdsa_probe(struct platform_device *pdev) ++{ ++ struct aspeed_engine_ecdsa *ecdsa_engine; ++ struct aspeed_ecdsa_dev *ecdsa_dev; ++ struct device *dev = &pdev->dev; ++ int rc; ++ ++ ecdsa_dev = devm_kzalloc(dev, sizeof(struct aspeed_ecdsa_dev), ++ GFP_KERNEL); ++ if (!ecdsa_dev) ++ return -ENOMEM; ++ ++ ecdsa_dev->dev = dev; ++ ++ platform_set_drvdata(pdev, ecdsa_dev); ++ ++ ecdsa_dev->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(ecdsa_dev->regs)) ++ return PTR_ERR(ecdsa_dev->regs); ++ ++#ifdef ASPEED_ECDSA_IRQ_MODE ++ /* Get irq number and register it */ ++ ecdsa_dev->irq = platform_get_irq(pdev, 0); ++ if (ecdsa_dev->irq < 0) ++ return -ENXIO; ++ ++ rc = devm_request_irq(dev, ecdsa_dev->irq, aspeed_ecdsa_irq, 0, ++ dev_name(dev), ecdsa_dev); ++ if (rc) { ++ dev_err(dev, "Failed to request irq.\n"); ++ return rc; ++ } ++ ++ /* Enable interrupt */ ++ ast_write(ecdsa_dev, 0x1, ASPEED_ECC_INT_EN); ++#endif ++ ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_warn(&pdev->dev, "No suitable DMA available\n"); ++ return rc; ++ } ++ ++ ecdsa_dev->clk = devm_clk_get_enabled(dev, NULL); ++ if (IS_ERR(ecdsa_dev->clk)) { ++ dev_err(dev, "Failed to get ecdsa clk\n"); ++ return PTR_ERR(ecdsa_dev->clk); ++ } ++ ++ ecdsa_dev->rst = devm_reset_control_get_shared(dev, NULL); ++ if (IS_ERR(ecdsa_dev->rst)) { ++ dev_err(dev, "Failed to get ecdsa reset\n"); ++ return PTR_ERR(ecdsa_dev->rst); ++ } ++ ++ rc = reset_control_deassert(ecdsa_dev->rst); ++ if (rc) { ++ dev_err(dev, "Deassert ecdsa reset failed\n"); ++ return rc; ++ } ++ ++ ecdsa_engine = &ecdsa_dev->ecdsa_engine; ++ ++ /* Initialize crypto hardware engine structure for ECDSA */ ++ ecdsa_dev->crypt_engine_ecdsa = crypto_engine_alloc_init(ecdsa_dev->dev, true); ++ if (!ecdsa_dev->crypt_engine_ecdsa) { ++ rc = -ENOMEM; ++ goto end; ++ } ++ ++ rc = crypto_engine_start(ecdsa_dev->crypt_engine_ecdsa); ++ if (rc) ++ goto err_engine_ecdsa_start; ++ ++ tasklet_init(&ecdsa_engine->done_task, aspeed_ecdsa_done_task, ++ (unsigned long)ecdsa_dev); ++ ++ /* Self-test */ ++ rc = aspeed_ecdsa_self_test(ecdsa_dev); ++ if (rc) ++ goto err_engine_ecdsa_start; ++ ++ rc = aspeed_ecdsa_register(ecdsa_dev); ++ if (rc) { ++ dev_err(dev, "ECDSA algo register failed\n"); ++ return rc; ++ } ++ ++ mutex_init(&ecdsa_dev->lock); ++ ++ dev_info(dev, "Aspeed ECDSA Hardware Accelerator successfully registered\n"); ++ ++ return 0; ++ ++err_engine_ecdsa_start: ++ crypto_engine_exit(ecdsa_dev->crypt_engine_ecdsa); ++end: ++ return rc; ++} ++ ++static void aspeed_ecdsa_remove(struct platform_device *pdev) ++{ ++ struct aspeed_ecdsa_dev *ecdsa_dev = platform_get_drvdata(pdev); ++ ++ aspeed_ecdsa_unregister(ecdsa_dev); ++} ++ ++MODULE_DEVICE_TABLE(of, aspeed_ecdsa_of_matches); ++ ++static struct platform_driver aspeed_ecdsa_driver = { ++ .probe = aspeed_ecdsa_probe, ++ .remove = aspeed_ecdsa_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_ecdsa_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_ecdsa_driver); ++MODULE_AUTHOR("Neal Liu "); ++MODULE_DESCRIPTION("ASPEED ECDSA algorithm driver acceleration"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/crypto/aspeed/aspeed-ecdsa.h b/drivers/crypto/aspeed/aspeed-ecdsa.h +--- a/drivers/crypto/aspeed/aspeed-ecdsa.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-ecdsa.h 2026-09-09 15:51:44.737237784 +0000 +@@ -0,0 +1,119 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++ ++#ifndef __ASPEED_ECDSA_H__ ++#define __ASPEED_ECDSA_H__ ++ ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_DEBUG ++#define AST_DBG(d, fmt, ...) \ ++ dev_info((d)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) ++#else ++#define AST_DBG(d, fmt, ...) \ ++ dev_dbg((d)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) ++#endif ++ ++/************************* ++ * * ++ * ECDSA regs definition * ++ * * ++ *************************/ ++#define ASPEED_ECC_STS_REG 0xb0 ++#define ASPEED_ECC_CTRL_REG 0xb4 ++#define ASPEED_ECC_CMD_REG 0xbc ++#define ASPEED_ECC_INT_EN 0xc0 ++#define ASPEED_ECC_INT_STS 0xc4 ++ ++#define ASPEED_ECC_DATA_BASE 0x800 ++#define ASPEED_ECC_PAR_GX_REG 0x800 ++#define ASPEED_ECC_PAR_GY_REG 0x840 ++#define ASPEED_ECC_PAR_QX_REG 0x880 ++#define ASPEED_ECC_PAR_QY_REG 0x8c0 ++#define ASPEED_ECC_PAR_P_REG 0x900 ++#define ASPEED_ECC_PAR_A_REG 0x940 ++#define ASPEED_ECC_PAR_N_REG 0x980 ++#define ASPEED_ECC_SIGN_R_REG 0x9c0 ++#define ASPEED_ECC_SIGN_S_REG 0xa00 ++#define ASPEED_ECC_MESSAGE_REG 0xa40 ++#define ASPEED_ECC_ECDSA_VERIFY 0xbc0 ++ ++/* sts */ ++#define ECC_IDLE BIT(0) ++#define ECC_VERIFY_PASS BIT(1) ++ ++/* ctrl/cmd */ ++#define ECC_EN BIT(0) ++#define ECDSA_384_EN 0x0 ++#define ECDSA_256_EN BIT(1) ++#define ADDR_BE BIT(2) ++#define DATA_BE BIT(3) ++ ++#define PAR_LEN_256 32 ++#define PAR_LEN_384 48 ++ ++#define ASPEED_ECC_POLLING_TIME 100 ++#define ASPEED_ECC_TIMEOUT 100000 /* 100 ms */ ++ ++#define CRYPTO_FLAGS_BUSY BIT(1) ++ ++#define ast_write(ast, val, offset) \ ++ writel((val), (ast)->regs + (offset)) ++ ++#define ast_read(ast, offset) \ ++ readl((ast)->regs + (offset)) ++ ++struct aspeed_ecdsa_dev; ++ ++typedef int (*aspeed_ecdsa_fn_t)(struct aspeed_ecdsa_dev *); ++ ++struct aspeed_ecc_ctx { ++ struct aspeed_ecdsa_dev *ecdsa_dev; ++ unsigned int curve_id; ++ const struct ecc_curve *curve; ++ ++ bool pub_key_set; ++ u64 x[ECC_MAX_DIGITS]; /* pub key x and y coordinates */ ++ u64 y[ECC_MAX_DIGITS]; ++ struct ecc_point pub_key; ++ ++ struct crypto_akcipher *fallback_tfm; ++ ++ aspeed_ecdsa_fn_t trigger; ++}; ++ ++struct ecdsa_signature_ctx { ++ const struct ecc_curve *curve; ++ u64 r[ECC_MAX_DIGITS]; ++ u64 s[ECC_MAX_DIGITS]; ++}; ++ ++struct aspeed_engine_ecdsa { ++ struct tasklet_struct done_task; ++ unsigned long flags; ++ struct akcipher_request *req; ++ int results; ++ ++ /* callback func */ ++ aspeed_ecdsa_fn_t resume; ++}; ++ ++struct aspeed_ecdsa_alg { ++ struct aspeed_ecdsa_dev *ecdsa_dev; ++ struct akcipher_engine_alg akcipher; ++}; ++ ++struct aspeed_ecdsa_dev { ++ void __iomem *regs; ++ struct device *dev; ++ struct clk *clk; ++ struct reset_control *rst; ++ int irq; ++ ++ /* Support ecdsa256/384 execution concurrent */ ++ struct mutex lock; ++ ++ struct crypto_engine *crypt_engine_ecdsa; ++ struct aspeed_engine_ecdsa ecdsa_engine; ++}; ++ ++extern const struct asn1_decoder ecdsasignature_decoder; ++ ++#endif +diff --git a/drivers/crypto/aspeed/aspeed-hace-crypto.c b/drivers/crypto/aspeed/aspeed-hace-crypto.c +--- a/drivers/crypto/aspeed/aspeed-hace-crypto.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-hace-crypto.c 2026-09-09 15:51:44.737237784 +0000 +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -24,6 +25,25 @@ + dev_dbg((h)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) + #endif + ++static int aspeed_hace_find_vault_key(struct aspeed_hace_dev *hace_dev, ++ const u8 *key, unsigned int keylen) ++{ ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ int i = 0; ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ for (i = 0; i < crypto_engine->vault_key_num; i++) { ++ if (!crypto_engine->dummy_key[i]) ++ continue; ++ ++ if (memcmp(crypto_engine->dummy_key[i], key, keylen) == 0) ++ return i; ++ } ++ ++ return ASPEED_VAULT_KEY_NOT_FOUND; ++} ++ + static int aspeed_crypto_do_fallback(struct skcipher_request *areq) + { + struct aspeed_cipher_reqctx *rctx = skcipher_request_ctx(areq); +@@ -143,6 +163,7 @@ + dma_unmap_sg(dev, req->src, rctx->src_nents, DMA_TO_DEVICE); + dma_unmap_sg(dev, req->dst, rctx->dst_nents, DMA_FROM_DEVICE); + } ++ up(&hace_dev->lock); + + return aspeed_sk_complete(hace_dev, 0); + } +@@ -206,10 +227,21 @@ + crypto_engine->resume = aspeed_sk_transfer; + + /* Trigger engines */ ++ ast_hace_write(hace_dev, crypto_engine->cipher_ctx_dma, ++ ASPEED_HACE_CONTEXT); + ast_hace_write(hace_dev, crypto_engine->cipher_dma_addr, + ASPEED_HACE_SRC); + ast_hace_write(hace_dev, crypto_engine->cipher_dma_addr, + ASPEED_HACE_DEST); ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ ast_hace_write(hace_dev, crypto_engine->cipher_ctx_dma >> 32, ++ ASPEED_HACE_CONTEXT_H); ++ ast_hace_write(hace_dev, crypto_engine->cipher_dma_addr >> 32, ++ ASPEED_HACE_SRC_H); ++ ast_hace_write(hace_dev, crypto_engine->cipher_dma_addr >> 32, ++ ASPEED_HACE_DEST_H); ++#endif ++ + ast_hace_write(hace_dev, req->cryptlen, ASPEED_HACE_DATA_LEN); + ast_hace_write(hace_dev, rctx->enc_cmd, ASPEED_HACE_CMD); + +@@ -222,6 +254,8 @@ + struct aspeed_sg_list *src_list, *dst_list; + dma_addr_t src_dma_addr, dst_dma_addr; + struct aspeed_cipher_reqctx *rctx; ++ struct crypto_skcipher *cipher; ++ struct aspeed_cipher_ctx *ctx; + struct skcipher_request *req; + struct scatterlist *s; + int src_sg_len; +@@ -232,6 +266,8 @@ + CIPHER_DBG(hace_dev, "\n"); + + req = crypto_engine->req; ++ cipher = crypto_skcipher_reqtfm(req); ++ ctx = crypto_skcipher_ctx(cipher); + rctx = skcipher_request_ctx(req); + + rctx->enc_cmd |= HACE_CMD_DES_SG_CTRL | HACE_CMD_SRC_SG_CTRL | +@@ -246,7 +282,6 @@ + dev_warn(hace_dev->dev, "dma_map_sg() src error\n"); + return -EINVAL; + } +- + } else { + src_sg_len = dma_map_sg(hace_dev->dev, req->src, + rctx->src_nents, DMA_TO_DEVICE); +@@ -331,9 +366,24 @@ + /* Memory barrier to ensure all data setup before engine starts */ + mb(); + ++ if (crypto_engine->vault_key_num && ++ ctx->key_idx < crypto_engine->vault_key_num) ++ ast_sbc_write(hace_dev, ctx->key_idx, ASPEED_VAULT_KEY_CTRL); ++ + /* Trigger engines */ ++ down(&hace_dev->lock); ++ ast_hace_write(hace_dev, crypto_engine->cipher_ctx_dma, ++ ASPEED_HACE_CONTEXT); + ast_hace_write(hace_dev, src_dma_addr, ASPEED_HACE_SRC); + ast_hace_write(hace_dev, dst_dma_addr, ASPEED_HACE_DEST); ++ ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ ast_hace_write(hace_dev, crypto_engine->cipher_ctx_dma >> 32, ++ ASPEED_HACE_CONTEXT_H); ++ ast_hace_write(hace_dev, src_dma_addr >> 32, ASPEED_HACE_SRC_H); ++ ast_hace_write(hace_dev, dst_dma_addr >> 32, ASPEED_HACE_DEST_H); ++#endif ++ + ast_hace_write(hace_dev, req->cryptlen, ASPEED_HACE_DATA_LEN); + ast_hace_write(hace_dev, rctx->enc_cmd, ASPEED_HACE_CMD); + +@@ -380,9 +430,6 @@ + rctx->dst_nents = sg_nents(req->dst); + rctx->src_nents = sg_nents(req->src); + +- ast_hace_write(hace_dev, crypto_engine->cipher_ctx_dma, +- ASPEED_HACE_CONTEXT); +- + if (rctx->enc_cmd & HACE_CMD_IV_REQUIRE) { + if (rctx->enc_cmd & HACE_CMD_DES_SELECT) + memcpy(crypto_engine->cipher_ctx + DES_BLOCK_SIZE, +@@ -392,7 +439,8 @@ + AES_BLOCK_SIZE); + } + +- if (hace_dev->version == AST2600_VERSION) { ++ if (hace_dev->version == AST2600_VERSION || ++ hace_dev->version == AST2700_VERSION) { + memcpy(crypto_engine->cipher_ctx + 16, ctx->key, ctx->key_len); + + return aspeed_sk_start_sg(hace_dev); +@@ -435,6 +483,9 @@ + + CIPHER_DBG(hace_dev, "keylen: %d bits\n", keylen); + ++ /* DES does not support vault key */ ++ ctx->key_idx = ASPEED_VAULT_KEY_NOT_FOUND; ++ + if (keylen != DES_KEY_SIZE && keylen != DES3_EDE_KEY_SIZE) { + dev_warn(hace_dev->dev, "invalid keylen: %d bits\n", keylen); + return -EINVAL; +@@ -539,6 +590,7 @@ + struct crypto_skcipher *cipher = crypto_skcipher_reqtfm(req); + struct aspeed_cipher_ctx *ctx = crypto_skcipher_ctx(cipher); + struct aspeed_hace_dev *hace_dev = ctx->hace_dev; ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; + u32 crypto_alg = cmd & HACE_CMD_OP_MODE_MASK; + + if (crypto_alg == HACE_CMD_CBC || crypto_alg == HACE_CMD_ECB) { +@@ -552,6 +604,9 @@ + cmd |= HACE_CMD_AES_SELECT | HACE_CMD_RI_WO_DATA_ENABLE | + HACE_CMD_CONTEXT_LOAD_ENABLE | HACE_CMD_CONTEXT_SAVE_ENABLE; + ++ if (ctx->key_idx < crypto_engine->vault_key_num) ++ cmd |= HACE_CMD_AES_KEY_FROM_OTP; ++ + switch (ctx->key_len) { + case AES_KEYSIZE_128: + cmd |= HACE_CMD_AES128; +@@ -580,6 +635,8 @@ + + CIPHER_DBG(hace_dev, "keylen: %d bits\n", (keylen * 8)); + ++ ctx->key_idx = aspeed_hace_find_vault_key(hace_dev, key, keylen); ++ + if (keylen != AES_KEYSIZE_128 && keylen != AES_KEYSIZE_192 && + keylen != AES_KEYSIZE_256) + return -EINVAL; +@@ -635,9 +692,8 @@ + { + struct aspeed_cipher_ctx *ctx = crypto_skcipher_ctx(tfm); + struct skcipher_alg *alg = crypto_skcipher_alg(tfm); +- const char *name = crypto_tfm_alg_name(&tfm->base); + struct aspeed_hace_alg *crypto_alg; +- ++ const char *name = crypto_tfm_alg_name(&tfm->base); + + crypto_alg = container_of(alg, struct aspeed_hace_alg, alg.skcipher.base); + ctx->hace_dev = crypto_alg->hace_dev; +@@ -912,6 +968,45 @@ + + }; + ++void aspeed_register_hace_vault_key(struct aspeed_hace_dev *hace_dev) ++{ ++ struct device *dev = hace_dev->dev; ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ static const char * const key[] = { ASPEED_VAULT_KEY_DTS }; ++ int i = 0; ++ int err = 0; ++ int key_num = hace_dev->version == AST2700_VERSION ? ++ ASPEED_G7_VAULT_KEY_MAX_NUM : ++ ASPEED_G6_VAULT_KEY_MAX_NUM; ++ ++ crypto_engine->vault_key_num = 0; ++ ++ if (hace_dev->version == AST2500_VERSION) ++ return; ++ ++ /* Check all vault key from dts and store in dummy_key array */ ++ for (i = 0; i < key_num; i++) { ++ crypto_engine->dummy_key[i] = ++ devm_kzalloc(dev, DUMMY_KEY_SIZE, GFP_KERNEL); ++ if (!crypto_engine->dummy_key[i]) { ++ dev_err(dev, "Failed to allocate memory for %s\n", key[i]); ++ continue; ++ } ++ ++ err = of_property_read_u32_array(dev->of_node, key[i], ++ crypto_engine->dummy_key[i], ++ DUMMY_KEY_SIZE / sizeof(u32)); ++ if (err) { ++ dev_err(dev, "Cannot find %s, skip key register\n", key[i]); ++ devm_kfree(dev, crypto_engine->dummy_key[i]); ++ crypto_engine->dummy_key[i] = NULL; ++ continue; ++ } ++ ++ crypto_engine->vault_key_num = key_num; ++ } ++} ++ + void aspeed_unregister_hace_crypto_algs(struct aspeed_hace_dev *hace_dev) + { + int i; +@@ -919,7 +1014,7 @@ + for (i = 0; i < ARRAY_SIZE(aspeed_crypto_algs); i++) + crypto_engine_unregister_skcipher(&aspeed_crypto_algs[i].alg.skcipher); + +- if (hace_dev->version != AST2600_VERSION) ++ if (hace_dev->version == AST2500_VERSION) + return; + + for (i = 0; i < ARRAY_SIZE(aspeed_crypto_algs_g6); i++) +@@ -941,7 +1036,7 @@ + } + } + +- if (hace_dev->version != AST2600_VERSION) ++ if (hace_dev->version == AST2500_VERSION) + return; + + for (i = 0; i < ARRAY_SIZE(aspeed_crypto_algs_g6); i++) { +@@ -953,3 +1048,94 @@ + } + } + } ++ ++static void aspeed_hace_crypto_done_task(unsigned long data) ++{ ++ struct aspeed_hace_dev *hace_dev = (struct aspeed_hace_dev *)data; ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ ++ crypto_engine->resume(hace_dev); ++} ++ ++int aspeed_hace_crypto_init(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aspeed_engine_crypto *crypto_engine; ++ int rc; ++ ++ crypto_engine = &hace_dev->crypto_engine; ++ ++ /* Initialize crypto hardware engine structure for crypto */ ++ hace_dev->crypt_engine_crypto = crypto_engine_alloc_init(hace_dev->dev, ++ true); ++ if (!hace_dev->crypt_engine_crypto) { ++ rc = -ENOMEM; ++ goto end; ++ } ++ ++ rc = crypto_engine_start(hace_dev->crypt_engine_crypto); ++ if (rc) ++ goto err_engine_crypto_start; ++ ++ tasklet_init(&crypto_engine->done_task, aspeed_hace_crypto_done_task, ++ (unsigned long)hace_dev); ++ ++ /* Allocate DMA buffer for crypto engine context used */ ++ crypto_engine->cipher_ctx = ++ dmam_alloc_coherent(hace_dev->dev, ++ PAGE_SIZE, ++ &crypto_engine->cipher_ctx_dma, ++ GFP_KERNEL); ++ if (!crypto_engine->cipher_ctx) { ++ dev_err(hace_dev->dev, "Failed to allocate cipher ctx dma\n"); ++ rc = -ENOMEM; ++ goto err_engine_crypto_start; ++ } ++ ++ /* Allocate DMA buffer for crypto engine input used */ ++ crypto_engine->cipher_addr = ++ dmam_alloc_coherent(hace_dev->dev, ++ ASPEED_CRYPTO_SRC_DMA_BUF_LEN, ++ &crypto_engine->cipher_dma_addr, ++ GFP_KERNEL); ++ if (!crypto_engine->cipher_addr) { ++ dev_err(hace_dev->dev, "Failed to allocate cipher addr dma\n"); ++ rc = -ENOMEM; ++ goto err_engine_crypto_start; ++ } ++ ++ /* Allocate DMA buffer for crypto engine output used */ ++ if (hace_dev->version == AST2600_VERSION || ++ hace_dev->version == AST2700_VERSION) { ++ crypto_engine->dst_sg_addr = ++ dmam_alloc_coherent(hace_dev->dev, ++ ASPEED_CRYPTO_DST_DMA_BUF_LEN, ++ &crypto_engine->dst_sg_dma_addr, ++ GFP_KERNEL); ++ if (!crypto_engine->dst_sg_addr) { ++ dev_err(hace_dev->dev, "Failed to allocate dst_sg dma\n"); ++ rc = -ENOMEM; ++ goto err_engine_crypto_start; ++ } ++ } ++ ++ /* Re-use destination buffer for aes-gcm tag output */ ++ crypto_engine->tag_addr = crypto_engine->dst_sg_addr + ++ ASPEED_CRYPTO_DST_DMA_BUF_LEN - AES_BLOCK_SIZE; ++ crypto_engine->tag_dma_addr = crypto_engine->dst_sg_dma_addr + ++ ASPEED_CRYPTO_DST_DMA_BUF_LEN - AES_BLOCK_SIZE; ++ ++ return 0; ++ ++err_engine_crypto_start: ++ crypto_engine_exit(hace_dev->crypt_engine_crypto); ++end: ++ return rc; ++} ++ ++void aspeed_hace_crypto_remove(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ ++ crypto_engine_exit(hace_dev->crypt_engine_crypto); ++ tasklet_kill(&crypto_engine->done_task); ++} +diff --git a/drivers/crypto/aspeed/aspeed-hace-gcm.c b/drivers/crypto/aspeed/aspeed-hace-gcm.c +--- a/drivers/crypto/aspeed/aspeed-hace-gcm.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-hace-gcm.c 2026-09-09 15:51:44.737237784 +0000 +@@ -0,0 +1,492 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (c) 2026 Aspeed Technology Inc. ++ */ ++ ++#include "aspeed-hace.h" ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_HACE_CRYPTO_DEBUG ++#define CIPHER_DBG(h, fmt, ...) \ ++ dev_info((h)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) ++#else ++#define CIPHER_DBG(h, fmt, ...) \ ++ dev_dbg((h)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) ++#endif ++ ++static bool aspeed_crypto_aead_need_fallback(struct aead_request *areq) ++{ ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(areq); ++ ++ if (areq->assoclen != 0) ++ return true; ++ ++ if (rctx->enc_cmd & HACE_CMD_ENCRYPT && areq->cryptlen == 0) ++ return true; ++ ++ if (!(rctx->enc_cmd & HACE_CMD_ENCRYPT) && ++ areq->cryptlen == AES_GCM_TAG_SIZE) ++ return true; ++ ++ return false; ++} ++ ++static int aspeed_crypto_aead_do_fallback(struct aead_request *areq) ++{ ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(areq); ++ struct crypto_aead *tfm = crypto_aead_reqtfm(areq); ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(tfm); ++ int err; ++ ++ aead_request_set_tfm(&rctx->fallback_aead_req, ctx->fallback_aead_tfm); ++ ++ aead_request_set_callback(&rctx->fallback_aead_req, areq->base.flags, ++ NULL, NULL); ++ aead_request_set_crypt(&rctx->fallback_aead_req, areq->src, areq->dst, ++ areq->cryptlen, areq->iv); ++ aead_request_set_ad(&rctx->fallback_aead_req, areq->assoclen); ++ ++ if (rctx->enc_cmd & HACE_CMD_ENCRYPT) ++ err = crypto_aead_encrypt(&rctx->fallback_aead_req); ++ else ++ err = crypto_aead_decrypt(&rctx->fallback_aead_req); ++ ++ return err; ++} ++ ++static int ++aspeed_hace_crypto_aead_handle_queue(struct aspeed_hace_dev *hace_dev, ++ struct aead_request *req) ++{ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ if (aspeed_crypto_aead_need_fallback(req)) ++ return aspeed_crypto_aead_do_fallback(req); ++ ++ return crypto_transfer_aead_request_to_engine(hace_dev->crypt_engine_crypto, req); ++} ++ ++static void aspeed_hace_aead_sg_unmap(struct aspeed_hace_dev *hace_dev, ++ struct aead_request *req) ++{ ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(req); ++ ++ if (rctx->dst_sg_len) { ++ dma_unmap_sg(hace_dev->dev, req->dst, rctx->dst_nents, ++ DMA_FROM_DEVICE); ++ } ++ ++ if (rctx->src_sg_len) { ++ dma_unmap_sg(hace_dev->dev, req->src, rctx->src_nents, ++ DMA_TO_DEVICE); ++ } ++} ++ ++static int aspeed_hace_aead_sg_map(struct aspeed_hace_dev *hace_dev, ++ struct aead_request *req) ++{ ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(req); ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ rctx->src_sg_len = dma_map_sg(hace_dev->dev, req->src, rctx->src_nents, ++ DMA_TO_DEVICE); ++ if (!rctx->src_sg_len) { ++ dev_warn(hace_dev->dev, "dma_map_sg() src error\n"); ++ return -EINVAL; ++ } ++ ++ rctx->dst_sg_len = dma_map_sg(hace_dev->dev, req->dst, rctx->dst_nents, ++ DMA_FROM_DEVICE); ++ if (!rctx->dst_sg_len) { ++ dev_warn(hace_dev->dev, "dma_map_sg() dst error\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_hace_aead_build_sg(struct aspeed_sg_list *sg_list, ++ struct scatterlist *src_sg, int src_sg_len, ++ int total) ++{ ++ struct scatterlist *s; ++ int i; ++ ++ for_each_sg(src_sg, s, src_sg_len, i) { ++ u32 phy_addr = sg_dma_address(s); ++ u32 len = sg_dma_len(s); ++ ++ len = total > len ? len : total | BIT(31); ++ total = total > len ? total - len : 0; ++ ++ sg_list[i].phy_addr = cpu_to_le32(phy_addr); ++ sg_list[i].len = cpu_to_le32(len); ++ } ++ ++ sg_list[src_sg_len].phy_addr = 0; ++ sg_list[src_sg_len].len = 0; ++ ++ if (total != 0) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static int aspeed_aead_complete(struct aspeed_hace_dev *hace_dev, int err) ++{ ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ struct aead_request *req = crypto_engine->aead_req; ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(req); ++ ++ if (rctx->enc_cmd & HACE_CMD_ENCRYPT) ++ scatterwalk_map_and_copy(crypto_engine->tag_addr, req->dst, ++ req->cryptlen, AES_GCM_TAG_SIZE, 1); ++ ++ if (!(rctx->enc_cmd & HACE_CMD_ENCRYPT)) ++ req->cryptlen += AES_GCM_TAG_SIZE; ++ ++ crypto_engine->flags &= ~CRYPTO_FLAGS_BUSY; ++ ++ crypto_finalize_aead_request(hace_dev->crypt_engine_crypto, req, err); ++ ++ return err; ++} ++ ++static int aspeed_aead_transfer_sg(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aead_request *req = hace_dev->crypto_engine.aead_req; ++ ++ aspeed_hace_aead_sg_unmap(hace_dev, req); ++ up(&hace_dev->lock); ++ ++ return aspeed_aead_complete(hace_dev, 0); ++} ++ ++static void aspeed_hace_trigger(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ struct aead_request *req = crypto_engine->aead_req; ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(req); ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ down(&hace_dev->lock); ++ ast_hace_write(hace_dev, crypto_engine->cipher_ctx_dma, ++ ASPEED_HACE_CONTEXT); ++ ast_hace_write(hace_dev, crypto_engine->tag_dma_addr, ++ ASPEED_HACE_GCM_TAG_BASE_ADDR); ++ ast_hace_write(hace_dev, crypto_engine->cipher_dma_addr, ++ ASPEED_HACE_SRC); ++ ast_hace_write(hace_dev, crypto_engine->dst_sg_dma_addr, ++ ASPEED_HACE_DEST); ++ ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ ast_hace_write(hace_dev, crypto_engine->tag_dma_addr >> 32, ++ ASPEED_HACE_TAG_H); ++ ast_hace_write(hace_dev, crypto_engine->cipher_ctx_dma >> 32, ++ ASPEED_HACE_CONTEXT_H); ++ ast_hace_write(hace_dev, crypto_engine->cipher_dma_addr >> 32, ++ ASPEED_HACE_SRC_H); ++ ast_hace_write(hace_dev, crypto_engine->dst_sg_dma_addr >> 32, ++ ASPEED_HACE_DEST_H); ++#endif ++ ++ ast_hace_write(hace_dev, req->cryptlen, ASPEED_HACE_DATA_LEN); ++ ast_hace_write(hace_dev, req->assoclen, ASPEED_HACE_GCM_ADD_LEN); ++ ast_hace_write(hace_dev, rctx->enc_cmd, ASPEED_HACE_CMD); ++} ++ ++static int aspeed_aead_start_sg(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ struct aead_request *req = crypto_engine->aead_req; ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(req); ++ struct aspeed_sg_list *src_list, *dst_list; ++ int ret = 0; ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ /* Scatter list mapping */ ++ ret = aspeed_hace_aead_sg_map(hace_dev, req); ++ if (ret) ++ goto fail; ++ ++ /* Build scatter-gather source list */ ++ src_list = (struct aspeed_sg_list *)crypto_engine->cipher_addr; ++ ret = aspeed_hace_aead_build_sg(src_list, req->src, rctx->src_sg_len, ++ req->cryptlen); ++ if (ret) ++ goto fail; ++ ++ /* Build scatter-gather destination list */ ++ dst_list = (struct aspeed_sg_list *)crypto_engine->dst_sg_addr; ++ ret = aspeed_hace_aead_build_sg(dst_list, req->dst, rctx->dst_sg_len, ++ req->cryptlen); ++ if (ret) ++ goto fail; ++ ++ crypto_engine->resume = aspeed_aead_transfer_sg; ++ ++ /* Memory barrier to ensure all data setup before engine starts */ ++ mb(); ++ aspeed_hace_trigger(hace_dev); ++ ++ return -EINPROGRESS; ++fail: ++ aspeed_hace_aead_sg_unmap(hace_dev, req); ++ return ret; ++} ++ ++static int aspeed_hace_aead_subkey(u8 *subkey, u8 *key, u32 keylen) ++{ ++ struct crypto_cipher *tfm = NULL; ++ int ret; ++ u8 src[AES_BLOCK_SIZE] = { 0 }; ++ ++ tfm = crypto_alloc_cipher("aes", 0, 0); ++ if (IS_ERR(tfm)) ++ return PTR_ERR(tfm); ++ ++ ret = crypto_cipher_setkey(tfm, key, keylen); ++ if (ret) ++ goto out; ++ ++ /* Encrypt one 16-byte block */ ++ crypto_cipher_encrypt_one(tfm, subkey, src); ++ ++out: ++ crypto_free_cipher(tfm); ++ return ret; ++} ++ ++static int aspeed_hace_aead_trigger(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; ++ struct aead_request *req = crypto_engine->aead_req; ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(req); ++ struct crypto_aead *aead = crypto_aead_reqtfm(req); ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(aead); ++ u8 iv_count[] = { 0x00, 0x00, 0x00, 0x01 }; ++ u8 subkey_offset = 0; ++ u8 subkey[AES_BLOCK_SIZE] = { 0 }; ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ if (!(rctx->enc_cmd & HACE_CMD_ENCRYPT)) ++ req->cryptlen = req->cryptlen - AES_GCM_TAG_SIZE; ++ ++ aspeed_hace_aead_subkey(subkey, ctx->key, ctx->key_len); ++ if (ctx->key_len == AES_KEYSIZE_128) ++ subkey_offset = ASPEED_HACE_CTX_128_SUBKEY_OFFSET; ++ else ++ subkey_offset = ASPEED_HACE_CTX_256_SUBKEY_OFFSET; ++ ++ rctx->dst_nents = sg_nents(req->dst); ++ rctx->src_nents = sg_nents(req->src); ++ ++ memcpy(crypto_engine->cipher_ctx, req->iv, GCM_AES_IV_SIZE); ++ memcpy(crypto_engine->cipher_ctx + GCM_AES_IV_SIZE, iv_count, ++ sizeof(iv_count)); ++ memcpy(crypto_engine->cipher_ctx + ASPEED_HACE_CTX_KEY_OFFSET, ctx->key, ++ ctx->key_len); ++ memcpy(crypto_engine->cipher_ctx + subkey_offset, subkey, ++ AES_BLOCK_SIZE); ++ ++ return aspeed_aead_start_sg(hace_dev); ++} ++ ++static int aspeed_aes_aead_setkey(struct crypto_aead *aead, const u8 *key, ++ unsigned int keylen) ++{ ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(aead); ++ struct aspeed_hace_dev *hace_dev = ctx->hace_dev; ++ ++ CIPHER_DBG(hace_dev, "keylen: %d bits\n", (keylen * 8)); ++ ++ ctx->key_idx = ASPEED_VAULT_KEY_NOT_FOUND; ++ ++ if (keylen != AES_KEYSIZE_128 && keylen != AES_KEYSIZE_192 && ++ keylen != AES_KEYSIZE_256) ++ return -EINVAL; ++ ++ memcpy(ctx->key, key, keylen); ++ ctx->key_len = keylen; ++ ++ crypto_aead_clear_flags(ctx->fallback_aead_tfm, CRYPTO_TFM_REQ_MASK); ++ crypto_aead_set_flags(ctx->fallback_aead_tfm, ++ aead->base.crt_flags & CRYPTO_TFM_REQ_MASK); ++ ++ return crypto_aead_setkey(ctx->fallback_aead_tfm, key, keylen); ++} ++ ++static int aspeed_aes_aead_setauthsize(struct crypto_aead *aead, ++ unsigned int authsize) ++{ ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(aead); ++ struct aspeed_hace_dev *hace_dev = ctx->hace_dev; ++ ++ CIPHER_DBG(hace_dev, "authsize: %d bits\n", (authsize * 8)); ++ ++ return crypto_aead_setauthsize(ctx->fallback_aead_tfm, authsize); ++} ++ ++static int aspeed_aes_gcm_crypt(struct aead_request *req, u32 cmd) ++{ ++ struct aspeed_cipher_reqctx *rctx = aead_request_ctx(req); ++ struct crypto_aead *aead = crypto_aead_reqtfm(req); ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(aead); ++ struct aspeed_hace_dev *hace_dev = ctx->hace_dev; ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ cmd = cmd | HACE_CMD_GCM | HACE_CMD_AES_SELECT | ++ HACE_CMD_RI_WO_DATA_ENABLE | HACE_CMD_CONTEXT_LOAD_ENABLE | ++ HACE_CMD_CONTEXT_SAVE_ENABLE | HACE_CMD_ISR_EN | ++ HACE_CMD_DES_SG_CTRL | HACE_CMD_SRC_SG_CTRL | ++ HACE_CMD_AES_KEY_HW_EXP | HACE_CMD_MBUS_REQ_SYNC_EN | ++ HACE_CMD_CTR_IV_AES_96 | HACE_CMD_GCM_TAG_ADDR_SEL; ++ ++ switch (ctx->key_len) { ++ case AES_KEYSIZE_128: ++ cmd |= HACE_CMD_AES128; ++ break; ++ case AES_KEYSIZE_192: ++ cmd |= HACE_CMD_AES192; ++ break; ++ case AES_KEYSIZE_256: ++ cmd |= HACE_CMD_AES256; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ rctx->enc_cmd = cmd; ++ ++ return aspeed_hace_crypto_aead_handle_queue(ctx->hace_dev, req); ++} ++ ++static int aspeed_aes_gcm_encrypt(struct aead_request *req) ++{ ++ return aspeed_aes_gcm_crypt(req, HACE_CMD_ENCRYPT); ++} ++ ++static int aspeed_aes_gcm_decrypt(struct aead_request *req) ++{ ++ return aspeed_aes_gcm_crypt(req, HACE_CMD_DECRYPT); ++} ++ ++static int aspeed_aead_do_request(struct crypto_engine *engine, void *areq) ++{ ++ struct aead_request *req = aead_request_cast(areq); ++ struct crypto_aead *aead = crypto_aead_reqtfm(req); ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(aead); ++ struct aspeed_hace_dev *hace_dev = ctx->hace_dev; ++ struct aspeed_engine_crypto *crypto_engine; ++ int rc; ++ ++ crypto_engine = &hace_dev->crypto_engine; ++ crypto_engine->aead_req = (struct aead_request *)req; ++ crypto_engine->flags |= CRYPTO_FLAGS_BUSY; ++ ++ rc = aspeed_hace_aead_trigger(hace_dev); ++ if (rc != -EINPROGRESS) ++ return -EIO; ++ ++ return 0; ++} ++ ++static int aspeed_crypto_aead_init(struct crypto_aead *aead) ++{ ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(aead); ++ struct aead_alg *alg = crypto_aead_alg(aead); ++ struct aspeed_hace_alg *crypto_alg = NULL; ++ const char *name = crypto_tfm_alg_name(&aead->base); ++ ++ crypto_alg = container_of(alg, struct aspeed_hace_alg, alg.aead.base); ++ ctx->hace_dev = crypto_alg->hace_dev; ++ ++ CIPHER_DBG(ctx->hace_dev, "\n"); ++ ++ ctx->fallback_aead_tfm = ++ crypto_alloc_aead(name, 0, CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK); ++ if (IS_ERR(ctx->fallback_aead_tfm)) { ++ dev_err(ctx->hace_dev->dev, "Failed to allocate %s %ld\n", name, ++ PTR_ERR(ctx->fallback_aead_tfm)); ++ return PTR_ERR(ctx->fallback_aead_tfm); ++ } ++ ++ crypto_aead_set_reqsize(aead, sizeof(struct aead_request) + ++ crypto_aead_reqsize(ctx->fallback_aead_tfm)); ++ ++ return 0; ++} ++ ++static void aspeed_crypto_aead_exit(struct crypto_aead *aead) ++{ ++ struct aspeed_cipher_ctx *ctx = crypto_aead_ctx(aead); ++ struct aspeed_hace_dev *hace_dev = ctx->hace_dev; ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ if (ctx->fallback_aead_tfm) { ++ crypto_free_aead(ctx->fallback_aead_tfm); ++ ctx->fallback_aead_tfm = NULL; ++ } ++ ++ memzero_explicit(ctx, sizeof(struct aspeed_cipher_ctx)); ++} ++ ++static struct aspeed_hace_alg aspeed_aead_algs[] = { ++ { ++ .alg.aead.base = { ++ .ivsize = GCM_AES_IV_SIZE, ++ .maxauthsize = AES_BLOCK_SIZE, ++ .setkey = aspeed_aes_aead_setkey, ++ .setauthsize = aspeed_aes_aead_setauthsize, ++ .encrypt = aspeed_aes_gcm_encrypt, ++ .decrypt = aspeed_aes_gcm_decrypt, ++ .init = aspeed_crypto_aead_init, ++ .exit = aspeed_crypto_aead_exit, ++ .base = { ++ .cra_name = "gcm(aes)", ++ .cra_driver_name = "aspeed-gcm-aes", ++ .cra_priority = 300, ++ .cra_flags = CRYPTO_ALG_TYPE_AEAD | ++ CRYPTO_ALG_ASYNC | ++ CRYPTO_ALG_NEED_FALLBACK, ++ .cra_blocksize = AES_BLOCK_SIZE, ++ .cra_ctxsize = sizeof(struct aspeed_cipher_ctx), ++ .cra_alignmask = 0x0f, ++ .cra_module = THIS_MODULE, ++ } ++ }, ++ .alg.aead.op = { ++ .do_one_request = aspeed_aead_do_request, ++ }, ++ }, ++}; ++ ++void aspeed_register_hace_aead_algs(struct aspeed_hace_dev *hace_dev) ++{ ++ int rc, i; ++ ++ CIPHER_DBG(hace_dev, "\n"); ++ ++ if (hace_dev->version == AST2500_VERSION || ++ hace_dev->version == AST2600_VERSION) ++ return; ++ ++ for (i = 0; i < ARRAY_SIZE(aspeed_aead_algs); i++) { ++ aspeed_aead_algs[i].hace_dev = hace_dev; ++ rc = crypto_engine_register_aead(&aspeed_aead_algs[i].alg.aead); ++ if (rc) { ++ CIPHER_DBG(hace_dev, "Failed to register %s\n", ++ aspeed_aead_algs[i].alg.aead.base.base.cra_name); ++ } ++ } ++} +diff --git a/drivers/crypto/aspeed/aspeed-hace-hash.c b/drivers/crypto/aspeed/aspeed-hace-hash.c +--- a/drivers/crypto/aspeed/aspeed-hace-hash.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-hace-hash.c 2026-09-09 15:51:44.737237784 +0000 +@@ -138,21 +138,13 @@ + return -EINVAL; + } + +- scatterwalk_map_and_copy(rctx->buffer, rctx->src_sg, +- rctx->offset, remain, 0); ++ scatterwalk_map_and_copy(rctx->buffer, rctx->src_sg, rctx->offset, ++ remain, 0); + + rctx->bufcnt = remain; +- rctx->digest_dma_addr = dma_map_single(hace_dev->dev, rctx->digest, +- SHA512_DIGEST_SIZE, +- DMA_BIDIRECTIONAL); +- if (dma_mapping_error(hace_dev->dev, rctx->digest_dma_addr)) { +- dev_warn(hace_dev->dev, "dma_map() rctx digest error\n"); +- return -ENOMEM; +- } + + hash_engine->src_length = length - remain; + hash_engine->src_dma = hash_engine->ahash_src_dma_addr; +- hash_engine->digest_dma = rctx->digest_dma_addr; + + return 0; + } +@@ -187,30 +179,12 @@ + } + + src_list = (struct aspeed_sg_list *)hash_engine->ahash_src_addr; +- rctx->digest_dma_addr = dma_map_single(hace_dev->dev, rctx->digest, +- SHA512_DIGEST_SIZE, +- DMA_BIDIRECTIONAL); +- if (dma_mapping_error(hace_dev->dev, rctx->digest_dma_addr)) { +- dev_warn(hace_dev->dev, "dma_map() rctx digest error\n"); +- rc = -ENOMEM; +- goto free_src_sg; +- } + + if (rctx->bufcnt != 0) { + u32 phy_addr; + u32 len; + +- rctx->buffer_dma_addr = dma_map_single(hace_dev->dev, +- rctx->buffer, +- rctx->block_size * 2, +- DMA_TO_DEVICE); +- if (dma_mapping_error(hace_dev->dev, rctx->buffer_dma_addr)) { +- dev_warn(hace_dev->dev, "dma_map() rctx buffer error\n"); +- rc = -ENOMEM; +- goto free_rctx_digest; +- } +- +- phy_addr = rctx->buffer_dma_addr; ++ phy_addr = hash_engine->buffer_dma_addr; + len = rctx->bufcnt; + length -= len; + +@@ -244,23 +218,15 @@ + + if (length != 0) { + rc = -EINVAL; +- goto free_rctx_buffer; ++ goto free_src_sg; + } + + rctx->offset = rctx->total - remain; + hash_engine->src_length = rctx->total + rctx->bufcnt - remain; + hash_engine->src_dma = hash_engine->ahash_src_dma_addr; +- hash_engine->digest_dma = rctx->digest_dma_addr; + + return 0; + +-free_rctx_buffer: +- if (rctx->bufcnt != 0) +- dma_unmap_single(hace_dev->dev, rctx->buffer_dma_addr, +- rctx->block_size * 2, DMA_TO_DEVICE); +-free_rctx_digest: +- dma_unmap_single(hace_dev->dev, rctx->digest_dma_addr, +- SHA512_DIGEST_SIZE, DMA_BIDIRECTIONAL); + free_src_sg: + dma_unmap_sg(hace_dev->dev, rctx->src_sg, rctx->src_nents, + DMA_TO_DEVICE); +@@ -294,13 +260,15 @@ + + AHASH_DBG(hace_dev, "\n"); + +- dma_unmap_single(hace_dev->dev, rctx->digest_dma_addr, +- SHA512_DIGEST_SIZE, DMA_BIDIRECTIONAL); +- +- dma_unmap_single(hace_dev->dev, rctx->buffer_dma_addr, +- rctx->block_size * 2, DMA_TO_DEVICE); ++ memcpy(req->result, hash_engine->digest_addr, rctx->digsize); + +- memcpy(req->result, rctx->digest, rctx->digsize); ++ /* ++ * Workaround for aes engine hang: The sha configuration may cause aes ++ * engine to hang. To address this issue, the hace engine is reset after ++ * the hash calculation is completed. ++ */ ++ aspeed_hace_reset(hace_dev); ++ up(&hace_dev->lock); + + return aspeed_ahash_complete(hace_dev); + } +@@ -315,13 +283,18 @@ + struct ahash_request *req = hash_engine->req; + struct aspeed_sham_reqctx *rctx = ahash_request_ctx(req); + ++ memcpy(hash_engine->digest_addr, rctx->digest, rctx->ivsize); ++ memcpy(hash_engine->buffer_addr, rctx->buffer, rctx->bufcnt); ++ hash_engine->digest_dma = hash_engine->digest_dma_addr; ++ + AHASH_DBG(hace_dev, "src_dma:%pad, digest_dma:%pad, length:%zu\n", + &hash_engine->src_dma, &hash_engine->digest_dma, + hash_engine->src_length); + +- rctx->cmd |= HASH_CMD_INT_ENABLE; ++ rctx->cmd |= HASH_CMD_INT_ENABLE | HASH_CMD_MBUS_REQ_SYNC_EN; + hash_engine->resume = resume; + ++ /* Trigger engines */ + ast_hace_write(hace_dev, hash_engine->src_dma, ASPEED_HACE_HASH_SRC); + ast_hace_write(hace_dev, hash_engine->digest_dma, + ASPEED_HACE_HASH_DIGEST_BUFF); +@@ -329,6 +302,14 @@ + ASPEED_HACE_HASH_KEY_BUFF); + ast_hace_write(hace_dev, hash_engine->src_length, + ASPEED_HACE_HASH_DATA_LEN); ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ ast_hace_write(hace_dev, hash_engine->src_dma >> 32, ++ ASPEED_HACE_HASH_SRC_H); ++ ast_hace_write(hace_dev, hash_engine->digest_dma >> 32, ++ ASPEED_HACE_HASH_DIGEST_BUFF_H); ++ ast_hace_write(hace_dev, hash_engine->digest_dma >> 32, ++ ASPEED_HACE_HASH_KEY_BUFF_H); ++#endif + + /* Memory barrier to ensure all data setup before engine starts */ + mb(); +@@ -351,15 +332,10 @@ + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct aspeed_sham_ctx *tctx = crypto_ahash_ctx(tfm); + struct aspeed_sha_hmac_ctx *bctx = tctx->base; +- int rc = 0; + + AHASH_DBG(hace_dev, "\n"); + +- dma_unmap_single(hace_dev->dev, rctx->digest_dma_addr, +- SHA512_DIGEST_SIZE, DMA_BIDIRECTIONAL); +- +- dma_unmap_single(hace_dev->dev, rctx->buffer_dma_addr, +- rctx->block_size * 2, DMA_TO_DEVICE); ++ memcpy(rctx->digest, hash_engine->digest_addr, rctx->ivsize); + + /* o key pad + hash sum 1 */ + memcpy(rctx->buffer, bctx->opad, rctx->block_size); +@@ -371,35 +347,10 @@ + aspeed_ahash_fill_padding(hace_dev, rctx); + memcpy(rctx->digest, rctx->sha_iv, rctx->ivsize); + +- rctx->digest_dma_addr = dma_map_single(hace_dev->dev, rctx->digest, +- SHA512_DIGEST_SIZE, +- DMA_BIDIRECTIONAL); +- if (dma_mapping_error(hace_dev->dev, rctx->digest_dma_addr)) { +- dev_warn(hace_dev->dev, "dma_map() rctx digest error\n"); +- rc = -ENOMEM; +- goto end; +- } +- +- rctx->buffer_dma_addr = dma_map_single(hace_dev->dev, rctx->buffer, +- rctx->block_size * 2, +- DMA_TO_DEVICE); +- if (dma_mapping_error(hace_dev->dev, rctx->buffer_dma_addr)) { +- dev_warn(hace_dev->dev, "dma_map() rctx buffer error\n"); +- rc = -ENOMEM; +- goto free_rctx_digest; +- } +- +- hash_engine->src_dma = rctx->buffer_dma_addr; ++ hash_engine->src_dma = hash_engine->buffer_dma_addr; + hash_engine->src_length = rctx->bufcnt; +- hash_engine->digest_dma = rctx->digest_dma_addr; + + return aspeed_hace_ahash_trigger(hace_dev, aspeed_ahash_transfer); +- +-free_rctx_digest: +- dma_unmap_single(hace_dev->dev, rctx->digest_dma_addr, +- SHA512_DIGEST_SIZE, DMA_BIDIRECTIONAL); +-end: +- return rc; + } + + static int aspeed_ahash_req_final(struct aspeed_hace_dev *hace_dev) +@@ -407,47 +358,19 @@ + struct aspeed_engine_hash *hash_engine = &hace_dev->hash_engine; + struct ahash_request *req = hash_engine->req; + struct aspeed_sham_reqctx *rctx = ahash_request_ctx(req); +- int rc = 0; + + AHASH_DBG(hace_dev, "\n"); + + aspeed_ahash_fill_padding(hace_dev, rctx); + +- rctx->digest_dma_addr = dma_map_single(hace_dev->dev, +- rctx->digest, +- SHA512_DIGEST_SIZE, +- DMA_BIDIRECTIONAL); +- if (dma_mapping_error(hace_dev->dev, rctx->digest_dma_addr)) { +- dev_warn(hace_dev->dev, "dma_map() rctx digest error\n"); +- rc = -ENOMEM; +- goto end; +- } +- +- rctx->buffer_dma_addr = dma_map_single(hace_dev->dev, +- rctx->buffer, +- rctx->block_size * 2, +- DMA_TO_DEVICE); +- if (dma_mapping_error(hace_dev->dev, rctx->buffer_dma_addr)) { +- dev_warn(hace_dev->dev, "dma_map() rctx buffer error\n"); +- rc = -ENOMEM; +- goto free_rctx_digest; +- } +- +- hash_engine->src_dma = rctx->buffer_dma_addr; ++ hash_engine->src_dma = hash_engine->buffer_dma_addr; + hash_engine->src_length = rctx->bufcnt; +- hash_engine->digest_dma = rctx->digest_dma_addr; + + if (rctx->flags & SHA_FLAGS_HMAC) + return aspeed_hace_ahash_trigger(hace_dev, + aspeed_ahash_hmac_resume); + + return aspeed_hace_ahash_trigger(hace_dev, aspeed_ahash_transfer); +- +-free_rctx_digest: +- dma_unmap_single(hace_dev->dev, rctx->digest_dma_addr, +- SHA512_DIGEST_SIZE, DMA_BIDIRECTIONAL); +-end: +- return rc; + } + + static int aspeed_ahash_update_resume_sg(struct aspeed_hace_dev *hace_dev) +@@ -458,17 +381,11 @@ + + AHASH_DBG(hace_dev, "\n"); + ++ memcpy(rctx->digest, hash_engine->digest_addr, rctx->ivsize); ++ + dma_unmap_sg(hace_dev->dev, rctx->src_sg, rctx->src_nents, + DMA_TO_DEVICE); + +- if (rctx->bufcnt != 0) +- dma_unmap_single(hace_dev->dev, rctx->buffer_dma_addr, +- rctx->block_size * 2, +- DMA_TO_DEVICE); +- +- dma_unmap_single(hace_dev->dev, rctx->digest_dma_addr, +- SHA512_DIGEST_SIZE, DMA_BIDIRECTIONAL); +- + scatterwalk_map_and_copy(rctx->buffer, rctx->src_sg, rctx->offset, + rctx->total - rctx->offset, 0); + +@@ -489,8 +406,7 @@ + + AHASH_DBG(hace_dev, "\n"); + +- dma_unmap_single(hace_dev->dev, rctx->digest_dma_addr, +- SHA512_DIGEST_SIZE, DMA_BIDIRECTIONAL); ++ memcpy(rctx->digest, hash_engine->digest_addr, rctx->ivsize); + + if (rctx->flags & SHA_FLAGS_FINUP) + return aspeed_ahash_req_final(hace_dev); +@@ -508,12 +424,12 @@ + + AHASH_DBG(hace_dev, "\n"); + +- if (hace_dev->version == AST2600_VERSION) { +- rctx->cmd |= HASH_CMD_HASH_SRC_SG_CTRL; +- resume = aspeed_ahash_update_resume_sg; ++ if (hace_dev->version == AST2500_VERSION) { ++ resume = aspeed_ahash_update_resume; + + } else { +- resume = aspeed_ahash_update_resume; ++ rctx->cmd |= HASH_CMD_HASH_SRC_SG_CTRL; ++ resume = aspeed_ahash_update_resume_sg; + } + + ret = hash_engine->dma_prepare(hace_dev); +@@ -526,8 +442,32 @@ + static int aspeed_hace_hash_handle_queue(struct aspeed_hace_dev *hace_dev, + struct ahash_request *req) + { +- return crypto_transfer_hash_request_to_engine( +- hace_dev->crypt_engine_hash, req); ++ struct aspeed_sham_reqctx *rctx = ahash_request_ctx(req); ++ struct aspeed_engine_hash *hash_engine = &hace_dev->hash_engine; ++ int ret = 0; ++ static bool init_req; ++ ++ /* The first request is enqueued, lock the queue */ ++ if (rctx->op & SHA_OP_INIT) { ++ mutex_lock(&hash_engine->queue_lock); ++ init_req = true; ++ return 0; ++ } ++ ++ /* The previous request is init request, enqueue the request with init flag */ ++ if (init_req) { ++ rctx->op |= SHA_OP_INIT; ++ init_req = false; ++ } ++ ++ ret = crypto_transfer_hash_request_to_engine(hace_dev->crypt_engine_hash, ++ req); ++ ++ /* The last request is enqueued, release the lock */ ++ if (rctx->op & SHA_OP_FINAL || rctx->flags & SHA_FLAGS_FINUP) ++ mutex_unlock(&hash_engine->queue_lock); ++ ++ return ret; + } + + static int aspeed_ahash_do_request(struct crypto_engine *engine, void *areq) +@@ -543,9 +483,14 @@ + hash_engine = &hace_dev->hash_engine; + hash_engine->flags |= CRYPTO_FLAGS_BUSY; + +- if (rctx->op == SHA_OP_UPDATE) ++ /* If the update/final is the first request, lock hace engine */ ++ if (rctx->op & SHA_OP_INIT) ++ down(&hace_dev->lock); ++ ++ /* Do the update/final operation no matter what */ ++ if (rctx->op & SHA_OP_UPDATE) + ret = aspeed_ahash_req_update(hace_dev); +- else if (rctx->op == SHA_OP_FINAL) ++ else if (rctx->op & SHA_OP_FINAL) + ret = aspeed_ahash_req_final(hace_dev); + + if (ret != -EINPROGRESS) +@@ -566,10 +511,10 @@ + hash_engine = &hace_dev->hash_engine; + hash_engine->req = req; + +- if (hace_dev->version == AST2600_VERSION) +- hash_engine->dma_prepare = aspeed_ahash_dma_prepare_sg; +- else ++ if (hace_dev->version == AST2500_VERSION) + hash_engine->dma_prepare = aspeed_ahash_dma_prepare; ++ else ++ hash_engine->dma_prepare = aspeed_ahash_dma_prepare_sg; + } + + static int aspeed_ahash_do_one(struct crypto_engine *engine, void *areq) +@@ -671,6 +616,7 @@ + crypto_ahash_digestsize(tfm)); + + rctx->cmd = HASH_CMD_ACC_MODE; ++ rctx->op = SHA_OP_INIT; + rctx->flags = 0; + + switch (crypto_ahash_digestsize(tfm)) { +@@ -740,7 +686,7 @@ + rctx->flags |= SHA_FLAGS_HMAC; + } + +- return 0; ++ return aspeed_hace_hash_handle_queue(hace_dev, req); + } + + static int aspeed_sham_digest(struct ahash_request *req) +@@ -1196,7 +1142,7 @@ + for (i = 0; i < ARRAY_SIZE(aspeed_ahash_algs); i++) + crypto_engine_unregister_ahash(&aspeed_ahash_algs[i].alg.ahash); + +- if (hace_dev->version != AST2600_VERSION) ++ if (hace_dev->version == AST2500_VERSION) + return; + + for (i = 0; i < ARRAY_SIZE(aspeed_ahash_algs_g6); i++) +@@ -1218,7 +1164,7 @@ + } + } + +- if (hace_dev->version != AST2600_VERSION) ++ if (hace_dev->version == AST2500_VERSION) + return; + + for (i = 0; i < ARRAY_SIZE(aspeed_ahash_algs_g6); i++) { +@@ -1230,3 +1176,82 @@ + } + } + } ++ ++static void aspeed_hace_hash_done_task(unsigned long data) ++{ ++ struct aspeed_hace_dev *hace_dev = (struct aspeed_hace_dev *)data; ++ struct aspeed_engine_hash *hash_engine = &hace_dev->hash_engine; ++ ++ hash_engine->resume(hace_dev); ++} ++ ++int aspeed_hace_hash_init(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aspeed_engine_hash *hash_engine; ++ int rc; ++ ++ hash_engine = &hace_dev->hash_engine; ++ ++ /* Initialize crypto hardware engine structure for hash */ ++ hace_dev->crypt_engine_hash = crypto_engine_alloc_init(hace_dev->dev, ++ true); ++ if (!hace_dev->crypt_engine_hash) { ++ rc = -ENOMEM; ++ goto end; ++ } ++ ++ rc = crypto_engine_start(hace_dev->crypt_engine_hash); ++ if (rc) ++ goto err_engine_hash_start; ++ ++ tasklet_init(&hash_engine->done_task, aspeed_hace_hash_done_task, ++ (unsigned long)hace_dev); ++ ++ /* Allocate DMA buffer for hash engine input used */ ++ hash_engine->ahash_src_addr = ++ dmam_alloc_coherent(hace_dev->dev, ++ ASPEED_HASH_SRC_DMA_BUF_LEN, ++ &hash_engine->ahash_src_dma_addr, ++ GFP_KERNEL); ++ if (!hash_engine->ahash_src_addr) { ++ dev_err(hace_dev->dev, "Failed to allocate dma buffer\n"); ++ rc = -ENOMEM; ++ goto err_engine_hash_start; ++ } ++ ++ hash_engine->buffer_addr = dmam_alloc_coherent(hace_dev->dev, SHA512_BLOCK_SIZE * 2, ++ &hash_engine->buffer_dma_addr, ++ GFP_KERNEL); ++ if (!hash_engine->buffer_addr) { ++ dev_err(hace_dev->dev, "Failed to allocate DMA buffer\n"); ++ rc = -ENOMEM; ++ goto err_engine_hash_start; ++ } ++ ++ hash_engine->digest_addr = dmam_alloc_coherent(hace_dev->dev, SHA512_DIGEST_SIZE, ++ &hash_engine->digest_dma_addr, ++ GFP_KERNEL); ++ if (!hash_engine->digest_addr) { ++ dev_err(hace_dev->dev, "Failed to allocate DMA digest buffer\n"); ++ rc = -ENOMEM; ++ goto err_engine_hash_start; ++ } ++ ++ /* Hash engine hardware initial done, prepare queue lock */ ++ mutex_init(&hash_engine->queue_lock); ++ ++ return 0; ++ ++err_engine_hash_start: ++ crypto_engine_exit(hace_dev->crypt_engine_hash); ++end: ++ return rc; ++} ++ ++void aspeed_hace_hash_remove(struct aspeed_hace_dev *hace_dev) ++{ ++ struct aspeed_engine_hash *hash_engine = &hace_dev->hash_engine; ++ ++ crypto_engine_exit(hace_dev->crypt_engine_hash); ++ tasklet_kill(&hash_engine->done_task); ++} +diff --git a/drivers/crypto/aspeed/aspeed-hace.c b/drivers/crypto/aspeed/aspeed-hace.c +--- a/drivers/crypto/aspeed/aspeed-hace.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-hace.c 2026-09-09 15:51:44.737237784 +0000 +@@ -6,15 +6,17 @@ + #include "aspeed-hace.h" + #include + #include ++#include + #include + #include + #include + #include + #include + #include ++#include ++#include + #include + #include +-#include + + #ifdef CONFIG_CRYPTO_DEV_ASPEED_DEBUG + #define HACE_DBG(d, fmt, ...) \ +@@ -24,6 +26,30 @@ + dev_dbg((d)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) + #endif + ++int aspeed_hace_reset(struct aspeed_hace_dev *hace_dev) ++{ ++ int rc; ++ ++ HACE_DBG(hace_dev, "\n"); ++ ++ if (!hace_dev->rst) ++ return -ENODEV; ++ ++ rc = reset_control_assert(hace_dev->rst); ++ if (rc) { ++ dev_err(hace_dev->dev, "Hace reset failed (assert).\n"); ++ return rc; ++ } ++ ++ rc = reset_control_deassert(hace_dev->rst); ++ if (rc) { ++ dev_err(hace_dev->dev, "Hace reset failed (deassert).\n"); ++ return rc; ++ } ++ ++ return 0; ++} ++ + /* HACE interrupt service routine */ + static irqreturn_t aspeed_hace_irq(int irq, void *dev) + { +@@ -51,23 +77,9 @@ + dev_warn(hace_dev->dev, "CRYPTO no active requests.\n"); + } + +- return IRQ_HANDLED; +-} +- +-static void aspeed_hace_crypto_done_task(unsigned long data) +-{ +- struct aspeed_hace_dev *hace_dev = (struct aspeed_hace_dev *)data; +- struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; +- +- crypto_engine->resume(hace_dev); +-} ++ HACE_DBG(hace_dev, "handled\n"); + +-static void aspeed_hace_hash_done_task(unsigned long data) +-{ +- struct aspeed_hace_dev *hace_dev = (struct aspeed_hace_dev *)data; +- struct aspeed_engine_hash *hash_engine = &hace_dev->hash_engine; +- +- hash_engine->resume(hace_dev); ++ return IRQ_HANDLED; + } + + static void aspeed_hace_register(struct aspeed_hace_dev *hace_dev) +@@ -77,6 +89,8 @@ + #endif + #ifdef CONFIG_CRYPTO_DEV_ASPEED_HACE_CRYPTO + aspeed_register_hace_crypto_algs(hace_dev); ++ aspeed_register_hace_aead_algs(hace_dev); ++ aspeed_register_hace_vault_key(hace_dev); + #endif + } + +@@ -93,30 +107,31 @@ + static const struct of_device_id aspeed_hace_of_matches[] = { + { .compatible = "aspeed,ast2500-hace", .data = (void *)5, }, + { .compatible = "aspeed,ast2600-hace", .data = (void *)6, }, ++ { .compatible = "aspeed,ast2700-hace", .data = (void *)7, }, + {}, + }; + + static int aspeed_hace_probe(struct platform_device *pdev) + { +- struct aspeed_engine_crypto *crypto_engine; +- struct aspeed_engine_hash *hash_engine; ++ const struct of_device_id *hace_dev_id; + struct aspeed_hace_dev *hace_dev; ++ struct device *dev = &pdev->dev; + int rc; + ++ /* Allocate and register hace driver in linux kernel */ + hace_dev = devm_kzalloc(&pdev->dev, sizeof(struct aspeed_hace_dev), + GFP_KERNEL); + if (!hace_dev) + return -ENOMEM; + +- hace_dev->version = (uintptr_t)device_get_match_data(&pdev->dev); +- if (!hace_dev->version) { ++ hace_dev_id = of_match_device(aspeed_hace_of_matches, &pdev->dev); ++ if (!hace_dev_id) { + dev_err(&pdev->dev, "Failed to match hace dev id\n"); + return -EINVAL; + } + + hace_dev->dev = &pdev->dev; +- hash_engine = &hace_dev->hash_engine; +- crypto_engine = &hace_dev->crypto_engine; ++ hace_dev->version = (unsigned long)hace_dev_id->data; + + platform_set_drvdata(pdev, hace_dev); + +@@ -124,6 +139,10 @@ + if (IS_ERR(hace_dev->regs)) + return PTR_ERR(hace_dev->regs); + ++ hace_dev->sbc = devm_platform_get_and_ioremap_resource(pdev, 1, NULL); ++ if (IS_ERR(hace_dev->sbc)) ++ return PTR_ERR(hace_dev->sbc); ++ + /* Get irq number and register it */ + hace_dev->irq = platform_get_irq(pdev, 0); + if (hace_dev->irq < 0) +@@ -149,115 +168,68 @@ + return rc; + } + +- /* Initialize crypto hardware engine structure for hash */ +- hace_dev->crypt_engine_hash = crypto_engine_alloc_init(hace_dev->dev, +- true); +- if (!hace_dev->crypt_engine_hash) { +- rc = -ENOMEM; +- goto clk_exit; +- } +- +- rc = crypto_engine_start(hace_dev->crypt_engine_hash); +- if (rc) +- goto err_engine_hash_start; +- +- tasklet_init(&hash_engine->done_task, aspeed_hace_hash_done_task, +- (unsigned long)hace_dev); +- +- /* Initialize crypto hardware engine structure for crypto */ +- hace_dev->crypt_engine_crypto = crypto_engine_alloc_init(hace_dev->dev, +- true); +- if (!hace_dev->crypt_engine_crypto) { +- rc = -ENOMEM; +- goto err_engine_hash_start; +- } +- +- rc = crypto_engine_start(hace_dev->crypt_engine_crypto); +- if (rc) +- goto err_engine_crypto_start; +- +- tasklet_init(&crypto_engine->done_task, aspeed_hace_crypto_done_task, +- (unsigned long)hace_dev); +- +- /* Allocate DMA buffer for hash engine input used */ +- hash_engine->ahash_src_addr = +- dmam_alloc_coherent(&pdev->dev, +- ASPEED_HASH_SRC_DMA_BUF_LEN, +- &hash_engine->ahash_src_dma_addr, +- GFP_KERNEL); +- if (!hash_engine->ahash_src_addr) { +- dev_err(&pdev->dev, "Failed to allocate dma buffer\n"); +- rc = -ENOMEM; +- goto err_engine_crypto_start; +- } +- +- /* Allocate DMA buffer for crypto engine context used */ +- crypto_engine->cipher_ctx = +- dmam_alloc_coherent(&pdev->dev, +- PAGE_SIZE, +- &crypto_engine->cipher_ctx_dma, +- GFP_KERNEL); +- if (!crypto_engine->cipher_ctx) { +- dev_err(&pdev->dev, "Failed to allocate cipher ctx dma\n"); +- rc = -ENOMEM; +- goto err_engine_crypto_start; +- } +- +- /* Allocate DMA buffer for crypto engine input used */ +- crypto_engine->cipher_addr = +- dmam_alloc_coherent(&pdev->dev, +- ASPEED_CRYPTO_SRC_DMA_BUF_LEN, +- &crypto_engine->cipher_dma_addr, +- GFP_KERNEL); +- if (!crypto_engine->cipher_addr) { +- dev_err(&pdev->dev, "Failed to allocate cipher addr dma\n"); +- rc = -ENOMEM; +- goto err_engine_crypto_start; +- } +- +- /* Allocate DMA buffer for crypto engine output used */ +- if (hace_dev->version == AST2600_VERSION) { +- crypto_engine->dst_sg_addr = +- dmam_alloc_coherent(&pdev->dev, +- ASPEED_CRYPTO_DST_DMA_BUF_LEN, +- &crypto_engine->dst_sg_dma_addr, +- GFP_KERNEL); +- if (!crypto_engine->dst_sg_addr) { +- dev_err(&pdev->dev, "Failed to allocate dst_sg dma\n"); +- rc = -ENOMEM; +- goto err_engine_crypto_start; +- } ++ /* Get rst and de-assert reset */ ++ hace_dev->rst = devm_reset_control_get_shared(dev, NULL); ++ if (IS_ERR(hace_dev->rst)) { ++ dev_err(&pdev->dev, "Failed to get hace reset\n"); ++ return PTR_ERR(hace_dev->rst); + } + ++ rc = reset_control_deassert(hace_dev->rst); ++ if (rc) { ++ dev_err(&pdev->dev, "Deassert hace reset failed\n"); ++ return rc; ++ } ++ ++ rc = aspeed_hace_reset(hace_dev); ++ if (rc) { ++ dev_err(&pdev->dev, "Hace reset failed\n"); ++ return rc; ++ } ++ ++ rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_warn(&pdev->dev, "No suitable DMA available\n"); ++ return rc; ++ } ++ ++ /* Init mutex lock for supporting hace concurrent*/ ++ sema_init(&hace_dev->lock, 1); ++ ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_HACE_HASH ++ rc = aspeed_hace_hash_init(hace_dev); ++ if (rc) { ++ dev_err(&pdev->dev, "Hash init failed\n"); ++ return rc; ++ } ++#endif ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_HACE_CRYPTO ++ rc = aspeed_hace_crypto_init(hace_dev); ++ if (rc) { ++ dev_err(&pdev->dev, "Crypto init failed\n"); ++ return rc; ++ } ++#endif + aspeed_hace_register(hace_dev); + + dev_info(&pdev->dev, "Aspeed Crypto Accelerator successfully registered\n"); + + return 0; +- +-err_engine_crypto_start: +- crypto_engine_exit(hace_dev->crypt_engine_crypto); +-err_engine_hash_start: +- crypto_engine_exit(hace_dev->crypt_engine_hash); +-clk_exit: +- clk_disable_unprepare(hace_dev->clk); +- +- return rc; + } + + static void aspeed_hace_remove(struct platform_device *pdev) + { + struct aspeed_hace_dev *hace_dev = platform_get_drvdata(pdev); +- struct aspeed_engine_crypto *crypto_engine = &hace_dev->crypto_engine; +- struct aspeed_engine_hash *hash_engine = &hace_dev->hash_engine; + + aspeed_hace_unregister(hace_dev); + +- crypto_engine_exit(hace_dev->crypt_engine_hash); +- crypto_engine_exit(hace_dev->crypt_engine_crypto); ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_HACE_HASH ++ aspeed_hace_hash_remove(hace_dev); ++#endif + +- tasklet_kill(&hash_engine->done_task); +- tasklet_kill(&crypto_engine->done_task); ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_HACE_CRYPTO ++ aspeed_hace_crypto_remove(hace_dev); ++#endif + + clk_disable_unprepare(hace_dev->clk); + } +@@ -266,7 +238,7 @@ + + static struct platform_driver aspeed_hace_driver = { + .probe = aspeed_hace_probe, +- .remove_new = aspeed_hace_remove, ++ .remove = aspeed_hace_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_hace_of_matches, +diff --git a/drivers/crypto/aspeed/aspeed-hace.h b/drivers/crypto/aspeed/aspeed-hace.h +--- a/drivers/crypto/aspeed/aspeed-hace.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-hace.h 2026-09-09 15:51:44.737237784 +0000 +@@ -10,6 +10,8 @@ + #include + #include + #include ++#include ++#include + + /***************************** + * * +@@ -23,7 +25,7 @@ + #define ASPEED_HACE_CMD 0x10 /* Crypto Engine Command Register */ + + /* G5 */ +-#define ASPEED_HACE_TAG 0x18 /* HACE Tag Register */ ++#define ASPEED_HACE_TAG 0x18 /* HACE Tag Write Buffer Base Address Register */ + /* G6 */ + #define ASPEED_HACE_GCM_ADD_LEN 0x14 /* Crypto AES-GCM Additional Data Length Register */ + #define ASPEED_HACE_GCM_TAG_BASE_ADDR 0x18 /* Crypto AES-GCM Tag Write Buff Base Address Reg */ +@@ -36,6 +38,22 @@ + #define ASPEED_HACE_HASH_DATA_LEN 0x2C /* Hash Data Length Register */ + #define ASPEED_HACE_HASH_CMD 0x30 /* Hash Engine Command Register */ + ++/* G7 */ ++#define ASPEED_HACE_SRC_H 0x80 /* Crypto Data Source Base High Address Register */ ++#define ASPEED_HACE_DEST_H 0x84 /* Crypto Data Destination Base High Address Register */ ++#define ASPEED_HACE_CONTEXT_H 0x88 /* Crypto Context Buffer Base High Address Register */ ++#define ASPEED_HACE_TAG_H 0x8C /* HACE Tag Write Buffer Base High Address Register */ ++#define ASPEED_HACE_HASH_SRC_H 0x90 /* Hash Data Source Base High Address Register */ ++#define ASPEED_HACE_HASH_DIGEST_BUFF_H 0x94 /* Hash Digest Write Buffer Base High Address Register */ ++#define ASPEED_HACE_HASH_KEY_BUFF_H 0x98 /* Hash HMAC Key Buffer Base High Address Register */ ++ ++/***************************** ++ * * ++ * SBC register definitions * ++ * * ++ * ***************************/ ++#define ASPEED_VAULT_KEY_CTRL 0x00C ++ + /* crypto cmd */ + #define HACE_CMD_SINGLE_DES 0 + #define HACE_CMD_TRIPLE_DES BIT(17) +@@ -109,8 +127,9 @@ + + #define CRYPTO_FLAGS_BUSY BIT(1) + +-#define SHA_OP_UPDATE 1 +-#define SHA_OP_FINAL 2 ++#define SHA_OP_INIT BIT(0) ++#define SHA_OP_UPDATE BIT(1) ++#define SHA_OP_FINAL BIT(2) + + #define SHA_FLAGS_SHA1 BIT(0) + #define SHA_FLAGS_SHA224 BIT(1) +@@ -129,8 +148,19 @@ + #define ASPEED_HASH_SRC_DMA_BUF_LEN 0xa000 + #define ASPEED_HASH_QUEUE_LENGTH 50 + +-#define HACE_CMD_IV_REQUIRE (HACE_CMD_CBC | HACE_CMD_CFB | \ +- HACE_CMD_OFB | HACE_CMD_CTR) ++#define HACE_CMD_IV_REQUIRE \ ++ (HACE_CMD_CBC | HACE_CMD_CFB | HACE_CMD_OFB | HACE_CMD_CTR) ++#define ASPEED_HACE_CTX_KEY_OFFSET (0x10) ++#define ASPEED_HACE_CTX_128_SUBKEY_OFFSET (0x20) ++#define ASPEED_HACE_CTX_256_SUBKEY_OFFSET (0x30) ++#define AES_GCM_TAG_SIZE (16) ++ ++#define ASPEED_VAULT_KEY_NOT_FOUND (0xffffffff) ++#define ASPEED_G6_VAULT_KEY_MAX_NUM 2 ++#define ASPEED_G7_VAULT_KEY_MAX_NUM 4 ++#define DUMMY_KEY_SIZE 32 ++#define ASPEED_VAULT_KEY_DTS \ ++ "dummy-key0", "dummy-key1", "dummy-key2", "dummy-key3" + + struct aspeed_hace_dev; + struct scatterlist; +@@ -147,10 +177,21 @@ + unsigned long flags; + struct ahash_request *req; + ++ /* Protects hash engine operation enqueue in order */ ++ struct mutex queue_lock; ++ + /* input buffer */ + void *ahash_src_addr; + dma_addr_t ahash_src_dma_addr; + ++ /* remain data buffer */ ++ u8 *buffer_addr; ++ dma_addr_t buffer_dma_addr; ++ ++ /* output buffer */ ++ void *digest_addr; ++ dma_addr_t digest_dma_addr; ++ + dma_addr_t src_dma; + dma_addr_t digest_dma; + +@@ -192,12 +233,10 @@ + + /* remain data buffer */ + u8 buffer[SHA512_BLOCK_SIZE * 2]; +- dma_addr_t buffer_dma_addr; + size_t bufcnt; /* buffer counter */ + + /* output buffer */ +- u8 digest[SHA512_DIGEST_SIZE] __aligned(64); +- dma_addr_t digest_dma_addr; ++ u8 digest[SHA512_DIGEST_SIZE]; + u64 digcnt[2]; + }; + +@@ -205,6 +244,7 @@ + struct tasklet_struct done_task; + unsigned long flags; + struct skcipher_request *req; ++ struct aead_request *aead_req; + + /* context buffer */ + void *cipher_ctx; +@@ -218,36 +258,54 @@ + void *dst_sg_addr; + dma_addr_t dst_sg_dma_addr; + ++ /* tag buffer, only used in scatter-gather lists */ ++ void *tag_addr; ++ dma_addr_t tag_dma_addr; ++ ++ /* Vault key */ ++ u32 *dummy_key[4]; ++ int vault_key_num; ++ + /* callback func */ +- aspeed_hace_fn_t resume; ++ aspeed_hace_fn_t resume; + }; + + struct aspeed_cipher_ctx { + struct aspeed_hace_dev *hace_dev; + int key_len; + u8 key[AES_MAX_KEYLENGTH]; ++ u32 key_idx; + + /* callback func */ + aspeed_hace_fn_t start; + +- struct crypto_skcipher *fallback_tfm; ++ struct crypto_aead *fallback_aead_tfm; ++ struct crypto_skcipher *fallback_tfm; + }; + + struct aspeed_cipher_reqctx { + int enc_cmd; + int src_nents; ++ int src_sg_len; + int dst_nents; ++ int dst_sg_len; + +- struct skcipher_request fallback_req; /* keep at the end */ ++ struct aead_request fallback_aead_req; ++ struct skcipher_request fallback_req; /* keep at the end */ + }; + + struct aspeed_hace_dev { + void __iomem *regs; ++ void __iomem *sbc; + struct device *dev; + int irq; + struct clk *clk; ++ struct reset_control *rst; + unsigned long version; + ++ /* Protects hace register access */ ++ struct semaphore lock; ++ + struct crypto_engine *crypt_engine_hash; + struct crypto_engine *crypt_engine_crypto; + +@@ -262,13 +320,15 @@ + + union { + struct skcipher_engine_alg skcipher; ++ struct aead_engine_alg aead; + struct ahash_engine_alg ahash; + } alg; + }; + + enum aspeed_version { + AST2500_VERSION = 5, +- AST2600_VERSION ++ AST2600_VERSION, ++ AST2700_VERSION, + }; + + #define ast_hace_write(hace, val, offset) \ +@@ -276,9 +336,21 @@ + #define ast_hace_read(hace, offset) \ + readl((hace)->regs + (offset)) + ++#define ast_sbc_write(hace, val, offset) \ ++ writel((val), (hace)->sbc + (offset)) ++#define ast_sbc_read(hace, offset) \ ++ readl((hace)->sbc + (offset)) ++ + void aspeed_register_hace_hash_algs(struct aspeed_hace_dev *hace_dev); + void aspeed_unregister_hace_hash_algs(struct aspeed_hace_dev *hace_dev); + void aspeed_register_hace_crypto_algs(struct aspeed_hace_dev *hace_dev); ++void aspeed_register_hace_aead_algs(struct aspeed_hace_dev *hace_dev); ++void aspeed_register_hace_vault_key(struct aspeed_hace_dev *hace_dev); + void aspeed_unregister_hace_crypto_algs(struct aspeed_hace_dev *hace_dev); ++int aspeed_hace_hash_init(struct aspeed_hace_dev *hace_dev); ++void aspeed_hace_hash_remove(struct aspeed_hace_dev *hace_dev); ++int aspeed_hace_crypto_init(struct aspeed_hace_dev *hace_dev); ++void aspeed_hace_crypto_remove(struct aspeed_hace_dev *hace_dev); ++int aspeed_hace_reset(struct aspeed_hace_dev *dev); + + #endif +diff --git a/drivers/crypto/aspeed/aspeed-rsss-hash.c b/drivers/crypto/aspeed/aspeed-rsss-hash.c +--- a/drivers/crypto/aspeed/aspeed-rsss-hash.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-rsss-hash.c 2026-09-09 15:51:44.737237784 +0000 +@@ -0,0 +1,901 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include "aspeed-rsss.h" ++ ++//#define RSSS_SHA3_POLLING_MODE ++ ++static int aspeed_sha3_self_test(struct aspeed_rsss_dev *rsss_dev) ++{ ++ u32 pattern = 0xbeef; ++ u32 val; ++ ++ ast_rsss_write(rsss_dev, pattern, ASPEED_SHA3_SRC_LO); ++ val = ast_rsss_read(rsss_dev, ASPEED_SHA3_SRC_LO); ++ if (val != pattern) ++ return -EIO; ++ ++ ast_rsss_write(rsss_dev, 0x0, ASPEED_SHA3_SRC_LO); ++ val = ast_rsss_read(rsss_dev, ASPEED_SHA3_SRC_LO); ++ if (val) ++ return -EIO; ++ ++ return 0; ++} ++ ++static int aspeed_sha3_dma_prepare(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx; ++ int length, remain; ++ ++ rctx = ahash_request_ctx(req); ++ remain = (rctx->total + rctx->bufcnt) % rctx->blksize; ++ length = rctx->total + rctx->bufcnt - remain; ++ ++ RSSS_DBG(rsss_dev, "%s:0x%x, %s:%zu, %s:0x%x, %s:0x%x, %s:0x%x\n", ++ "rctx total", rctx->total, "bufcnt", rctx->bufcnt, ++ "offset", rctx->offset, "length", length, ++ "remain", remain); ++ ++ if (rctx->bufcnt) ++ memcpy(sha3_engine->ahash_src_addr, rctx->buffer, ++ rctx->bufcnt); ++ ++ if (length < ASPEED_HASH_SRC_DMA_BUF_LEN) { ++ scatterwalk_map_and_copy(sha3_engine->ahash_src_addr + rctx->bufcnt, ++ rctx->src_sg, rctx->offset, ++ rctx->total - remain, 0); ++ rctx->offset += rctx->total - remain; ++ ++ } else { ++ dev_warn(rsss_dev->dev, "SHA3 input data length is too large\n"); ++ return -EINVAL; ++ } ++ ++ /* Copy remain data into buffer */ ++ scatterwalk_map_and_copy(rctx->buffer, rctx->src_sg, ++ rctx->offset, remain, 0); ++ rctx->bufcnt = remain; ++ ++ sha3_engine->src_length = length; ++ sha3_engine->src_dma = sha3_engine->ahash_src_dma_addr; ++ ++ return 0; ++} ++ ++/* ++ * Prepare DMA buffer as SG list buffer before ++ * hardware engine processing. ++ */ ++static int aspeed_sha3_dma_prepare_sg(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx; ++ struct aspeed_sg_list *src_list; ++ struct scatterlist *s; ++ int length, remain, sg_len; ++ int i, rc = 0; ++ ++ rctx = ahash_request_ctx(req); ++ remain = (rctx->total + rctx->bufcnt) % rctx->blksize; ++ length = rctx->total + rctx->bufcnt - remain; ++ ++ RSSS_DBG(rsss_dev, "%s:0x%x, %s:%zu, %s:0x%x, %s:0x%x\n", ++ "rctx total", rctx->total, "bufcnt", rctx->bufcnt, ++ "length", length, "remain", remain); ++ ++ sg_len = dma_map_sg(rsss_dev->dev, rctx->src_sg, rctx->src_nents, ++ DMA_TO_DEVICE); ++ /* ++ * Need dma_sync_sg_for_device()? ++ */ ++ if (!sg_len) { ++ dev_warn(rsss_dev->dev, "dma_map_sg() src error\n"); ++ rc = -ENOMEM; ++ goto end; ++ } ++ ++ src_list = (struct aspeed_sg_list *)sha3_engine->ahash_src_addr; ++ ++ if (rctx->bufcnt != 0) { ++ u64 phy_addr; ++ u32 len; ++ ++ phy_addr = sha3_engine->buffer_dma_addr; ++ len = rctx->bufcnt; ++ length -= len; ++ ++ /* Last sg list */ ++ if (length == 0) ++ len |= SG_LAST_LIST; ++ ++ src_list[0].phy_addr = cpu_to_le64(phy_addr); ++ src_list[0].len = cpu_to_le32(len); ++ ++ RSSS_DBG(rsss_dev, "Remain buffer first, addr:%llx, len:0x%x\n", ++ src_list[0].phy_addr, src_list[0].len); ++ ++ src_list++; ++ } ++ ++ if (length != 0) { ++ for_each_sg(rctx->src_sg, s, sg_len, i) { ++ u64 phy_addr = sg_dma_address(s); ++ u32 len = sg_dma_len(s); ++ u8 *va = sg_virt(s); ++ ++ RSSS_DBG(rsss_dev, "SG[%d] PA:%llx, VA:%llx, len:0x%x\n", ++ i, sg_dma_address(s), (u64)va, len); ++ ++ if (length > len) { ++ length -= len; ++ } else { ++ /* Last sg list */ ++ len = length; ++ len |= SG_LAST_LIST; ++ length = 0; ++ } ++ ++ src_list[i].phy_addr = cpu_to_le64(phy_addr); ++ src_list[i].len = cpu_to_le32(len); ++ ++ len = len & 0xffff; ++ } ++ } ++ ++ if (length != 0) { ++ rc = -EINVAL; ++ goto free_src_sg; ++ } ++ ++ rctx->offset = rctx->total - remain; ++ sha3_engine->src_length = rctx->total + rctx->bufcnt - remain; ++ sha3_engine->src_dma = sha3_engine->ahash_src_dma_addr; ++ ++ return 0; ++ ++free_src_sg: ++ RSSS_DBG(rsss_dev, "dma_unmap_sg()\n"); ++ dma_unmap_sg(rsss_dev->dev, rctx->src_sg, rctx->src_nents, ++ DMA_TO_DEVICE); ++end: ++ return rc; ++} ++ ++static int aspeed_sha3_complete(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ sha3_engine->flags &= ~CRYPTO_FLAGS_BUSY; ++ ++ crypto_finalize_hash_request(rsss_dev->crypt_engine_sha3, req, 0); ++ ++ return 0; ++} ++ ++/* ++ * Copy digest to the corresponding request result. ++ * This function will be called at final() stage. ++ */ ++static int aspeed_sha3_transfer(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ rctx = ahash_request_ctx(req); ++ ++ /* add usleep for DMA done */ ++ udelay(8); ++ memcpy(req->result, sha3_engine->digest_addr, rctx->digsize); ++ ++ return aspeed_sha3_complete(rsss_dev); ++} ++ ++#ifdef RSSS_SHA3_POLLING_MODE ++static int aspeed_sha3_wait_complete(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ u32 sts; ++ int ret; ++ ++ ret = readl_poll_timeout(rsss_dev->regs + ASPEED_SHA3_BUSY_STS, sts, ++ ((sts & SHA3_STS) == 0x0), ++ ASPEED_RSSS_POLLING_TIME, ++ ASPEED_RSSS_TIMEOUT * 10); ++ if (ret) { ++ dev_err(rsss_dev->dev, "SHA3 wrong engine status\n"); ++ return -EIO; ++ } ++ ++ ret = readl_poll_timeout(rsss_dev->regs + ASPEED_RSSS_INT_STS, sts, ++ ((sts & SHA3_INT_DONE) == SHA3_INT_DONE), ++ ASPEED_RSSS_POLLING_TIME, ++ ASPEED_RSSS_TIMEOUT); ++ if (ret) { ++ dev_err(rsss_dev->dev, "SHA3 wrong interrupt status\n"); ++ return -EIO; ++ } ++ ++ ast_rsss_write(rsss_dev, sts, ASPEED_RSSS_INT_STS); ++ ++ RSSS_DBG(rsss_dev, "irq sts:0x%x\n", sts); ++ ++ if (sts & SHA3_INT_DONE) { ++ if (sha3_engine->flags & CRYPTO_FLAGS_BUSY) ++ tasklet_schedule(&sha3_engine->done_task); ++ else ++ dev_err(rsss_dev->dev, "SHA3 no active requests.\n"); ++ } ++ ++ return 0; ++} ++#endif ++ ++/* ++ * Trigger hardware engines to do the math. ++ */ ++static int aspeed_sha3_trigger(struct aspeed_rsss_dev *rsss_dev, ++ aspeed_rsss_fn_t resume) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx; ++ ++ RSSS_DBG(rsss_dev, "src_dma:%pad, digest_dma:%pad, length:%zu\n", ++ &sha3_engine->src_dma, &sha3_engine->digest_dma_addr, ++ sha3_engine->src_length); ++ ++ rctx = ahash_request_ctx(req); ++ sha3_engine->resume = resume; ++ ++ memcpy(sha3_engine->digest_addr, rctx->digest, rctx->digsize); ++ memcpy(sha3_engine->buffer_addr, rctx->buffer, rctx->bufcnt); ++ ++ ast_rsss_write(rsss_dev, sha3_engine->src_dma, ++ ASPEED_SHA3_SRC_LO); ++ /* TODO - SRC_HI */ ++ ast_rsss_write(rsss_dev, sha3_engine->src_dma >> 32, ++ ASPEED_SHA3_SRC_HI); ++ ++ ast_rsss_write(rsss_dev, sha3_engine->digest_dma_addr, ++ ASPEED_SHA3_DST_LO); ++ /* TODO - DST_HI */ ++ ast_rsss_write(rsss_dev, sha3_engine->digest_dma_addr >> 32, ++ ASPEED_SHA3_DST_HI); ++ ++ if (!sha3_engine->sg_mode) ++ ast_rsss_write(rsss_dev, sha3_engine->src_length, ++ ASPEED_SHA3_SRC_LEN); ++ ++ ast_rsss_write(rsss_dev, rctx->cmd, ASPEED_SHA3_CMD); ++ ++ /* Memory barrier to ensure all data setup before engine starts */ ++ mb(); ++ ++ rctx->cmd |= SHA3_CMD_TRIG; ++ ++ RSSS_DBG(rsss_dev, "cmd:0x%x\n", rctx->cmd); ++ ++ ast_rsss_write(rsss_dev, rctx->cmd, ASPEED_SHA3_CMD); ++ ++#ifdef RSSS_SHA3_POLLING_MODE ++ return aspeed_sha3_wait_complete(rsss_dev); ++#else ++ return -EINPROGRESS; ++#endif ++} ++ ++static int aspeed_sha3_req_final(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ int remain_pad; ++ u8 *src; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ /* A0 padding issue */ ++ remain_pad = rctx->blksize - rctx->bufcnt; ++ if (remain_pad < 16) { ++ /* SW padding */ ++ RSSS_DBG(rsss_dev, "Use SW padding, pad size:0x%x\n", ++ remain_pad); ++ src = (u8 *)rctx->buffer; ++ src[rctx->bufcnt] = 0x06; ++ memset(src + rctx->bufcnt + 1, 0, remain_pad - 1); ++ src[rctx->bufcnt + remain_pad - 1] |= 0x80; ++ ++ rctx->bufcnt += remain_pad; ++ ++ } else { ++ rctx->cmd |= SHA3_CMD_HW_PAD; ++ } ++ ++ if (sha3_engine->sg_mode) { ++ struct aspeed_sg_list *src_list = ++ (struct aspeed_sg_list *)sha3_engine->ahash_src_addr; ++ u64 phy_addr; ++ u32 len; ++ ++ phy_addr = sha3_engine->buffer_dma_addr; ++ len = rctx->bufcnt; ++ len |= SG_LAST_LIST; ++ ++ src_list[0].phy_addr = cpu_to_le64(phy_addr); ++ src_list[0].len = cpu_to_le32(len); ++ ++ RSSS_DBG(rsss_dev, "Final SG, addr:%llx, len:0x%x\n", ++ src_list[0].phy_addr, src_list[0].len); ++ ++ rctx->cmd |= SHA3_CMD_SG_MODE; ++ sha3_engine->src_dma = sha3_engine->ahash_src_dma_addr; ++ ++ } else { ++ sha3_engine->src_dma = sha3_engine->buffer_dma_addr; ++ sha3_engine->src_length = rctx->bufcnt; ++ } ++ ++ rctx->cmd |= SHA3_CMD_ACC_FINAL; ++ ++ return aspeed_sha3_trigger(rsss_dev, aspeed_sha3_transfer); ++} ++ ++static int aspeed_sha3_update_resume(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ rctx = ahash_request_ctx(req); ++ ++ memcpy(rctx->digest, sha3_engine->digest_addr, rctx->digsize); ++ rctx->cmd &= ~SHA3_CMD_TRIG; ++ ++ if (rctx->flags & SHA3_FLAGS_FINUP) ++ return aspeed_sha3_req_final(rsss_dev); ++ ++ return aspeed_sha3_complete(rsss_dev); ++} ++ ++static int aspeed_sha3_update_resume_sg(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx; ++ int remain; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ rctx = ahash_request_ctx(req); ++ ++ memcpy(rctx->digest, sha3_engine->digest_addr, rctx->digsize); ++ remain = rctx->total - rctx->offset; ++ ++ RSSS_DBG(rsss_dev, "Copy remain data from 0x%x, size:0x%x\n", ++ rctx->offset, remain); ++ ++ dma_unmap_sg(rsss_dev->dev, rctx->src_sg, rctx->src_nents, ++ DMA_TO_DEVICE); ++ ++ scatterwalk_map_and_copy(rctx->buffer, rctx->src_sg, rctx->offset, ++ remain, 0); ++ ++ rctx->bufcnt = remain; ++ rctx->cmd &= ~(SHA3_CMD_TRIG | SHA3_CMD_SG_MODE); ++ ++ if (rctx->flags & SHA3_FLAGS_FINUP) ++ return aspeed_sha3_req_final(rsss_dev); ++ ++ return aspeed_sha3_complete(rsss_dev); ++} ++ ++static int aspeed_sha3_req_update(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct ahash_request *req = sha3_engine->req; ++ struct aspeed_sha3_reqctx *rctx; ++ aspeed_rsss_fn_t resume; ++ int ret; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ rctx = ahash_request_ctx(req); ++ ++ if (sha3_engine->sg_mode) { ++ rctx->cmd |= SHA3_CMD_SG_MODE; ++ resume = aspeed_sha3_update_resume_sg; ++ ++ } else { ++ resume = aspeed_sha3_update_resume; ++ } ++ ++ ret = sha3_engine->dma_prepare(rsss_dev); ++ if (ret) ++ return ret; ++ ++ return aspeed_sha3_trigger(rsss_dev, resume); ++} ++ ++static int aspeed_sha3_do_request(struct crypto_engine *engine, void *areq) ++{ ++ struct ahash_request *req = ahash_request_cast(areq); ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); ++ struct aspeed_sha3_ctx *tctx = crypto_ahash_ctx(tfm); ++ struct aspeed_rsss_dev *rsss_dev = tctx->rsss_dev; ++ struct aspeed_engine_sha3 *sha3_engine; ++ int ret = 0; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ sha3_engine = &rsss_dev->sha3_engine; ++ sha3_engine->flags |= CRYPTO_FLAGS_BUSY; ++ sha3_engine->req = req; ++ ++ if (sha3_engine->sg_mode) ++ sha3_engine->dma_prepare = aspeed_sha3_dma_prepare_sg; ++ else ++ sha3_engine->dma_prepare = aspeed_sha3_dma_prepare; ++ ++ if (rctx->op == SHA_OP_UPDATE) ++ ret = aspeed_sha3_req_update(rsss_dev); ++ else if (rctx->op == SHA_OP_FINAL) ++ ret = aspeed_sha3_req_final(rsss_dev); ++ ++ if (ret != -EINPROGRESS) ++ return ret; ++ ++ return 0; ++} ++ ++static int aspeed_sha3_handle_queue(struct aspeed_rsss_dev *rsss_dev, ++ struct ahash_request *req) ++{ ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ int ret = 0; ++ ++ if (rctx->op & SHA_OP_INIT) { ++ mutex_lock(&sha3_engine->queue_lock); ++ return 0; ++ } ++ ++ ret = crypto_transfer_hash_request_to_engine(rsss_dev->crypt_engine_sha3, ++ req); ++ ++ /* The last request is enqueued, release the lock */ ++ if (rctx->op & SHA_OP_FINAL || rctx->flags & SHA3_FLAGS_FINUP) ++ mutex_unlock(&sha3_engine->queue_lock); ++ ++ return ret; ++} ++ ++static int aspeed_sha3_update(struct ahash_request *req) ++{ ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); ++ struct aspeed_sha3_ctx *tctx = crypto_ahash_ctx(tfm); ++ struct aspeed_rsss_dev *rsss_dev = tctx->rsss_dev; ++ struct aspeed_engine_sha3 *sha3_engine; ++ ++ RSSS_DBG(rsss_dev, "req->nbytes: %d\n", req->nbytes); ++ ++ sha3_engine = &rsss_dev->sha3_engine; ++ ++ rctx->total = req->nbytes; ++ rctx->src_sg = req->src; ++ rctx->offset = 0; ++ rctx->src_nents = sg_nents(req->src); ++ rctx->op = SHA_OP_UPDATE; ++ ++ RSSS_DBG(rsss_dev, "total:0x%x, src_nents:0x%x\n", rctx->total, rctx->src_nents); ++ ++ rctx->digcnt[0] += rctx->total; ++ if (rctx->digcnt[0] < rctx->total) ++ rctx->digcnt[1]++; ++ ++ if (rctx->bufcnt + rctx->total < rctx->blksize) { ++ scatterwalk_map_and_copy(rctx->buffer + rctx->bufcnt, ++ rctx->src_sg, rctx->offset, ++ rctx->total, 0); ++ rctx->bufcnt += rctx->total; ++ ++ return 0; ++ } ++ ++ return aspeed_sha3_handle_queue(rsss_dev, req); ++} ++ ++static int aspeed_sha3_final(struct ahash_request *req) ++{ ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); ++ struct aspeed_sha3_ctx *tctx = crypto_ahash_ctx(tfm); ++ struct aspeed_rsss_dev *rsss_dev = tctx->rsss_dev; ++ ++ RSSS_DBG(rsss_dev, "req->nbytes:%d, rctx->total:%d\n", ++ req->nbytes, rctx->total); ++ rctx->op = SHA_OP_FINAL; ++ ++ return aspeed_sha3_handle_queue(rsss_dev, req); ++} ++ ++static int aspeed_sha3_finup(struct ahash_request *req) ++{ ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); ++ struct aspeed_sha3_ctx *tctx = crypto_ahash_ctx(tfm); ++ struct aspeed_rsss_dev *rsss_dev = tctx->rsss_dev; ++ int rc1, rc2; ++ ++ RSSS_DBG(rsss_dev, "req->nbytes: %d\n", req->nbytes); ++ ++ rctx->flags |= SHA3_FLAGS_FINUP; ++ ++ rc1 = aspeed_sha3_update(req); ++ if (rc1 == -EINPROGRESS || rc1 == -EBUSY) ++ return rc1; ++ ++ /* ++ * final() has to be always called to cleanup resources ++ * even if update() failed, except EINPROGRESS ++ */ ++ rc2 = aspeed_sha3_final(req); ++ ++ return rc1 ? : rc2; ++} ++ ++static int aspeed_sha3_init(struct ahash_request *req) ++{ ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); ++ struct aspeed_sha3_ctx *tctx = crypto_ahash_ctx(tfm); ++ struct aspeed_rsss_dev *rsss_dev = tctx->rsss_dev; ++ ++ RSSS_DBG(rsss_dev, "%s: digest size:%d\n", ++ crypto_tfm_alg_name(&tfm->base), ++ crypto_ahash_digestsize(tfm)); ++ ++ rctx->cmd = SHA3_CMD_ACC; ++ rctx->op = SHA_OP_INIT; ++ rctx->flags = 0; ++ ++ switch (crypto_ahash_digestsize(tfm)) { ++ case SHA3_224_DIGEST_SIZE: ++ rctx->cmd |= SHA3_CMD_MODE_224; ++ rctx->flags |= SHA3_FLAGS_SHA224; ++ rctx->digsize = SHA3_224_DIGEST_SIZE; ++ rctx->blksize = SHA3_224_BLOCK_SIZE; ++ break; ++ case SHA3_256_DIGEST_SIZE: ++ rctx->cmd |= SHA3_CMD_MODE_256; ++ rctx->flags |= SHA3_FLAGS_SHA256; ++ rctx->digsize = SHA3_256_DIGEST_SIZE; ++ rctx->blksize = SHA3_256_BLOCK_SIZE; ++ break; ++ case SHA3_384_DIGEST_SIZE: ++ rctx->cmd |= SHA3_CMD_MODE_384; ++ rctx->flags |= SHA3_FLAGS_SHA384; ++ rctx->digsize = SHA3_384_DIGEST_SIZE; ++ rctx->blksize = SHA3_384_BLOCK_SIZE; ++ break; ++ case SHA3_512_DIGEST_SIZE: ++ rctx->cmd |= SHA3_CMD_MODE_512; ++ rctx->flags |= SHA3_FLAGS_SHA512; ++ rctx->digsize = SHA3_512_DIGEST_SIZE; ++ rctx->blksize = SHA3_512_BLOCK_SIZE; ++ break; ++ default: ++ dev_warn(tctx->rsss_dev->dev, "digest size %d not support\n", ++ crypto_ahash_digestsize(tfm)); ++ return -EINVAL; ++ } ++ ++ rctx->bufcnt = 0; ++ rctx->total = 0; ++ rctx->digcnt[0] = 0; ++ rctx->digcnt[1] = 0; ++ ++ memset(rctx->digest, 0x0, SHA3_512_DIGEST_SIZE); ++ ++ return aspeed_sha3_handle_queue(rsss_dev, req); ++} ++ ++static int aspeed_sha3_digest(struct ahash_request *req) ++{ ++ return aspeed_sha3_init(req) ? : aspeed_sha3_finup(req); ++} ++ ++static int aspeed_sha3_export(struct ahash_request *req, void *out) ++{ ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ ++ memcpy(out, rctx, sizeof(*rctx)); ++ ++ return 0; ++} ++ ++static int aspeed_sha3_import(struct ahash_request *req, const void *in) ++{ ++ struct aspeed_sha3_reqctx *rctx = ahash_request_ctx(req); ++ ++ memcpy(rctx, in, sizeof(*rctx)); ++ ++ return 0; ++} ++ ++static int aspeed_sha3_cra_init(struct crypto_tfm *tfm) ++{ ++ struct ahash_alg *alg = __crypto_ahash_alg(tfm->__crt_alg); ++ struct aspeed_sha3_ctx *tctx = crypto_tfm_ctx(tfm); ++ struct aspeed_rsss_alg *ast_alg; ++ ++ ast_alg = container_of(alg, struct aspeed_rsss_alg, alg.ahash.base); ++ tctx->rsss_dev = ast_alg->rsss_dev; ++ ++ crypto_ahash_set_reqsize(__crypto_ahash_cast(tfm), ++ sizeof(struct aspeed_sha3_reqctx)); ++ ++ return 0; ++} ++ ++static void aspeed_sha3_cra_exit(struct crypto_tfm *tfm) ++{ ++ struct aspeed_sha3_ctx *tctx = crypto_tfm_ctx(tfm); ++ struct aspeed_rsss_dev *rsss_dev = tctx->rsss_dev; ++ ++ RSSS_DBG(rsss_dev, "%s\n", crypto_tfm_alg_name(tfm)); ++} ++ ++struct aspeed_rsss_alg aspeed_rsss_algs_sha3_224 = { ++ .type = ASPEED_ALGO_TYPE_AHASH, ++ .alg.ahash.base = { ++ .init = aspeed_sha3_init, ++ .update = aspeed_sha3_update, ++ .final = aspeed_sha3_final, ++ .finup = aspeed_sha3_finup, ++ .digest = aspeed_sha3_digest, ++ .export = aspeed_sha3_export, ++ .import = aspeed_sha3_import, ++ .halg = { ++ .digestsize = SHA3_224_DIGEST_SIZE, ++ .statesize = sizeof(struct aspeed_sha3_reqctx), ++ .base = { ++ .cra_name = "sha3-224", ++ .cra_driver_name = "aspeed-sha3-224", ++ .cra_priority = 300, ++ .cra_flags = CRYPTO_ALG_TYPE_AHASH | ++ CRYPTO_ALG_ASYNC | ++ CRYPTO_ALG_KERN_DRIVER_ONLY, ++ .cra_blocksize = SHA3_224_BLOCK_SIZE, ++ .cra_ctxsize = sizeof(struct aspeed_sha3_ctx), ++ .cra_module = THIS_MODULE, ++ .cra_init = aspeed_sha3_cra_init, ++ .cra_exit = aspeed_sha3_cra_exit, ++ } ++ } ++ }, ++ .alg.ahash.op = { ++ .do_one_request = aspeed_sha3_do_request, ++ }, ++}; ++ ++struct aspeed_rsss_alg aspeed_rsss_algs_sha3_256 = { ++ .type = ASPEED_ALGO_TYPE_AHASH, ++ .alg.ahash.base = { ++ .init = aspeed_sha3_init, ++ .update = aspeed_sha3_update, ++ .final = aspeed_sha3_final, ++ .finup = aspeed_sha3_finup, ++ .digest = aspeed_sha3_digest, ++ .export = aspeed_sha3_export, ++ .import = aspeed_sha3_import, ++ .halg = { ++ .digestsize = SHA3_256_DIGEST_SIZE, ++ .statesize = sizeof(struct aspeed_sha3_reqctx), ++ .base = { ++ .cra_name = "sha3-256", ++ .cra_driver_name = "aspeed-sha3-256", ++ .cra_priority = 300, ++ .cra_flags = CRYPTO_ALG_TYPE_AHASH | ++ CRYPTO_ALG_ASYNC | ++ CRYPTO_ALG_KERN_DRIVER_ONLY, ++ .cra_blocksize = SHA3_256_BLOCK_SIZE, ++ .cra_ctxsize = sizeof(struct aspeed_sha3_ctx), ++ .cra_module = THIS_MODULE, ++ .cra_init = aspeed_sha3_cra_init, ++ .cra_exit = aspeed_sha3_cra_exit, ++ } ++ } ++ }, ++ .alg.ahash.op = { ++ .do_one_request = aspeed_sha3_do_request, ++ }, ++}; ++ ++struct aspeed_rsss_alg aspeed_rsss_algs_sha3_384 = { ++ .type = ASPEED_ALGO_TYPE_AHASH, ++ .alg.ahash.base = { ++ .init = aspeed_sha3_init, ++ .update = aspeed_sha3_update, ++ .final = aspeed_sha3_final, ++ .finup = aspeed_sha3_finup, ++ .digest = aspeed_sha3_digest, ++ .export = aspeed_sha3_export, ++ .import = aspeed_sha3_import, ++ .halg = { ++ .digestsize = SHA3_384_DIGEST_SIZE, ++ .statesize = sizeof(struct aspeed_sha3_reqctx), ++ .base = { ++ .cra_name = "sha3-384", ++ .cra_driver_name = "aspeed-sha3-384", ++ .cra_priority = 300, ++ .cra_flags = CRYPTO_ALG_TYPE_AHASH | ++ CRYPTO_ALG_ASYNC | ++ CRYPTO_ALG_KERN_DRIVER_ONLY, ++ .cra_blocksize = SHA3_384_BLOCK_SIZE, ++ .cra_ctxsize = sizeof(struct aspeed_sha3_ctx), ++ .cra_module = THIS_MODULE, ++ .cra_init = aspeed_sha3_cra_init, ++ .cra_exit = aspeed_sha3_cra_exit, ++ } ++ } ++ }, ++ .alg.ahash.op = { ++ .do_one_request = aspeed_sha3_do_request, ++ }, ++}; ++ ++struct aspeed_rsss_alg aspeed_rsss_algs_sha3_512 = { ++ .type = ASPEED_ALGO_TYPE_AHASH, ++ .alg.ahash.base = { ++ .init = aspeed_sha3_init, ++ .update = aspeed_sha3_update, ++ .final = aspeed_sha3_final, ++ .finup = aspeed_sha3_finup, ++ .digest = aspeed_sha3_digest, ++ .export = aspeed_sha3_export, ++ .import = aspeed_sha3_import, ++ .halg = { ++ .digestsize = SHA3_512_DIGEST_SIZE, ++ .statesize = sizeof(struct aspeed_sha3_reqctx), ++ .base = { ++ .cra_name = "sha3-512", ++ .cra_driver_name = "aspeed-sha3-512", ++ .cra_priority = 300, ++ .cra_flags = CRYPTO_ALG_TYPE_AHASH | ++ CRYPTO_ALG_ASYNC | ++ CRYPTO_ALG_KERN_DRIVER_ONLY, ++ .cra_blocksize = SHA3_512_BLOCK_SIZE, ++ .cra_ctxsize = sizeof(struct aspeed_sha3_ctx), ++ .cra_module = THIS_MODULE, ++ .cra_init = aspeed_sha3_cra_init, ++ .cra_exit = aspeed_sha3_cra_exit, ++ } ++ } ++ }, ++ .alg.ahash.op = { ++ .do_one_request = aspeed_sha3_do_request, ++ }, ++}; ++ ++static void aspeed_rsss_sha3_done_task(unsigned long data) ++{ ++ struct aspeed_rsss_dev *rsss_dev = (struct aspeed_rsss_dev *)data; ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ ++ (void)sha3_engine->resume(rsss_dev); ++} ++ ++void aspeed_rsss_sha3_exit(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ ++ crypto_engine_exit(rsss_dev->crypt_engine_sha3); ++ tasklet_kill(&sha3_engine->done_task); ++} ++ ++int aspeed_rsss_sha3_init(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_sha3 *sha3_engine; ++ u32 val; ++ int rc; ++ ++ rc = reset_control_deassert(rsss_dev->reset_sha3); ++ if (rc) { ++ dev_err(rsss_dev->dev, "Deassert SHA3 reset failed\n"); ++ goto end; ++ } ++ ++ sha3_engine = &rsss_dev->sha3_engine; ++ ++ /* Initialize crypto hardware engine structure for SHA3 */ ++ rsss_dev->crypt_engine_sha3 = crypto_engine_alloc_init(rsss_dev->dev, true); ++ if (!rsss_dev->crypt_engine_sha3) { ++ rc = -ENOMEM; ++ goto end; ++ } ++ ++ rc = crypto_engine_start(rsss_dev->crypt_engine_sha3); ++ if (rc) ++ goto err_engine_sha3_start; ++ ++ tasklet_init(&sha3_engine->done_task, aspeed_rsss_sha3_done_task, ++ (unsigned long)rsss_dev); ++ ++ /* Allocate DMA buffer for hash engine input used */ ++ sha3_engine->ahash_src_addr = ++ dmam_alloc_coherent(rsss_dev->dev, ++ ASPEED_HASH_SRC_DMA_BUF_LEN, ++ &sha3_engine->ahash_src_dma_addr, ++ GFP_KERNEL); ++ if (!sha3_engine->ahash_src_addr) { ++ dev_err(rsss_dev->dev, "Failed to allocate DMA src buffer\n"); ++ rc = -ENOMEM; ++ goto err_engine_sha3_start; ++ } ++ ++ sha3_engine->buffer_addr = dmam_alloc_coherent(rsss_dev->dev, SHA3_224_BLOCK_SIZE, ++ &sha3_engine->buffer_dma_addr, ++ GFP_KERNEL); ++ if (!sha3_engine->buffer_addr) { ++ dev_err(rsss_dev->dev, "Failed to allocate DMA buffer\n"); ++ rc = -ENOMEM; ++ goto err_engine_sha3_start; ++ } ++ ++ sha3_engine->digest_addr = dmam_alloc_coherent(rsss_dev->dev, SHA3_512_DIGEST_SIZE, ++ &sha3_engine->digest_dma_addr, ++ GFP_KERNEL); ++ if (!sha3_engine->digest_addr) { ++ dev_err(rsss_dev->dev, "Failed to allocate DMA digest buffer\n"); ++ rc = -ENOMEM; ++ goto err_engine_sha3_start; ++ } ++ ++ /* ++ * Set 1 to use scatter-gather mode. ++ * Set 0 to use direct mode. ++ */ ++ sha3_engine->sg_mode = 0; ++ ++ /* Self-test */ ++ rc = aspeed_sha3_self_test(rsss_dev); ++ if (rc) ++ goto err_engine_sha3_start; ++ ++ /* Sha3 engine hardware init done, prepare queue lock */ ++ mutex_init(&sha3_engine->queue_lock); ++ ++ /* Enable SHA3 interrupt */ ++ val = ast_rsss_read(rsss_dev, ASPEED_RSSS_INT_EN); ++ ast_rsss_write(rsss_dev, val | SHA3_INT_EN, ASPEED_RSSS_INT_EN); ++ dev_info(rsss_dev->dev, "Aspeed RSSS SHA3 interrupt mode.\n"); ++ ++ dev_info(rsss_dev->dev, "Aspeed RSSS SHA3 initialized (%s mode)\n", ++ sha3_engine->sg_mode ? "SG" : "Direct"); ++ ++ return 0; ++ ++err_engine_sha3_start: ++ crypto_engine_exit(rsss_dev->crypt_engine_sha3); ++end: ++ return rc; ++} +diff --git a/drivers/crypto/aspeed/aspeed-rsss-rsa.c b/drivers/crypto/aspeed/aspeed-rsss-rsa.c +--- a/drivers/crypto/aspeed/aspeed-rsss-rsa.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-rsss-rsa.c 2026-09-09 15:51:44.737237784 +0000 +@@ -0,0 +1,575 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++ ++#include ++#include "aspeed-rsss.h" ++ ++static u8 data_rev[SRAM_BLOCK_SIZE]; ++static u8 data[SRAM_BLOCK_SIZE]; ++static int dbg; ++ ++static void hexdump(char *name, unsigned char *buf, unsigned int len) ++{ ++ if (!dbg) ++ return; ++ ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_DEBUG ++ pr_info("%s:\n", name); ++ print_hex_dump(KERN_CONT, "", DUMP_PREFIX_OFFSET, ++ 16, 1, buf, len, false); ++#endif ++} ++ ++static int aspeed_rsa_self_test(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_rsa *rsa_engine; ++ const u32 pattern = 0xffffffff; ++ u32 val; ++ ++ rsa_engine = &rsss_dev->rsa_engine; ++ ++ /* Set SRAM access control - CPU */ ++ val = ast_rsss_read(rsss_dev, ASPEED_RSSS_CTRL); ++ ast_rsss_write(rsss_dev, val | SRAM_AHB_MODE_CPU, ASPEED_RSSS_CTRL); ++ ++ writel(pattern, rsa_engine->sram_exp); ++ val = readl(rsa_engine->sram_exp); ++ if (val != pattern) ++ return -EIO; ++ ++ writel(0x0, rsa_engine->sram_exp); ++ ++ return 0; ++} ++ ++static inline struct akcipher_request * ++ akcipher_request_cast(struct crypto_async_request *req) ++{ ++ return container_of(req, struct akcipher_request, base); ++} ++ ++static int aspeed_rsa_do_fallback(struct akcipher_request *req) ++{ ++ struct crypto_akcipher *cipher = crypto_akcipher_reqtfm(req); ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(cipher); ++ int err; ++ ++ akcipher_request_set_tfm(req, ctx->fallback_tfm); ++ ++ if (ctx->enc) ++ err = crypto_akcipher_encrypt(req); ++ else ++ err = crypto_akcipher_decrypt(req); ++ ++ akcipher_request_set_tfm(req, cipher); ++ ++ return err; ++} ++ ++static bool aspeed_rsa_need_fallback(struct akcipher_request *req) ++{ ++ struct crypto_akcipher *cipher = crypto_akcipher_reqtfm(req); ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(cipher); ++ ++ return ctx->key.n_sz > ASPEED_RSA_MAX_KEY_LEN; ++} ++ ++static int aspeed_rsa_handle_queue(struct aspeed_rsss_dev *rsss_dev, ++ struct akcipher_request *req) ++{ ++ if (aspeed_rsa_need_fallback(req)) { ++ RSSS_DBG(rsss_dev, "SW fallback\n"); ++ return aspeed_rsa_do_fallback(req); ++ } ++ ++ return crypto_transfer_akcipher_request_to_engine(rsss_dev->crypt_engine_rsa, req); ++} ++ ++static int aspeed_rsa_do_request(struct crypto_engine *engine, void *areq) ++{ ++ struct akcipher_request *req = akcipher_request_cast(areq); ++ struct crypto_akcipher *cipher = crypto_akcipher_reqtfm(req); ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(cipher); ++ struct aspeed_rsss_dev *rsss_dev = ctx->rsss_dev; ++ struct aspeed_engine_rsa *rsa_engine; ++ ++ rsa_engine = &rsss_dev->rsa_engine; ++ rsa_engine->req = req; ++ rsa_engine->flags |= CRYPTO_FLAGS_BUSY; ++ ++ return ctx->trigger(rsss_dev); ++} ++ ++static int aspeed_rsa_complete(struct aspeed_rsss_dev *rsss_dev, int err) ++{ ++ struct aspeed_engine_rsa *rsa_engine = &rsss_dev->rsa_engine; ++ struct akcipher_request *req = rsa_engine->req; ++ ++ rsa_engine->flags &= ~CRYPTO_FLAGS_BUSY; ++ ++ crypto_finalize_akcipher_request(rsss_dev->crypt_engine_rsa, req, err); ++ ++ return err; ++} ++ ++/* ++ * Copy Data to SRAM buffer for engine used. ++ */ ++static void aspeed_rsa_sg_copy_to_buffer(struct aspeed_rsss_dev *rsss_dev, ++ void __iomem *buf, struct scatterlist *src, ++ size_t nbytes) ++{ ++ RSSS_DBG(rsss_dev, "src len:%zu\n", nbytes); ++ ++ memset(data_rev, 0, SRAM_BLOCK_SIZE); ++ memset(data, 0, SRAM_BLOCK_SIZE); ++ ++ scatterwalk_map_and_copy(data, src, 0, nbytes, 0); ++ ++ hexdump("data", data, nbytes); ++ for (int i = 0; i < nbytes; i++) ++ data_rev[nbytes - i - 1] = data[i]; ++ ++ /* align 8 bytes */ ++ memcpy_toio(buf, data_rev, (nbytes + 7) & ~(8 - 1)); ++} ++ ++/* ++ * Copy Exp/Mod to SRAM buffer for engine used. ++ * ++ * Params: ++ * - mode 0 : Exponential ++ * - mode 1 : Modulus ++ */ ++static int aspeed_rsa_ctx_copy(struct aspeed_rsss_dev *rsss_dev, void __iomem *dst, ++ const u8 *src, size_t nbytes, ++ enum aspeed_rsa_key_mode mode) ++{ ++ RSSS_DBG(rsss_dev, "nbytes:%zu, mode:%d\n", nbytes, mode); ++ ++ if (nbytes > ASPEED_RSA_MAX_KEY_LEN) ++ return -ENOMEM; ++ ++ memset(data, 0, SRAM_BLOCK_SIZE); ++ ++ /* Remove leading zeros */ ++ while (nbytes > 0 && src[0] == 0) { ++ src++; ++ nbytes--; ++ } ++ ++ for (int i = 0; i < nbytes; i++) ++ data[nbytes - i - 1] = src[i]; ++ ++ /* align 8 bytes */ ++ memcpy_toio(dst, data, (nbytes + 7) & ~(8 - 1)); ++ ++ return nbytes * 8; ++} ++ ++static int aspeed_rsa_transfer(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_rsa *rsa_engine = &rsss_dev->rsa_engine; ++ struct akcipher_request *req = rsa_engine->req; ++ struct scatterlist *out_sg = req->dst; ++ size_t nbytes = req->dst_len; ++ u8 data[SRAM_BLOCK_SIZE]; ++ u32 val; ++ ++ RSSS_DBG(rsss_dev, "nbytes:%zu\n", nbytes); ++ ++ /* Set SRAM access control - CPU */ ++ val = ast_rsss_read(rsss_dev, ASPEED_RSSS_CTRL); ++ ast_rsss_write(rsss_dev, val | SRAM_AHB_MODE_CPU, ASPEED_RSSS_CTRL); ++ ++ for (int i = 0; i < nbytes; i++) ++ data[nbytes - i - 1] = readb(rsa_engine->sram_data + i); ++ ++ scatterwalk_map_and_copy(data, out_sg, 0, nbytes, 1); ++ ++ return aspeed_rsa_complete(rsss_dev, 0); ++} ++ ++#ifdef RSSS_RSA_POLLING_MODE ++static int aspeed_rsa_wait_complete(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_rsa *rsa_engine = &rsss_dev->rsa_engine; ++ u32 sts; ++ int ret; ++ ++ ret = readl_poll_timeout(rsss_dev->regs + ASPEED_RSA_ENG_STS, sts, ++ ((sts & RSA_STS) == 0x0), ++ ASPEED_RSSS_POLLING_TIME, ++ ASPEED_RSSS_TIMEOUT * 10); ++ if (ret) { ++ dev_err(rsss_dev->dev, "RSA wrong engine status\n"); ++ return -EIO; ++ } ++ ++ ret = readl_poll_timeout(rsss_dev->regs + ASPEED_RSSS_INT_STS, sts, ++ ((sts & RSA_INT_DONE) == RSA_INT_DONE), ++ ASPEED_RSSS_POLLING_TIME, ++ ASPEED_RSSS_TIMEOUT); ++ if (ret) { ++ dev_err(rsss_dev->dev, "RSA wrong interrupt status\n"); ++ return -EIO; ++ } ++ ++ ast_rsss_write(rsss_dev, sts, ASPEED_RSSS_INT_STS); ++ ++ RSSS_DBG(rsss_dev, "irq sts:0x%x\n", sts); ++ ++ if (sts & RSA_INT_DONE) { ++ /* Stop RSA engine */ ++ ast_rsss_write(rsss_dev, 0, ASPEED_RSA_TRIGGER); ++ ++ if (rsa_engine->flags & CRYPTO_FLAGS_BUSY) ++ tasklet_schedule(&rsa_engine->done_task); ++ else ++ dev_err(rsss_dev->dev, "RSA no active requests.\n"); ++ } ++ ++ return 0; ++} ++#endif ++ ++static int aspeed_rsa_trigger(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_rsa *rsa_engine = &rsss_dev->rsa_engine; ++ struct akcipher_request *req = rsa_engine->req; ++ struct crypto_akcipher *cipher; ++ struct aspeed_rsa_ctx *ctx; ++ int ne, nm; ++ u32 val; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ cipher = crypto_akcipher_reqtfm(req); ++ ctx = akcipher_tfm_ctx(cipher); ++ ++ if (!ctx->n_sz) { ++ dev_err(rsss_dev->dev, "%s: key n is not set\n", __func__); ++ return -EINVAL; ++ } ++ ++ /* Set SRAM access control - CPU */ ++ val = ast_rsss_read(rsss_dev, ASPEED_RSSS_CTRL); ++ ast_rsss_write(rsss_dev, val | SRAM_AHB_MODE_CPU, ASPEED_RSSS_CTRL); ++ ++ memset_io(rsa_engine->sram_exp, 0, SRAM_BLOCK_SIZE); ++ memset_io(rsa_engine->sram_mod, 0, SRAM_BLOCK_SIZE); ++ memset_io(rsa_engine->sram_data, 0, SRAM_BLOCK_SIZE); ++ ++ /* Copy source data to SRAM buffer */ ++ aspeed_rsa_sg_copy_to_buffer(rsss_dev, rsa_engine->sram_data, ++ req->src, req->src_len); ++ ++ nm = aspeed_rsa_ctx_copy(rsss_dev, rsa_engine->sram_mod, ctx->n, ++ ctx->n_sz, ASPEED_RSA_MOD_MODE); ++ ++ /* Set dst len as modulus size */ ++ req->dst_len = nm / 8; ++ ++ if (ctx->enc) { ++ if (!ctx->e_sz) { ++ dev_err(rsss_dev->dev, "%s: key e is not set\n", ++ __func__); ++ return -EINVAL; ++ } ++ /* Copy key e to SRAM buffer */ ++ ne = aspeed_rsa_ctx_copy(rsss_dev, rsa_engine->sram_exp, ++ ctx->e, ctx->e_sz, ++ ASPEED_RSA_EXP_MODE); ++ } else { ++ if (!ctx->d_sz) { ++ dev_err(rsss_dev->dev, "%s: key d is not set\n", ++ __func__); ++ return -EINVAL; ++ } ++ /* Copy key d to SRAM buffer */ ++ ne = aspeed_rsa_ctx_copy(rsss_dev, rsa_engine->sram_exp, ++ ctx->key.d, ctx->key.d_sz, ++ ASPEED_RSA_EXP_MODE); ++ } ++ ++ hexdump("exp", rsa_engine->sram_exp, ctx->e_sz); ++ hexdump("mod", rsa_engine->sram_mod, ctx->n_sz); ++ hexdump("data", rsa_engine->sram_data, req->src_len); ++ ++ rsa_engine->resume = aspeed_rsa_transfer; ++ ++ ast_rsss_write(rsss_dev, (ne << 16) + nm, ++ ASPEED_RSA_KEY_INFO); ++ ++ /* Set SRAM access control - Engine */ ++ val = ast_rsss_read(rsss_dev, ASPEED_RSSS_CTRL); ++ ast_rsss_write(rsss_dev, val & ~SRAM_AHB_MODE_CPU, ASPEED_RSSS_CTRL); ++ ++ /* Trigger RSA engines */ ++ ast_rsss_write(rsss_dev, RSA_TRIGGER, ASPEED_RSA_TRIGGER); ++ ++#ifdef RSSS_RSA_POLLING_MODE ++ return aspeed_rsa_wait_complete(rsss_dev); ++#else ++ return 0; ++#endif ++} ++ ++static int aspeed_rsa_enc(struct akcipher_request *req) ++{ ++ struct crypto_akcipher *cipher = crypto_akcipher_reqtfm(req); ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(cipher); ++ struct aspeed_rsss_dev *rsss_dev = ctx->rsss_dev; ++ ++ ctx->trigger = aspeed_rsa_trigger; ++ ctx->enc = 1; ++ ++ return aspeed_rsa_handle_queue(rsss_dev, req); ++} ++ ++static int aspeed_rsa_dec(struct akcipher_request *req) ++{ ++ struct crypto_akcipher *cipher = crypto_akcipher_reqtfm(req); ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(cipher); ++ struct aspeed_rsss_dev *rsss_dev = ctx->rsss_dev; ++ ++ ctx->trigger = aspeed_rsa_trigger; ++ ctx->enc = 0; ++ ++ return aspeed_rsa_handle_queue(rsss_dev, req); ++} ++ ++static int aspeed_rsa_set_n(struct aspeed_rsa_ctx *ctx, u8 *value, ++ size_t len) ++{ ++ ctx->n_sz = len; ++ memcpy(ctx->n, value, len); ++ ++ return 0; ++} ++ ++static int aspeed_rsa_set_e(struct aspeed_rsa_ctx *ctx, u8 *value, ++ size_t len) ++{ ++ ctx->e_sz = len; ++ memcpy(ctx->e, value, len); ++ ++ return 0; ++} ++ ++static int aspeed_rsa_set_d(struct aspeed_rsa_ctx *ctx, u8 *value, ++ size_t len) ++{ ++ ctx->d_sz = len; ++ memcpy(ctx->d, value, len); ++ ++ return 0; ++} ++ ++static int aspeed_rsa_setkey(struct crypto_akcipher *tfm, const void *key, ++ unsigned int keylen, int priv) ++{ ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(tfm); ++ struct aspeed_rsss_dev *rsss_dev = ctx->rsss_dev; ++ int ret; ++ ++ RSSS_DBG(rsss_dev, "\n"); ++ ++ if (priv) ++ ret = rsa_parse_priv_key(&ctx->key, key, keylen); ++ else ++ ret = rsa_parse_pub_key(&ctx->key, key, keylen); ++ ++ if (ret) { ++ dev_err(rsss_dev->dev, "rsss parse key failed, ret:0x%x\n", ++ ret); ++ return ret; ++ } ++ ++ /* Aspeed engine supports up to 4096 bits, ++ * Use software fallback instead. ++ */ ++ if (ctx->key.n_sz > ASPEED_RSA_MAX_KEY_LEN) ++ return 0; ++ ++ hexdump("n", (u8 *)ctx->key.n, ctx->key.n_sz); ++ aspeed_rsa_set_n(ctx, (u8 *)ctx->key.n, ctx->key.n_sz); ++ ++ hexdump("e", (u8 *)ctx->key.e, ctx->key.e_sz); ++ aspeed_rsa_set_e(ctx, (u8 *)ctx->key.e, ctx->key.e_sz); ++ ++ if (priv) { ++ hexdump("d", (u8 *)ctx->key.d, ctx->key.d_sz); ++ aspeed_rsa_set_d(ctx, (u8 *)ctx->key.d, ctx->key.d_sz); ++ } ++ ++ return 0; ++} ++ ++static int aspeed_rsa_set_pub_key(struct crypto_akcipher *tfm, ++ const void *key, ++ unsigned int keylen) ++{ ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(tfm); ++ int ret; ++ ++ ret = crypto_akcipher_set_pub_key(ctx->fallback_tfm, key, keylen); ++ if (ret) ++ return ret; ++ ++ return aspeed_rsa_setkey(tfm, key, keylen, 0); ++} ++ ++static int aspeed_rsa_set_priv_key(struct crypto_akcipher *tfm, ++ const void *key, ++ unsigned int keylen) ++{ ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(tfm); ++ int ret; ++ ++ ret = crypto_akcipher_set_priv_key(ctx->fallback_tfm, key, keylen); ++ if (ret) ++ return ret; ++ ++ return aspeed_rsa_setkey(tfm, key, keylen, 1); ++} ++ ++static unsigned int aspeed_rsa_max_size(struct crypto_akcipher *tfm) ++{ ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(tfm); ++ ++ if (ctx->key.n_sz > ASPEED_RSA_MAX_KEY_LEN) ++ return crypto_akcipher_maxsize(ctx->fallback_tfm); ++ ++ return ctx->n_sz; ++} ++ ++static int aspeed_rsa_init_tfm(struct crypto_akcipher *tfm) ++{ ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(tfm); ++ struct akcipher_alg *alg = crypto_akcipher_alg(tfm); ++ const char *name = crypto_tfm_alg_name(&tfm->base); ++ struct aspeed_rsss_alg *rsa_alg; ++ ++ rsa_alg = container_of(alg, struct aspeed_rsss_alg, alg.akcipher.base); ++ ++ ctx->rsss_dev = rsa_alg->rsss_dev; ++ ++ ctx->fallback_tfm = crypto_alloc_akcipher(name, 0, CRYPTO_ALG_ASYNC | ++ CRYPTO_ALG_NEED_FALLBACK); ++ if (IS_ERR(ctx->fallback_tfm)) { ++ dev_err(ctx->rsss_dev->dev, "ERROR: Cannot allocate fallback for %s %ld\n", ++ name, PTR_ERR(ctx->fallback_tfm)); ++ return PTR_ERR(ctx->fallback_tfm); ++ } ++ ++ return 0; ++} ++ ++static void aspeed_rsa_exit_tfm(struct crypto_akcipher *tfm) ++{ ++ struct aspeed_rsa_ctx *ctx = akcipher_tfm_ctx(tfm); ++ ++ crypto_free_akcipher(ctx->fallback_tfm); ++} ++ ++struct aspeed_rsss_alg aspeed_rsss_algs_rsa = { ++ .type = ASPEED_ALGO_TYPE_AKCIPHER, ++ .alg.akcipher.base = { ++ .encrypt = aspeed_rsa_enc, ++ .decrypt = aspeed_rsa_dec, ++ .sign = aspeed_rsa_dec, ++ .verify = aspeed_rsa_enc, ++ .set_pub_key = aspeed_rsa_set_pub_key, ++ .set_priv_key = aspeed_rsa_set_priv_key, ++ .max_size = aspeed_rsa_max_size, ++ .init = aspeed_rsa_init_tfm, ++ .exit = aspeed_rsa_exit_tfm, ++ .base = { ++ .cra_name = "rsa", ++ .cra_driver_name = "aspeed-rsa", ++ .cra_priority = 300, ++ .cra_flags = CRYPTO_ALG_TYPE_AKCIPHER | ++ CRYPTO_ALG_ASYNC | ++ CRYPTO_ALG_KERN_DRIVER_ONLY | ++ CRYPTO_ALG_NEED_FALLBACK, ++ .cra_module = THIS_MODULE, ++ .cra_ctxsize = sizeof(struct aspeed_rsa_ctx), ++ }, ++ }, ++ .alg.akcipher.op = { ++ .do_one_request = aspeed_rsa_do_request, ++ }, ++}; ++ ++static void aspeed_rsa_done_task(unsigned long data) ++{ ++ struct aspeed_rsss_dev *rsss_dev = (struct aspeed_rsss_dev *)data; ++ struct aspeed_engine_rsa *rsa_engine = &rsss_dev->rsa_engine; ++ ++ (void)rsa_engine->resume(rsss_dev); ++} ++ ++void aspeed_rsss_rsa_exit(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_rsa *rsa_engine = &rsss_dev->rsa_engine; ++ ++ crypto_engine_exit(rsss_dev->crypt_engine_rsa); ++ tasklet_kill(&rsa_engine->done_task); ++} ++ ++int aspeed_rsss_rsa_init(struct aspeed_rsss_dev *rsss_dev) ++{ ++ struct aspeed_engine_rsa *rsa_engine; ++ u32 val; ++ int rc; ++ ++ rc = reset_control_deassert(rsss_dev->reset_rsa); ++ if (rc) { ++ dev_err(rsss_dev->dev, "Deassert RSA reset failed\n"); ++ goto end; ++ } ++ ++ rsa_engine = &rsss_dev->rsa_engine; ++ ++ /* Initialize crypto hardware engine structure for RSA */ ++ rsss_dev->crypt_engine_rsa = crypto_engine_alloc_init(rsss_dev->dev, true); ++ if (!rsss_dev->crypt_engine_rsa) { ++ rc = -ENOMEM; ++ goto end; ++ } ++ ++ rc = crypto_engine_start(rsss_dev->crypt_engine_rsa); ++ if (rc) ++ goto err_engine_rsa_start; ++ ++ tasklet_init(&rsa_engine->done_task, aspeed_rsa_done_task, ++ (unsigned long)rsss_dev); ++ ++ rsa_engine->sram_exp = rsss_dev->regs + SRAM_OFFSET_EXP; ++ rsa_engine->sram_mod = rsss_dev->regs + SRAM_OFFSET_MOD; ++ rsa_engine->sram_data = rsss_dev->regs + SRAM_OFFSET_DATA; ++ ++ /* Set SRAM for RSA operation */ ++ ast_rsss_write(rsss_dev, RSA_OPERATION, ASPEED_RSSS_CTRL); ++ ++ /* Self-test */ ++ rc = aspeed_rsa_self_test(rsss_dev); ++ if (rc) ++ goto err_engine_rsa_start; ++ ++ /* Enable RSA interrupt */ ++ val = ast_rsss_read(rsss_dev, ASPEED_RSSS_INT_EN); ++ ast_rsss_write(rsss_dev, val | RSA_INT_EN, ASPEED_RSSS_INT_EN); ++ ++ dev_info(rsss_dev->dev, "Aspeed RSSS RSA initialized\n"); ++ ++ return 0; ++ ++err_engine_rsa_start: ++ crypto_engine_exit(rsss_dev->crypt_engine_rsa); ++end: ++ return rc; ++} +diff --git a/drivers/crypto/aspeed/aspeed-rsss.c b/drivers/crypto/aspeed/aspeed-rsss.c +--- a/drivers/crypto/aspeed/aspeed-rsss.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-rsss.c 2026-09-09 15:51:44.737237784 +0000 +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include "aspeed-rsss.h" ++ ++static struct aspeed_rsss_alg *aspeed_rsss_algs[] = { ++ &aspeed_rsss_algs_rsa, ++ &aspeed_rsss_algs_sha3_224, ++ &aspeed_rsss_algs_sha3_256, ++ &aspeed_rsss_algs_sha3_384, ++ &aspeed_rsss_algs_sha3_512, ++}; ++ ++static void aspeed_rsss_register(struct aspeed_rsss_dev *rsss_dev) ++{ ++ char *cra_name; ++ int rc; ++ ++ for (int i = 0; i < ARRAY_SIZE(aspeed_rsss_algs); i++) { ++ aspeed_rsss_algs[i]->rsss_dev = rsss_dev; ++ if (aspeed_rsss_algs[i]->type == ASPEED_ALGO_TYPE_AKCIPHER) { ++ rc = crypto_engine_register_akcipher(&aspeed_rsss_algs[i]->alg.akcipher); ++ cra_name = aspeed_rsss_algs[i]->alg.akcipher.base.base.cra_name; ++ ++ } else if (aspeed_rsss_algs[i]->type == ASPEED_ALGO_TYPE_AHASH) { ++ rc = crypto_engine_register_ahash(&aspeed_rsss_algs[i]->alg.ahash); ++ cra_name = aspeed_rsss_algs[i]->alg.ahash.base.halg.base.cra_name; ++ } ++ ++ if (rc) ++ dev_warn(rsss_dev->dev, "Failed to register [%d] %s(0x%x)\n", i, cra_name, rc); ++ } ++} ++ ++static void aspeed_rsss_unregister(struct aspeed_rsss_dev *rsss_dev) ++{ ++ for (int i = 0; i < ARRAY_SIZE(aspeed_rsss_algs); i++) { ++ if (aspeed_rsss_algs[i]->type == ASPEED_ALGO_TYPE_AKCIPHER) ++ crypto_engine_unregister_akcipher(&aspeed_rsss_algs[i]->alg.akcipher); ++ ++ else if (aspeed_rsss_algs[i]->type == ASPEED_ALGO_TYPE_AHASH) ++ crypto_engine_unregister_ahash(&aspeed_rsss_algs[i]->alg.ahash); ++ } ++} ++ ++/* RSSS interrupt service routine. */ ++static irqreturn_t aspeed_rsss_irq(int irq, void *dev) ++{ ++ struct aspeed_rsss_dev *rsss_dev = (struct aspeed_rsss_dev *)dev; ++ struct aspeed_engine_sha3 *sha3_engine = &rsss_dev->sha3_engine; ++ struct aspeed_engine_rsa *rsa_engine = &rsss_dev->rsa_engine; ++ u32 sts; ++ ++ sts = ast_rsss_read(rsss_dev, ASPEED_RSSS_INT_STS); ++ ast_rsss_write(rsss_dev, sts, ASPEED_RSSS_INT_STS); ++ ++ RSSS_DBG(rsss_dev, "irq sts:0x%x\n", sts); ++ ++ if (sts & RSA_INT_DONE) { ++ /* Stop RSA engine */ ++ ast_rsss_write(rsss_dev, 0, ASPEED_RSA_TRIGGER); ++ ++ if (rsa_engine->flags & CRYPTO_FLAGS_BUSY) ++ tasklet_schedule(&rsa_engine->done_task); ++ else ++ dev_err(rsss_dev->dev, "RSA no active requests.\n"); ++ } ++ ++ if (sts & SHA3_INT_DONE) { ++ if (sha3_engine->flags & CRYPTO_FLAGS_BUSY) ++ tasklet_schedule(&sha3_engine->done_task); ++ else ++ dev_err(rsss_dev->dev, "SHA3 no active requests.\n"); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static const struct of_device_id aspeed_rsss_of_matches[] = { ++ { .compatible = "aspeed,ast2700-rsss", }, ++ {}, ++}; ++ ++static int aspeed_rsss_probe(struct platform_device *pdev) ++{ ++ struct aspeed_rsss_dev *rsss_dev; ++ struct device *dev = &pdev->dev; ++ int rc; ++ ++ rsss_dev = devm_kzalloc(dev, sizeof(struct aspeed_rsss_dev), ++ GFP_KERNEL); ++ if (!rsss_dev) ++ return -ENOMEM; ++ ++ rsss_dev->dev = dev; ++ ++ platform_set_drvdata(pdev, rsss_dev); ++ ++ rsss_dev->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(rsss_dev->regs)) ++ return PTR_ERR(rsss_dev->regs); ++ ++ /* Get irq number and register it */ ++ rsss_dev->irq = platform_get_irq(pdev, 0); ++ if (rsss_dev->irq < 0) ++ return -ENXIO; ++ ++ rc = devm_request_irq(dev, rsss_dev->irq, aspeed_rsss_irq, 0, ++ dev_name(dev), rsss_dev); ++ if (rc) { ++ dev_err(dev, "Failed to request irq.\n"); ++ return rc; ++ } ++ ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_warn(&pdev->dev, "No suitable DMA available\n"); ++ return rc; ++ } ++ ++ rsss_dev->clk = devm_clk_get_enabled(dev, NULL); ++ if (IS_ERR(rsss_dev->clk)) { ++ dev_err(dev, "Failed to get rsss clk\n"); ++ return PTR_ERR(rsss_dev->clk); ++ } ++ ++ rsss_dev->reset_rsa = devm_reset_control_get(dev, "rsa"); ++ if (IS_ERR(rsss_dev->reset_rsa)) { ++ dev_err(dev, "Failed to get rsa reset\n"); ++ return PTR_ERR(rsss_dev->reset_rsa); ++ } ++ ++ rsss_dev->reset_sha3 = devm_reset_control_get(dev, "sha3"); ++ if (IS_ERR(rsss_dev->reset_sha3)) { ++ dev_err(dev, "Failed to get sha3 reset\n"); ++ return PTR_ERR(rsss_dev->reset_sha3); ++ } ++ ++ rc = aspeed_rsss_rsa_init(rsss_dev); ++ if (rc) { ++ dev_err(dev, "RSA init failed\n"); ++ return rc; ++ } ++ ++ rc = aspeed_rsss_sha3_init(rsss_dev); ++ if (rc) { ++ dev_err(dev, "SHA3 init failed\n"); ++ return rc; ++ } ++ ++ aspeed_rsss_register(rsss_dev); ++ ++ dev_info(dev, "Aspeed RSSS Hardware Accelerator successfully registered\n"); ++ ++ return 0; ++} ++ ++static void aspeed_rsss_remove(struct platform_device *pdev) ++{ ++ struct aspeed_rsss_dev *rsss_dev = platform_get_drvdata(pdev); ++ ++ aspeed_rsss_unregister(rsss_dev); ++ aspeed_rsss_rsa_exit(rsss_dev); ++ aspeed_rsss_sha3_exit(rsss_dev); ++} ++ ++MODULE_DEVICE_TABLE(of, aspeed_rsss_of_matches); ++ ++static struct platform_driver aspeed_rsss_driver = { ++ .probe = aspeed_rsss_probe, ++ .remove = aspeed_rsss_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_rsss_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_rsss_driver); ++ ++MODULE_AUTHOR("Neal Liu "); ++MODULE_DESCRIPTION("ASPEED RSSS driver for multiple cryptographic engines"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/crypto/aspeed/aspeed-rsss.h b/drivers/crypto/aspeed/aspeed-rsss.h +--- a/drivers/crypto/aspeed/aspeed-rsss.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/crypto/aspeed/aspeed-rsss.h 2026-09-09 15:51:44.738237769 +0000 +@@ -0,0 +1,275 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++ ++#ifndef __ASPEED_RSSS_H__ ++#define __ASPEED_RSSS_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_CRYPTO_DEV_ASPEED_DEBUG ++#define RSSS_DBG(d, fmt, ...) \ ++ dev_info((d)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) ++#else ++#define RSSS_DBG(d, fmt, ...) \ ++ dev_dbg((d)->dev, "%s() " fmt, __func__, ##__VA_ARGS__) ++#endif ++ ++/***************************** ++ * * ++ * RSSS register definitions * ++ * * ++ * ***************************/ ++#define ASPEED_RSSS_INT_STS 0xc00 /* RSSS interrupt status */ ++#define ASPEED_RSSS_INT_EN 0xc04 /* RSSS interrupt enable */ ++#define ASPEED_RSSS_CTRL 0xc08 /* RSSS generic control */ ++#define ASPEED_RSA_TRIGGER 0xe00 /* RSA Engine Control: trigger */ ++#define ASPEED_RSA_KEY_INFO 0xe08 /* RSA Exp/Mod Key Length (Bits) */ ++#define ASPEED_RSA_ENG_STS 0xe0c /* RSA Engine Status */ ++ ++#define ASPEED_SHA3_CMD 0xe80 ++#define ASPEED_SHA3_SRC_LO 0xe84 ++#define ASPEED_SHA3_SRC_HI 0xe88 ++#define ASPEED_SHA3_SRC_LEN 0xe8c ++#define ASPEED_SHA3_DST_LO 0xe90 ++#define ASPEED_SHA3_DST_HI 0xe94 ++#define ASPEED_SHA3_BUSY_STS 0xe98 ++#define ASPEED_SHA3_ENG_STS 0xe9c ++ ++/* RSSS interrupt status */ ++#define SM4_INT_DONE BIT(3) ++#define SM3_INT_DONE BIT(2) ++#define SHA3_INT_DONE BIT(1) ++#define RSA_INT_DONE BIT(0) ++ ++/* RSSS interrupt enable */ ++#define SM4_INT_EN BIT(3) ++#define SM3_INT_EN BIT(2) ++#define SHA3_INT_EN BIT(1) ++#define RSA_INT_EN BIT(0) ++ ++/* RSSS generic control */ ++#define RSA_OPERATION (BIT(18) | BIT(19)) ++#define SRAM_AHB_MODE_CPU BIT(16) ++#define SRAM_AHB_MODE_ENGINE 0x0 ++#define SRAM_BUFF_PD (BIT(5) | BIT(4)) ++#define SM4_DISABLE BIT(3) ++#define SM3_DISABLE BIT(2) ++#define SHA3_DISABLE BIT(1) ++ ++/* RSA trigger */ ++#define RSA_TRIGGER BIT(0) ++ ++/* RSA key len */ ++#define RSA_E_BITS_LEN(x) ((x) << 16) ++#define RSA_M_BITS_LEN(x) (x) ++ ++#define RSA_STS (BIT(0) | BIT(1)) ++ ++/* RSA SRAM */ ++#define SRAM_OFFSET_EXP 0x0 ++#define SRAM_OFFSET_MOD 0x400 ++#define SRAM_OFFSET_DATA 0x800 ++#define SRAM_BLOCK_SIZE 0x400 ++ ++#define ASPEED_RSA_MAX_KEY_LEN 512 /* RSA maximum key length (Bytes) */ ++ ++#define CRYPTO_FLAGS_BUSY BIT(1) ++ ++/* SHA3 command */ ++#define SHA3_CMD_TRIG BIT(31) ++#define SHA3_CMD_MODE_224 (0x0 << 28) ++#define SHA3_CMD_MODE_256 (0x1 << 28) ++#define SHA3_CMD_MODE_384 (0x2 << 28) ++#define SHA3_CMD_MODE_512 (0x3 << 28) ++#define SHA3_CMD_MODE_S128 (0x4 << 28) ++#define SHA3_CMD_MODE_S256 (0x5 << 28) ++#define SHA3_CMD_HW_PAD BIT(27) ++#define SHA3_CMD_ACC_FINAL BIT(26) ++#define SHA3_CMD_ACC BIT(25) ++#define SHA3_CMD_SG_MODE BIT(24) ++#define SHA3_CMD_IN_RST BIT(21) ++#define SHA3_CMD_OUT_RST BIT(20) ++#define SHA3_CMD_OUT_LEN(x) ((x) & 0x1ffff) ++ ++#define SHA3_FLAGS_SHA224 BIT(0) ++#define SHA3_FLAGS_SHA256 BIT(1) ++#define SHA3_FLAGS_SHA384 BIT(2) ++#define SHA3_FLAGS_SHA512 BIT(3) ++#define SHA3_FLAGS_FINUP BIT(0xa) ++#define SHA3_FLAGS_MASK (0xff) ++ ++#define SHA3_STS BIT(0) ++ ++#define SG_LAST_LIST BIT(31) ++ ++#define SHA_OP_INIT BIT(0) ++#define SHA_OP_UPDATE BIT(1) ++#define SHA_OP_FINAL BIT(2) ++ ++#define ASPEED_HASH_SRC_DMA_BUF_LEN 0xa000 ++ ++#define ASPEED_RSSS_POLLING_TIME 100 ++#define ASPEED_RSSS_TIMEOUT 100000 /* 100 ms */ ++ ++struct aspeed_rsss_dev; ++ ++typedef int (*aspeed_rsss_fn_t)(struct aspeed_rsss_dev *); ++ ++struct aspeed_sg_list { ++ __le64 phy_addr; ++ __le32 len; ++}; ++ ++struct aspeed_engine_rsa { ++ struct tasklet_struct done_task; ++ unsigned long flags; ++ struct akcipher_request *req; ++ ++ /* RSA input/output SRAM buffer */ ++ void __iomem *sram_exp; ++ void __iomem *sram_mod; ++ void __iomem *sram_data; ++ ++ /* callback func */ ++ aspeed_rsss_fn_t resume; ++}; ++ ++struct aspeed_engine_sha3 { ++ struct tasklet_struct done_task; ++ unsigned long flags; ++ struct ahash_request *req; ++ ++ /* Protects sha3 engine operation enqueue in order */ ++ struct mutex queue_lock; ++ ++ /* input buffer for SG */ ++ void *ahash_src_addr; ++ dma_addr_t ahash_src_dma_addr; ++ ++ /* input buffer for remain */ ++ void *buffer_addr; ++ dma_addr_t buffer_dma_addr; ++ ++ /* output buffer */ ++ void *digest_addr; ++ dma_addr_t digest_dma_addr; ++ ++ dma_addr_t src_dma; ++ size_t src_length; ++ ++ /* callback func */ ++ aspeed_rsss_fn_t resume; ++ aspeed_rsss_fn_t dma_prepare; ++ ++ unsigned sg_mode:1; ++}; ++ ++struct aspeed_rsss_dev { ++ void __iomem *regs; ++ struct device *dev; ++ int irq; ++ struct clk *clk; ++ struct reset_control *reset_rsa; ++ struct reset_control *reset_sha3; ++ ++ struct crypto_engine *crypt_engine_rsa; ++ struct crypto_engine *crypt_engine_sha3; ++ ++ struct aspeed_engine_rsa rsa_engine; ++ struct aspeed_engine_sha3 sha3_engine; ++}; ++ ++enum aspeed_algo_type { ++ ASPEED_ALGO_TYPE_AKCIPHER, ++ ASPEED_ALGO_TYPE_AHASH, ++}; ++ ++struct aspeed_rsss_alg { ++ struct aspeed_rsss_dev *rsss_dev; ++ enum aspeed_algo_type type; ++ union { ++ struct akcipher_engine_alg akcipher; ++ struct ahash_engine_alg ahash; ++ } alg; ++}; ++ ++/* RSA related */ ++struct aspeed_rsa_ctx { ++ struct aspeed_rsss_dev *rsss_dev; ++ ++ struct rsa_key key; ++ int enc; ++ u8 n[ASPEED_RSA_MAX_KEY_LEN]; ++ u8 e[ASPEED_RSA_MAX_KEY_LEN]; ++ u8 d[ASPEED_RSA_MAX_KEY_LEN]; ++ size_t n_sz; ++ size_t e_sz; ++ size_t d_sz; ++ ++ aspeed_rsss_fn_t trigger; ++ ++ struct crypto_akcipher *fallback_tfm; ++}; ++ ++enum aspeed_rsa_key_mode { ++ ASPEED_RSA_EXP_MODE = 0, ++ ASPEED_RSA_MOD_MODE, ++ ASPEED_RSA_DATA_MODE, ++}; ++ ++/* Hash related */ ++struct aspeed_sha3_ctx { ++ struct aspeed_rsss_dev *rsss_dev; ++}; ++ ++struct aspeed_sha3_reqctx { ++ unsigned long flags; /* final update flag should no use */ ++ unsigned long op; /* final or update */ ++ u32 cmd; /* trigger cmd */ ++ ++ /* walk state */ ++ struct scatterlist *src_sg; ++ int src_nents; ++ unsigned int offset; /* offset in current sg */ ++ unsigned int total; /* per update length */ ++ ++ size_t digsize; ++ size_t blksize; ++ ++ /* remain data buffer */ ++ u8 buffer[SHA3_512_BLOCK_SIZE * 2]; ++ size_t bufcnt; /* buffer counter */ ++ ++ /* output buffer */ ++ u8 digest[SHA3_512_DIGEST_SIZE]; ++ u64 digcnt[2]; ++}; ++ ++/******************************************************************************/ ++ ++#define ast_rsss_write(rsss, val, offset) \ ++ writel((val), (rsss)->regs + (offset)) ++ ++#define ast_rsss_read(rsss, offset) \ ++ readl((rsss)->regs + (offset)) ++ ++int aspeed_rsss_rsa_init(struct aspeed_rsss_dev *rsss_dev); ++void aspeed_rsss_rsa_exit(struct aspeed_rsss_dev *rsss_dev); ++int aspeed_rsss_sha3_init(struct aspeed_rsss_dev *rsss_dev); ++void aspeed_rsss_sha3_exit(struct aspeed_rsss_dev *rsss_dev); ++ ++extern struct aspeed_rsss_alg aspeed_rsss_algs_rsa; ++extern struct aspeed_rsss_alg aspeed_rsss_algs_sha3_224; ++extern struct aspeed_rsss_alg aspeed_rsss_algs_sha3_256; ++extern struct aspeed_rsss_alg aspeed_rsss_algs_sha3_384; ++extern struct aspeed_rsss_alg aspeed_rsss_algs_sha3_512; ++ ++#endif /* __ASPEED_RSSS_H__ */ +diff --git a/drivers/edac/edac_ast2700.c b/drivers/edac/edac_ast2700.c +--- a/drivers/edac/edac_ast2700.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/edac/edac_ast2700.c 2026-09-09 15:51:44.378243209 +0000 +@@ -0,0 +1,354 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2018, 2019 Cisco Systems ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "edac_module.h" ++ ++#define DRV_NAME "aspeed-edac" ++ ++#define DRAMC_PROT 0x00 /* protection key register */ ++#define DRAMC_INT_STS 0x04 /* interrupt status register */ ++#define INT_STS_ECC_REC BIT(5) /* ECC recoverable error interrupt */ ++#define INT_STS_ECC_ERR BIT(4) /* ECC error interrupt */ ++#define DRAMC_INT_CLR 0x08 /* interrupt clear register */ ++#define DRAMC_INT_MASK 0x0c /* interrupt mask register */ ++#define DRAMC_MCFG 0x10 /* main configuration register */ ++#define MCFG_ECC_ENABLE BIT(6) ++#define MCFG_DRAM_SIZE_MASK GENMASK(5, 4) /* 2=1GB, 3=2GB, 4=4GB, 5=8GB */ ++#define MCFG_DRAM_SIZE_SHIFT (2) ++#define MCFG_DRAM_TYPE BIT(0) /* 0=DDR4, 1=DDR5 */ ++ ++#define DRAMC_ECC_STS 0x78 /* ECC status register */ ++#define ECC_STS_REC_CNT_MASK GENMASK(15, 8) /* recoverable error count */ ++#define ECC_STS_REC_CNT_SHIFT (8) ++#define ECC_STS_UNREC_CNT_MASK GENMASK(7, 0) /* unrecoverable error count */ ++#define ECC_STS_UNREC_CNT_SHIFT (0) ++#define DRAMC_ECC_FAIL_ADDR 0x7c /* ECC fail address register */ ++ ++static struct regmap *aspeed_regmap; ++ ++static int regmap_reg_write(void *context, unsigned int reg, unsigned int val) ++{ ++ void __iomem *regs = (void __iomem *)context; ++ ++ writel(val, regs + reg); ++ ++ return 0; ++} ++ ++static int regmap_reg_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ void __iomem *regs = (void __iomem *)context; ++ ++ *val = readl(regs + reg); ++ ++ return 0; ++} ++ ++static bool regmap_is_volatile(struct device *dev, unsigned int reg) ++{ ++ return 1; ++} ++ ++static const struct regmap_config aspeed_regmap_config = { ++ .reg_bits = 32, ++ .val_bits = 32, ++ .reg_stride = 4, ++ .max_register = DRAMC_ECC_FAIL_ADDR, ++ .reg_write = regmap_reg_write, ++ .reg_read = regmap_reg_read, ++ .volatile_reg = regmap_is_volatile, ++ .fast_io = true, ++}; ++ ++static void count_rec(struct mem_ctl_info *mci, u8 rec_cnt, u32 rec_addr) ++{ ++ struct csrow_info *csrow = mci->csrows[0]; ++ u32 page, offset, syndrome; ++ ++ if (!rec_cnt) ++ return; ++ ++ /* report first few errors (if there are) */ ++ /* note: no addresses are recorded */ ++ if (rec_cnt > 1) { ++ /* page, offset and syndrome are not available */ ++ page = 0; ++ offset = 0; ++ syndrome = 0; ++ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, rec_cnt - 1, ++ page, offset, syndrome, 0, 0, -1, ++ "address(es) not available", ""); ++ } ++ ++ /* report last error */ ++ /* note: rec_addr is the last recoverable error addr */ ++ page = rec_addr >> PAGE_SHIFT; ++ offset = rec_addr & ~PAGE_MASK; ++ /* syndrome is not available */ ++ syndrome = 0; ++ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, ++ csrow->first_page + page, offset, syndrome, ++ 0, 0, -1, "", ""); ++} ++ ++static void count_un_rec(struct mem_ctl_info *mci, u8 un_rec_cnt, ++ u32 un_rec_addr) ++{ ++ struct csrow_info *csrow = mci->csrows[0]; ++ u32 page, offset, syndrome; ++ ++ if (!un_rec_cnt) ++ return; ++ ++ /* report 1. error */ ++ /* note: un_rec_addr is the first unrecoverable error addr */ ++ page = un_rec_addr >> PAGE_SHIFT; ++ offset = un_rec_addr & ~PAGE_MASK; ++ /* syndrome is not available */ ++ syndrome = 0; ++ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, ++ csrow->first_page + page, offset, syndrome, ++ 0, 0, -1, "", ""); ++ ++ /* report further errors (if there are) */ ++ /* note: no addresses are recorded */ ++ if (un_rec_cnt > 1) { ++ /* page, offset and syndrome are not available */ ++ page = 0; ++ offset = 0; ++ syndrome = 0; ++ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, un_rec_cnt - 1, ++ page, offset, syndrome, 0, 0, -1, ++ "address(es) not available", ""); ++ } ++} ++ ++static irqreturn_t mcr_isr(int irq, void *arg) ++{ ++ struct mem_ctl_info *mci = arg; ++ u32 reg, ecc_sts, ecc_fail_addr; ++ u16 rec_cnt, un_rec_cnt; ++ ++ regmap_read(aspeed_regmap, DRAMC_INT_STS, ®); ++ ++ regmap_read(aspeed_regmap, DRAMC_ECC_STS, &ecc_sts); ++ ++ /* collect data about recoverable and unrecoverable errors */ ++ rec_cnt = (ecc_sts & ECC_STS_REC_CNT_MASK) >> ECC_STS_REC_CNT_SHIFT; ++ un_rec_cnt = (ecc_sts & ECC_STS_UNREC_CNT_MASK) >> ECC_STS_UNREC_CNT_SHIFT; ++ ++ regmap_read(aspeed_regmap, DRAMC_ECC_FAIL_ADDR, &ecc_fail_addr); ++ ++ /* clear interrupt flags */ ++ regmap_update_bits(aspeed_regmap, DRAMC_INT_CLR, ++ reg, reg); ++ ++ /* process recoverable and unrecoverable errors */ ++ count_rec(mci, rec_cnt, ecc_fail_addr); ++ count_un_rec(mci, un_rec_cnt, ecc_fail_addr); ++ ++ return IRQ_HANDLED; ++} ++ ++static int config_irq(void *ctx, struct platform_device *pdev) ++{ ++ int irq; ++ int rc; ++ ++ /* register interrupt handler */ ++ irq = platform_get_irq(pdev, 0); ++ dev_dbg(&pdev->dev, "got irq %d\n", irq); ++ if (irq < 0) ++ return irq; ++ ++ rc = devm_request_irq(&pdev->dev, irq, mcr_isr, IRQF_TRIGGER_HIGH, ++ DRV_NAME, ctx); ++ if (rc) { ++ dev_err(&pdev->dev, "unable to request irq %d\n", irq); ++ return rc; ++ } ++ ++ /* enable interrupts */ ++ regmap_clear_bits(aspeed_regmap, DRAMC_INT_MASK, ++ INT_STS_ECC_REC | INT_STS_ECC_ERR); ++ ++ return 0; ++} ++ ++static int init_csrows(struct mem_ctl_info *mci) ++{ ++ struct csrow_info *csrow = mci->csrows[0]; ++ u32 nr_pages, dram_type; ++ struct dimm_info *dimm; ++ struct device_node *np; ++ struct resource r; ++ u32 reg04; ++ int rc; ++ ++ pr_info("%s: initializing csrows\n", DRV_NAME); ++ ++ /* retrieve info about physical memory from device tree */ ++ np = of_find_node_by_name(NULL, "memory"); ++ if (!np) { ++ dev_err(mci->pdev, "dt: missing /memory node\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_address_to_resource(np, 0, &r); ++ ++ of_node_put(np); ++ ++ if (rc) { ++ dev_err(mci->pdev, "dt: failed requesting resource for /memory node\n"); ++ return rc; ++ } ++ ++ dev_dbg(mci->pdev, "dt: /memory node resources: first page %pR, PAGE_SHIFT macro=0x%x\n", ++ &r, PAGE_SHIFT); ++ ++ csrow->first_page = r.start >> PAGE_SHIFT; ++ nr_pages = resource_size(&r) >> PAGE_SHIFT; ++ csrow->last_page = csrow->first_page + nr_pages - 1; ++ ++ regmap_read(aspeed_regmap, DRAMC_MCFG, ®04); ++ dram_type = (reg04 & MCFG_DRAM_TYPE) ? MEM_DDR5 : MEM_DDR4; ++ ++ dimm = csrow->channels[0]->dimm; ++ dimm->mtype = dram_type; ++ dimm->edac_mode = EDAC_SECDED; ++ dimm->nr_pages = nr_pages / csrow->nr_channels; ++ ++ dev_dbg(mci->pdev, "initialized dimm with first_page=0x%lx and nr_pages=0x%x\n", ++ csrow->first_page, nr_pages); ++ ++ return 0; ++} ++ ++static int aspeed_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct edac_mc_layer layers[2]; ++ struct mem_ctl_info *mci; ++ void __iomem *regs; ++ //u32 reg04; ++ u32 reg; ++ int rc; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ aspeed_regmap = devm_regmap_init(dev, NULL, (__force void *)regs, ++ &aspeed_regmap_config); ++ if (IS_ERR(aspeed_regmap)) ++ return PTR_ERR(aspeed_regmap); ++ ++ /* bail out if ECC mode is not configured */ ++ regmap_read(aspeed_regmap, DRAMC_MCFG, ®); ++ if (!(reg & MCFG_ECC_ENABLE)) { ++ dev_err(&pdev->dev, "ECC mode is not configured in u-boot\n"); ++ return -EPERM; ++ } ++ ++ edac_op_state = EDAC_OPSTATE_INT; ++ ++ /* allocate & init EDAC MC data structure */ ++ layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; ++ layers[0].size = 1; ++ layers[0].is_virt_csrow = true; ++ layers[1].type = EDAC_MC_LAYER_CHANNEL; ++ layers[1].size = 1; ++ layers[1].is_virt_csrow = false; ++ ++ mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); ++ if (!mci) ++ return -ENOMEM; ++ ++ mci->pdev = &pdev->dev; ++ mci->mtype_cap = MEM_FLAG_DDR4 | MEM_FLAG_DDR5; ++ mci->edac_ctl_cap = EDAC_FLAG_SECDED; ++ mci->edac_cap = EDAC_FLAG_SECDED; ++ mci->scrub_cap = SCRUB_FLAG_HW_SRC; ++ mci->scrub_mode = SCRUB_HW_SRC; ++ mci->mod_name = DRV_NAME; ++ mci->ctl_name = "MIC"; ++ mci->dev_name = dev_name(&pdev->dev); ++ ++ rc = init_csrows(mci); ++ if (rc) { ++ dev_err(&pdev->dev, "failed to init csrows\n"); ++ goto probe_exit02; ++ } ++ ++ platform_set_drvdata(pdev, mci); ++ ++ /* register with edac core */ ++ rc = edac_mc_add_mc(mci); ++ if (rc) { ++ dev_err(&pdev->dev, "failed to register with EDAC core\n"); ++ goto probe_exit02; ++ } ++ ++ /* register interrupt handler and enable interrupts */ ++ rc = config_irq(mci, pdev); ++ if (rc) { ++ dev_err(&pdev->dev, "failed setting up irq\n"); ++ goto probe_exit01; ++ } ++ ++ return 0; ++ ++probe_exit01: ++ edac_mc_del_mc(&pdev->dev); ++probe_exit02: ++ edac_mc_free(mci); ++ return rc; ++} ++ ++static void aspeed_remove(struct platform_device *pdev) ++{ ++ struct mem_ctl_info *mci; ++ ++ /* disable interrupts */ ++ regmap_update_bits(aspeed_regmap, DRAMC_INT_MASK, ++ INT_STS_ECC_REC | INT_STS_ECC_ERR, ++ INT_STS_ECC_REC | INT_STS_ECC_ERR); ++ ++ /* free resources */ ++ mci = edac_mc_del_mc(&pdev->dev); ++ if (mci) ++ edac_mc_free(mci); ++} ++ ++static const struct of_device_id aspeed_of_match[] = { ++ { .compatible = "aspeed,ast2700-sdram-edac" }, ++ {}, ++}; ++ ++MODULE_DEVICE_TABLE(of, aspeed_of_match); ++ ++static struct platform_driver aspeed_driver = { ++ .driver = { ++ .name = DRV_NAME, ++ .of_match_table = aspeed_of_match ++ }, ++ .probe = aspeed_probe, ++ .remove = aspeed_remove ++}; ++module_platform_driver(aspeed_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Stefan Schaeckeler "); ++MODULE_DESCRIPTION("Aspeed BMC SoC EDAC driver"); ++MODULE_VERSION("1.0"); +diff --git a/drivers/fsi/fsi-master-aspeed.c b/drivers/fsi/fsi-master-aspeed.c +--- a/drivers/fsi/fsi-master-aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/fsi/fsi-master-aspeed.c 2026-09-09 15:51:44.520241063 +0000 +@@ -3,6 +3,7 @@ + // FSI master driver for AST2600 + + #include ++#include + #include + #include + #include +@@ -24,9 +25,14 @@ + struct device *dev; + void __iomem *base; + struct clk *clk; ++ struct reset_control *rst; + struct gpio_desc *cfam_reset_gpio; + }; + ++struct aspeed_fsi_match_data { ++ bool sup_ahb_access; ++}; ++ + #define to_fsi_master_aspeed(m) \ + container_of(m, struct fsi_master_aspeed, master) + +@@ -60,6 +66,8 @@ + #define OPB1_READ_ORDER2 0x60 + + #define OPB_RETRY_COUNTER 0x64 ++#define OPB_ACCESS_CTRL_REG BIT(18) ++#define OPB_ACCESS_CPU_BRIDGE BIT(19) + + /* OPBn_STATUS */ + #define STATUS_HALFWORD_ACK BIT(0) +@@ -539,6 +547,7 @@ + struct fsi_master_aspeed *aspeed; + int rc, links, reg; + __be32 raw; ++ const struct aspeed_fsi_match_data *match_data; + + rc = tacoma_cabled_fsi_fixup(&pdev->dev); + if (rc) { +@@ -558,16 +567,22 @@ + goto err_free_aspeed; + } + ++ aspeed->rst = devm_reset_control_get_shared(&pdev->dev, NULL); ++ if (IS_ERR(aspeed->rst)) ++ dev_warn(aspeed->dev, "couldn't get reset\n"); ++ else ++ reset_control_deassert(aspeed->rst); ++ + aspeed->clk = devm_clk_get(aspeed->dev, NULL); + if (IS_ERR(aspeed->clk)) { + dev_err(aspeed->dev, "couldn't get clock\n"); + rc = PTR_ERR(aspeed->clk); +- goto err_free_aspeed; ++ goto err_reset; + } + rc = clk_prepare_enable(aspeed->clk); + if (rc) { + dev_err(aspeed->dev, "couldn't enable clock\n"); +- goto err_free_aspeed; ++ goto err_reset; + } + + rc = setup_cfam_reset(aspeed); +@@ -580,7 +595,12 @@ + aspeed->base + OPB_IRQ_MASK); + + /* TODO: determine an appropriate value */ +- writel(0x10, aspeed->base + OPB_RETRY_COUNTER); ++ match_data = of_device_get_match_data(&pdev->dev); ++ if (match_data->sup_ahb_access) ++ writel(0x10 | OPB_ACCESS_CTRL_REG | OPB_ACCESS_CPU_BRIDGE, ++ aspeed->base + OPB_RETRY_COUNTER); ++ else ++ writel(0x10, aspeed->base + OPB_RETRY_COUNTER); + + writel(ctrl_base, aspeed->base + OPB_CTRL_BASE); + writel(fsi_base, aspeed->base + OPB_FSI_BASE); +@@ -641,6 +661,9 @@ + + err_release: + clk_disable_unprepare(aspeed->clk); ++err_reset: ++ if (!IS_ERR(aspeed->rst)) ++ reset_control_assert(aspeed->rst); + err_free_aspeed: + kfree(aspeed); + return rc; +@@ -652,10 +675,21 @@ + + fsi_master_unregister(&aspeed->master); + clk_disable_unprepare(aspeed->clk); ++ if (!IS_ERR(aspeed->rst)) ++ reset_control_assert(aspeed->rst); + } + ++static const struct aspeed_fsi_match_data ast2600_match_data = { ++ .sup_ahb_access = 0, ++}; ++ ++static const struct aspeed_fsi_match_data ast2700_match_data = { ++ .sup_ahb_access = 1, ++}; ++ + static const struct of_device_id fsi_master_aspeed_match[] = { +- { .compatible = "aspeed,ast2600-fsi-master" }, ++ { .compatible = "aspeed,ast2600-fsi-master", .data = &ast2600_match_data }, ++ { .compatible = "aspeed,ast2700-fsi-master", .data = &ast2700_match_data }, + { }, + }; + MODULE_DEVICE_TABLE(of, fsi_master_aspeed_match); +diff --git a/drivers/gpio/gpio-aspeed-ltpi.c b/drivers/gpio/gpio-aspeed-ltpi.c +--- a/drivers/gpio/gpio-aspeed-ltpi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/gpio/gpio-aspeed-ltpi.c 2026-09-09 15:51:44.642239220 +0000 +@@ -0,0 +1,469 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2024 ASPEED ++ * ++ * Author: Billy Tsai ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define LTPI_GPIO_IRQ_STS_BASE 0x200 ++#define LTPI_GPIO_IRQ_STS_OFFSET(x) (LTPI_GPIO_IRQ_STS_BASE + (x) * 0x4) ++#define LTPI_GPIO_CTRL_REG_BASE 0x0 ++#define LTPI_GPIO_CTRL_REG_OFFSET(x) (LTPI_GPIO_CTRL_REG_BASE + (x) * 0x4) ++#define LTPI_GPIO_OUT_DATA BIT(0) ++#define LTPI_GPIO_IRQ_EN BIT(2) ++#define LTPI_GPIO_IRQ_TYPE0 BIT(3) ++#define LTPI_GPIO_IRQ_TYPE1 BIT(4) ++#define LTPI_GPIO_IRQ_TYPE2 BIT(5) ++#define LTPI_GPIO_RST_TOLERANCE BIT(6) ++#define LTPI_GPIO_IRQ_STS BIT(12) ++#define LTPI_GPIO_IN_DATA BIT(13) ++ ++static inline u32 field_get(u32 _mask, u32 _val) ++{ ++ return (((_val) & (_mask)) >> (ffs(_mask) - 1)); ++} ++ ++static inline u32 field_prep(u32 _mask, u32 _val) ++{ ++ return (((_val) << (ffs(_mask) - 1)) & (_mask)); ++} ++ ++static inline void ast_write_bits(void __iomem *addr, u32 mask, u32 val) ++{ ++ iowrite32((ioread32(addr) & ~(mask)) | field_prep(mask, val), addr); ++} ++ ++static inline void ast_clr_bits(void __iomem *addr, u32 mask) ++{ ++ iowrite32((ioread32(addr) & ~(mask)), addr); ++} ++ ++struct aspeed_ltpi_gpio { ++ struct gpio_chip chip; ++ struct device *dev; ++ raw_spinlock_t lock; ++ void __iomem *base; ++ int irq; ++}; ++ ++static int aspeed_ltpi_gpio_init_valid_mask(struct gpio_chip *gc, ++ unsigned long *valid_mask, ++ unsigned int ngpios) ++{ ++ bitmap_set(valid_mask, 0, ngpios); ++ return 0; ++} ++ ++static void aspeed_ltpi_gpio_irq_init_valid_mask(struct gpio_chip *gc, ++ unsigned long *valid_mask, ++ unsigned int ngpios) ++{ ++ unsigned int i; ++ ++ /* input GPIOs are even bits */ ++ for (i = 0; i < ngpios; i++) { ++ if (i % 2) ++ clear_bit(i, valid_mask); ++ } ++} ++ ++static int aspeed_ltpi_gpio_irq_init_hw(struct gpio_chip *gc) ++{ ++ struct aspeed_ltpi_gpio *gpio = gpiochip_get_data(gc); ++ void __iomem *addr; ++ unsigned long flags; ++ int i; ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ ++ for (i = 0; i < (gc->ngpio >> 1); i++) { ++ addr = gpio->base + LTPI_GPIO_CTRL_REG_OFFSET(i); ++ ast_clr_bits(addr, LTPI_GPIO_IRQ_EN | LTPI_GPIO_IRQ_TYPE0 | ++ LTPI_GPIO_IRQ_TYPE1 | ++ LTPI_GPIO_IRQ_TYPE2); ++ ast_write_bits(addr, LTPI_GPIO_IRQ_STS, 1); ++ } ++ ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ return 0; ++} ++ ++static bool aspeed_ltpi_gpio_is_input(unsigned int offset) ++{ ++ return !(offset % 2); ++} ++ ++static int aspeed_ltpi_gpio_get(struct gpio_chip *gc, unsigned int offset) ++{ ++ struct aspeed_ltpi_gpio *gpio = gpiochip_get_data(gc); ++ void __iomem const *addr = ++ gpio->base + LTPI_GPIO_CTRL_REG_OFFSET(offset >> 1); ++ unsigned long flags; ++ u32 mask; ++ int rc = 0; ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ ++ mask = aspeed_ltpi_gpio_is_input(offset) ? LTPI_GPIO_IN_DATA : ++ LTPI_GPIO_OUT_DATA; ++ rc = !!(field_get(mask, ioread32(addr))); ++ ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ return rc; ++} ++ ++static int ltpi_gpio_set_value(struct gpio_chip *gc, unsigned int offset, ++ int val) ++{ ++ struct aspeed_ltpi_gpio *gpio = gpiochip_get_data(gc); ++ void __iomem *addr = ++ gpio->base + LTPI_GPIO_CTRL_REG_OFFSET(offset >> 1); ++ u32 reg = 0; ++ ++ if (aspeed_ltpi_gpio_is_input(offset)) ++ return -EINVAL; ++ ++ reg = ioread32(addr); ++ ++ if (val) ++ reg |= LTPI_GPIO_OUT_DATA; ++ else ++ reg &= ~LTPI_GPIO_OUT_DATA; ++ ++ iowrite32(reg, addr); ++ ++ return 0; ++} ++ ++static void aspeed_ltpi_gpio_set(struct gpio_chip *gc, unsigned int offset, ++ int val) ++{ ++ struct aspeed_ltpi_gpio *gpio = gpiochip_get_data(gc); ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ ltpi_gpio_set_value(gc, offset, val); ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++} ++ ++static int aspeed_ltpi_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) ++{ ++ return aspeed_ltpi_gpio_is_input(offset) ? 0 : -EINVAL; ++} ++ ++static int aspeed_ltpi_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, ++ int val) ++{ ++ struct aspeed_ltpi_gpio *gpio = gpiochip_get_data(gc); ++ unsigned long flags; ++ int rc; ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ rc = ltpi_gpio_set_value(gc, offset, val); ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ return rc; ++} ++ ++static int aspeed_ltpi_gpio_get_direction(struct gpio_chip *gc, ++ unsigned int offset) ++{ ++ return !!aspeed_ltpi_gpio_is_input(offset); ++} ++ ++static void irqd_to_aspeed_ltpi_gpio_data(struct irq_data *d, ++ struct aspeed_ltpi_gpio **gpio, ++ int *offset) ++{ ++ struct aspeed_ltpi_gpio *internal; ++ ++ *offset = irqd_to_hwirq(d); ++ internal = irq_data_get_irq_chip_data(d); ++ WARN_ON(!internal); ++ ++ *gpio = internal; ++} ++ ++static void aspeed_ltpi_gpio_irq_ack(struct irq_data *d) ++{ ++ struct aspeed_ltpi_gpio *gpio; ++ unsigned long flags; ++ void __iomem *status_addr; ++ int offset; ++ ++ irqd_to_aspeed_ltpi_gpio_data(d, &gpio, &offset); ++ ++ status_addr = gpio->base + LTPI_GPIO_CTRL_REG_OFFSET(offset >> 1); ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ ++ ast_write_bits(status_addr, LTPI_GPIO_IRQ_STS, 1); ++ ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++} ++ ++static void aspeed_ltpi_gpio_irq_set_mask(struct irq_data *d, bool set) ++{ ++ struct aspeed_ltpi_gpio *gpio; ++ unsigned long flags; ++ void __iomem *addr; ++ int offset; ++ ++ irqd_to_aspeed_ltpi_gpio_data(d, &gpio, &offset); ++ addr = gpio->base + LTPI_GPIO_CTRL_REG_OFFSET(offset >> 1); ++ ++ /* Unmasking the IRQ */ ++ if (set) ++ gpiochip_enable_irq(&gpio->chip, irqd_to_hwirq(d)); ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ if (set) ++ ast_write_bits(addr, LTPI_GPIO_IRQ_EN, 1); ++ else ++ ast_clr_bits(addr, LTPI_GPIO_IRQ_EN); ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ /* Masking the IRQ */ ++ if (!set) ++ gpiochip_disable_irq(&gpio->chip, irqd_to_hwirq(d)); ++} ++ ++static void aspeed_ltpi_gpio_irq_mask(struct irq_data *d) ++{ ++ aspeed_ltpi_gpio_irq_set_mask(d, false); ++} ++ ++static void aspeed_ltpi_gpio_irq_unmask(struct irq_data *d) ++{ ++ aspeed_ltpi_gpio_irq_set_mask(d, true); ++} ++ ++static int aspeed_ltpi_gpio_set_type(struct irq_data *d, unsigned int type) ++{ ++ u32 type0 = 0; ++ u32 type1 = 0; ++ u32 type2 = 0; ++ irq_flow_handler_t handler; ++ struct aspeed_ltpi_gpio *gpio; ++ unsigned long flags; ++ void __iomem *addr; ++ int offset; ++ ++ irqd_to_aspeed_ltpi_gpio_data(d, &gpio, &offset); ++ addr = gpio->base + LTPI_GPIO_CTRL_REG_OFFSET(offset >> 1); ++ ++ switch (type & IRQ_TYPE_SENSE_MASK) { ++ case IRQ_TYPE_EDGE_BOTH: ++ type2 = 1; ++ fallthrough; ++ case IRQ_TYPE_EDGE_RISING: ++ type0 = 1; ++ fallthrough; ++ case IRQ_TYPE_EDGE_FALLING: ++ handler = handle_edge_irq; ++ break; ++ case IRQ_TYPE_LEVEL_HIGH: ++ type0 = 1; ++ fallthrough; ++ case IRQ_TYPE_LEVEL_LOW: ++ type1 = 1; ++ handler = handle_level_irq; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ ++ ast_write_bits(addr, LTPI_GPIO_IRQ_TYPE2, type2); ++ ast_write_bits(addr, LTPI_GPIO_IRQ_TYPE1, type1); ++ ast_write_bits(addr, LTPI_GPIO_IRQ_TYPE0, type0); ++ ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ irq_set_handler_locked(d, handler); ++ return 0; ++} ++ ++static void aspeed_ltpi_gpio_irq_handler(struct irq_desc *desc) ++{ ++ struct gpio_chip *gc = irq_desc_get_handler_data(desc); ++ struct irq_chip *ic = irq_desc_get_chip(desc); ++ struct aspeed_ltpi_gpio const *gpio = gpiochip_get_data(gc); ++ unsigned int i, p, banks; ++ unsigned long reg; ++ void __iomem const *addr; ++ ++ chained_irq_enter(ic, desc); ++ ++ banks = DIV_ROUND_UP(gpio->chip.ngpio >> 1, 32); ++ for (i = 0; i < banks; i++) { ++ addr = gpio->base + LTPI_GPIO_IRQ_STS_OFFSET(i); ++ ++ reg = ioread32(addr); ++ ++ for_each_set_bit(p, ®, 32) ++ generic_handle_domain_irq(gc->irq.domain, ++ (i * 32 + p) * 2); ++ } ++ chained_irq_exit(ic, desc); ++} ++ ++static void aspeed_ltpi_gpio_irq_print_chip(struct irq_data *d, ++ struct seq_file *p) ++{ ++ struct aspeed_ltpi_gpio *gpio; ++ int offset; ++ ++ irqd_to_aspeed_ltpi_gpio_data(d, &gpio, &offset); ++ seq_printf(p, dev_name(gpio->dev)); ++} ++ ++static const struct irq_chip aspeed_ltpi_gpio_irq_chip = { ++ .irq_ack = aspeed_ltpi_gpio_irq_ack, ++ .irq_mask = aspeed_ltpi_gpio_irq_mask, ++ .irq_unmask = aspeed_ltpi_gpio_irq_unmask, ++ .irq_set_type = aspeed_ltpi_gpio_set_type, ++ .irq_print_chip = aspeed_ltpi_gpio_irq_print_chip, ++ .flags = IRQCHIP_IMMUTABLE, ++ GPIOCHIP_IRQ_RESOURCE_HELPERS, ++}; ++ ++static int aspeed_ltpi_gpio_setup_irqs(struct aspeed_ltpi_gpio *gpio, ++ struct platform_device *pdev) ++{ ++ int rc; ++ struct gpio_irq_chip *irq; ++ ++ rc = platform_get_irq(pdev, 0); ++ if (rc < 0) ++ return rc; ++ ++ gpio->irq = rc; ++ ++ irq = &gpio->chip.irq; ++ gpio_irq_chip_set_chip(irq, &aspeed_ltpi_gpio_irq_chip); ++ irq->init_valid_mask = aspeed_ltpi_gpio_irq_init_valid_mask; ++ irq->handler = handle_bad_irq; ++ irq->init_hw = aspeed_ltpi_gpio_irq_init_hw; ++ irq->default_type = IRQ_TYPE_NONE; ++ irq->parent_handler = aspeed_ltpi_gpio_irq_handler; ++ irq->parent_handler_data = gpio; ++ irq->parents = &gpio->irq; ++ irq->num_parents = 1; ++ ++ return 0; ++} ++ ++static int aspeed_ltpi_gpio_reset_tolerance(struct gpio_chip *chip, ++ unsigned int offset, bool enable) ++{ ++ struct aspeed_ltpi_gpio *gpio = gpiochip_get_data(chip); ++ unsigned long flags; ++ void __iomem *reg; ++ ++ reg = gpio->base + LTPI_GPIO_CTRL_REG_OFFSET(offset >> 1); ++ ++ raw_spin_lock_irqsave(&gpio->lock, flags); ++ ++ if (enable) ++ ast_write_bits(reg, LTPI_GPIO_RST_TOLERANCE, 1); ++ else ++ ast_clr_bits(reg, LTPI_GPIO_RST_TOLERANCE); ++ ++ raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ return 0; ++} ++ ++static int aspeed_ltpi_gpio_set_config(struct gpio_chip *chip, ++ unsigned int offset, ++ unsigned long config) ++{ ++ unsigned long param = pinconf_to_config_param(config); ++ u32 arg = pinconf_to_config_argument(config); ++ ++ if (param == PIN_CONFIG_PERSIST_STATE) ++ return aspeed_ltpi_gpio_reset_tolerance(chip, offset, arg); ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct of_device_id aspeed_ltpi_gpio_of_table[] = { ++ { .compatible = "aspeed,ast2700-ltpi-gpio" }, ++ {} ++}; ++ ++MODULE_DEVICE_TABLE(of, aspeed_ltpi_gpio_of_table); ++ ++static int __init aspeed_ltpi_gpio_probe(struct platform_device *pdev) ++{ ++ u32 nr_gpios; ++ struct aspeed_ltpi_gpio *gpio; ++ int rc; ++ ++ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); ++ if (!gpio) ++ return -ENOMEM; ++ ++ gpio->base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(gpio->base)) ++ return PTR_ERR(gpio->base); ++ ++ gpio->dev = &pdev->dev; ++ ++ rc = device_property_read_u32(&pdev->dev, "ngpios", &nr_gpios); ++ if (rc < 0) { ++ dev_err(&pdev->dev, "Could not read ngpios property\n"); ++ return -EINVAL; ++ } ++ ++ raw_spin_lock_init(&gpio->lock); ++ ++ gpio->chip.parent = &pdev->dev; ++ gpio->chip.ngpio = nr_gpios * 2; ++ gpio->chip.init_valid_mask = aspeed_ltpi_gpio_init_valid_mask; ++ gpio->chip.direction_input = aspeed_ltpi_gpio_dir_in; ++ gpio->chip.direction_output = aspeed_ltpi_gpio_dir_out; ++ gpio->chip.get_direction = aspeed_ltpi_gpio_get_direction; ++ gpio->chip.request = NULL; ++ gpio->chip.free = NULL; ++ gpio->chip.get = aspeed_ltpi_gpio_get; ++ gpio->chip.set = aspeed_ltpi_gpio_set; ++ gpio->chip.set_config = aspeed_ltpi_gpio_set_config; ++ gpio->chip.label = dev_name(&pdev->dev); ++ gpio->chip.base = -1; ++ ++ aspeed_ltpi_gpio_setup_irqs(gpio, pdev); ++ ++ rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio); ++ if (rc < 0) ++ return rc; ++ ++ return 0; ++} ++ ++static struct platform_driver aspeed_ltpi_gpio_driver = { ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_ltpi_gpio_of_table, ++ }, ++}; ++ ++module_platform_driver_probe(aspeed_ltpi_gpio_driver, aspeed_ltpi_gpio_probe); ++MODULE_DESCRIPTION("Aspeed LTPI GPIO Driver"); +diff --git a/drivers/gpio/gpio-aspeed-sgpio.c b/drivers/gpio/gpio-aspeed-sgpio.c +--- a/drivers/gpio/gpio-aspeed-sgpio.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpio/gpio-aspeed-sgpio.c 2026-09-09 15:51:44.637239295 +0000 +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -18,14 +19,50 @@ + #include + #include + +-#define ASPEED_SGPIO_CTRL 0x54 ++/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */ ++#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) ++#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask)) ++ ++#define SGPIO_G7_IRQ_STS_BASE 0x40 ++#define SGPIO_G7_IRQ_STS_OFFSET(x) (SGPIO_G7_IRQ_STS_BASE + (x) * 0x4) ++#define SGPIO_G7_CTRL_REG_BASE 0x80 ++#define SGPIO_G7_CTRL_REG_OFFSET(x) (SGPIO_G7_CTRL_REG_BASE + (x) * 0x4) ++#define SGPIO_G7_OUT_DATA BIT(0) ++#define SGPIO_G7_PARALLEL_OUT_DATA BIT(1) ++#define SGPIO_G7_IRQ_EN BIT(2) ++#define SGPIO_G7_IRQ_TYPE0 BIT(3) ++#define SGPIO_G7_IRQ_TYPE1 BIT(4) ++#define SGPIO_G7_IRQ_TYPE2 BIT(5) ++#define SGPIO_G7_RST_TOLERANCE BIT(6) ++#define SGPIO_G7_INPUT_MASK BIT(9) ++#define SGPIO_G7_HW_BYPASS_EN BIT(10) ++#define SGPIO_G7_HW_IN_SEL BIT(11) ++#define SGPIO_G7_IRQ_STS BIT(12) ++#define SGPIO_G7_IN_DATA BIT(13) ++#define SGPIO_G7_PARALLEL_IN_DATA BIT(14) ++#define SGPIO_G7_SERIAL_OUT_SEL GENMASK(17, 16) ++#define SGPIO_G7_PARALLEL_OUT_SEL GENMASK(19, 18) ++#define SELECT_FROM_CSR 0 ++#define SELECT_FROM_PARALLEL_IN 1 ++#define SELECT_FROM_SERIAL_IN 2 ++ ++#define BMC_CONTROL_START_INDEX 128 ++#define BMC_CONTROL_END_INDEX 143 ++ ++#define ASPEED_SGPIO_G4_CFG_OFFSET 0x54 ++#define ASPEED_SGPIO_G7_CFG_OFFSET 0x0 + + #define ASPEED_SGPIO_CLK_DIV_MASK GENMASK(31, 16) + #define ASPEED_SGPIO_ENABLE BIT(0) + #define ASPEED_SGPIO_PINS_SHIFT 6 + ++#define ASPEED_SGPIO_G7_SERIAL_OUT_LOCK BIT(12) ++ + struct aspeed_sgpio_pdata { + const u32 pin_mask; ++ const struct aspeed_sgpio_llops *llops; ++ const u32 cfg_offset; ++ const bool slave; + }; + + struct aspeed_sgpio { +@@ -35,6 +72,7 @@ + raw_spinlock_t lock; + void __iomem *base; + int irq; ++ const struct aspeed_sgpio_pdata *pdata; + }; + + struct aspeed_sgpio_bank { +@@ -42,7 +80,6 @@ + u16 rdata_reg; + u16 irq_regs; + u16 tolerance_regs; +- const char names[4][3]; + }; + + /* +@@ -58,28 +95,24 @@ + .rdata_reg = 0x0070, + .irq_regs = 0x0004, + .tolerance_regs = 0x0018, +- .names = { "A", "B", "C", "D" }, + }, + { + .val_regs = 0x001C, + .rdata_reg = 0x0074, + .irq_regs = 0x0020, + .tolerance_regs = 0x0034, +- .names = { "E", "F", "G", "H" }, + }, + { + .val_regs = 0x0038, + .rdata_reg = 0x0078, + .irq_regs = 0x003C, + .tolerance_regs = 0x0050, +- .names = { "I", "J", "K", "L" }, + }, + { + .val_regs = 0x0090, + .rdata_reg = 0x007C, + .irq_regs = 0x0094, + .tolerance_regs = 0x00A8, +- .names = { "M", "N", "O", "P" }, + }, + }; + +@@ -92,6 +125,19 @@ + reg_irq_type2, + reg_irq_status, + reg_tolerance, ++ reg_serial_out_sel, ++ reg_parallel_out_sel, ++}; ++ ++struct aspeed_sgpio_llops { ++ void (*reg_bit_set)(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg, bool val); ++ bool (*reg_bit_get)(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg); ++ void (*reg_bank_set)(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg, u32 val); ++ int (*reg_bank_get)(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg); + }; + + #define GPIO_VAL_VALUE 0x00 +@@ -101,9 +147,9 @@ + #define GPIO_IRQ_TYPE2 0x0C + #define GPIO_IRQ_STATUS 0x10 + +-static void __iomem *bank_reg(struct aspeed_sgpio *gpio, +- const struct aspeed_sgpio_bank *bank, +- const enum aspeed_sgpio_reg reg) ++static void __iomem *aspeed_sgpio_g4_bank_reg(struct aspeed_sgpio *gpio, ++ const struct aspeed_sgpio_bank *bank, ++ const enum aspeed_sgpio_reg reg) + { + switch (reg) { + case reg_val: +@@ -128,6 +174,34 @@ + } + } + ++static u32 aspeed_sgpio_g7_reg_mask(const enum aspeed_sgpio_reg reg) ++{ ++ switch (reg) { ++ case reg_val: ++ case reg_rdata: ++ return SGPIO_G7_OUT_DATA; ++ case reg_irq_enable: ++ return SGPIO_G7_IRQ_EN; ++ case reg_irq_type0: ++ return SGPIO_G7_IRQ_TYPE0; ++ case reg_irq_type1: ++ return SGPIO_G7_IRQ_TYPE1; ++ case reg_irq_type2: ++ return SGPIO_G7_IRQ_TYPE2; ++ case reg_irq_status: ++ return SGPIO_G7_IRQ_STS; ++ case reg_tolerance: ++ return SGPIO_G7_RST_TOLERANCE; ++ case reg_serial_out_sel: ++ return SGPIO_G7_SERIAL_OUT_SEL; ++ case reg_parallel_out_sel: ++ return SGPIO_G7_PARALLEL_OUT_SEL; ++ default: ++ WARN_ON_ONCE(1); ++ return 0; ++ } ++} ++ + #define GPIO_BANK(x) ((x) >> 6) + #define GPIO_OFFSET(x) ((x) & GENMASK(5, 0)) + #define GPIO_BIT(x) BIT(GPIO_OFFSET(x) >> 1) +@@ -166,20 +240,24 @@ + return !(offset % 2); + } + ++static bool aspeed_sgpios_ctrl_by_csr(unsigned int offset) ++{ ++ if (offset >= BMC_CONTROL_START_INDEX && ++ offset <= BMC_CONTROL_END_INDEX) ++ return true; ++ return false; ++} ++ + static int aspeed_sgpio_get(struct gpio_chip *gc, unsigned int offset) + { + struct aspeed_sgpio *gpio = gpiochip_get_data(gc); +- const struct aspeed_sgpio_bank *bank = to_bank(offset); +- unsigned long flags; + enum aspeed_sgpio_reg reg; + int rc = 0; + +- raw_spin_lock_irqsave(&gpio->lock, flags); ++ guard(raw_spinlock_irqsave)(&gpio->lock); + + reg = aspeed_sgpio_is_input(offset) ? reg_val : reg_rdata; +- rc = !!(ioread32(bank_reg(gpio, bank, reg)) & GPIO_BIT(offset)); +- +- raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ rc = gpio->pdata->llops->reg_bit_get(gpio, offset, reg); + + return rc; + } +@@ -187,26 +265,11 @@ + static int sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val) + { + struct aspeed_sgpio *gpio = gpiochip_get_data(gc); +- const struct aspeed_sgpio_bank *bank = to_bank(offset); +- void __iomem *addr_r, *addr_w; +- u32 reg = 0; + + if (aspeed_sgpio_is_input(offset)) + return -EINVAL; + +- /* Since this is an output, read the cached value from rdata, then +- * update val. */ +- addr_r = bank_reg(gpio, bank, reg_rdata); +- addr_w = bank_reg(gpio, bank, reg_val); +- +- reg = ioread32(addr_r); +- +- if (val) +- reg |= GPIO_BIT(offset); +- else +- reg &= ~GPIO_BIT(offset); +- +- iowrite32(reg, addr_w); ++ gpio->pdata->llops->reg_bit_set(gpio, offset, reg_val, val); + + return 0; + } +@@ -214,13 +277,11 @@ + static void aspeed_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val) + { + struct aspeed_sgpio *gpio = gpiochip_get_data(gc); +- unsigned long flags; +- +- raw_spin_lock_irqsave(&gpio->lock, flags); + ++ guard(raw_spinlock_irqsave)(&gpio->lock); + sgpio_set_value(gc, offset, val); + +- raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ return; + } + + static int aspeed_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset) +@@ -231,15 +292,14 @@ + static int aspeed_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val) + { + struct aspeed_sgpio *gpio = gpiochip_get_data(gc); +- unsigned long flags; + int rc; + + /* No special action is required for setting the direction; we'll + * error-out in sgpio_set_value if this isn't an output GPIO */ + +- raw_spin_lock_irqsave(&gpio->lock, flags); ++ guard(raw_spinlock_irqsave)(&gpio->lock); ++ + rc = sgpio_set_value(gc, offset, val); +- raw_spin_unlock_irqrestore(&gpio->lock, flags); + + return rc; + } +@@ -249,75 +309,33 @@ + return !!aspeed_sgpio_is_input(offset); + } + +-static void irqd_to_aspeed_sgpio_data(struct irq_data *d, +- struct aspeed_sgpio **gpio, +- const struct aspeed_sgpio_bank **bank, +- u32 *bit, int *offset) +-{ +- struct aspeed_sgpio *internal; +- +- *offset = irqd_to_hwirq(d); +- internal = irq_data_get_irq_chip_data(d); +- WARN_ON(!internal); +- +- *gpio = internal; +- *bank = to_bank(*offset); +- *bit = GPIO_BIT(*offset); +-} +- + static void aspeed_sgpio_irq_ack(struct irq_data *d) + { +- const struct aspeed_sgpio_bank *bank; +- struct aspeed_sgpio *gpio; +- unsigned long flags; +- void __iomem *status_addr; +- int offset; +- u32 bit; +- +- irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset); +- +- status_addr = bank_reg(gpio, bank, reg_irq_status); ++ struct aspeed_sgpio *gpio = irq_data_get_irq_chip_data(d); ++ int offset = irqd_to_hwirq(d); + +- raw_spin_lock_irqsave(&gpio->lock, flags); ++ guard(raw_spinlock_irqsave)(&gpio->lock); + +- iowrite32(bit, status_addr); +- +- raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ gpio->pdata->llops->reg_bit_set(gpio, offset, reg_irq_status, 1); + } + + static void aspeed_sgpio_irq_set_mask(struct irq_data *d, bool set) + { +- const struct aspeed_sgpio_bank *bank; +- struct aspeed_sgpio *gpio; +- unsigned long flags; +- u32 reg, bit; +- void __iomem *addr; +- int offset; +- +- irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset); +- addr = bank_reg(gpio, bank, reg_irq_enable); ++ struct aspeed_sgpio *gpio = irq_data_get_irq_chip_data(d); ++ int offset = irqd_to_hwirq(d); + + /* Unmasking the IRQ */ + if (set) +- gpiochip_enable_irq(&gpio->chip, irqd_to_hwirq(d)); +- +- raw_spin_lock_irqsave(&gpio->lock, flags); +- +- reg = ioread32(addr); +- if (set) +- reg |= bit; +- else +- reg &= ~bit; +- +- iowrite32(reg, addr); +- +- raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ gpiochip_enable_irq(&gpio->chip, offset); ++ scoped_guard(raw_spinlock_irqsave, &gpio->lock) ++ { ++ gpio->pdata->llops->reg_bit_set(gpio, offset, reg_irq_enable, ++ set); ++ } + + /* Masking the IRQ */ + if (!set) +- gpiochip_disable_irq(&gpio->chip, irqd_to_hwirq(d)); +- +- ++ gpiochip_disable_irq(&gpio->chip, offset); + } + + static void aspeed_sgpio_irq_mask(struct irq_data *d) +@@ -335,55 +353,36 @@ + u32 type0 = 0; + u32 type1 = 0; + u32 type2 = 0; +- u32 bit, reg; +- const struct aspeed_sgpio_bank *bank; + irq_flow_handler_t handler; +- struct aspeed_sgpio *gpio; +- unsigned long flags; +- void __iomem *addr; +- int offset; +- +- irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset); ++ struct aspeed_sgpio *gpio = irq_data_get_irq_chip_data(d); ++ int offset = irqd_to_hwirq(d); + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_BOTH: +- type2 |= bit; ++ type2 = 1; + fallthrough; + case IRQ_TYPE_EDGE_RISING: +- type0 |= bit; ++ type0 = 1; + fallthrough; + case IRQ_TYPE_EDGE_FALLING: + handler = handle_edge_irq; + break; + case IRQ_TYPE_LEVEL_HIGH: +- type0 |= bit; ++ type0 = 1; + fallthrough; + case IRQ_TYPE_LEVEL_LOW: +- type1 |= bit; ++ type1 = 1; + handler = handle_level_irq; + break; + default: + return -EINVAL; + } + +- raw_spin_lock_irqsave(&gpio->lock, flags); +- +- addr = bank_reg(gpio, bank, reg_irq_type0); +- reg = ioread32(addr); +- reg = (reg & ~bit) | type0; +- iowrite32(reg, addr); +- +- addr = bank_reg(gpio, bank, reg_irq_type1); +- reg = ioread32(addr); +- reg = (reg & ~bit) | type1; +- iowrite32(reg, addr); +- +- addr = bank_reg(gpio, bank, reg_irq_type2); +- reg = ioread32(addr); +- reg = (reg & ~bit) | type2; +- iowrite32(reg, addr); +- +- raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ scoped_guard(raw_spinlock_irqsave, &gpio->lock) { ++ gpio->pdata->llops->reg_bit_set(gpio, offset, reg_irq_type0, type0); ++ gpio->pdata->llops->reg_bit_set(gpio, offset, reg_irq_type1, type1); ++ gpio->pdata->llops->reg_bit_set(gpio, offset, reg_irq_type2, type2); ++ } + + irq_set_handler_locked(d, handler); + +@@ -395,15 +394,14 @@ + struct gpio_chip *gc = irq_desc_get_handler_data(desc); + struct irq_chip *ic = irq_desc_get_chip(desc); + struct aspeed_sgpio *data = gpiochip_get_data(gc); +- unsigned int i, p; ++ unsigned int i, p, banks; + unsigned long reg; + + chained_irq_enter(ic, desc); + +- for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) { +- const struct aspeed_sgpio_bank *bank = &aspeed_sgpio_banks[i]; +- +- reg = ioread32(bank_reg(data, bank, reg_irq_status)); ++ banks = DIV_ROUND_UP(gc->ngpio, 64); ++ for (i = 0; i < banks; i++) { ++ reg = data->pdata->llops->reg_bank_get(data, i << 6, reg_irq_status); + + for_each_set_bit(p, ®, 32) + generic_handle_domain_irq(gc->irq.domain, (i * 32 + p) * 2); +@@ -414,13 +412,9 @@ + + static void aspeed_sgpio_irq_print_chip(struct irq_data *d, struct seq_file *p) + { +- const struct aspeed_sgpio_bank *bank; +- struct aspeed_sgpio *gpio; +- u32 bit; +- int offset; ++ struct aspeed_sgpio *gpio = irq_data_get_irq_chip_data(d); + +- irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset); +- seq_printf(p, dev_name(gpio->dev)); ++ seq_puts(p, dev_name(gpio->dev)); + } + + static const struct irq_chip aspeed_sgpio_irq_chip = { +@@ -437,7 +431,6 @@ + struct platform_device *pdev) + { + int rc, i; +- const struct aspeed_sgpio_bank *bank; + struct gpio_irq_chip *irq; + + rc = platform_get_irq(pdev, 0); +@@ -447,12 +440,11 @@ + gpio->irq = rc; + + /* Disable IRQ and clear Interrupt status registers for all SGPIO Pins. */ +- for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) { +- bank = &aspeed_sgpio_banks[i]; ++ for (i = 0; i < gpio->chip.ngpio; i += 2) { + /* disable irq enable bits */ +- iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_enable)); ++ gpio->pdata->llops->reg_bit_set(gpio, i, reg_irq_enable, 0); + /* clear status bits */ +- iowrite32(0xffffffff, bank_reg(gpio, bank, reg_irq_status)); ++ gpio->pdata->llops->reg_bit_set(gpio, i, reg_irq_status, 1); + } + + irq = &gpio->chip.irq; +@@ -466,45 +458,98 @@ + irq->num_parents = 1; + + /* Apply default IRQ settings */ +- for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) { +- bank = &aspeed_sgpio_banks[i]; ++ for (i = 0; i < gpio->chip.ngpio; i += 2) { + /* set falling or level-low irq */ +- iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_type0)); ++ gpio->pdata->llops->reg_bit_set(gpio, i, reg_irq_type0, 0); + /* trigger type is edge */ +- iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_type1)); ++ gpio->pdata->llops->reg_bit_set(gpio, i, reg_irq_type1, 0); + /* single edge trigger */ +- iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_type2)); ++ gpio->pdata->llops->reg_bit_set(gpio, i, reg_irq_type2, 0); + } + + return 0; + } + ++static void aspeed_sgpio_g4_reg_bit_set(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg, bool val) ++{ ++ const struct aspeed_sgpio_bank *bank = to_bank(offset); ++ void __iomem *addr = aspeed_sgpio_g4_bank_reg(gpio, bank, reg); ++ u32 temp; ++ ++ if (reg == reg_val) { ++ /* Since this is an output, read the cached value from rdata, then update val. */ ++ addr = aspeed_sgpio_g4_bank_reg(gpio, bank, reg_rdata); ++ temp = ioread32(addr); ++ if (val) ++ temp |= GPIO_BIT(offset); ++ else ++ temp &= ~GPIO_BIT(offset); ++ ++ addr = aspeed_sgpio_g4_bank_reg(gpio, bank, reg_val); ++ iowrite32(temp, addr); ++ } else if (reg == reg_irq_status) { ++ if (val) ++ iowrite32(GPIO_BIT(offset), addr); ++ } else { ++ /* When setting other registers, we read from the register itself */ ++ temp = ioread32(addr); ++ if (val) ++ temp |= GPIO_BIT(offset); ++ else ++ temp &= ~GPIO_BIT(offset); ++ iowrite32(temp, addr); ++ } ++} ++ ++static bool aspeed_sgpio_g4_reg_bit_get(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg) ++{ ++ const struct aspeed_sgpio_bank *bank = to_bank(offset); ++ void __iomem *addr = aspeed_sgpio_g4_bank_reg(gpio, bank, reg); ++ ++ return !!(ioread32(addr) & GPIO_BIT(offset)); ++} ++ ++static int aspeed_sgpio_g4_reg_bank_get(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg) ++{ ++ const struct aspeed_sgpio_bank *bank = to_bank(offset); ++ void __iomem *addr = aspeed_sgpio_g4_bank_reg(gpio, bank, reg); ++ ++ if (reg == reg_irq_status) ++ return ioread32(addr); ++ else ++ return -EOPNOTSUPP; ++} ++ ++static void aspeed_sgpio_g4_reg_bank_set(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg, u32 val) ++{ ++ /* G4 doesn't support bank set for now */ ++} ++ ++static const struct aspeed_sgpio_llops aspeed_sgpio_g4_llops = { ++ .reg_bit_set = aspeed_sgpio_g4_reg_bit_set, ++ .reg_bit_get = aspeed_sgpio_g4_reg_bit_get, ++ .reg_bank_set = aspeed_sgpio_g4_reg_bank_set, ++ .reg_bank_get = aspeed_sgpio_g4_reg_bank_get, ++}; ++ + static const struct aspeed_sgpio_pdata ast2400_sgpio_pdata = { + .pin_mask = GENMASK(9, 6), ++ .llops = &aspeed_sgpio_g4_llops, ++ .cfg_offset = ASPEED_SGPIO_G4_CFG_OFFSET, + }; + + static int aspeed_sgpio_reset_tolerance(struct gpio_chip *chip, + unsigned int offset, bool enable) + { + struct aspeed_sgpio *gpio = gpiochip_get_data(chip); +- unsigned long flags; +- void __iomem *reg; +- u32 val; +- +- reg = bank_reg(gpio, to_bank(offset), reg_tolerance); +- +- raw_spin_lock_irqsave(&gpio->lock, flags); +- +- val = readl(reg); +- +- if (enable) +- val |= GPIO_BIT(offset); +- else +- val &= ~GPIO_BIT(offset); + +- writel(val, reg); ++ guard(raw_spinlock_irqsave)(&gpio->lock); + +- raw_spin_unlock_irqrestore(&gpio->lock, flags); ++ gpio->pdata->llops->reg_bit_set(gpio, offset, reg_tolerance, enable); + + return 0; + } +@@ -523,12 +568,151 @@ + + static const struct aspeed_sgpio_pdata ast2600_sgpiom_pdata = { + .pin_mask = GENMASK(10, 6), ++ .llops = &aspeed_sgpio_g4_llops, ++ .cfg_offset = ASPEED_SGPIO_G4_CFG_OFFSET, ++}; ++ ++static void aspeed_sgpio_g7_serial_out_lock(struct aspeed_sgpio *gpio, bool lock) ++{ ++ void __iomem *cfg = gpio->base + gpio->pdata->cfg_offset; ++ u32 val = ioread32(cfg); ++ ++ if (lock) ++ val |= ASPEED_SGPIO_G7_SERIAL_OUT_LOCK; ++ else ++ val &= ~ASPEED_SGPIO_G7_SERIAL_OUT_LOCK; ++ iowrite32(val, cfg); ++} ++ ++static void aspeed_sgpio_g7_reg_bit_set(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg, bool val) ++{ ++ u32 mask = aspeed_sgpio_g7_reg_mask(reg); ++ void __iomem *addr = gpio->base + SGPIO_G7_CTRL_REG_OFFSET(offset >> 1); ++ bool serial_out = false; ++ u32 write_val; ++ ++ if (reg == reg_val || reg == reg_rdata) { ++ if (gpio->pdata->slave && !aspeed_sgpios_ctrl_by_csr(offset)) { ++ // Ensure the parallel out value control by the software. ++ gpio->pdata->llops->reg_bank_set(gpio, offset, reg_parallel_out_sel, ++ SELECT_FROM_CSR); ++ mask = SGPIO_G7_PARALLEL_OUT_DATA; ++ } else { ++ // Ensure the serial out value control by the software. ++ gpio->pdata->llops->reg_bank_set(gpio, offset, reg_serial_out_sel, ++ SELECT_FROM_CSR); ++ mask = SGPIO_G7_OUT_DATA; ++ serial_out = (reg == reg_val); ++ } ++ } ++ ++ if (mask) { ++ /* ++ * Freeze the serial-out shifter across the value update so a ++ * half-written level can never be clocked out mid-waveform, ++ * then release it once the new value is in place. ++ */ ++ if (serial_out) ++ aspeed_sgpio_g7_serial_out_lock(gpio, true); ++ write_val = (ioread32(addr) & ~(mask)) | field_prep(mask, val); ++ iowrite32(write_val, addr); ++ if (serial_out) ++ aspeed_sgpio_g7_serial_out_lock(gpio, false); ++ } ++} ++ ++static bool aspeed_sgpio_g7_reg_bit_get(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg) ++{ ++ u32 mask = aspeed_sgpio_g7_reg_mask(reg); ++ void __iomem *addr; ++ ++ addr = gpio->base + SGPIO_G7_CTRL_REG_OFFSET(offset >> 1); ++ if (reg == reg_val) { ++ if (gpio->pdata->slave && !aspeed_sgpios_ctrl_by_csr(offset)) { ++ unsigned int pin = offset >> 1; ++ ++ /* ++ * Slave pins 15:0 split their input source: data[3:0] ++ * (pins 0..3) come from the parallel-in pads, while ++ * data[15:4] (pins 4..15) are sampled from the serial ++ * input stream driven by the master. ++ */ ++ if (pin >= 4 && pin <= 15) ++ mask = SGPIO_G7_IN_DATA; ++ else ++ mask = SGPIO_G7_PARALLEL_IN_DATA; ++ } else { ++ mask = SGPIO_G7_IN_DATA; ++ } ++ } ++ ++ if (reg == reg_rdata) { ++ if (gpio->pdata->slave && !aspeed_sgpios_ctrl_by_csr(offset)) ++ mask = SGPIO_G7_PARALLEL_OUT_DATA; ++ else ++ mask = SGPIO_G7_OUT_DATA; ++ } ++ ++ if (mask) ++ return field_get(mask, ioread32(addr)); ++ else ++ return 0; ++} ++ ++static int aspeed_sgpio_g7_reg_bank_get(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg) ++{ ++ void __iomem *addr; ++ ++ if (reg == reg_irq_status) { ++ addr = gpio->base + SGPIO_G7_IRQ_STS_OFFSET(offset >> 6); ++ return ioread32(addr); ++ } else { ++ return -EOPNOTSUPP; ++ } ++} ++ ++static void aspeed_sgpio_g7_reg_bank_set(struct aspeed_sgpio *gpio, unsigned int offset, ++ const enum aspeed_sgpio_reg reg, u32 val) ++{ ++ void __iomem *addr = gpio->base + SGPIO_G7_CTRL_REG_OFFSET(offset >> 1); ++ u32 mask, write_val; ++ ++ if (reg == reg_serial_out_sel || reg == reg_parallel_out_sel) { ++ mask = aspeed_sgpio_g7_reg_mask(reg); ++ write_val = (ioread32(addr) & ~(mask)) | field_prep(mask, val); ++ iowrite32(write_val, addr); ++ } ++} ++ ++static const struct aspeed_sgpio_llops aspeed_sgpio_g7_llops = { ++ .reg_bit_set = aspeed_sgpio_g7_reg_bit_set, ++ .reg_bit_get = aspeed_sgpio_g7_reg_bit_get, ++ .reg_bank_set = aspeed_sgpio_g7_reg_bank_set, ++ .reg_bank_get = aspeed_sgpio_g7_reg_bank_get, ++}; ++ ++static const struct aspeed_sgpio_pdata ast2700_sgpiom_pdata = { ++ .pin_mask = GENMASK(11, 6), ++ .llops = &aspeed_sgpio_g7_llops, ++ .cfg_offset = ASPEED_SGPIO_G7_CFG_OFFSET, ++}; ++ ++static const struct aspeed_sgpio_pdata ast2700_sgpios_pdata = { ++ .pin_mask = GENMASK(11, 6), ++ .llops = &aspeed_sgpio_g7_llops, ++ .cfg_offset = ASPEED_SGPIO_G7_CFG_OFFSET, ++ .slave = true, + }; + + static const struct of_device_id aspeed_sgpio_of_table[] = { + { .compatible = "aspeed,ast2400-sgpio", .data = &ast2400_sgpio_pdata, }, + { .compatible = "aspeed,ast2500-sgpio", .data = &ast2400_sgpio_pdata, }, + { .compatible = "aspeed,ast2600-sgpiom", .data = &ast2600_sgpiom_pdata, }, ++ { .compatible = "aspeed,ast2700-sgpiom", .data = &ast2700_sgpiom_pdata, }, ++ { .compatible = "aspeed,ast2700-sgpios", .data = &ast2700_sgpios_pdata, }, + {} + }; + +@@ -537,7 +721,6 @@ + static int aspeed_sgpio_probe(struct platform_device *pdev) + { + u32 nr_gpios, sgpio_freq, sgpio_clk_div, gpio_cnt_regval, pin_mask; +- const struct aspeed_sgpio_pdata *pdata; + struct aspeed_sgpio *gpio; + unsigned long apb_freq; + int rc; +@@ -552,12 +735,11 @@ + + gpio->dev = &pdev->dev; + +- pdata = device_get_match_data(&pdev->dev); +- if (!pdata) ++ gpio->pdata = device_get_match_data(&pdev->dev); ++ if (!gpio->pdata) + return -EINVAL; + +- pin_mask = pdata->pin_mask; +- ++ pin_mask = gpio->pdata->pin_mask; + rc = device_property_read_u32(&pdev->dev, "ngpios", &nr_gpios); + if (rc < 0) { + dev_err(&pdev->dev, "Could not read ngpios property\n"); +@@ -568,40 +750,50 @@ + return -EINVAL; + } + +- rc = device_property_read_u32(&pdev->dev, "bus-frequency", &sgpio_freq); +- if (rc < 0) { +- dev_err(&pdev->dev, "Could not read bus-frequency property\n"); +- return -EINVAL; +- } +- +- gpio->pclk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(gpio->pclk)) { +- dev_err(&pdev->dev, "devm_clk_get failed\n"); +- return PTR_ERR(gpio->pclk); +- } +- +- apb_freq = clk_get_rate(gpio->pclk); +- +- /* +- * From the datasheet, +- * SGPIO period = 1/PCLK * 2 * (GPIO254[31:16] + 1) +- * period = 2 * (GPIO254[31:16] + 1) / PCLK +- * frequency = 1 / (2 * (GPIO254[31:16] + 1) / PCLK) +- * frequency = PCLK / (2 * (GPIO254[31:16] + 1)) +- * frequency * 2 * (GPIO254[31:16] + 1) = PCLK +- * GPIO254[31:16] = PCLK / (frequency * 2) - 1 +- */ +- if (sgpio_freq == 0) +- return -EINVAL; +- +- sgpio_clk_div = (apb_freq / (sgpio_freq * 2)) - 1; +- +- if (sgpio_clk_div > (1 << 16) - 1) +- return -EINVAL; ++ if (!gpio->pdata->slave) { ++ int i; + +- gpio_cnt_regval = ((nr_gpios / 8) << ASPEED_SGPIO_PINS_SHIFT) & pin_mask; +- iowrite32(FIELD_PREP(ASPEED_SGPIO_CLK_DIV_MASK, sgpio_clk_div) | gpio_cnt_regval | +- ASPEED_SGPIO_ENABLE, gpio->base + ASPEED_SGPIO_CTRL); ++ for (i = 0; i < nr_gpios; i++) ++ gpio->pdata->llops->reg_bank_set(gpio, i << 1, reg_serial_out_sel, ++ SELECT_FROM_CSR); ++ ++ rc = device_property_read_u32(&pdev->dev, "bus-frequency", &sgpio_freq); ++ if (rc < 0) { ++ dev_err(&pdev->dev, "Could not read bus-frequency property\n"); ++ return -EINVAL; ++ } ++ ++ gpio->pclk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(gpio->pclk)) { ++ dev_err(&pdev->dev, "devm_clk_get failed\n"); ++ return PTR_ERR(gpio->pclk); ++ } ++ ++ apb_freq = clk_get_rate(gpio->pclk); ++ ++ /* ++ * From the datasheet, ++ * SGPIO period = 1/PCLK * 2 * (GPIO254[31:16] + 1) ++ * period = 2 * (GPIO254[31:16] + 1) / PCLK ++ * frequency = 1 / (2 * (GPIO254[31:16] + 1) / PCLK) ++ * frequency = PCLK / (2 * (GPIO254[31:16] + 1)) ++ * frequency * 2 * (GPIO254[31:16] + 1) = PCLK ++ * GPIO254[31:16] = PCLK / (frequency * 2) - 1 ++ */ ++ if (sgpio_freq == 0) ++ return -EINVAL; ++ ++ sgpio_clk_div = (apb_freq / (sgpio_freq * 2)) - 1; ++ ++ if (sgpio_clk_div > (1 << 16) - 1) ++ return -EINVAL; ++ ++ gpio_cnt_regval = ((nr_gpios / 8) << ASPEED_SGPIO_PINS_SHIFT) & pin_mask; ++ iowrite32(FIELD_PREP(ASPEED_SGPIO_CLK_DIV_MASK, sgpio_clk_div) | gpio_cnt_regval | ++ ASPEED_SGPIO_ENABLE, gpio->base + gpio->pdata->cfg_offset); ++ } else { ++ iowrite32(ASPEED_SGPIO_ENABLE, gpio->base + gpio->pdata->cfg_offset); ++ } + + raw_spin_lock_init(&gpio->lock); + +diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c +--- a/drivers/gpio/gpio-aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpio/gpio-aspeed.c 2026-09-09 15:51:44.631239386 +0000 +@@ -30,6 +30,27 @@ + #include + #include "gpiolib.h" + ++/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */ ++#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) ++#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask)) ++ ++#define GPIO_G7_IRQ_STS_BASE 0x100 ++#define GPIO_G7_IRQ_STS_OFFSET(x) (GPIO_G7_IRQ_STS_BASE + (x) * 0x4) ++#define GPIO_G7_CTRL_REG_BASE 0x180 ++#define GPIO_G7_CTRL_REG_OFFSET(x) (GPIO_G7_CTRL_REG_BASE + (x) * 0x4) ++#define GPIO_G7_CTRL_OUT_DATA BIT(0) ++#define GPIO_G7_CTRL_DIR BIT(1) ++#define GPIO_G7_CTRL_IRQ_EN BIT(2) ++#define GPIO_G7_CTRL_IRQ_TYPE0 BIT(3) ++#define GPIO_G7_CTRL_IRQ_TYPE1 BIT(4) ++#define GPIO_G7_CTRL_IRQ_TYPE2 BIT(5) ++#define GPIO_G7_CTRL_RST_TOLERANCE BIT(6) ++#define GPIO_G7_CTRL_DEBOUNCE_SEL2 BIT(7) ++#define GPIO_G7_CTRL_DEBOUNCE_SEL1 BIT(8) ++#define GPIO_G7_CTRL_INPUT_MASK BIT(9) ++#define GPIO_G7_CTRL_IRQ_STS BIT(12) ++#define GPIO_G7_CTRL_IN_DATA BIT(13) ++ + struct aspeed_bank_props { + unsigned int bank; + u32 input; +@@ -39,6 +60,10 @@ + struct aspeed_gpio_config { + unsigned int nr_gpios; + const struct aspeed_bank_props *props; ++ const struct aspeed_gpio_llops *llops; ++ const int *debounce_timers_array; ++ int debounce_timers_num; ++ bool require_dcache; + }; + + /* +@@ -77,7 +102,6 @@ + uint16_t debounce_regs; + uint16_t tolerance_regs; + uint16_t cmdsrc_regs; +- const char names[4][3]; + }; + + /* +@@ -92,6 +116,22 @@ + */ + + static const int debounce_timers[4] = { 0x00, 0x50, 0x54, 0x58 }; ++static const int g7_debounce_timers[4] = { 0x00, 0x00, 0x04, 0x08 }; ++ ++/* ++ * The debounce timers array is used to configure the debounce timer settings.Here’s how it works: ++ * Array Value: Indicates the offset for configuring the debounce timer. ++ * Array Index: Corresponds to the debounce setting register. ++ * The debounce timers array follows this pattern for configuring the debounce setting registers: ++ * Array Index 0: No debounce timer is set; ++ * Array Value is irrelevant (don’t care). ++ * Array Index 1: Debounce setting #2 is set to 1, and debounce setting #1 is set to 0. ++ * Array Value: offset for configuring debounce timer 0 (g4: 0x50, g7: 0x00) ++ * Array Index 2: Debounce setting #2 is set to 0, and debounce setting #1 is set to 1. ++ * Array Value: offset for configuring debounce timer 1 (g4: 0x54, g7: 0x04) ++ * Array Index 3: Debounce setting #2 is set to 1, and debounce setting #1 is set to 1. ++ * Array Value: offset for configuring debounce timer 2 (g4: 0x58, g7: 0x8) ++ */ + + static const struct aspeed_gpio_copro_ops *copro_ops; + static void *copro_data; +@@ -104,7 +144,6 @@ + .debounce_regs = 0x0040, + .tolerance_regs = 0x001c, + .cmdsrc_regs = 0x0060, +- .names = { "A", "B", "C", "D" }, + }, + { + .val_regs = 0x0020, +@@ -113,7 +152,6 @@ + .debounce_regs = 0x0048, + .tolerance_regs = 0x003c, + .cmdsrc_regs = 0x0068, +- .names = { "E", "F", "G", "H" }, + }, + { + .val_regs = 0x0070, +@@ -122,7 +160,6 @@ + .debounce_regs = 0x00b0, + .tolerance_regs = 0x00ac, + .cmdsrc_regs = 0x0090, +- .names = { "I", "J", "K", "L" }, + }, + { + .val_regs = 0x0078, +@@ -131,7 +168,6 @@ + .debounce_regs = 0x0100, + .tolerance_regs = 0x00fc, + .cmdsrc_regs = 0x00e0, +- .names = { "M", "N", "O", "P" }, + }, + { + .val_regs = 0x0080, +@@ -140,7 +176,6 @@ + .debounce_regs = 0x0130, + .tolerance_regs = 0x012c, + .cmdsrc_regs = 0x0110, +- .names = { "Q", "R", "S", "T" }, + }, + { + .val_regs = 0x0088, +@@ -149,7 +184,6 @@ + .debounce_regs = 0x0160, + .tolerance_regs = 0x015c, + .cmdsrc_regs = 0x0140, +- .names = { "U", "V", "W", "X" }, + }, + { + .val_regs = 0x01E0, +@@ -158,7 +192,6 @@ + .debounce_regs = 0x0190, + .tolerance_regs = 0x018c, + .cmdsrc_regs = 0x0170, +- .names = { "Y", "Z", "AA", "AB" }, + }, + { + .val_regs = 0x01e8, +@@ -167,7 +200,6 @@ + .debounce_regs = 0x01c0, + .tolerance_regs = 0x01bc, + .cmdsrc_regs = 0x01a0, +- .names = { "AC", "", "", "" }, + }, + }; + +@@ -187,6 +219,19 @@ + reg_cmdsrc1, + }; + ++struct aspeed_gpio_llops { ++ void (*reg_bit_set)(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg, bool val); ++ bool (*reg_bit_get)(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg); ++ int (*reg_bank_get)(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg); ++ void (*privilege_ctrl)(struct aspeed_gpio *gpio, unsigned int offset, int owner); ++ void (*privilege_init)(struct aspeed_gpio *gpio); ++ bool (*copro_request)(struct aspeed_gpio *gpio, unsigned int offset); ++ void (*copro_release)(struct aspeed_gpio *gpio, unsigned int offset); ++}; ++ + #define GPIO_VAL_VALUE 0x00 + #define GPIO_VAL_DIR 0x04 + +@@ -207,9 +252,9 @@ + #define GPIO_CMDSRC_RESERVED 3 + + /* This will be resolved at compile time */ +-static inline void __iomem *bank_reg(struct aspeed_gpio *gpio, +- const struct aspeed_gpio_bank *bank, +- const enum aspeed_gpio_reg reg) ++static void __iomem *aspeed_gpio_g4_bank_reg(struct aspeed_gpio *gpio, ++ const struct aspeed_gpio_bank *bank, ++ const enum aspeed_gpio_reg reg) + { + switch (reg) { + case reg_val: +@@ -242,14 +287,43 @@ + BUG(); + } + ++static u32 aspeed_gpio_g7_reg_mask(const enum aspeed_gpio_reg reg) ++{ ++ switch (reg) { ++ case reg_val: ++ return GPIO_G7_CTRL_OUT_DATA; ++ case reg_dir: ++ return GPIO_G7_CTRL_DIR; ++ case reg_irq_enable: ++ return GPIO_G7_CTRL_IRQ_EN; ++ case reg_irq_type0: ++ return GPIO_G7_CTRL_IRQ_TYPE0; ++ case reg_irq_type1: ++ return GPIO_G7_CTRL_IRQ_TYPE1; ++ case reg_irq_type2: ++ return GPIO_G7_CTRL_IRQ_TYPE2; ++ case reg_tolerance: ++ return GPIO_G7_CTRL_RST_TOLERANCE; ++ case reg_debounce_sel1: ++ return GPIO_G7_CTRL_DEBOUNCE_SEL1; ++ case reg_debounce_sel2: ++ return GPIO_G7_CTRL_DEBOUNCE_SEL2; ++ case reg_rdata: ++ return GPIO_G7_CTRL_OUT_DATA; ++ case reg_irq_status: ++ return GPIO_G7_CTRL_IRQ_STS; ++ case reg_cmdsrc0: ++ case reg_cmdsrc1: ++ default: ++ WARN_ON_ONCE(1); ++ return 0; ++ } ++} ++ + #define GPIO_BANK(x) ((x) >> 5) + #define GPIO_OFFSET(x) ((x) & 0x1f) + #define GPIO_BIT(x) BIT(GPIO_OFFSET(x)) + +-#define _GPIO_SET_DEBOUNCE(t, o, i) ((!!((t) & BIT(i))) << GPIO_OFFSET(o)) +-#define GPIO_SET_DEBOUNCE1(t, o) _GPIO_SET_DEBOUNCE(t, o, 1) +-#define GPIO_SET_DEBOUNCE2(t, o) _GPIO_SET_DEBOUNCE(t, o, 0) +- + static const struct aspeed_gpio_bank *to_bank(unsigned int offset) + { + unsigned int bank = GPIO_BANK(offset); +@@ -280,11 +354,11 @@ + static inline bool have_gpio(struct aspeed_gpio *gpio, unsigned int offset) + { + const struct aspeed_bank_props *props = find_bank_props(gpio, offset); +- const struct aspeed_gpio_bank *bank = to_bank(offset); +- unsigned int group = GPIO_OFFSET(offset) / 8; + +- return bank->names[group][0] != '\0' && +- (!props || ((props->input | props->output) & GPIO_BIT(offset))); ++ if (offset >= gpio->chip.ngpio) ++ return false; ++ ++ return (!props || ((props->input | props->output) & GPIO_BIT(offset))); + } + + static inline bool have_input(struct aspeed_gpio *gpio, unsigned int offset) +@@ -304,110 +378,49 @@ + return !props || (props->output & GPIO_BIT(offset)); + } + +-static void aspeed_gpio_change_cmd_source(struct aspeed_gpio *gpio, +- const struct aspeed_gpio_bank *bank, +- int bindex, int cmdsrc) +-{ +- void __iomem *c0 = bank_reg(gpio, bank, reg_cmdsrc0); +- void __iomem *c1 = bank_reg(gpio, bank, reg_cmdsrc1); +- u32 bit, reg; +- +- /* +- * Each register controls 4 banks, so take the bottom 2 +- * bits of the bank index, and use them to select the +- * right control bit (0, 8, 16 or 24). +- */ +- bit = BIT((bindex & 3) << 3); +- +- /* Source 1 first to avoid illegal 11 combination */ +- reg = ioread32(c1); +- if (cmdsrc & 2) +- reg |= bit; +- else +- reg &= ~bit; +- iowrite32(reg, c1); +- +- /* Then Source 0 */ +- reg = ioread32(c0); +- if (cmdsrc & 1) +- reg |= bit; +- else +- reg &= ~bit; +- iowrite32(reg, c0); ++static void aspeed_gpio_change_cmd_source(struct aspeed_gpio *gpio, unsigned int offset, int cmdsrc) ++{ ++ if (gpio->config->llops->privilege_ctrl) ++ gpio->config->llops->privilege_ctrl(gpio, offset, cmdsrc); + } + + static bool aspeed_gpio_copro_request(struct aspeed_gpio *gpio, + unsigned int offset) + { +- const struct aspeed_gpio_bank *bank = to_bank(offset); ++ if (gpio->config->llops->copro_request) ++ return gpio->config->llops->copro_request(gpio, offset); + +- if (!copro_ops || !gpio->cf_copro_bankmap) +- return false; +- if (!gpio->cf_copro_bankmap[offset >> 3]) +- return false; +- if (!copro_ops->request_access) +- return false; +- +- /* Pause the coprocessor */ +- copro_ops->request_access(copro_data); +- +- /* Change command source back to ARM */ +- aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3, GPIO_CMDSRC_ARM); +- +- /* Update cache */ +- gpio->dcache[GPIO_BANK(offset)] = ioread32(bank_reg(gpio, bank, reg_rdata)); +- +- return true; ++ return false; + } + + static void aspeed_gpio_copro_release(struct aspeed_gpio *gpio, + unsigned int offset) + { +- const struct aspeed_gpio_bank *bank = to_bank(offset); +- +- if (!copro_ops || !gpio->cf_copro_bankmap) +- return; +- if (!gpio->cf_copro_bankmap[offset >> 3]) +- return; +- if (!copro_ops->release_access) +- return; +- +- /* Change command source back to ColdFire */ +- aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3, +- GPIO_CMDSRC_COLDFIRE); ++ if (gpio->config->llops->copro_release) ++ gpio->config->llops->copro_release(gpio, offset); ++} + +- /* Restart the coprocessor */ +- copro_ops->release_access(copro_data); ++static bool aspeed_gpio_support_copro(struct aspeed_gpio *gpio) ++{ ++ return gpio->config->llops->copro_request && gpio->config->llops->copro_release && ++ gpio->config->llops->privilege_ctrl && gpio->config->llops->privilege_init; + } + + static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset) + { + struct aspeed_gpio *gpio = gpiochip_get_data(gc); +- const struct aspeed_gpio_bank *bank = to_bank(offset); + +- return !!(ioread32(bank_reg(gpio, bank, reg_val)) & GPIO_BIT(offset)); ++ return gpio->config->llops->reg_bit_get(gpio, offset, reg_val); + } + + static void __aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, + int val) + { + struct aspeed_gpio *gpio = gpiochip_get_data(gc); +- const struct aspeed_gpio_bank *bank = to_bank(offset); +- void __iomem *addr; +- u32 reg; +- +- addr = bank_reg(gpio, bank, reg_val); +- reg = gpio->dcache[GPIO_BANK(offset)]; + +- if (val) +- reg |= GPIO_BIT(offset); +- else +- reg &= ~GPIO_BIT(offset); +- gpio->dcache[GPIO_BANK(offset)] = reg; +- +- iowrite32(reg, addr); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_val, val); + /* Flush write */ +- ioread32(addr); ++ gpio->config->llops->reg_bit_get(gpio, offset, reg_val); + } + + static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, +@@ -415,7 +428,7 @@ + { + struct aspeed_gpio *gpio = gpiochip_get_data(gc); + unsigned long flags; +- bool copro; ++ bool copro = false; + + raw_spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); +@@ -430,22 +443,16 @@ + static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) + { + struct aspeed_gpio *gpio = gpiochip_get_data(gc); +- const struct aspeed_gpio_bank *bank = to_bank(offset); +- void __iomem *addr = bank_reg(gpio, bank, reg_dir); + unsigned long flags; +- bool copro; +- u32 reg; ++ bool copro = false; + + if (!have_input(gpio, offset)) + return -ENOTSUPP; + + raw_spin_lock_irqsave(&gpio->lock, flags); + +- reg = ioread32(addr); +- reg &= ~GPIO_BIT(offset); +- + copro = aspeed_gpio_copro_request(gpio, offset); +- iowrite32(reg, addr); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_dir, 0); + if (copro) + aspeed_gpio_copro_release(gpio, offset); + +@@ -458,23 +465,17 @@ + unsigned int offset, int val) + { + struct aspeed_gpio *gpio = gpiochip_get_data(gc); +- const struct aspeed_gpio_bank *bank = to_bank(offset); +- void __iomem *addr = bank_reg(gpio, bank, reg_dir); + unsigned long flags; +- bool copro; +- u32 reg; ++ bool copro = false; + + if (!have_output(gpio, offset)) + return -ENOTSUPP; + + raw_spin_lock_irqsave(&gpio->lock, flags); + +- reg = ioread32(addr); +- reg |= GPIO_BIT(offset); +- + copro = aspeed_gpio_copro_request(gpio, offset); + __aspeed_gpio_set(gc, offset, val); +- iowrite32(reg, addr); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_dir, 1); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); +@@ -486,7 +487,6 @@ + static int aspeed_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) + { + struct aspeed_gpio *gpio = gpiochip_get_data(gc); +- const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + u32 val; + +@@ -498,7 +498,7 @@ + + raw_spin_lock_irqsave(&gpio->lock, flags); + +- val = ioread32(bank_reg(gpio, bank, reg_dir)) & GPIO_BIT(offset); ++ val = gpio->config->llops->reg_bit_get(gpio, offset, reg_dir); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); + +@@ -507,8 +507,7 @@ + + static inline int irqd_to_aspeed_gpio_data(struct irq_data *d, + struct aspeed_gpio **gpio, +- const struct aspeed_gpio_bank **bank, +- u32 *bit, int *offset) ++ int *offset) + { + struct aspeed_gpio *internal; + +@@ -521,32 +520,25 @@ + return -ENOTSUPP; + + *gpio = internal; +- *bank = to_bank(*offset); +- *bit = GPIO_BIT(*offset); + + return 0; + } + + static void aspeed_gpio_irq_ack(struct irq_data *d) + { +- const struct aspeed_gpio_bank *bank; + struct aspeed_gpio *gpio; + unsigned long flags; +- void __iomem *status_addr; + int rc, offset; +- bool copro; +- u32 bit; ++ bool copro = false; + +- rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); ++ rc = irqd_to_aspeed_gpio_data(d, &gpio, &offset); + if (rc) + return; + +- status_addr = bank_reg(gpio, bank, reg_irq_status); +- + raw_spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); + +- iowrite32(bit, status_addr); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_irq_status, 1); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); +@@ -555,20 +547,15 @@ + + static void aspeed_gpio_irq_set_mask(struct irq_data *d, bool set) + { +- const struct aspeed_gpio_bank *bank; + struct aspeed_gpio *gpio; + unsigned long flags; +- u32 reg, bit; +- void __iomem *addr; + int rc, offset; +- bool copro; ++ bool copro = false; + +- rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); ++ rc = irqd_to_aspeed_gpio_data(d, &gpio, &offset); + if (rc) + return; + +- addr = bank_reg(gpio, bank, reg_irq_enable); +- + /* Unmasking the IRQ */ + if (set) + gpiochip_enable_irq(&gpio->chip, irqd_to_hwirq(d)); +@@ -576,12 +563,7 @@ + raw_spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); + +- reg = ioread32(addr); +- if (set) +- reg |= bit; +- else +- reg &= ~bit; +- iowrite32(reg, addr); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_irq_enable, set); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); +@@ -607,34 +589,31 @@ + u32 type0 = 0; + u32 type1 = 0; + u32 type2 = 0; +- u32 bit, reg; +- const struct aspeed_gpio_bank *bank; + irq_flow_handler_t handler; + struct aspeed_gpio *gpio; + unsigned long flags; +- void __iomem *addr; + int rc, offset; +- bool copro; ++ bool copro = false; + +- rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); ++ rc = irqd_to_aspeed_gpio_data(d, &gpio, &offset); + if (rc) + return -EINVAL; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_BOTH: +- type2 |= bit; ++ type2 = 1; + fallthrough; + case IRQ_TYPE_EDGE_RISING: +- type0 |= bit; ++ type0 = 1; + fallthrough; + case IRQ_TYPE_EDGE_FALLING: + handler = handle_edge_irq; + break; + case IRQ_TYPE_LEVEL_HIGH: +- type0 |= bit; ++ type0 = 1; + fallthrough; + case IRQ_TYPE_LEVEL_LOW: +- type1 |= bit; ++ type1 = 1; + handler = handle_level_irq; + break; + default: +@@ -644,20 +623,9 @@ + raw_spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); + +- addr = bank_reg(gpio, bank, reg_irq_type0); +- reg = ioread32(addr); +- reg = (reg & ~bit) | type0; +- iowrite32(reg, addr); +- +- addr = bank_reg(gpio, bank, reg_irq_type1); +- reg = ioread32(addr); +- reg = (reg & ~bit) | type1; +- iowrite32(reg, addr); +- +- addr = bank_reg(gpio, bank, reg_irq_type2); +- reg = ioread32(addr); +- reg = (reg & ~bit) | type2; +- iowrite32(reg, addr); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_irq_type0, type0); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_irq_type1, type1); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_irq_type2, type2); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); +@@ -672,7 +640,6 @@ + { + struct gpio_chip *gc = irq_desc_get_handler_data(desc); + struct irq_chip *ic = irq_desc_get_chip(desc); +- struct aspeed_gpio *data = gpiochip_get_data(gc); + unsigned int i, p, banks; + unsigned long reg; + struct aspeed_gpio *gpio = gpiochip_get_data(gc); +@@ -681,9 +648,7 @@ + + banks = DIV_ROUND_UP(gpio->chip.ngpio, 32); + for (i = 0; i < banks; i++) { +- const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i]; +- +- reg = ioread32(bank_reg(data, bank, reg_irq_status)); ++ reg = gpio->config->llops->reg_bank_get(gpio, i * 32, reg_irq_status); + + for_each_set_bit(p, ®, 32) + generic_handle_domain_irq(gc->irq.domain, i * 32 + p); +@@ -722,23 +687,12 @@ + { + struct aspeed_gpio *gpio = gpiochip_get_data(chip); + unsigned long flags; +- void __iomem *treg; +- bool copro; +- u32 val; +- +- treg = bank_reg(gpio, to_bank(offset), reg_tolerance); ++ bool copro = false; + + raw_spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); + +- val = readl(treg); +- +- if (enable) +- val |= GPIO_BIT(offset); +- else +- val &= ~GPIO_BIT(offset); +- +- writel(val, treg); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_tolerance, enable); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); +@@ -832,21 +786,11 @@ + static void configure_timer(struct aspeed_gpio *gpio, unsigned int offset, + unsigned int timer) + { +- const struct aspeed_gpio_bank *bank = to_bank(offset); +- const u32 mask = GPIO_BIT(offset); +- void __iomem *addr; +- u32 val; +- + /* Note: Debounce timer isn't under control of the command + * source registers, so no need to sync with the coprocessor + */ +- addr = bank_reg(gpio, bank, reg_debounce_sel1); +- val = ioread32(addr); +- iowrite32((val & ~mask) | GPIO_SET_DEBOUNCE1(timer, offset), addr); +- +- addr = bank_reg(gpio, bank, reg_debounce_sel2); +- val = ioread32(addr); +- iowrite32((val & ~mask) | GPIO_SET_DEBOUNCE2(timer, offset), addr); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_debounce_sel1, !!(timer & BIT(1))); ++ gpio->config->llops->reg_bit_set(gpio, offset, reg_debounce_sel2, !!(timer & BIT(0))); + } + + static int enable_debounce(struct gpio_chip *chip, unsigned int offset, +@@ -877,15 +821,15 @@ + } + + /* Try to find a timer already configured for the debounce period */ +- for (i = 1; i < ARRAY_SIZE(debounce_timers); i++) { ++ for (i = 1; i < gpio->config->debounce_timers_num; i++) { + u32 cycles; + +- cycles = ioread32(gpio->base + debounce_timers[i]); ++ cycles = ioread32(gpio->base + gpio->config->debounce_timers_array[i]); + if (requested_cycles == cycles) + break; + } + +- if (i == ARRAY_SIZE(debounce_timers)) { ++ if (i == gpio->config->debounce_timers_num) { + int j; + + /* +@@ -899,8 +843,8 @@ + + if (j == ARRAY_SIZE(gpio->timer_users)) { + dev_warn(chip->parent, +- "Debounce timers exhausted, cannot debounce for period %luus\n", +- usecs); ++ "Debounce timers exhausted, cannot debounce for period %luus\n", ++ usecs); + + rc = -EPERM; + +@@ -916,7 +860,7 @@ + + i = j; + +- iowrite32(requested_cycles, gpio->base + debounce_timers[i]); ++ iowrite32(requested_cycles, gpio->base + gpio->config->debounce_timers_array[i]); + } + + if (WARN(i == 0, "Cannot register index of disabled timer\n")) { +@@ -1019,6 +963,9 @@ + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + ++ if (!aspeed_gpio_support_copro(gpio)) ++ return -EOPNOTSUPP; ++ + if (!gpio->cf_copro_bankmap) + gpio->cf_copro_bankmap = kzalloc(gpio->chip.ngpio >> 3, GFP_KERNEL); + if (!gpio->cf_copro_bankmap) +@@ -1038,7 +985,7 @@ + + /* Switch command source */ + if (gpio->cf_copro_bankmap[bindex] == 1) +- aspeed_gpio_change_cmd_source(gpio, bank, bindex, ++ aspeed_gpio_change_cmd_source(gpio, offset, + GPIO_CMDSRC_COLDFIRE); + + if (vreg_offset) +@@ -1062,9 +1009,11 @@ + struct gpio_chip *chip = gpiod_to_chip(desc); + struct aspeed_gpio *gpio = gpiochip_get_data(chip); + int rc = 0, bindex, offset = gpio_chip_hwgpio(desc); +- const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + ++ if (!aspeed_gpio_support_copro(gpio)) ++ return -EOPNOTSUPP; ++ + if (!gpio->cf_copro_bankmap) + return -ENXIO; + +@@ -1083,7 +1032,7 @@ + + /* Switch command source */ + if (gpio->cf_copro_bankmap[bindex] == 0) +- aspeed_gpio_change_cmd_source(gpio, bank, bindex, ++ aspeed_gpio_change_cmd_source(gpio, offset, + GPIO_CMDSRC_ARM); + bail: + raw_spin_unlock_irqrestore(&gpio->lock, flags); +@@ -1093,12 +1042,10 @@ + + static void aspeed_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) + { +- const struct aspeed_gpio_bank *bank; + struct aspeed_gpio *gpio; +- u32 bit; + int rc, offset; + +- rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); ++ rc = irqd_to_aspeed_gpio_data(d, &gpio, &offset); + if (rc) + return; + +@@ -1115,6 +1062,173 @@ + GPIOCHIP_IRQ_RESOURCE_HELPERS, + }; + ++static void aspeed_g4_reg_bit_set(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg, bool val) ++{ ++ const struct aspeed_gpio_bank *bank = to_bank(offset); ++ void __iomem *addr = aspeed_gpio_g4_bank_reg(gpio, bank, reg); ++ u32 temp; ++ ++ if (reg == reg_val) ++ temp = gpio->dcache[GPIO_BANK(offset)]; ++ else ++ temp = ioread32(addr); ++ ++ if (val) ++ temp |= GPIO_BIT(offset); ++ else ++ temp &= ~GPIO_BIT(offset); ++ ++ if (reg == reg_val) ++ gpio->dcache[GPIO_BANK(offset)] = temp; ++ iowrite32(temp, addr); ++} ++ ++static bool aspeed_g4_reg_bit_get(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg) ++{ ++ const struct aspeed_gpio_bank *bank = to_bank(offset); ++ void __iomem *addr = aspeed_gpio_g4_bank_reg(gpio, bank, reg); ++ ++ return !!(ioread32(addr) & GPIO_BIT(offset)); ++} ++ ++static int aspeed_g4_reg_bank_get(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg) ++{ ++ const struct aspeed_gpio_bank *bank = to_bank(offset); ++ void __iomem *addr = aspeed_gpio_g4_bank_reg(gpio, bank, reg); ++ ++ if (reg == reg_rdata || reg == reg_irq_status) ++ return ioread32(addr); ++ else ++ return -EOPNOTSUPP; ++} ++ ++static void aspeed_g4_privilege_ctrl(struct aspeed_gpio *gpio, unsigned int offset, int cmdsrc) ++{ ++ /* ++ * The command source register is only valid in bits 0, 8, 16, and 24, so we use ++ * (offset & ~(0x7)) to ensure that reg_bits_set always targets a valid bit. ++ */ ++ /* Source 1 first to avoid illegal 11 combination */ ++ aspeed_g4_reg_bit_set(gpio, offset & ~(0x7), reg_cmdsrc1, !!(cmdsrc & BIT(1))); ++ /* Then Source 0 */ ++ aspeed_g4_reg_bit_set(gpio, offset & ~(0x7), reg_cmdsrc0, !!(cmdsrc & BIT(0))); ++} ++ ++static void aspeed_g4_privilege_init(struct aspeed_gpio *gpio) ++{ ++ u32 i; ++ ++ /* Switch all command sources to the ARM by default */ ++ for (i = 0; i < DIV_ROUND_UP(gpio->chip.ngpio, 32); i++) { ++ aspeed_g4_privilege_ctrl(gpio, (i << 5) + 0, GPIO_CMDSRC_ARM); ++ aspeed_g4_privilege_ctrl(gpio, (i << 5) + 8, GPIO_CMDSRC_ARM); ++ aspeed_g4_privilege_ctrl(gpio, (i << 5) + 16, GPIO_CMDSRC_ARM); ++ aspeed_g4_privilege_ctrl(gpio, (i << 5) + 24, GPIO_CMDSRC_ARM); ++ } ++} ++ ++static bool aspeed_g4_copro_request(struct aspeed_gpio *gpio, unsigned int offset) ++{ ++ if (!copro_ops || !gpio->cf_copro_bankmap) ++ return false; ++ if (!gpio->cf_copro_bankmap[offset >> 3]) ++ return false; ++ if (!copro_ops->request_access) ++ return false; ++ ++ /* Pause the coprocessor */ ++ copro_ops->request_access(copro_data); ++ ++ /* Change command source back to ARM */ ++ aspeed_g4_privilege_ctrl(gpio, offset, GPIO_CMDSRC_ARM); ++ ++ /* Update cache */ ++ gpio->dcache[GPIO_BANK(offset)] = aspeed_g4_reg_bank_get(gpio, offset, reg_rdata); ++ ++ return true; ++} ++ ++static void aspeed_g4_copro_release(struct aspeed_gpio *gpio, unsigned int offset) ++{ ++ if (!copro_ops || !gpio->cf_copro_bankmap) ++ return; ++ if (!gpio->cf_copro_bankmap[offset >> 3]) ++ return; ++ if (!copro_ops->release_access) ++ return; ++ ++ /* Change command source back to ColdFire */ ++ aspeed_g4_privilege_ctrl(gpio, offset, GPIO_CMDSRC_COLDFIRE); ++ ++ /* Restart the coprocessor */ ++ copro_ops->release_access(copro_data); ++} ++ ++static const struct aspeed_gpio_llops aspeed_g4_llops = { ++ .reg_bit_set = aspeed_g4_reg_bit_set, ++ .reg_bit_get = aspeed_g4_reg_bit_get, ++ .reg_bank_get = aspeed_g4_reg_bank_get, ++ .privilege_ctrl = aspeed_g4_privilege_ctrl, ++ .privilege_init = aspeed_g4_privilege_init, ++ .copro_request = aspeed_g4_copro_request, ++ .copro_release = aspeed_g4_copro_release, ++}; ++ ++static void aspeed_g7_reg_bit_set(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg, bool val) ++{ ++ u32 mask = aspeed_gpio_g7_reg_mask(reg); ++ void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset); ++ u32 write_val; ++ ++ if (mask) { ++ write_val = (ioread32(addr) & ~(mask)) | field_prep(mask, val); ++ iowrite32(write_val, addr); ++ } ++} ++ ++static bool aspeed_g7_reg_bit_get(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg) ++{ ++ u32 mask = aspeed_gpio_g7_reg_mask(reg); ++ void __iomem *addr; ++ ++ addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset); ++ if (reg == reg_val) ++ mask = GPIO_G7_CTRL_IN_DATA; ++ ++ if (mask) ++ return field_get(mask, ioread32(addr)); ++ else ++ return 0; ++} ++ ++static int aspeed_g7_reg_bank_get(struct aspeed_gpio *gpio, unsigned int offset, ++ const enum aspeed_gpio_reg reg) ++{ ++ void __iomem *addr; ++ ++ if (reg == reg_irq_status) { ++ addr = gpio->base + GPIO_G7_IRQ_STS_OFFSET(offset >> 5); ++ return ioread32(addr); ++ } else { ++ return -EOPNOTSUPP; ++ } ++} ++ ++static const struct aspeed_gpio_llops aspeed_g7_llops = { ++ .reg_bit_set = aspeed_g7_reg_bit_set, ++ .reg_bit_get = aspeed_g7_reg_bit_get, ++ .reg_bank_get = aspeed_g7_reg_bank_get, ++ .privilege_ctrl = NULL, ++ .privilege_init = NULL, ++ .copro_request = NULL, ++ .copro_release = NULL, ++}; ++ + /* + * Any banks not specified in a struct aspeed_bank_props array are assumed to + * have the properties: +@@ -1131,7 +1245,14 @@ + + static const struct aspeed_gpio_config ast2400_config = + /* 220 for simplicity, really 216 with two 4-GPIO holes, four at end */ +- { .nr_gpios = 220, .props = ast2400_bank_props, }; ++ { ++ .nr_gpios = 220, ++ .props = ast2400_bank_props, ++ .llops = &aspeed_g4_llops, ++ .debounce_timers_array = debounce_timers, ++ .debounce_timers_num = ARRAY_SIZE(debounce_timers), ++ .require_dcache = true, ++ }; + + static const struct aspeed_bank_props ast2500_bank_props[] = { + /* input output */ +@@ -1143,7 +1264,14 @@ + + static const struct aspeed_gpio_config ast2500_config = + /* 232 for simplicity, actual number is 228 (4-GPIO hole in GPIOAB) */ +- { .nr_gpios = 232, .props = ast2500_bank_props, }; ++ { ++ .nr_gpios = 232, ++ .props = ast2500_bank_props, ++ .llops = &aspeed_g4_llops, ++ .debounce_timers_array = debounce_timers, ++ .debounce_timers_num = ARRAY_SIZE(debounce_timers), ++ .require_dcache = true, ++ }; + + static const struct aspeed_bank_props ast2600_bank_props[] = { + /* input output */ +@@ -1159,17 +1287,48 @@ + * We expect ngpio being set in the device tree and this is a fallback + * option. + */ +- { .nr_gpios = 208, .props = ast2600_bank_props, }; ++ { ++ .nr_gpios = 208, ++ .props = ast2600_bank_props, ++ .llops = &aspeed_g4_llops, ++ .debounce_timers_array = debounce_timers, ++ .debounce_timers_num = ARRAY_SIZE(debounce_timers), ++ .require_dcache = true, ++ }; ++ ++static const struct aspeed_bank_props ast2700_bank_props[] = { ++ /* input output */ ++ { 1, 0x0fffffff, 0x0fffffff }, /* E/F/G/H, 4-GPIO hole */ ++ { 6, 0x00ffffff, 0x00ff0000 }, /* Y/Z/AA */ ++ {}, ++}; ++ ++static const struct aspeed_gpio_config ast2700_config = ++ /* ++ * ast2700 has two controllers one with 212 GPIOs and one with 16 GPIOs. ++ * 216 for simplicity, actual number is 212 (4-GPIO hole in GPIOH) ++ * We expect ngpio being set in the device tree and this is a fallback ++ * option. ++ */ ++ { ++ .nr_gpios = 216, ++ .props = ast2700_bank_props, ++ .llops = &aspeed_g7_llops, ++ .debounce_timers_array = g7_debounce_timers, ++ .debounce_timers_num = ARRAY_SIZE(g7_debounce_timers), ++ .require_dcache = false, ++ }; + + static const struct of_device_id aspeed_gpio_of_table[] = { + { .compatible = "aspeed,ast2400-gpio", .data = &ast2400_config, }, + { .compatible = "aspeed,ast2500-gpio", .data = &ast2500_config, }, + { .compatible = "aspeed,ast2600-gpio", .data = &ast2600_config, }, ++ { .compatible = "aspeed,ast2700-gpio", .data = &ast2700_config, }, + {} + }; + MODULE_DEVICE_TABLE(of, aspeed_gpio_of_table); + +-static int __init aspeed_gpio_probe(struct platform_device *pdev) ++static int aspeed_gpio_probe(struct platform_device *pdev) + { + const struct of_device_id *gpio_id; + struct gpio_irq_chip *girq; +@@ -1202,6 +1361,10 @@ + + gpio->config = gpio_id->data; + ++ if (!gpio->config->llops->reg_bit_set || !gpio->config->llops->reg_bit_get || ++ !gpio->config->llops->reg_bank_get) ++ return -EINVAL; ++ + gpio->chip.parent = &pdev->dev; + err = of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpio); + gpio->chip.ngpio = (u16) ngpio; +@@ -1218,27 +1381,23 @@ + gpio->chip.label = dev_name(&pdev->dev); + gpio->chip.base = -1; + +- /* Allocate a cache of the output registers */ +- banks = DIV_ROUND_UP(gpio->chip.ngpio, 32); +- gpio->dcache = devm_kcalloc(&pdev->dev, +- banks, sizeof(u32), GFP_KERNEL); +- if (!gpio->dcache) +- return -ENOMEM; +- +- /* +- * Populate it with initial values read from the HW and switch +- * all command sources to the ARM by default +- */ +- for (i = 0; i < banks; i++) { +- const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i]; +- void __iomem *addr = bank_reg(gpio, bank, reg_rdata); +- gpio->dcache[i] = ioread32(addr); +- aspeed_gpio_change_cmd_source(gpio, bank, 0, GPIO_CMDSRC_ARM); +- aspeed_gpio_change_cmd_source(gpio, bank, 1, GPIO_CMDSRC_ARM); +- aspeed_gpio_change_cmd_source(gpio, bank, 2, GPIO_CMDSRC_ARM); +- aspeed_gpio_change_cmd_source(gpio, bank, 3, GPIO_CMDSRC_ARM); ++ if (gpio->config->require_dcache) { ++ /* Allocate a cache of the output registers */ ++ banks = DIV_ROUND_UP(gpio->chip.ngpio, 32); ++ gpio->dcache = devm_kcalloc(&pdev->dev, banks, sizeof(u32), GFP_KERNEL); ++ if (!gpio->dcache) ++ return -ENOMEM; ++ /* ++ * Populate it with initial values read from the HW ++ */ ++ for (i = 0; i < banks; i++) ++ gpio->dcache[i] = ++ gpio->config->llops->reg_bank_get(gpio, (i << 5), reg_rdata); + } + ++ if (gpio->config->llops->privilege_init) ++ gpio->config->llops->privilege_init(gpio); ++ + /* Set up an irqchip */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) +@@ -1270,13 +1429,14 @@ + } + + static struct platform_driver aspeed_gpio_driver = { ++ .probe = aspeed_gpio_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_gpio_of_table, + }, + }; + +-module_platform_driver_probe(aspeed_gpio_driver, aspeed_gpio_probe); ++module_platform_driver(aspeed_gpio_driver); + + MODULE_DESCRIPTION("Aspeed GPIO Driver"); + MODULE_LICENSE("GPL"); +diff --git a/drivers/gpu/drm/aspeed/Kconfig b/drivers/gpu/drm/aspeed/Kconfig +--- a/drivers/gpu/drm/aspeed/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpu/drm/aspeed/Kconfig 2026-09-09 15:51:29.983460728 +0000 +@@ -4,6 +4,7 @@ + depends on DRM && OF + depends on (COMPILE_TEST || ARCH_ASPEED) + depends on MMU ++ select FB + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select DMA_CMA if HAVE_DMA_CONTIGUOUS +diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx.h b/drivers/gpu/drm/aspeed/aspeed_gfx.h +--- a/drivers/gpu/drm/aspeed/aspeed_gfx.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpu/drm/aspeed/aspeed_gfx.h 2026-09-09 15:51:44.558240489 +0000 +@@ -8,14 +8,32 @@ + struct drm_device drm; + void __iomem *base; + struct clk *clk; +- struct reset_control *rst; ++ struct reset_control *rst_crt; ++ struct reset_control *rst_engine; + struct regmap *scu; ++ struct regmap *scu1; ++ struct regmap *dp; ++ struct regmap *dpmcu; ++ struct regmap *pcie_ep; ++ ++ u8 dp_support; ++ u8 pcie_advance; ++ u8 pcie_active; + + u32 dac_reg; + u32 int_clr_reg; + u32 vga_scratch_reg; + u32 throd_val; + u32 scan_line_max; ++ u32 flags; ++ u32 pcie_int_reg; ++ u32 pcie_int_mask; ++ u32 pcie_int_l_to_h; ++ u32 pcie_int_h_to_l; ++ u32 pcie_link_reg; ++ u32 pcie_link_bit; ++ u32 soc_crt_bit; ++ u32 soc_dp_bit; + + struct drm_simple_display_pipe pipe; + struct drm_connector connector; +@@ -83,6 +101,10 @@ + #define CRT_CTRL_VBLANK_LINE(x) (((x) << 20) & CRT_CTRL_VBLANK_LINE_MASK) + #define CRT_CTRL_VBLANK_LINE_MASK GENMASK(31, 20) + ++/* CURSOR define */ ++#define CRT_CTRL_CURSOR0 0x3f3f ++#define CRT_CTRL_CURSOR1 0xfff1fff ++ + /* CRT_HORIZ0 */ + #define CRT_H_TOTAL(x) (x) + #define CRT_H_DE(x) ((x) << 16) +@@ -106,3 +128,58 @@ + /* CRT_THROD */ + #define CRT_THROD_LOW(x) (x) + #define CRT_THROD_HIGH(x) ((x) << 8) ++ ++/* SCU control */ ++#define G4_DISABLE_D2_PLL BIT(4) ++#define G4_40_CLK 0x46314 ++#define G6_CLK_SOURCE 0x300 ++#define G6_CLK_SOURCE_MASK (BIT(8) | BIT(9) | BIT(10)) ++#define G6_CLK_SOURCE_HPLL (BIT(8) | BIT(9) | BIT(10)) ++#define G6_CLK_SOURCE_USB BIT(9) ++#define G6_CLK_SEL3 0x308 ++#define G6_CLK_DIV_MASK 0x3F000 ++#define G6_CLK_DIV_16 (BIT(16) | BIT(15) | BIT(13) | BIT(12)) ++#define G6_USB_40_CLK BIT(9) ++ ++/* GFX FLAGS */ ++#define RESET_MASK BIT(0) ++#define RESET_G6 BIT(0) ++#define CLK_MASK (BIT(4) | BIT(5) | BIT(6)) ++#define CLK_G4 BIT(4) ++#define CLK_G6 BIT(5) ++#define CLK_G7 BIT(6) ++#define ADDR_64 BIT(12) ++ ++/* PCIE interrupt */ ++#define PCIE_PERST_L_T_H_G5 BIT(18) ++#define PCIE_PERST_H_T_L_G5 BIT(19) ++#define PCIE_PERST_L_T_H_G7 BIT(3) ++#define PCIE_PERST_H_T_L_G7 BIT(2) ++ ++/* PCIE end pointer define */ ++#define PCIE_LINK_REG_G5 0xC0 ++#define PCIE_LINK_STATUS_G5 BIT(5) ++#define PCIE_LINK_REG_G7 0x358 ++#define PCIE_LINK_STATUS_G7 BIT(8) ++ ++/* Adaptor function define */ ++/* AST2600: DP adaptor define */ ++#define DP_26_CP_NAME "aspeed,ast2600-displayport" ++#define DP_26_MCU_CP_NAME "aspeed,ast2600-displayport-mcu" ++/* AST2600 */ ++#define SCU_DP_STATUS 0x100 /* SCU100 VGA function handshake */ ++/* AST2700: DP adaptor define */ ++#define DP_27_CP_NAME "aspeed,ast2700-displayport" ++#define DP_27_MCU_CP_NAME "aspeed,ast2700-displayport-mcu" ++#define SCU_PCIE0_DP_STATUS 0x900 /* SCU900 PCIE0 handshake */ ++#define SCU_PCIE1_DP_STATUS 0x910 /* SCU910 PCIE1 handshake */ ++#define DP_LOCATE_PCIE1 BIT(8) /* DP located on PCIE1 */ ++ ++/* AST DP */ ++#define DP_EXECUTE 0x2E /* DP Status */ ++#define DP_SOURCE 0xb8 /* DPB8 dp source */ ++#define DP_CONTROL_FROM_SOC (BIT(24) | BIT(28)) ++/* AST DP MCU */ ++#define DP_RESOLUTION 0xde0 /* DPMCUDE0 dp resolution */ ++#define DP_800 0x01050020 /* 800 x 600 60Hz */ ++#define DP_1024 0x010a0020 /* 1024 x 768 70Hz */ +diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c b/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c +--- a/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c 2026-09-09 15:51:44.552240580 +0000 +@@ -23,6 +23,45 @@ + return container_of(pipe, struct aspeed_gfx, pipe); + } + ++static void aspeed_gfx_set_g4_clock(struct aspeed_gfx *priv) ++{ ++ /* turn on d2 pll for soc disply at ast2400 */ ++ regmap_update_bits(priv->scu, priv->dac_reg, G4_DISABLE_D2_PLL, 0); ++ /* apply 800 x 600 @ 60 for soc disply at ast2400 */ ++ writel(G4_40_CLK, priv->base + CRT_MISC); ++} ++ ++static void aspeed_gfx_set_g6_clock_source(struct aspeed_gfx *priv, int mode_width) ++{ ++ regmap_update_bits(priv->scu, G6_CLK_SOURCE, G6_CLK_SOURCE_MASK, 0x0); ++ regmap_update_bits(priv->scu, G6_CLK_SEL3, G6_CLK_DIV_MASK, 0x0); ++ ++ switch (mode_width) { ++ case 1024: ++ /* hpll div 16 = 75Mhz */ ++ regmap_update_bits(priv->scu, G6_CLK_SOURCE, G6_CLK_SOURCE_MASK, G6_CLK_SOURCE_HPLL); ++ regmap_update_bits(priv->scu, G6_CLK_SEL3, G6_CLK_DIV_MASK, G6_CLK_DIV_16); ++ break; ++ case 800: ++ default: ++ /* usb 40Mhz */ ++ regmap_update_bits(priv->scu, G6_CLK_SOURCE, G6_CLK_SOURCE_MASK, G6_CLK_SOURCE_USB); ++ break; ++ } ++} ++ ++static void aspeed_gfx_set_g7_clock(struct aspeed_gfx *priv) ++{ ++ /* apply 800 x 600 @ 60 on ast2700 */ ++ regmap_update_bits(priv->scu, 0x288, BIT(14), BIT(14)); ++ regmap_write(priv->scu, 0x340, 0x00190002); ++ ++ /* apply 800 x 600 @ 60 on ast2700 DAC */ ++ /* Move the scu1.d0 to the aspeed_gfx_enable_controller first check */ ++ regmap_write(priv->scu1, 0x324, 0x00000007); ++ regmap_write(priv->scu1, 0x320, 0x1048000F); ++} ++ + static int aspeed_gfx_set_pixel_fmt(struct aspeed_gfx *priv, u32 *bpp) + { + struct drm_crtc *crtc = &priv->pipe.crtc; +@@ -59,11 +98,24 @@ + u32 ctrl1 = readl(priv->base + CRT_CTRL1); + u32 ctrl2 = readl(priv->base + CRT_CTRL2); + +- /* Set DAC source for display output to Graphics CRT (GFX) */ +- regmap_update_bits(priv->scu, priv->dac_reg, BIT(16), BIT(16)); ++ /* change the display source is coming from soc display */ ++ if (!priv->pcie_advance || !priv->pcie_active) { ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_crt_bit, priv->soc_crt_bit); ++ if (priv->dp_support) ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_dp_bit, priv->soc_dp_bit); ++ if ((priv->flags & CLK_MASK) == CLK_G7) ++ regmap_update_bits(priv->scu1, 0xd0, BIT(10), BIT(10)); ++ } ++ ++ /* remove the cursor and osd usage */ ++ ctrl1 &= ~(CRT_CTRL_HW_CURSOR_EN | CRT_CTRL_OSD_EN); + + writel(ctrl1 | CRT_CTRL_EN, priv->base + CRT_CTRL1); + writel(ctrl2 | CRT_CTRL_DAC_EN, priv->base + CRT_CTRL2); ++ ++ /* trigger cursor position to apply cursor disable */ ++ writel(CRT_CTRL_CURSOR0, priv->base + CRT_CURSOR0); ++ writel(CRT_CTRL_CURSOR1, priv->base + CRT_CURSOR1); + } + + static void aspeed_gfx_disable_controller(struct aspeed_gfx *priv) +@@ -74,7 +126,42 @@ + writel(ctrl1 & ~CRT_CTRL_EN, priv->base + CRT_CTRL1); + writel(ctrl2 & ~CRT_CTRL_DAC_EN, priv->base + CRT_CTRL2); + +- regmap_update_bits(priv->scu, priv->dac_reg, BIT(16), 0); ++ /* Set display source for display output to pcie host display */ ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_crt_bit, 0); ++ if (priv->dp_support) ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_dp_bit, 0); ++} ++ ++static void aspeed_gfx_set_clk(struct aspeed_gfx *priv, int mode_width) ++{ ++ switch (priv->flags & CLK_MASK) { ++ case CLK_G4: ++ aspeed_gfx_set_g4_clock(priv); ++ break; ++ case CLK_G6: ++ aspeed_gfx_set_g6_clock_source(priv, mode_width); ++ break; ++ case CLK_G7: ++ aspeed_gfx_set_g7_clock(priv); ++ break; ++ default: ++ break; ++ } ++} ++ ++static void aspeed_gfx_dp_mode_set(struct aspeed_gfx *priv, int mode_width) ++{ ++ switch (mode_width) { ++ case 1024: ++ /* hpll div 16 = 75Mhz */ ++ regmap_write(priv->dpmcu, DP_RESOLUTION, DP_1024); ++ break; ++ case 800: ++ default: ++ /* usb 40Mhz */ ++ regmap_write(priv->dpmcu, DP_RESOLUTION, DP_800); ++ break; ++ } + } + + static void aspeed_gfx_crtc_mode_set_nofb(struct aspeed_gfx *priv) +@@ -87,6 +174,8 @@ + if (err) + return; + ++ aspeed_gfx_set_clk(priv, m->hdisplay); ++ + #if 0 + /* TODO: we have only been able to test with the 40MHz USB clock. The + * clock is fixed, so we cannot adjust it here. */ +@@ -137,6 +226,10 @@ + * per line, rounded up) + */ + writel(priv->throd_val, priv->base + CRT_THROD); ++ ++ /* set the dp mode index */ ++ if (priv->dp_support) ++ aspeed_gfx_dp_mode_set(priv, m->hdisplay); + } + + static void aspeed_gfx_pipe_enable(struct drm_simple_display_pipe *pipe, +@@ -187,12 +280,17 @@ + gem = drm_fb_dma_get_gem_obj(fb, 0); + if (!gem) + return; +- writel(gem->dma_addr, priv->base + CRT_ADDR); ++ ++ if (priv->flags & ADDR_64) ++ writel((gem->dma_addr >> 2), priv->base + CRT_ADDR); ++ else ++ writel(gem->dma_addr, priv->base + CRT_ADDR); + } + + static int aspeed_gfx_enable_vblank(struct drm_simple_display_pipe *pipe) + { + struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); ++ + u32 reg = readl(priv->base + CRT_CTRL1); + + /* Clear pending VBLANK IRQ */ +@@ -207,6 +305,7 @@ + static void aspeed_gfx_disable_vblank(struct drm_simple_display_pipe *pipe) + { + struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); ++ + u32 reg = readl(priv->base + CRT_CTRL1); + + reg &= ~CRT_CTRL_VERTICAL_INTR_EN; +diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c +--- a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c 2026-09-09 15:51:44.565240383 +0000 +@@ -63,6 +63,15 @@ + u32 vga_scratch_reg; /* VGA scratch register in SCU */ + u32 throd_val; /* Default Threshold Seting */ + u32 scan_line_max; /* Max memory size of one scan line */ ++ u32 gfx_flags; /* Flags for gfx chip caps */ ++ u32 pcie_int_reg; /* pcie interrupt */ ++ u32 pcie_int_mask; /* pcie PERST# mask */ ++ u32 pcie_int_l_to_h; /* pcie PERST# low to high */ ++ u32 pcie_int_h_to_l; /* pcie PERST# high to low */ ++ u32 pcie_link_reg; /* pcie link status offset */ ++ u32 pcie_link_bit; /* pcie link status bit */ ++ u32 soc_crt_bit; /* soc display crt switch flag*/ ++ u32 soc_dp_bit; /* soc display dp switch flag*/ + }; + + static const struct aspeed_gfx_config ast2400_config = { +@@ -71,6 +80,15 @@ + .vga_scratch_reg = 0x50, + .throd_val = CRT_THROD_LOW(0x1e) | CRT_THROD_HIGH(0x12), + .scan_line_max = 64, ++ .gfx_flags = CLK_G4, ++ .pcie_int_reg = 0x0, ++ .pcie_int_mask = 0x0, ++ .pcie_int_l_to_h = 0x0, ++ .pcie_int_h_to_l = 0x0, ++ .pcie_link_reg = 0x0, ++ .pcie_link_bit = 0x0, ++ .soc_crt_bit = BIT(16), ++ .soc_dp_bit = 0x0, + }; + + static const struct aspeed_gfx_config ast2500_config = { +@@ -79,6 +97,15 @@ + .vga_scratch_reg = 0x50, + .throd_val = CRT_THROD_LOW(0x24) | CRT_THROD_HIGH(0x3c), + .scan_line_max = 128, ++ .gfx_flags = 0, ++ .pcie_int_reg = 0x18, ++ .pcie_int_mask = (PCIE_PERST_L_T_H_G5 | PCIE_PERST_H_T_L_G5), ++ .pcie_int_l_to_h = PCIE_PERST_L_T_H_G5, ++ .pcie_int_h_to_l = PCIE_PERST_H_T_L_G5, ++ .pcie_link_reg = PCIE_LINK_REG_G5, ++ .pcie_link_bit = PCIE_LINK_STATUS_G5, ++ .soc_crt_bit = BIT(16), ++ .soc_dp_bit = 0x0, + }; + + static const struct aspeed_gfx_config ast2600_config = { +@@ -87,12 +114,39 @@ + .vga_scratch_reg = 0x50, + .throd_val = CRT_THROD_LOW(0x50) | CRT_THROD_HIGH(0x70), + .scan_line_max = 128, ++ .gfx_flags = RESET_G6 | CLK_G6, ++ .pcie_int_reg = 0x560, ++ .pcie_int_mask = (PCIE_PERST_L_T_H_G5 | PCIE_PERST_H_T_L_G5), ++ .pcie_int_l_to_h = PCIE_PERST_L_T_H_G5, ++ .pcie_int_h_to_l = PCIE_PERST_H_T_L_G5, ++ .pcie_link_reg = PCIE_LINK_REG_G5, ++ .pcie_link_bit = PCIE_LINK_STATUS_G5, ++ .soc_crt_bit = BIT(16), ++ .soc_dp_bit = BIT(18), ++}; ++ ++static const struct aspeed_gfx_config ast2700_config = { ++ .dac_reg = 0x414, ++ .int_clear_reg = 0x68, ++ .vga_scratch_reg = 0x50, ++ .throd_val = CRT_THROD_LOW(0x50) | CRT_THROD_HIGH(0x70), ++ .scan_line_max = 128, ++ .gfx_flags = CLK_G7 | ADDR_64, ++ .pcie_int_reg = 0x1D4, ++ .pcie_int_mask = (PCIE_PERST_L_T_H_G7 | PCIE_PERST_H_T_L_G7), ++ .pcie_int_l_to_h = PCIE_PERST_L_T_H_G7, ++ .pcie_int_h_to_l = PCIE_PERST_H_T_L_G7, ++ .pcie_link_reg = PCIE_LINK_REG_G7, ++ .pcie_link_bit = PCIE_LINK_STATUS_G7, ++ .soc_crt_bit = BIT(11), ++ .soc_dp_bit = BIT(9), + }; + + static const struct of_device_id aspeed_gfx_match[] = { + { .compatible = "aspeed,ast2400-gfx", .data = &ast2400_config }, + { .compatible = "aspeed,ast2500-gfx", .data = &ast2500_config }, + { .compatible = "aspeed,ast2600-gfx", .data = &ast2600_config }, ++ { .compatible = "aspeed,ast2700-gfx", .data = &ast2700_config }, + { }, + }; + MODULE_DEVICE_TABLE(of, aspeed_gfx_match); +@@ -105,6 +159,7 @@ + + static int aspeed_gfx_setup_mode_config(struct drm_device *drm) + { ++ struct aspeed_gfx *priv = to_aspeed_gfx(drm); + int ret; + + ret = drmm_mode_config_init(drm); +@@ -113,13 +168,67 @@ + + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; +- drm->mode_config.max_width = 800; +- drm->mode_config.max_height = 600; ++ ++ switch (priv->flags & CLK_MASK) { ++ case CLK_G6: ++ drm->mode_config.max_width = 1024; ++ drm->mode_config.max_height = 768; ++ break; ++ default: ++ drm->mode_config.max_width = 800; ++ drm->mode_config.max_height = 600; ++ break; ++ } ++ + drm->mode_config.funcs = &aspeed_gfx_mode_config_funcs; + + return ret; + } + ++static irqreturn_t aspeed_host_irq_handler(int irq, void *data) ++{ ++ struct drm_device *drm = data; ++ struct aspeed_gfx *priv = to_aspeed_gfx(drm); ++ u32 reg; ++ ++ regmap_read(priv->scu, priv->pcie_int_reg, ®); ++ ++ if (reg & priv->pcie_int_mask) { ++ if (reg & priv->pcie_int_l_to_h) { ++ dev_dbg(drm->dev, "pcie active.\n"); ++ /*Change the DP back to host*/ ++ if (priv->dp_support) { ++ /*Change the DP back to host*/ ++ regmap_update_bits(priv->dp, DP_SOURCE, DP_CONTROL_FROM_SOC, 0); ++ dev_dbg(drm->dev, "dp set at 0 int L_T_H.\n"); ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_dp_bit, 0); ++ } ++ ++ /*Change the CRT back to host*/ ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_crt_bit, 0); ++ if ((priv->flags & CLK_MASK) == CLK_G7) ++ regmap_update_bits(priv->scu1, 0xD0, BIT(10), 0); ++ } else if (reg & priv->pcie_int_h_to_l) { ++ dev_dbg(drm->dev, "pcie de-active.\n"); ++ /*Change the DP into host*/ ++ if (priv->dp_support) { ++ /*Change the DP back to soc*/ ++ regmap_update_bits(priv->dp, DP_SOURCE, DP_CONTROL_FROM_SOC, DP_CONTROL_FROM_SOC); ++ dev_dbg(drm->dev, "dp set at 11 int H_T_L.\n"); ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_dp_bit, priv->soc_dp_bit); ++ } ++ ++ /*Change the CRT into soc*/ ++ regmap_update_bits(priv->scu, priv->dac_reg, priv->soc_crt_bit, priv->soc_crt_bit); ++ if ((priv->flags & CLK_MASK) == CLK_G7) ++ regmap_update_bits(priv->scu1, 0xD0, BIT(10), BIT(10)); ++ } ++ return IRQ_HANDLED; ++ } ++ ++ return IRQ_NONE; ++} ++ + static irqreturn_t aspeed_gfx_irq_handler(int irq, void *data) + { + struct drm_device *drm = data; +@@ -137,6 +246,158 @@ + return IRQ_NONE; + } + ++static int aspeed_pcie_active_detect(struct drm_device *drm) ++{ ++ struct aspeed_gfx *priv = to_aspeed_gfx(drm); ++ u32 reg = 0; ++ ++ /* map pcie ep resource */ ++ priv->pcie_ep = syscon_regmap_lookup_by_compatible("aspeed,ast2500-pcie-ep"); ++ if (IS_ERR(priv->pcie_ep)) { ++ priv->pcie_ep = syscon_regmap_lookup_by_compatible("aspeed,ast2600-pcie-phy"); ++ if (IS_ERR(priv->pcie_ep)) { ++ priv->pcie_ep = syscon_regmap_lookup_by_compatible("aspeed,ast2700-pcie-phy"); ++ if (IS_ERR(priv->pcie_ep)) { ++ dev_err(drm->dev, "failed to find pcie_ep regmap\n"); ++ return PTR_ERR(priv->pcie_ep); ++ } ++ } ++ } ++ ++ /* check pcie rst status */ ++ regmap_read(priv->pcie_ep, priv->pcie_link_reg, ®); ++ ++ /* host vga is on or not */ ++ if (reg & priv->pcie_link_bit) ++ priv->pcie_active = 0x1; ++ else ++ priv->pcie_active = 0x0; ++ ++ dev_dbg(drm->dev, "pcie_active %x\n", priv->pcie_active); ++ ++ return 0; ++} ++ ++static int aspeed_adaptor_detect(struct drm_device *drm) ++{ ++ struct platform_device *pdev = to_platform_device(drm->dev); ++ struct aspeed_gfx *priv = to_aspeed_gfx(drm); ++ struct device_node *np = pdev->dev.of_node; ++ ++ u32 dp_status_offset = 0, reg = 0; ++ ++ switch (priv->flags & CLK_MASK) { ++ case CLK_G6: ++ /* check AST DP is executed or not*/ ++ regmap_read(priv->scu, SCU_DP_STATUS, ®); ++ if (((reg >> 8) & DP_EXECUTE) == DP_EXECUTE) { ++ priv->dp_support = 0x1; ++ ++ priv->dp = syscon_regmap_lookup_by_compatible(DP_26_CP_NAME); ++ if (IS_ERR(priv->dp)) { ++ dev_err(drm->dev, "failed to find DP regmap\n"); ++ return PTR_ERR(priv->dp); ++ } ++ ++ priv->dpmcu = syscon_regmap_lookup_by_compatible(DP_26_MCU_CP_NAME); ++ if (IS_ERR(priv->dpmcu)) { ++ dev_err(drm->dev, "failed to find DP MCU regmap\n"); ++ return PTR_ERR(priv->dpmcu); ++ } ++ ++ /* change the dp setting is coming from soc display */ ++ if (!priv->pcie_active) ++ regmap_update_bits(priv->dp, DP_SOURCE, DP_CONTROL_FROM_SOC, DP_CONTROL_FROM_SOC); ++ } ++ break; ++ case CLK_G7: ++ /* check AST DP is located on PCIE0 or PCIE1 */ ++ regmap_read(priv->scu, priv->dac_reg, ®); ++ ++ if (reg & DP_LOCATE_PCIE1) ++ dp_status_offset = SCU_PCIE1_DP_STATUS; ++ else ++ dp_status_offset = SCU_PCIE0_DP_STATUS; ++ ++ /* check AST DP is executed or not*/ ++ regmap_read(priv->scu, dp_status_offset, ®); ++ if (((reg >> 8) & DP_EXECUTE) == DP_EXECUTE) { ++ priv->dp_support = 0x1; ++ ++ priv->dp = syscon_regmap_lookup_by_compatible(DP_27_CP_NAME); ++ if (IS_ERR(priv->dp)) { ++ dev_err(drm->dev, "failed to find DP regmap\n"); ++ return PTR_ERR(priv->dp); ++ } ++ ++ priv->dpmcu = syscon_regmap_lookup_by_compatible(DP_27_MCU_CP_NAME); ++ if (IS_ERR(priv->dpmcu)) { ++ dev_err(drm->dev, "failed to find DP MCU regmap\n"); ++ return PTR_ERR(priv->dpmcu); ++ } ++ ++ /* change the dp setting is coming from soc display */ ++ regmap_update_bits(priv->dp, DP_SOURCE, DP_CONTROL_FROM_SOC, DP_CONTROL_FROM_SOC); ++ } ++ ++ /* get the scu1 for DAC setting */ ++ priv->scu1 = syscon_regmap_lookup_by_phandle(np, "syscon_io"); ++ if (IS_ERR(priv->scu1)) { ++ priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2700-scu1"); ++ if (IS_ERR(priv->scu1)) { ++ dev_err(drm->dev, "failed to find SCU1 regmap\n"); ++ return PTR_ERR(priv->scu1); ++ } ++ } ++ ++ break; ++ default: ++ priv->dp_support = 0x0; ++ priv->dp = NULL; ++ priv->dpmcu = NULL; ++ break; ++ } ++ return 0; ++} ++ ++static int aspeed_gfx_reset(struct drm_device *drm) ++{ ++ struct platform_device *pdev = to_platform_device(drm->dev); ++ struct aspeed_gfx *priv = to_aspeed_gfx(drm); ++ ++ switch (priv->flags & RESET_MASK) { ++ case RESET_G6: ++ priv->rst_crt = devm_reset_control_get(&pdev->dev, "crt"); ++ if (IS_ERR(priv->rst_crt)) { ++ dev_err(&pdev->dev, ++ "missing or invalid crt reset controller device tree entry"); ++ return PTR_ERR(priv->rst_crt); ++ } ++ reset_control_deassert(priv->rst_crt); ++ ++ priv->rst_engine = devm_reset_control_get(&pdev->dev, "engine"); ++ if (IS_ERR(priv->rst_engine)) { ++ dev_err(&pdev->dev, ++ "missing or invalid engine reset controller device tree entry"); ++ return PTR_ERR(priv->rst_engine); ++ } ++ reset_control_deassert(priv->rst_engine); ++ break; ++ ++ default: ++ priv->rst_crt = devm_reset_control_get_exclusive(&pdev->dev, NULL); ++ if (IS_ERR(priv->rst_crt)) { ++ dev_err(&pdev->dev, ++ "missing or invalid reset controller device tree entry"); ++ return PTR_ERR(priv->rst_crt); ++ } ++ reset_control_deassert(priv->rst_crt); ++ break; ++ } ++ ++ return 0; ++} ++ + static int aspeed_gfx_load(struct drm_device *drm) + { + struct platform_device *pdev = to_platform_device(drm->dev); +@@ -144,6 +405,7 @@ + struct device_node *np = pdev->dev.of_node; + const struct aspeed_gfx_config *config; + struct resource *res; ++ u64 dma_mask = 0; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +@@ -160,13 +422,40 @@ + priv->vga_scratch_reg = config->vga_scratch_reg; + priv->throd_val = config->throd_val; + priv->scan_line_max = config->scan_line_max; ++ priv->flags = config->gfx_flags; ++ priv->pcie_int_reg = config->pcie_int_reg; ++ priv->pcie_int_mask = config->pcie_int_mask; ++ priv->pcie_int_l_to_h = config->pcie_int_l_to_h; ++ priv->pcie_int_h_to_l = config->pcie_int_h_to_l; ++ priv->pcie_link_reg = config->pcie_link_reg; ++ priv->pcie_link_bit = config->pcie_link_bit; ++ priv->soc_crt_bit = config->soc_crt_bit; ++ priv->soc_dp_bit = config->soc_dp_bit; ++ ++ /* Add pcie auto detect if the register has been assigned */ ++ if (priv->pcie_int_reg != 0x0) ++ priv->pcie_advance = 1; ++ else ++ priv->pcie_advance = 0; ++ ++ /* Set the DMA mask by addr */ ++ if (priv->flags & ADDR_64) ++ dma_mask = DMA_BIT_MASK(64); ++ else ++ dma_mask = DMA_BIT_MASK(32); + + priv->scu = syscon_regmap_lookup_by_phandle(np, "syscon"); + if (IS_ERR(priv->scu)) { +- priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu"); ++ priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2400-scu"); + if (IS_ERR(priv->scu)) { +- dev_err(&pdev->dev, "failed to find SCU regmap\n"); +- return PTR_ERR(priv->scu); ++ priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu"); ++ if (IS_ERR(priv->scu)) { ++ priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2600-scu"); ++ if (IS_ERR(priv->scu)) { ++ dev_err(&pdev->dev, "failed to find SCU regmap\n"); ++ return PTR_ERR(priv->scu); ++ } ++ } + } + } + +@@ -177,19 +466,18 @@ + return ret; + } + +- ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32)); ++ ret = dma_set_mask_and_coherent(drm->dev, dma_mask); + if (ret) { + dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret); + return ret; + } + +- priv->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); +- if (IS_ERR(priv->rst)) { ++ ret = aspeed_gfx_reset(drm); ++ if (ret) { + dev_err(&pdev->dev, + "missing or invalid reset controller device tree entry"); +- return PTR_ERR(priv->rst); ++ return ret; + } +- reset_control_deassert(priv->rst); + + priv->clk = devm_clk_get(drm->dev, NULL); + if (IS_ERR(priv->clk)) { +@@ -199,6 +487,22 @@ + } + clk_prepare_enable(priv->clk); + ++ if (priv->pcie_advance) { ++ ret = aspeed_pcie_active_detect(drm); ++ if (ret) { ++ dev_err(&pdev->dev, ++ "missing or invalid pcie-ep controller device tree entry"); ++ return ret; ++ } ++ } ++ ++ ret = aspeed_adaptor_detect(drm); ++ if (ret) { ++ dev_err(&pdev->dev, ++ "missing or invalid adaptor controller device tree entry"); ++ return ret; ++ } ++ + /* Sanitize control registers */ + writel(0, priv->base + CRT_CTRL1); + writel(0, priv->base + CRT_CTRL2); +@@ -232,6 +536,23 @@ + return ret; + } + ++ /* install pcie reset detect */ ++ if (of_property_read_bool(np, "pcie-reset-detect") && priv->pcie_advance) { ++ dev_dbg(drm->dev, "hook pcie reset.\n"); ++ ++ /* Special watch the host power up / down */ ++ ret = devm_request_irq(drm->dev, platform_get_irq(pdev, 1), aspeed_host_irq_handler, IRQF_SHARED, "aspeed host active", drm); ++ if (ret < 0) { ++ dev_err(drm->dev, "Failed to install HOST active handler\n"); ++ return ret; ++ } ++ ret = devm_request_irq(drm->dev, platform_get_irq(pdev, 2), aspeed_host_irq_handler, IRQF_SHARED, "aspeed host deactivate", drm); ++ if (ret < 0) { ++ dev_err(drm->dev, "Failed to install HOST de-active handler\n"); ++ return ret; ++ } ++ } ++ + drm_mode_config_reset(drm); + + return 0; +@@ -239,6 +560,12 @@ + + static void aspeed_gfx_unload(struct drm_device *drm) + { ++ struct aspeed_gfx *priv = drm->dev_private; ++ ++ /* change the dp setting is coming from host side */ ++ if (priv->dp_support) ++ regmap_update_bits(priv->dp, DP_SOURCE, DP_CONTROL_FROM_SOC, 0); ++ + drm_kms_helper_poll_fini(drm); + } + +diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_out.c b/drivers/gpu/drm/aspeed/aspeed_gfx_out.c +--- a/drivers/gpu/drm/aspeed/aspeed_gfx_out.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpu/drm/aspeed/aspeed_gfx_out.c 2026-09-09 15:51:44.545240686 +0000 +@@ -10,7 +10,19 @@ + + static int aspeed_gfx_get_modes(struct drm_connector *connector) + { +- return drm_add_modes_noedid(connector, 800, 600); ++ struct aspeed_gfx *priv = container_of(connector, struct aspeed_gfx, connector); ++ int mode_count = 0; ++ ++ switch (priv->flags & CLK_MASK) { ++ case CLK_G6: ++ mode_count = drm_add_modes_noedid(connector, 1024, 768); ++ break; ++ default: ++ mode_count = drm_add_modes_noedid(connector, 800, 600); ++ break; ++ } ++ ++ return mode_count; + } + + static const struct +diff --git a/drivers/hwmon/aspeed-chassis.c b/drivers/hwmon/aspeed-chassis.c +--- a/drivers/hwmon/aspeed-chassis.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/hwmon/aspeed-chassis.c 2026-09-09 15:51:44.149246669 +0000 +@@ -0,0 +1,221 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2021 ASPEED Technology Inc. ++ * ++ * CHASSIS driver for the Aspeed SoC ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* #define USE_INTERRUPTS */ ++/******************************************************************************/ ++union chassis_ctrl_register { ++ u32 value; ++ struct { ++ uint32_t intrusion_status_clear : 1; /*[0]*/ ++ uint32_t intrusion_int_enable : 1; /*[1]*/ ++ uint32_t intrusion_status : 1; /*[2]*/ ++ uint32_t battery_power_good : 1; /*[3]*/ ++ uint32_t chassis_raw_status : 1; /*[4]*/ ++ uint32_t reserved0 : 3; /*[5-7]*/ ++ uint32_t io_power_status_clear : 1; /*[8]*/ ++ uint32_t io_power_int_enable : 1; /*[9]*/ ++ uint32_t core_power_status : 1; /*[10]*/ ++ uint32_t reserved1 : 5; /*[11-15]*/ ++ uint32_t core_power_status_clear : 1; /*[16]*/ ++ uint32_t core_power_int_enable : 1; /*[17]*/ ++ uint32_t io_power_status : 1; /*[18]*/ ++ uint32_t reserved2 : 13; /*[19-31]*/ ++ } fields; ++}; ++ ++struct aspeed_chassis { ++ struct device *dev; ++ void __iomem *base; ++ int irq; ++ /* for hwmon */ ++ const struct attribute_group *groups[2]; ++}; ++ ++static ssize_t ++intrusion_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ unsigned long val; ++ struct aspeed_chassis *chassis = dev_get_drvdata(dev); ++ union chassis_ctrl_register chassis_ctrl; ++ ++ if (kstrtoul(buf, 10, &val) < 0 || val != 0) ++ return -EINVAL; ++ ++ chassis_ctrl.value = readl(chassis->base); ++ chassis_ctrl.fields.intrusion_status_clear = 1; ++ writel(chassis_ctrl.value, chassis->base); ++ chassis_ctrl.fields.intrusion_status_clear = 0; ++ writel(chassis_ctrl.value, chassis->base); ++ return count; ++} ++ ++static ssize_t intrusion_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); ++ int index = sensor_attr->index; ++ struct aspeed_chassis *chassis = dev_get_drvdata(dev); ++ union chassis_ctrl_register chassis_ctrl; ++ u8 ret; ++ ++ chassis_ctrl.value = readl(chassis->base); ++ ++ switch (index) { ++ case 0: ++ ret = chassis_ctrl.fields.core_power_status; ++ break; ++ case 1: ++ ret = chassis_ctrl.fields.io_power_status; ++ break; ++ case 2: ++ ret = chassis_ctrl.fields.intrusion_status; ++ break; ++ } ++ ++ return sprintf(buf, "%d\n", ret); ++} ++ ++static SENSOR_DEVICE_ATTR_RO(core_power, intrusion, 0); ++static SENSOR_DEVICE_ATTR_RO(io_power, intrusion, 1); ++static SENSOR_DEVICE_ATTR_RW(intrusion0_alarm, intrusion, 2); ++ ++static struct attribute *intrusion_dev_attrs[] = { ++ &sensor_dev_attr_core_power.dev_attr.attr, ++ &sensor_dev_attr_io_power.dev_attr.attr, ++ &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, NULL ++}; ++ ++static const struct attribute_group intrusion_dev_group = { ++ .attrs = intrusion_dev_attrs, ++ .is_visible = NULL, ++}; ++ ++#ifdef USE_INTERRUPTS ++static void aspeed_chassis_status_check(struct aspeed_chassis *chassis) ++{ ++ union chassis_ctrl_register chassis_ctrl; ++ ++ chassis_ctrl.value = readl(chassis->base); ++ if (chassis_ctrl.fields.intrusion_status) { ++ dev_info(chassis->dev, "CHASI# pin has been pulled low"); ++ chassis_ctrl.fields.intrusion_status_clear = 1; ++ writel(chassis_ctrl.value, chassis->base); ++ chassis_ctrl.fields.intrusion_status_clear = 0; ++ writel(chassis_ctrl.value, chassis->base); ++ } ++ ++ if (chassis_ctrl.fields.core_power_status) { ++ dev_info(chassis->dev, "Core power has been pulled low"); ++ chassis_ctrl.fields.core_power_status_clear = 1; ++ writel(chassis_ctrl.value, chassis->base); ++ chassis_ctrl.fields.core_power_status_clear = 0; ++ writel(chassis_ctrl.value, chassis->base); ++ } ++ ++ if (chassis_ctrl.fields.io_power_status) { ++ dev_info(chassis->dev, "IO power has been pulled low"); ++ chassis_ctrl.fields.io_power_status_clear = 1; ++ writel(chassis_ctrl.value, chassis->base); ++ chassis_ctrl.fields.io_power_status_clear = 0; ++ writel(chassis_ctrl.value, chassis->base); ++ } ++} ++ ++static irqreturn_t aspeed_chassis_isr(int this_irq, void *dev_id) ++{ ++ struct aspeed_chassis *chassis = dev_id; ++ ++ aspeed_chassis_status_check(chassis); ++ return IRQ_HANDLED; ++} ++#endif ++ ++static void aspeed_chassis_int_ctrl(struct aspeed_chassis *chassis, bool ctrl) ++{ ++ union chassis_ctrl_register chassis_ctrl; ++ ++ chassis_ctrl.value = readl(chassis->base); ++ chassis_ctrl.fields.intrusion_int_enable = ctrl; ++ chassis_ctrl.fields.io_power_int_enable = ctrl; ++ chassis_ctrl.fields.core_power_int_enable = ctrl; ++ writel(chassis_ctrl.value, chassis->base); ++} ++ ++static const struct of_device_id aspeed_chassis_of_table[] = { ++ { .compatible = "aspeed,ast2600-chassis" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aspeed_chassis_of_table); ++ ++static int aspeed_chassis_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct aspeed_chassis *priv; ++ struct device *hwmon; ++ int __maybe_unused ret; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->dev = dev; ++ priv->base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(priv->base)) ++ return PTR_ERR(priv->base); ++#ifdef USE_INTERRUPTS ++ priv->irq = platform_get_irq(pdev, 0); ++ if (priv->irq < 0) { ++ dev_err(dev, "no irq specified\n"); ++ return -ENOENT; ++ } ++ ++ ret = devm_request_irq(dev, priv->irq, aspeed_chassis_isr, 0, ++ dev_name(dev), priv); ++ if (ret) { ++ dev_err(dev, "Chassis Unable to get IRQ"); ++ return ret; ++ } ++ aspeed_chassis_int_ctrl(priv, true); ++#else ++ aspeed_chassis_int_ctrl(priv, false); ++#endif ++ ++ priv->groups[0] = &intrusion_dev_group; ++ priv->groups[1] = NULL; ++ ++ hwmon = devm_hwmon_device_register_with_groups(dev, "aspeed_chassis", ++ priv, priv->groups); ++ ++ return PTR_ERR_OR_ZERO(hwmon); ++} ++ ++static struct platform_driver aspeed_chassis_driver = { ++ .probe = aspeed_chassis_probe, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_chassis_of_table, ++ }, ++}; ++ ++module_platform_driver(aspeed_chassis_driver); ++ ++MODULE_AUTHOR("Billy Tsai"); ++MODULE_DESCRIPTION("ASPEED CHASSIS Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/aspeed-g6-pwm-tach.c b/drivers/hwmon/aspeed-g6-pwm-tach.c +--- a/drivers/hwmon/aspeed-g6-pwm-tach.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/hwmon/aspeed-g6-pwm-tach.c 2026-09-09 15:51:44.156246564 +0000 +@@ -56,6 +56,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -137,7 +138,7 @@ + struct reset_control *reset; + unsigned long clk_rate; + bool tach_present[TACH_ASPEED_NR_TACHS]; +- u32 tach_divisor; ++ u32 tach_divisor[TACH_ASPEED_NR_TACHS]; + }; + + static inline struct aspeed_pwm_tach_data * +@@ -282,12 +283,14 @@ + priv->base + TACH_ASPEED_CTRL(tach_ch)); + } + +-static int aspeed_tach_val_to_rpm(struct aspeed_pwm_tach_data *priv, u32 tach_val) ++static int aspeed_tach_val_to_rpm(struct aspeed_pwm_tach_data *priv, ++ u32 tach_val, u8 fan_tach_ch) + { + u64 rpm; + u32 tach_div; + +- tach_div = tach_val * priv->tach_divisor * DEFAULT_FAN_PULSE_PR; ++ tach_div = tach_val * priv->tach_divisor[fan_tach_ch] * ++ DEFAULT_FAN_PULSE_PR; + + dev_dbg(priv->dev, "clk %ld, tach_val %d , tach_div %d\n", + priv->clk_rate, tach_val, tach_div); +@@ -308,7 +311,7 @@ + if (!(val & TACH_ASPEED_FULL_MEASUREMENT)) + return 0; + val = FIELD_GET(TACH_ASPEED_VALUE_MASK, val); +- return aspeed_tach_val_to_rpm(priv, val); ++ return aspeed_tach_val_to_rpm(priv, val, fan_tach_ch); + } + + static int aspeed_tach_hwmon_read(struct device *dev, +@@ -345,11 +348,11 @@ + if (!is_power_of_2(val) || (ilog2(val) % 2) || + DIV_TO_REG(val) > 0xb) + return -EINVAL; +- priv->tach_divisor = val; ++ priv->tach_divisor[channel] = val; + reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel)); + reg_val &= ~TACH_ASPEED_CLK_DIV_T_MASK; + reg_val |= FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK, +- DIV_TO_REG(priv->tach_divisor)); ++ DIV_TO_REG(priv->tach_divisor[channel])); + writel(reg_val, priv->base + TACH_ASPEED_CTRL(channel)); + break; + default: +@@ -407,7 +410,7 @@ + for (index = 0; index < count; index++) { + ch = tach_ch[index]; + priv->tach_present[ch] = true; +- priv->tach_divisor = DEFAULT_TACH_DIV; ++ priv->tach_divisor[ch] = DEFAULT_TACH_DIV; + + val = readl(priv->base + TACH_ASPEED_CTRL(ch)); + val &= ~(TACH_ASPEED_INVERS_LIMIT | TACH_ASPEED_DEBOUNCE_MASK | +@@ -416,7 +419,7 @@ + val |= (DEBOUNCE_3_CLK << TACH_ASPEED_DEBOUNCE_BIT) | + F2F_EDGES | + FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK, +- DIV_TO_REG(priv->tach_divisor)); ++ DIV_TO_REG(priv->tach_divisor[ch])); + writel(val, priv->base + TACH_ASPEED_CTRL(ch)); + + aspeed_tach_ch_enable(priv, ch, true); +@@ -452,6 +455,50 @@ + reset_control_assert(rst); + } + ++static void aspeed_pwm_set_wdt_reload(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ u64 reload_duty_cycle) ++{ ++ struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip); ++ u32 hwpwm = pwm->hwpwm, val; ++ ++ val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm)); ++ val &= ~PWM_ASPEED_DUTY_CYCLE_POINT_AS_WDT; ++ val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_POINT_AS_WDT, ++ reload_duty_cycle); ++ writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm)); ++ ++ val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm)); ++ val |= PWM_ASPEED_CTRL_DUTY_LOAD_AS_WDT_ENABLE; ++ writel(val, priv->base + PWM_ASPEED_CTRL(hwpwm)); ++} ++ ++static struct pwm_device * ++aspeed_pwm_xlate(struct pwm_chip *chip, const struct of_phandle_args *args) ++{ ++ struct pwm_device *pwm; ++ ++ /* period in the second cell and flags in the third cell are optional */ ++ if (args->args_count < 1) ++ return ERR_PTR(-EINVAL); ++ ++ pwm = pwm_request_from_chip(chip, args->args[0], NULL); ++ if (IS_ERR(pwm)) ++ return pwm; ++ ++ if (args->args_count > 1) ++ pwm->args.period = args->args[1]; ++ ++ pwm->args.polarity = PWM_POLARITY_NORMAL; ++ if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED) ++ pwm->args.polarity = PWM_POLARITY_INVERSED; ++ ++ if (args->args_count > 3 && args->args[3] < U8_MAX) ++ aspeed_pwm_set_wdt_reload(chip, pwm, args->args[3]); ++ ++ return pwm; ++} ++ + static int aspeed_pwm_tach_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev, *hwmon; +@@ -493,6 +540,8 @@ + pwmchip_set_drvdata(chip, priv); + chip->ops = &aspeed_pwm_ops; + ++ chip->of_xlate = aspeed_pwm_xlate; ++ + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); +@@ -521,6 +570,9 @@ + { + .compatible = "aspeed,ast2600-pwm-tach", + }, ++ { ++ .compatible = "aspeed,ast2700-pwm-tach", ++ }, + {}, + }; + MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match); +diff --git a/drivers/i2c/busses/i2c-ast2600.c b/drivers/i2c/busses/i2c-ast2600.c +--- a/drivers/i2c/busses/i2c-ast2600.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i2c/busses/i2c-ast2600.c 2026-09-09 15:51:44.101247395 +0000 +@@ -0,0 +1,3134 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * ASPEED AST2600 new register set I2C controller driver ++ * ++ * Copyright (C) 2023 ASPEED Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define AST2600_I2CG_ISR 0x00 ++#define AST2600_I2CG_SLAVE_ISR 0x04 ++#define AST2600_I2CG_OWNER 0x08 ++#define AST2600_I2CG_CTRL 0x0C ++#define AST2600_I2CG_CLK_DIV_CTRL 0x10 ++ ++#define AST2600_I2CG_SLAVE_PKT_NAK BIT(4) ++#define AST2600_I2CG_M_S_SEPARATE_INTR BIT(3) ++#define AST2600_I2CG_CTRL_NEW_REG BIT(2) ++#define AST2600_I2CG_CTRL_NEW_CLK_DIV BIT(1) ++#define AST2600_GLOBAL_INIT \ ++ (AST2600_I2CG_CTRL_NEW_REG | AST2600_I2CG_CTRL_NEW_CLK_DIV) ++/* ++ * APB clk : 100Mhz ++ * div : scl : baseclk [APB/((div/2) + 1)] : tBuf [1/bclk * 16] ++ * I2CG10[31:24] base clk4 for i2c auto recovery timeout counter (0xC6) ++ * I2CG10[23:16] base clk3 for Standard-mode (100Khz) min tBuf 4.7us ++ * 0x3c : 100.8Khz : 3.225Mhz : 4.96us ++ * 0x3d : 99.2Khz : 3.174Mhz : 5.04us ++ * 0x3e : 97.65Khz : 3.125Mhz : 5.12us ++ * 0x40 : 97.75Khz : 3.03Mhz : 5.28us ++ * 0x41 : 99.5Khz : 2.98Mhz : 5.36us (default) ++ * I2CG10[15:8] base clk2 for Fast-mode (400Khz) min tBuf 1.3us ++ * 0x12 : 400Khz : 10Mhz : 1.6us ++ * I2CG10[7:0] base clk1 for Fast-mode Plus (1Mhz) min tBuf 0.5us ++ * 0x08 : 1Mhz : 20Mhz : 0.8us ++ */ ++#define AST2600_I2CCG_DIV_CTRL 0xC6411208 ++#define AST2700_I2CCG_DIV_CTRL 0xC6220904 ++#define AST2700_MIN_AC_TIMING 12000 ++#define AST_DEFAULT_AC_TIMING 100000 ++ ++/* 0x00 : I2CC Controller/Target Function Control Register */ ++#define AST2600_I2CC_FUN_CTRL 0x00 ++#define AST2600_I2CC_SLAVE_ADDR_RX_EN BIT(20) ++#define AST2600_I2CC_MASTER_RETRY_MASK GENMASK(19, 18) ++#define AST2600_I2CC_MASTER_RETRY(x) (((x) & GENMASK(1, 0)) << 18) ++#define AST2600_I2CC_BUS_AUTO_RELEASE BIT(17) ++#define AST2600_I2CC_M_SDA_LOCK_EN BIT(16) ++#define AST2600_I2CC_MULTI_MASTER_DIS BIT(15) ++#define AST2600_I2CC_M_SCL_DRIVE_EN BIT(14) ++#define AST2600_I2CC_MSB_STS BIT(9) ++#define AST2600_I2CC_SDA_DRIVE_1T_EN BIT(8) ++#define AST2600_I2CC_M_SDA_DRIVE_1T_EN BIT(7) ++#define AST2600_I2CC_M_HIGH_SPEED_EN BIT(6) ++#define AST2700_I2CC_MANUAL_DEBOUNCE GENMASK(5, 4) ++/* reserver 5 : 2 */ ++#define AST2600_I2CC_SLAVE_EN BIT(1) ++#define AST2600_I2CC_MASTER_EN BIT(0) ++ ++/* 0x04 : I2CC Controller/Target Clock and AC Timing Control Register #1 */ ++#define AST2600_I2CC_AC_TIMING 0x04 ++#define AST2600_I2CC_TTIMEOUT(x) (((x) & GENMASK(4, 0)) << 24) ++#define AST2700_I2CC_TTIMEOUT(x) (((x) & GENMASK(5, 0)) << 24) ++#define AST2600_I2CC_TCKHIGHMIN(x) (((x) & GENMASK(3, 0)) << 20) ++#define AST2600_I2CC_TCKHIGH(x) (((x) & GENMASK(3, 0)) << 16) ++#define AST2600_I2CC_TCKLOW(x) (((x) & GENMASK(3, 0)) << 12) ++#define AST2600_I2CC_THDDAT(x) (((x) & GENMASK(1, 0)) << 10) ++#define AST2600_I2CC_TOUTBASECLK(x) (((x) & GENMASK(1, 0)) << 8) ++#define AST2600_I2CC_TBASECLK(x) ((x) & GENMASK(3, 0)) ++#define AST2600_I2CC_AC_TIMING_MASK GENMASK(23, 0) ++ ++/* 0x08 : I2CC Controller/Target Transmit/Receive Byte Buffer Register */ ++#define AST2600_I2CC_STS_AND_BUFF 0x08 ++#define AST2600_I2CC_TX_DIR_MASK GENMASK(31, 29) ++#define AST2600_I2CC_SDA_OE BIT(28) ++#define AST2600_I2CC_SDA_O BIT(27) ++#define AST2600_I2CC_SCL_OE BIT(26) ++#define AST2600_I2CC_SCL_O BIT(25) ++ ++#define AST2600_I2CC_SCL_LINE_STS BIT(18) ++#define AST2600_I2CC_SDA_LINE_STS BIT(17) ++#define AST2600_I2CC_BUS_BUSY_STS BIT(16) ++ ++#define AST2600_I2CC_GET_RX_BUFF(x) (((x) >> 8) & GENMASK(7, 0)) ++ ++/* 0x0C : I2CC Controller/Target Pool Buffer Control Register */ ++#define AST2600_I2CC_BUFF_CTRL 0x0C ++#define AST2600_I2CC_GET_RX_BUF_LEN(x) (((x) & GENMASK(29, 24)) >> 24) ++#define AST2600_I2CC_SET_RX_BUF_LEN(x) (((((x) - 1) & GENMASK(4, 0)) << 16) | BIT(0)) ++#define AST2600_I2CC_SET_TX_BUF_LEN(x) (((((x) - 1) & GENMASK(4, 0)) << 8) | BIT(0)) ++#define AST2600_I2CC_GET_TX_BUF_LEN(x) ((((x) & GENMASK(12, 8)) >> 8) + 1) ++ ++/* 0x10 : I2CM Controller Interrupt Control Register */ ++#define AST2600_I2CM_IER 0x10 ++/* 0x14 : I2CM Controller Interrupt Status Register : WC */ ++#define AST2600_I2CM_ISR 0x14 ++ ++#define AST2600_I2CM_ISR_MASK GENMASK(31, 21) ++#define AST2600_I2CM_SW_ISR_MASK GENMASK(31, 19) ++ ++#define AST2600_I2CM_PKT_TIMEOUT BIT(18) ++#define AST2600_I2CM_PKT_ERROR BIT(17) ++#define AST2600_I2CM_PKT_DONE BIT(16) ++ ++#define AST2600_I2CM_BUS_RECOVER_FAIL BIT(15) ++#define AST2600_I2CM_SDA_DL_TO BIT(14) ++#define AST2600_I2CM_BUS_RECOVER BIT(13) ++#define AST2600_I2CM_SMBUS_ALERT BIT(12) ++#define AST2700_I2CM_ABNORMAL_ACTION BIT(8) ++ ++#define AST2600_I2CM_SCL_LOW_TO BIT(6) ++#define AST2600_I2CM_ABNORMAL BIT(5) ++#define AST2600_I2CM_NORMAL_STOP BIT(4) ++#define AST2600_I2CM_ARBIT_LOSS BIT(3) ++#define AST2600_I2CM_RX_DONE BIT(2) ++#define AST2600_I2CM_TX_NAK BIT(1) ++#define AST2600_I2CM_TX_ACK BIT(0) ++ ++/* 0x18 : I2CM Controller Command/Status Register */ ++#define AST2600_I2CM_CMD_STS 0x18 ++#define AST2600_I2CM_PKT_ADDR(x) (((x) & GENMASK(6, 0)) << 24) ++#define AST2600_I2CM_PKT_EN BIT(16) ++#define AST2600_I2CM_SDA_OE_OUT_DIR BIT(15) ++#define AST2600_I2CM_SDA_O_OUT_DIR BIT(14) ++#define AST2600_I2CM_SCL_OE_OUT_DIR BIT(13) ++#define AST2600_I2CM_SCL_O_OUT_DIR BIT(12) ++#define AST2600_I2CM_RECOVER_CMD_EN BIT(11) ++ ++#define AST2600_I2CM_RX_DMA_EN BIT(9) ++#define AST2600_I2CM_TX_DMA_EN BIT(8) ++/* Command Bit */ ++#define AST2600_I2CM_RX_BUFF_EN BIT(7) ++#define AST2600_I2CM_TX_BUFF_EN BIT(6) ++#define AST2600_I2CM_STOP_CMD BIT(5) ++#define AST2600_I2CM_RX_CMD_LAST BIT(4) ++#define AST2600_I2CM_RX_CMD BIT(3) ++ ++#define AST2600_I2CM_TX_CMD BIT(1) ++#define AST2600_I2CM_START_CMD BIT(0) ++ ++/* 0x1C : I2CM Controller DMA Transfer Length Register */ ++#define AST2600_I2CM_DMA_LEN 0x1C ++/* Controller Tx Rx support length 1 ~ 4096 */ ++#define AST2600_I2CM_SET_RX_DMA_LEN(x) ((((x) & GENMASK(11, 0)) << 16) | BIT(31)) ++#define AST2600_I2CM_SET_TX_DMA_LEN(x) (((x) & GENMASK(11, 0)) | BIT(15)) ++ ++/* 0x20 : I2CS Target Interrupt Control Register */ ++#define AST2600_I2CS_IER 0x20 ++/* 0x24 : I2CS Target Interrupt Status Register */ ++#define AST2600_I2CS_ISR 0x24 ++ ++#define AST2600_I2CS_ADDR_INDICATE_MASK GENMASK(31, 30) ++#define AST2600_I2CS_SLAVE_PENDING BIT(29) ++#define AST2600_I2CS_SADDR_PENDING BIT(28) ++ ++#define AST2600_I2CS_WAIT_TX_DMA BIT(25) ++#define AST2600_I2CS_WAIT_RX_DMA BIT(24) ++ ++#define AST2600_I2CS_ADDR3_NAK BIT(22) ++#define AST2600_I2CS_ADDR2_NAK BIT(21) ++#define AST2600_I2CS_ADDR1_NAK BIT(20) ++ ++#define AST2600_I2CS_ADDR_NAK_MASK GENMASK(22, 20) ++#define AST2600_I2CS_ADDR_MASK GENMASK(19, 18) ++#define AST2600_I2CS_GET_TARGET(x) (((x) >> 30) & 0x3) ++#define AST2600_I2CS_PKT_ERROR BIT(17) ++#define AST2600_I2CS_PKT_DONE BIT(16) ++#define AST2600_I2CS_INACTIVE_TO BIT(15) ++ ++#define AST2600_I2CS_SLAVE_MATCH BIT(7) ++#define AST2600_I2CS_ABNOR_STOP BIT(5) ++#define AST2600_I2CS_STOP BIT(4) ++#define AST2600_I2CS_RX_DONE_NAK BIT(3) ++#define AST2600_I2CS_RX_DONE BIT(2) ++#define AST2600_I2CS_TX_NAK BIT(1) ++#define AST2600_I2CS_TX_ACK BIT(0) ++ ++/* 0x28 : I2CS Target CMD/Status Register */ ++#define AST2600_I2CS_CMD_STS 0x28 ++#define AST2600_I2CS_ACTIVE_ALL GENMASK(18, 17) ++#define AST2600_I2CS_PKT_MODE_EN BIT(16) ++#define AST2600_I2CS_AUTO_NAK_NOADDR BIT(15) ++#define AST2600_I2CS_AUTO_NAK_EN BIT(14) ++ ++#define AST2600_I2CS_ALT_EN BIT(10) ++#define AST2600_I2CS_RX_DMA_EN BIT(9) ++#define AST2600_I2CS_TX_DMA_EN BIT(8) ++#define AST2600_I2CS_RX_BUFF_EN BIT(7) ++#define AST2600_I2CS_TX_BUFF_EN BIT(6) ++#define AST2600_I2CS_RX_CMD_LAST BIT(4) ++ ++#define AST2600_I2CS_TX_CMD BIT(2) ++ ++#define AST2600_I2CS_DMA_LEN 0x2C ++ ++/* Target Tx Rx support length 1 ~ 4096 */ ++#define AST2600_I2CS_SET_RX_DMA_LEN(x) (((((x) - 1) & GENMASK(11, 0)) << 16) | BIT(31)) ++#define AST2600_I2CS_SET_TX_DMA_LEN(x) ((((x) - 1) & GENMASK(11, 0)) | BIT(15)) ++ ++/* I2CM Controller DMA Tx Buffer Register */ ++#define AST2600_I2CM_TX_DMA 0x30 ++/* I2CM Controller DMA Rx Buffer Register */ ++#define AST2600_I2CM_RX_DMA 0x34 ++/* I2CS Target DMA Tx Buffer Register */ ++#define AST2600_I2CS_TX_DMA 0x38 ++/* I2CS Target DMA Rx Buffer Register */ ++#define AST2600_I2CS_RX_DMA 0x3C ++ ++/* I2CM Controller DMA Rx Buffer High part Register */ ++#define AST2600_I2CM_TX_DMA_H 0x60 ++/* I2CM Controller DMA Rx Buffer High part Register */ ++#define AST2600_I2CM_RX_DMA_H 0x64 ++/* I2CS Target DMA Tx Buffer High part Register */ ++#define AST2600_I2CS_TX_DMA_H 0x68 ++/* I2CS Target DMA Rx Buffer High part Register */ ++#define AST2600_I2CS_RX_DMA_H 0x6C ++ ++#define AST2600_I2CS_ADDR_CTRL 0x40 ++ ++#define AST2600_I2CS_ADDR3_MASK GENMASK(22, 16) ++#define AST2600_I2CS_ADDR2_MASK GENMASK(14, 8) ++#define AST2600_I2CS_ADDR1_MASK GENMASK(6, 0) ++ ++#define AST2600_I2CM_DMA_LEN_STS 0x48 ++#define AST2600_I2CS_DMA_LEN_STS 0x4C ++ ++#define AST2600_I2C_GET_TX_DMA_LEN(x) ((x) & GENMASK(12, 0)) ++#define AST2600_I2C_GET_RX_DMA_LEN(x) (((x) & GENMASK(28, 16)) >> 16) ++ ++/* 0x40 : Target Device Address Register */ ++#define AST2600_I2CS_ADDR3_ENABLE BIT(23) ++#define AST2600_I2CS_ADDR3(x) ((x) << 16) ++#define AST2600_I2CS_ADDR2_ENABLE BIT(15) ++#define AST2600_I2CS_ADDR2(x) ((x) << 8) ++#define AST2600_I2CS_ADDR1_ENABLE BIT(7) ++#define AST2600_I2CS_ADDR1(x) (x) ++ ++/* 0x74 : Target Device Address Register */ ++#define MSIC_CONFIG_ACTIMING1 0x74 ++#define MSIC_I2C_SET_TIMEOUT(s, m) (((s) << 16) | (m)) ++ ++/* 0x78 : Misc status */ ++#define MSIC_STATUS 0x78 ++ ++/* 0x84 : Byte data log */ ++#define BYTE_DATA_LOG 0x84 ++ ++#define AST2700_I2CC_GET_BUFF(x) ((x) & GENMASK(7, 0)) ++ ++/* 0x8c : Target sirq log */ ++#define AST2700_I2CC_SIRQ_LOG 0x8c ++#define SLAVE_ADDR_SHIFT 8 ++#define SLAVE_ADDR_MASK GENMASK(15, 8) ++#define SADDR_NACK BIT(5) ++#define SLAVE_PKT_DONE BIT(4) ++#define SADDR_HIT BIT(3) ++#define SRX_DONE BIT(2) ++#define STX_DONE BIT(1) ++#define SLAVE_STOP BIT(0) ++ ++/* 0x94 : I2CC_VERSION_CTRL */ ++#define I2CC_VERSION_CTRL 0x94 ++#define FUNC_CFG_DMA_EN BIT(2) ++/* 0x9c : Misc_2 Debounce Setting */ ++#define MSIC2_CONFIG 0x9C ++#define AST2700_DEBOUNCE_MASK GENMASK(7, 0) ++#define AST2700_DEBOUNCE_LEVEL_MAX 0xFF ++#define AST2700_DEBOUNCE_LEVEL_MIN 0x2 ++ ++#define I2C_TARGET_MSG_BUF_SIZE 4096 ++ ++#define AST2600_I2C_DMA_SIZE 4096 ++ ++#define CONTROLLER_TRIGGER_LAST_STOP (AST2600_I2CM_RX_CMD_LAST | AST2600_I2CM_STOP_CMD) ++#define TARGET_TRIGGER_CMD (AST2600_I2CS_ACTIVE_ALL | AST2600_I2CS_PKT_MODE_EN) ++ ++#define AST2600_I2C_TIMEOUT_CLK 0x1 ++#define AST2700_I2C_TIMEOUT_CLK 0x3 ++ ++#define AST2600_I2C_TARGET_COUNT 0x3 ++ ++#define HIGH_MIN_DIV 100000000 ++#define STANDARD_HIGH_MIN_400NS 400 ++#define FAST_HIGH_MIN_60NS 60 ++#define FAST_PLUS_HIGH_MIN_26NS 26 ++ ++#define DATAHOLD_MAX_LEVEL 3 ++ ++enum xfer_mode { ++ BYTE_MODE, ++ BUFF_MODE, ++ DMA_MODE, ++}; ++ ++enum i2c_version { ++ AST2600, ++ AST2700, ++}; ++ ++struct i2c_divisor { ++ u32 baseclk_idx; ++ u32 divisor; ++ u8 baseclk_limit; ++}; ++ ++struct ast2600_i2c_bus { ++ struct i2c_adapter adap; ++ struct device *dev; ++ void __iomem *reg_base; ++ struct regmap *global_regs; ++ struct clk *clk; ++ struct i2c_timings timing_info; ++ struct completion cmd_complete; ++ struct i2c_msg *msgs; ++ u8 *controller_dma_buf; ++ dma_addr_t controller_dma_addr; ++ u32 apb_clk; ++ struct i2c_divisor clk_divisor; ++ u32 timeout; ++ int irq; ++ int cmd_err; ++ int msgs_index; ++ int msgs_count; ++ int controller_xfer_cnt; ++ size_t buf_index; ++ size_t buf_size; ++ enum xfer_mode mode; ++ enum i2c_version version; ++ bool multi_master; ++ u32 debounce_level; ++ u32 manual_min_high; ++ u32 manual_data_hold; ++ /* Buffer mode */ ++ void __iomem *buf_base; ++ int (*setup_tx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); ++ int (*setup_rx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); ++ void (*ac_timing_config)(struct ast2600_i2c_bus *i2c_bus); ++ int (*irq_err_to_errno)(u32 irq_status); ++ void (*irq_clear)(struct ast2600_i2c_bus *i2c_bus, u32 sts); ++ /* smbus alert */ ++ bool alert_enable; ++ struct i2c_smbus_alert_setup alert_data; ++ struct i2c_client *ara; ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ int target_operate; ++ int previous_idx; ++ unsigned char *target_dma_buf; ++ dma_addr_t target_dma_addr; ++ u8 target_attached; ++ struct i2c_client *multi_target[AST2600_I2C_TARGET_COUNT]; ++ struct i2c_client *target; ++ void (*target_packet_irq)(struct ast2600_i2c_bus *i2c_bus, u32 isr); ++ void (*target_byte_irq)(struct ast2600_i2c_bus *i2c_bus, u32 isr); ++#endif ++}; ++ ++static u32 i2c_cal_high_min_config(struct ast2600_i2c_bus *i2c_bus, u64 base_clk) ++{ ++ u64 cal, spec_high_min = STANDARD_HIGH_MIN_400NS; ++ ++ /* fill the spec high min value */ ++ if (i2c_bus->timing_info.bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ && ++ i2c_bus->timing_info.bus_freq_hz > I2C_MAX_STANDARD_MODE_FREQ) ++ spec_high_min = FAST_HIGH_MIN_60NS; ++ else if (i2c_bus->timing_info.bus_freq_hz <= I2C_MAX_FAST_MODE_PLUS_FREQ && ++ i2c_bus->timing_info.bus_freq_hz > I2C_MAX_FAST_MODE_FREQ) ++ spec_high_min = FAST_PLUS_HIGH_MIN_26NS; ++ ++ /* round down high minimum value to minimize the impact on the clock high time */ ++ cal = base_clk * spec_high_min; ++ return (u32)DIV_ROUND_DOWN_ULL(cal, HIGH_MIN_DIV); ++} ++ ++static void ast2600_i2c_ac_timing_config(struct ast2600_i2c_bus *i2c_bus) ++{ ++ unsigned long base_clk[16]; ++ int baseclk_idx = 0; ++ int divisor = 0; ++ u32 clk_div_reg; ++ u32 scl_low = 0; ++ u32 scl_high = 0; ++ u32 scl_high_min = 0; ++ u32 sda_data_hold = 0; ++ u32 data = 0; ++ ++ /* Check the maxmum i2c bus frequency */ ++ if (i2c_bus->timing_info.bus_freq_hz > I2C_MAX_ULTRA_FAST_MODE_FREQ) { ++ dev_err(i2c_bus->dev, "The frequency over spec. Set to 100KHz.\n"); ++ i2c_bus->timing_info.bus_freq_hz = AST_DEFAULT_AC_TIMING; ++ } ++ ++ regmap_read(i2c_bus->global_regs, AST2600_I2CG_CLK_DIV_CTRL, &clk_div_reg); ++ ++ for (int i = 0; i < ARRAY_SIZE(base_clk); i++) { ++ if (i == 0) ++ base_clk[i] = i2c_bus->apb_clk; ++ else if (i < 5) ++ base_clk[i] = (i2c_bus->apb_clk * 2) / ++ (((clk_div_reg >> ((i - 1) * 8)) & GENMASK(7, 0)) + 2); ++ else ++ base_clk[i] = base_clk[4] >> (i - 4); ++ if ((base_clk[i] / i2c_bus->timing_info.bus_freq_hz) <= 32) { ++ baseclk_idx = i; ++ divisor = DIV_ROUND_UP(base_clk[i], i2c_bus->timing_info.bus_freq_hz); ++ /* calculate the high min value */ ++ scl_high_min = i2c_cal_high_min_config(i2c_bus, (u64)base_clk[i]); ++ dev_dbg(i2c_bus->dev, "scl_high_min: %d\n", scl_high_min); ++ break; ++ } ++ } ++ ++ baseclk_idx = min(baseclk_idx, 15); ++ divisor = min(divisor, 32); ++ scl_low = min(divisor * 9 / 16 - 1, 15); ++ scl_high = (divisor - scl_low - 2) & GENMASK(3, 0); ++ ++ if (i2c_bus->manual_min_high) { ++ if (i2c_bus->manual_min_high > scl_high) ++ dev_info(i2c_bus->dev, "invalid manual high min: %d\n", ++ i2c_bus->manual_min_high); ++ else ++ scl_high_min = i2c_bus->manual_min_high; ++ } ++ ++ if (i2c_bus->manual_data_hold) { ++ if (i2c_bus->manual_data_hold > DATAHOLD_MAX_LEVEL) ++ dev_info(i2c_bus->dev, "invalid manual data hold: %d\n", ++ i2c_bus->manual_data_hold); ++ else ++ sda_data_hold = i2c_bus->manual_data_hold; ++ } ++ ++ data = baseclk_idx; ++ data |= scl_high_min << 20 | scl_high << 16 | scl_low << 12 | sda_data_hold << 10; ++ ++ if (i2c_bus->timeout) { ++ i2c_bus->timeout = min(i2c_bus->timeout, 31); ++ data |= AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); ++ data |= AST2600_I2CC_TOUTBASECLK(AST2600_I2C_TIMEOUT_CLK); ++ } ++ dev_dbg(i2c_bus->dev, "ac: 0x%x\n", data); ++ ++ writel(data, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++} ++ ++static void ast2700_i2c_ac_timing_config(struct ast2600_i2c_bus *i2c_bus) ++{ ++ unsigned long base_clk; ++ int baseclk_idx = 0; ++ int divisor = 0; ++ u32 clk_div_reg = 0; ++ u32 scl_low = 0; ++ u32 scl_high = 0; ++ u32 scl_high_min = 0; ++ u32 sda_data_hold = 0; ++ u32 data = 0; ++ u8 divid_term = 0; ++ ++ /* Check the maxmum i2c bus frequency */ ++ /* The i2c minmum ac-timing is 12KHz */ ++ if (i2c_bus->timing_info.bus_freq_hz > I2C_MAX_ULTRA_FAST_MODE_FREQ) { ++ dev_err(i2c_bus->dev, "The frequency over spec. Set to 100KHz.\n"); ++ i2c_bus->timing_info.bus_freq_hz = AST_DEFAULT_AC_TIMING; ++ } else if (i2c_bus->timing_info.bus_freq_hz < AST2700_MIN_AC_TIMING) { ++ dev_err(i2c_bus->dev, "The frequency could not be lower than 12KHz.\n"); ++ i2c_bus->timing_info.bus_freq_hz = AST2700_MIN_AC_TIMING; ++ } ++ ++ regmap_read(i2c_bus->global_regs, AST2600_I2CG_CLK_DIV_CTRL, &clk_div_reg); ++ ++ /* Find the most used ac-timing */ ++ for (int i = 0; i < 3; i++) { ++ divid_term = ((clk_div_reg >> (i << 3)) & GENMASK(7, 0)); ++ base_clk = (i2c_bus->apb_clk) / (divid_term + 1); ++ if ((base_clk / i2c_bus->timing_info.bus_freq_hz) <= 32) { ++ baseclk_idx = divid_term; ++ divisor = DIV_ROUND_UP(base_clk, i2c_bus->timing_info.bus_freq_hz); ++ break; ++ } ++ } ++ ++ /* Can't find a ac-timing then search a fitting one */ ++ if (baseclk_idx == 0) { ++ for (int i = 0; i < 0x100; i++) { ++ base_clk = (i2c_bus->apb_clk) / (i + 1); ++ if ((base_clk / i2c_bus->timing_info.bus_freq_hz) <= 32) { ++ baseclk_idx = i; ++ divisor = DIV_ROUND_UP(base_clk, i2c_bus->timing_info.bus_freq_hz); ++ break; ++ } ++ } ++ } ++ ++ /* calculate the high min value */ ++ scl_high_min = i2c_cal_high_min_config(i2c_bus, (u64)base_clk); ++ dev_dbg(i2c_bus->dev, "scl_high_min: %d\n", scl_high_min); ++ ++ baseclk_idx = min(baseclk_idx, 0xff); ++ divisor = min(divisor, 32); ++ scl_low = min((DIV_ROUND_UP(divisor * 9, 16)) - 1, 15); ++ scl_high = (divisor - scl_low - 2) & GENMASK(3, 0); ++ ++ /* fill manual min high value */ ++ if (i2c_bus->manual_min_high) { ++ if (i2c_bus->manual_min_high > scl_high) ++ dev_info(i2c_bus->dev, ++ "invalid manual high min: %d\n", i2c_bus->manual_min_high); ++ else ++ scl_high_min = i2c_bus->manual_min_high; ++ } ++ ++ /* fill manual sda hold value */ ++ if (i2c_bus->manual_data_hold) { ++ if (i2c_bus->manual_data_hold > DATAHOLD_MAX_LEVEL) ++ dev_info(i2c_bus->dev, ++ "invalid manual data hold: %d\n", i2c_bus->manual_data_hold); ++ else ++ sda_data_hold = i2c_bus->manual_data_hold; ++ } ++ ++ data = baseclk_idx; ++ data |= scl_high_min << 20 | scl_high << 16 | scl_low << 12 | sda_data_hold << 10; ++ ++ if (i2c_bus->timeout) { ++ i2c_bus->timeout = min(i2c_bus->timeout, 255); ++ writel(MSIC_I2C_SET_TIMEOUT(i2c_bus->timeout, 0), ++ i2c_bus->reg_base + MSIC_CONFIG_ACTIMING1); ++ /* timeout_base set as 1ms */ ++ data |= AST2600_I2CC_TOUTBASECLK(AST2700_I2C_TIMEOUT_CLK); ++ } ++ dev_dbg(i2c_bus->dev, "ac: 0x%x\n", data); ++ ++ writel(data, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++} ++ ++static int ast2600_i2c_recover_bus(struct ast2600_i2c_bus *i2c_bus) ++{ ++ u32 state = readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); ++ u32 ctrl; ++ int ret = 0; ++ int r; ++ ++ dev_dbg(i2c_bus->dev, "%d-bus recovery bus [%x]\n", i2c_bus->adap.nr, state); ++ ++ /* reset i2c controller to avoid the bus getting stuck */ ++ ctrl = readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ writel(ctrl & ~AST2600_I2CC_MASTER_EN, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ ++ reinit_completion(&i2c_bus->cmd_complete); ++ i2c_bus->cmd_err = 0; ++ ++ /* Check SDA/SCL status in the status register. */ ++ state = readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); ++ if (!(state & AST2600_I2CC_SDA_LINE_STS) && (state & AST2600_I2CC_SCL_LINE_STS)) { ++ writel(AST2600_I2CM_RECOVER_CMD_EN, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ r = wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->adap.timeout); ++ if (r == 0) { ++ dev_dbg(i2c_bus->dev, "recovery timed out\n"); ++ return -ETIMEDOUT; ++ } else if (i2c_bus->cmd_err) { ++ dev_dbg(i2c_bus->dev, "recovery error\n"); ++ ret = -EPROTO; ++ } ++ } ++ ++ /* Recovery done */ ++ state = readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); ++ if (state & AST2600_I2CC_BUS_BUSY_STS) { ++ dev_dbg(i2c_bus->dev, "Can't recover bus [%x]\n", state); ++ ret = -EPROTO; ++ } ++ ++ return ret; ++} ++ ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++static void ast2700_i2c_get_target(struct ast2600_i2c_bus *i2c_bus, u8 addr) ++{ ++ u8 i = 0; ++ bool target_find = false; ++ ++ /* find target by address */ ++ for (i = 0; i < AST2600_I2C_TARGET_COUNT; i++) { ++ if (i2c_bus->multi_target[i]) { ++ if (i2c_bus->multi_target[i]->addr == addr) { ++ dev_dbg(i2c_bus->dev, "address [%x] on %d\n", addr, i); ++ i2c_bus->target = i2c_bus->multi_target[i]; ++ target_find = true; ++ } ++ } ++ } ++ ++ if (!target_find) ++ dev_err(i2c_bus->dev, "address [%x] could not find\n", addr); ++} ++ ++static void ast2700_i2c_target_packet_dma_irq(struct ast2600_i2c_bus *i2c_bus, u32 isr) ++{ ++ int target_rx_len = 0; ++ u32 cmd = 0; ++ u8 value; ++ int i; ++ u32 sirq_log; ++ u32 sts; ++ ++ writel(AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_WAIT_RX_DMA, ++ i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ ++ sts = isr & ~(AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_ADDR_NAK_MASK); ++ /* Handle i2c target timeout condition */ ++ if (sts & AST2600_I2CS_INACTIVE_TO) { ++ dev_dbg(i2c_bus->dev, "The target timeout occurs isr: 0x%08x.\n", isr); ++ /* Reset timeout counter */ ++ u32 ac_timing = readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & ++ AST2600_I2CC_AC_TIMING_MASK; ++ ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ ac_timing |= AST2700_I2CC_TTIMEOUT(i2c_bus->timeout); ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ /* clear sirq log */ ++ while ((sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG))) { ++ /* assign the target client*/ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, ++ sirq_log >> SLAVE_ADDR_SHIFT); ++ } ++ }; ++ writel(isr, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ } ++ return; ++ } ++ ++ if (sts & AST2600_I2CS_ABNOR_STOP) { ++ dev_err(i2c_bus->dev, "The target abnomal protocol occurs isr: 0x%08x.\n", isr); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ /* clear sirq log */ ++ while ((sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG))) { ++ /* assign the target client*/ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, ++ sirq_log >> SLAVE_ADDR_SHIFT); ++ } ++ }; ++ writel(isr, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ } ++ return; ++ } ++ ++ sts &= ~(AST2600_I2CS_PKT_DONE | AST2600_I2CS_PKT_ERROR); ++ ++ switch (sts) { ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_RX_DMA | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ writel(AST2600_I2CS_SLAVE_MATCH, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ /* bug workaround */ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ } ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_SLAVE_MATCH | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ /* workaround: false alarm target match check */ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ } ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ /* workaround new target match */ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ } ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP | AST2600_I2CS_SLAVE_MATCH: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ /* workaround: not clear target match due to wait next isr check tx or rx */ ++ isr &= ~AST2600_I2CS_SLAVE_MATCH; ++ break; ++ case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_TX_NAK | ++ AST2600_I2CS_STOP | AST2600_I2CS_SLAVE_MATCH: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_TX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_TX_ACK | AST2600_I2CS_WAIT_TX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_SLAVE_MATCH | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_TX_DMA | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ writel(AST2600_I2CS_SLAVE_MATCH, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_TX_DMA | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ writel(AST2600_I2CS_SLAVE_MATCH, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ default: ++ dev_dbg(i2c_bus->dev, "unhandled target isr case %x, sts %x\n", sts, ++ readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); ++ ++ /* clear sirq log */ ++ while (readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG)) ++ ; ++ break; ++ } ++ ++ if (cmd) ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ ++ writel(isr, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++} ++ ++static void ast2700_i2c_target_packet_buff_irq(struct ast2600_i2c_bus *i2c_bus, u32 isr) ++{ ++ int target_rx_len = 0; ++ u32 cmd = 0; ++ u8 value; ++ int i; ++ u32 sirq_log; ++ u32 sts; ++ ++ writel(AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_WAIT_RX_DMA, ++ i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ ++ sts = isr & ~(AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_ADDR_NAK_MASK); ++ /* Handle i2c target timeout condition */ ++ if (sts & AST2600_I2CS_INACTIVE_TO) { ++ dev_dbg(i2c_bus->dev, "The target timeout occurs isr: 0x%08x.\n", isr); ++ /* Reset timeout counter */ ++ u32 ac_timing = readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & ++ AST2600_I2CC_AC_TIMING_MASK; ++ ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ ac_timing |= AST2700_I2CC_TTIMEOUT(i2c_bus->timeout); ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ writel(TARGET_TRIGGER_CMD, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ /* clear sirq log */ ++ while ((sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG))) { ++ /* assign the target client*/ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, ++ sirq_log >> SLAVE_ADDR_SHIFT); ++ } ++ }; ++ writel(isr, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ } ++ i2c_bus->target_operate = 0; ++ return; ++ } ++ ++ if (sts & AST2600_I2CS_ABNOR_STOP) { ++ dev_err(i2c_bus->dev, "The target abnomal protocol occurs isr: 0x%08x.\n", isr); ++ writel(TARGET_TRIGGER_CMD, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ /* clear sirq log */ ++ while ((sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG))) { ++ /* assign the target client*/ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, ++ sirq_log >> SLAVE_ADDR_SHIFT); ++ } ++ }; ++ writel(isr, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ } ++ i2c_bus->target_operate = 0; ++ return; ++ } ++ ++ if (sts & AST2600_I2CS_SLAVE_MATCH) ++ i2c_bus->target_operate = 1; ++ ++ sts &= ~(AST2600_I2CS_PKT_DONE | AST2600_I2CS_PKT_ERROR); ++ dev_dbg(i2c_bus->dev, "sts %x\n", sts); ++ switch (sts) { ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_RX_DMA | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ writel(AST2600_I2CS_SLAVE_MATCH, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ /* bug workaround */ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ } ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_SLAVE_MATCH | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ /* workaround: false alarm target match check */ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ } ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ /* workaround new target match */ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ } ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP | AST2600_I2CS_SLAVE_MATCH: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ /* workaround: not clear target match due to wait next isr check tx or rx */ ++ isr &= ~AST2600_I2CS_SLAVE_MATCH; ++ break; ++ case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_TX_NAK | ++ AST2600_I2CS_STOP | AST2600_I2CS_SLAVE_MATCH: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_TX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base + 0x20); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_RX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_TX_ACK | AST2600_I2CS_WAIT_TX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, &value); ++ writeb(value, i2c_bus->buf_base + 0x20); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_SLAVE_MATCH | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base + 0x20); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base + 0x20); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_TX_DMA | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ writel(AST2600_I2CS_SLAVE_MATCH, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base + 0x20); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_SADDR_PENDING | AST2600_I2CS_WAIT_TX_DMA | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ writel(AST2600_I2CS_SLAVE_MATCH, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base + 0x20); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base + 0x20); ++ } ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_RX_DONE: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ /* workaround: false alarm target match check */ ++ if (sirq_log & SADDR_HIT) { ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ } ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_STOP: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (i2c_bus->target) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target = NULL; ++ break; ++ case AST2600_I2CS_WAIT_RX_DMA: ++ sirq_log = readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG); ++ if (!i2c_bus->target) ++ ast2700_i2c_get_target(i2c_bus, sirq_log >> SLAVE_ADDR_SHIFT); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ if (i2c_bus->target) { ++ value = readb(i2c_bus->buf_base + 0x30 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ default: ++ dev_err(i2c_bus->dev, "unhandled target isr case %x, isr %x, sts %x\n", sts, ++ isr, ++ readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); ++ ++ /* clear sirq log */ ++ while (readl(i2c_bus->reg_base + AST2700_I2CC_SIRQ_LOG)) ++ ; ++ break; ++ } ++ ++ if (cmd) ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ ++ writel(isr, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ ++ if ((sts & AST2600_I2CS_STOP) && !(sts & AST2600_I2CS_SLAVE_PENDING)) ++ i2c_bus->target_operate = 0; ++ else ++ i2c_bus->previous_idx = AST2600_I2CS_GET_TARGET(sts); ++} ++ ++static void ast2600_i2c_target_packet_dma_irq(struct ast2600_i2c_bus *i2c_bus, u32 sts) ++{ ++ int target_rx_len = 0; ++ u32 cmd = 0; ++ u8 value; ++ int i; ++ ++ sts &= ~(AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_SADDR_PENDING ++ | AST2600_I2CS_ADDR_NAK_MASK); ++ ++ i2c_bus->target = i2c_bus->multi_target[AST2600_I2CS_GET_TARGET(sts)]; ++ ++ /* Handle i2c target timeout condition */ ++ if (sts & AST2600_I2CS_INACTIVE_TO) { ++ /* Reset time out counter */ ++ u32 ac_timing = readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & ++ AST2600_I2CC_AC_TIMING_MASK; ++ ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ ac_timing |= AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ /* set rx dma length ,re-send target trigger command and clear irq status */ ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ writel(TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN, ++ i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ return; ++ } ++ ++ sts &= ~(AST2600_I2CS_PKT_DONE | AST2600_I2CS_PKT_ERROR | AST2600_I2CS_ADDR_INDICATE_MASK); ++ ++ switch (sts) { ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA: ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_RX_DMA: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_STOP: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE_NAK | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_RX_DMA | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_RX_DONE_NAK | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_STOP: ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA: ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ if (sts & AST2600_I2CS_SLAVE_MATCH) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ if (sts & AST2600_I2CS_STOP) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ ++ /* it is Mw data Mr coming -> it need send tx */ ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_TX_DMA: ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_TX_DMA: ++ /* it should be repeat start read */ ++ if (sts & AST2600_I2CS_SLAVE_MATCH) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ ++ target_rx_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CS_DMA_LEN_STS)); ++ for (i = 0; i < target_rx_len; i++) { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, ++ &i2c_bus->target_dma_buf[i]); ++ } ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_TX_DMA: ++ /* First Start read */ ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, ++ &i2c_bus->target_dma_buf[0]); ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA: ++ /* it should be next start read */ ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, ++ &i2c_bus->target_dma_buf[0]); ++ writel(AST2600_I2CS_SET_TX_DMA_LEN(1), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; ++ break; ++ case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP | AST2600_I2CS_SLAVE_MATCH: ++ case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP: ++ /* it just tx complete */ ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ break; ++ case AST2600_I2CS_STOP: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ break; ++ default: ++ dev_dbg(i2c_bus->dev, "unhandled target isr case %x, sts %x\n", sts, ++ readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); ++ break; ++ } ++ ++ if (cmd) ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ ++ writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++} ++ ++static void ast2600_i2c_target_packet_buff_irq(struct ast2600_i2c_bus *i2c_bus, u32 sts) ++{ ++ int target_rx_len = 0; ++ u32 cmd = 0; ++ u8 value; ++ int i; ++ ++ /* due to controller target is common buffer, need force the master stop not issue */ ++ if (readl(i2c_bus->reg_base + AST2600_I2CM_CMD_STS) & GENMASK(15, 0)) { ++ writel(0, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ i2c_bus->cmd_err = -EBUSY; ++ writel(0, i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ complete(&i2c_bus->cmd_complete); ++ } ++ ++ i2c_bus->target = i2c_bus->multi_target[AST2600_I2CS_GET_TARGET(sts)]; ++ ++ /* Handle i2c target timeout condition */ ++ if (sts & AST2600_I2CS_INACTIVE_TO) { ++ /* Reset timeout counter */ ++ u32 ac_timing = readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & ++ AST2600_I2CC_AC_TIMING_MASK; ++ ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ ac_timing |= AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ /* Re-send target trigger command and clear irq */ ++ writel(TARGET_TRIGGER_CMD, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target_operate = 0; ++ return; ++ } ++ ++ sts &= ~(AST2600_I2CS_PKT_DONE | AST2600_I2CS_PKT_ERROR | AST2600_I2CS_ADDR_INDICATE_MASK); ++ ++ if (sts & AST2600_I2CS_SLAVE_MATCH) ++ i2c_bus->target_operate = 1; ++ ++ switch (sts) { ++ case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_WAIT_RX_DMA | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_SLAVE_PENDING | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ case AST2600_I2CS_SLAVE_PENDING | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_STOP: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ fallthrough; ++ case AST2600_I2CS_SLAVE_PENDING | ++ AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ cmd = TARGET_TRIGGER_CMD; ++ if (sts & AST2600_I2CS_RX_DONE) { ++ target_rx_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ for (i = 0; i < target_rx_len; i++) { ++ value = readb(i2c_bus->buf_base + 0x10 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ } ++ if (readl(i2c_bus->reg_base + AST2600_I2CS_CMD_STS) & AST2600_I2CS_RX_BUFF_EN) ++ cmd = 0; ++ else ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_BUFF_EN; ++ ++ writel(AST2600_I2CC_SET_RX_BUF_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ break; ++ case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_RX_DONE: ++ cmd = TARGET_TRIGGER_CMD; ++ target_rx_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ for (i = 0; i < target_rx_len; i++) { ++ value = readb(i2c_bus->buf_base + 0x10 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ cmd |= AST2600_I2CS_RX_BUFF_EN; ++ writel(AST2600_I2CC_SET_RX_BUF_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ break; ++ case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_WAIT_RX_DMA | ++ AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ cmd = TARGET_TRIGGER_CMD; ++ target_rx_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ for (i = 0; i < target_rx_len; i++) { ++ value = readb(i2c_bus->buf_base + 0x10 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ cmd |= AST2600_I2CS_RX_BUFF_EN; ++ writel(AST2600_I2CC_SET_RX_BUF_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ break; ++ case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ cmd = TARGET_TRIGGER_CMD; ++ target_rx_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ for (i = 0; i < target_rx_len; i++) { ++ value = readb(i2c_bus->buf_base + 0x10 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ /* workaround for avoid next start with len != 0 */ ++ writel(BIT(0), i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ break; ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: ++ cmd = TARGET_TRIGGER_CMD; ++ target_rx_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ for (i = 0; i < target_rx_len; i++) { ++ value = readb(i2c_bus->buf_base + 0x10 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ /* workaround for avoid next start with len != 0 */ ++ writel(BIT(0), i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ break; ++ case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_RX_DONE | ++ AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_STOP: ++ target_rx_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ for (i = 0; i < target_rx_len; i++) { ++ value = readb(i2c_bus->buf_base + 0x10 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base); ++ break; ++ case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_SLAVE_MATCH: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ writeb(value, i2c_bus->buf_base); ++ writel(AST2600_I2CC_SET_TX_BUF_LEN(1), ++ i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_BUFF_EN; ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_RX_DONE: ++ case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_RX_DONE: ++ case AST2600_I2CS_WAIT_TX_DMA: ++ /* it should be repeat start read */ ++ if (sts & AST2600_I2CS_SLAVE_MATCH) ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ ++ if (sts & AST2600_I2CS_RX_DONE) { ++ target_rx_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ for (i = 0; i < target_rx_len; i++) { ++ value = readb(i2c_bus->buf_base + 0x10 + i); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); ++ } ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); ++ } else { ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, &value); ++ } ++ writeb(value, i2c_bus->buf_base); ++ writel(AST2600_I2CC_SET_TX_BUF_LEN(1), ++ i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_TX_BUFF_EN; ++ break; ++ /* workaround : trigger the cmd twice to fix next state keep 1000000 */ ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ cmd = TARGET_TRIGGER_CMD | AST2600_I2CS_RX_BUFF_EN; ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ break; ++ /* the pending slave needs to be cleared with TX_NAK and STOP here */ ++ /* other flags will be handled in the next irq callback */ ++ /* the slave index will be updated when the slave match occurs */ ++ /* use the pervious idx to do the slave stop event */ ++ case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: ++ case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP | ++ AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA: ++ cmd = TARGET_TRIGGER_CMD; ++ i2c_bus->target = i2c_bus->multi_target[i2c_bus->previous_idx]; ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target_operate = 0; ++ break; ++ case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP: ++ case AST2600_I2CS_STOP: ++ cmd = TARGET_TRIGGER_CMD; ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ break; ++ default: ++ dev_dbg(i2c_bus->dev, "unhandled target isr case %x, sts %x\n", sts, ++ readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); ++ break; ++ } ++ ++ if (cmd) ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ ++ if ((sts & AST2600_I2CS_STOP) && !(sts & AST2600_I2CS_SLAVE_PENDING)) ++ i2c_bus->target_operate = 0; ++ else ++ i2c_bus->previous_idx = AST2600_I2CS_GET_TARGET(sts); ++} ++ ++static void ast2600_i2c_target_byte_irq(struct ast2600_i2c_bus *i2c_bus, u32 sts) ++{ ++ u32 i2c_buff = readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); ++ u32 cmd = AST2600_I2CS_ACTIVE_ALL; ++ u8 byte_data; ++ u8 value; ++ ++ i2c_bus->target = i2c_bus->multi_target[AST2600_I2CS_GET_TARGET(sts)]; ++ ++ /* Handle i2c target timeout condition */ ++ if (sts & AST2600_I2CS_INACTIVE_TO) { ++ /* Reset time out counter */ ++ u32 ac_timing = readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & ++ AST2600_I2CC_AC_TIMING_MASK; ++ ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ ac_timing |= AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); ++ writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); ++ /* Re-send target trigger command and clear irq */ ++ writel(AST2600_I2CS_ACTIVE_ALL, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ writel(sts, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ i2c_bus->target_operate = 0; ++ return; ++ } ++ ++ sts &= ~(AST2600_I2CS_ADDR_INDICATE_MASK); ++ ++ switch (sts) { ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); ++ /* first address match is address */ ++ byte_data = AST2600_I2CC_GET_RX_BUFF(i2c_buff); ++ break; ++ case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA: ++ byte_data = AST2600_I2CC_GET_RX_BUFF(i2c_buff); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &byte_data); ++ break; ++ case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_TX_DMA: ++ cmd |= AST2600_I2CS_TX_CMD; ++ byte_data = AST2600_I2CC_GET_RX_BUFF(i2c_buff); ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &byte_data); ++ writel(byte_data, i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); ++ break; ++ case AST2600_I2CS_TX_ACK | AST2600_I2CS_WAIT_TX_DMA: ++ cmd |= AST2600_I2CS_TX_CMD; ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, &byte_data); ++ writel(byte_data, i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); ++ break; ++ case AST2600_I2CS_STOP: ++ case AST2600_I2CS_STOP | AST2600_I2CS_TX_NAK: ++ i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); ++ break; ++ default: ++ dev_dbg(i2c_bus->dev, "unhandled pkt isr %x\n", sts); ++ break; ++ } ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ writel(sts, i2c_bus->reg_base + AST2600_I2CS_ISR); ++ readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++} ++ ++static int ast2600_i2c_target_irq(struct ast2600_i2c_bus *i2c_bus) ++{ ++ u32 ier = readl(i2c_bus->reg_base + AST2600_I2CS_IER); ++ u32 isr = readl(i2c_bus->reg_base + AST2600_I2CS_ISR); ++ ++ if (!(isr & ier)) ++ return 0; ++ ++ /* ++ * Target interrupt coming after Controller packet done ++ * So need handle controller first. ++ * avoid case: Mr Dr P -> S match ++ */ ++ if (readl(i2c_bus->reg_base + AST2600_I2CM_ISR) & AST2600_I2CM_PKT_DONE) ++ return 0; ++ ++ isr &= ~(AST2600_I2CS_ADDR_MASK); ++ ++ if (AST2600_I2CS_PKT_DONE & isr) ++ i2c_bus->target_packet_irq(i2c_bus, isr); ++ else ++ i2c_bus->target_byte_irq(i2c_bus, isr); ++ ++ return 1; ++} ++#endif ++ ++static int ast2600_i2c_setup_dma_tx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len = msg->len - i2c_bus->controller_xfer_cnt; ++ ++ cmd |= AST2600_I2CM_PKT_EN; ++ ++ if (xfer_len > AST2600_I2C_DMA_SIZE) ++ xfer_len = AST2600_I2C_DMA_SIZE; ++ else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) ++ cmd |= AST2600_I2CM_STOP_CMD; ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ if (xfer_len) { ++ memcpy(i2c_bus->controller_dma_buf, msg->buf, xfer_len); ++ cmd |= AST2600_I2CM_TX_DMA_EN | AST2600_I2CM_TX_CMD; ++ writel(AST2600_I2CM_SET_TX_DMA_LEN(xfer_len - 1), ++ i2c_bus->reg_base + AST2600_I2CM_DMA_LEN); ++ } ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2700_i2c_setup_buff_tx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len = msg->len - i2c_bus->controller_xfer_cnt; ++ ++ cmd |= AST2600_I2CM_PKT_EN; ++ ++ if (xfer_len > i2c_bus->buf_size) ++ xfer_len = i2c_bus->buf_size; ++ else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) ++ cmd |= AST2600_I2CM_STOP_CMD; ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ if (xfer_len) { ++ u32 wbuf_dword; ++ ++ cmd |= AST2600_I2CM_TX_DMA_EN | AST2600_I2CM_TX_CMD; ++ /* ++ * The controller's buffer register supports dword writes only. ++ * Therefore, write dwords to the buffer register in a 4-byte aligned, ++ * and write the remaining unaligned data at the end. ++ */ ++ for (int i = 0; i < xfer_len; i += 4) { ++ int xfer_cnt = i2c_bus->controller_xfer_cnt + i; ++ ++ switch (min(xfer_len - i, 4) % 4) { ++ case 1: ++ wbuf_dword = msg->buf[xfer_cnt]; ++ break; ++ case 2: ++ wbuf_dword = get_unaligned_le16(&msg->buf[xfer_cnt]); ++ break; ++ case 3: ++ wbuf_dword = get_unaligned_le24(&msg->buf[xfer_cnt]); ++ break; ++ default: ++ wbuf_dword = get_unaligned_le32(&msg->buf[xfer_cnt]); ++ break; ++ } ++ writel(wbuf_dword, i2c_bus->buf_base + i); ++ } ++ ++ dev_dbg(i2c_bus->dev, "len : %d\n", xfer_len); ++ writel(AST2600_I2CM_SET_TX_DMA_LEN(xfer_len - 1), ++ i2c_bus->reg_base + AST2600_I2CM_DMA_LEN); ++ } ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2600_i2c_setup_buff_tx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len = msg->len - i2c_bus->controller_xfer_cnt; ++ ++ cmd |= AST2600_I2CM_PKT_EN; ++ ++ if (xfer_len > i2c_bus->buf_size) ++ xfer_len = i2c_bus->buf_size; ++ else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) ++ cmd |= AST2600_I2CM_STOP_CMD; ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ if (xfer_len) { ++ u32 wbuf_dword; ++ ++ cmd |= AST2600_I2CM_TX_BUFF_EN | AST2600_I2CM_TX_CMD; ++ /* ++ * The controller's buffer register supports dword writes only. ++ * Therefore, write dwords to the buffer register in a 4-byte aligned, ++ * and write the remaining unaligned data at the end. ++ */ ++ if (readl(i2c_bus->reg_base + AST2600_I2CS_ISR)) ++ return -EBUSY; ++ for (int i = 0; i < xfer_len; i += 4) { ++ int xfer_cnt = i2c_bus->controller_xfer_cnt + i; ++ ++ switch (min(xfer_len - i, 4) % 4) { ++ case 1: ++ wbuf_dword = msg->buf[xfer_cnt]; ++ break; ++ case 2: ++ wbuf_dword = get_unaligned_le16(&msg->buf[xfer_cnt]); ++ break; ++ case 3: ++ wbuf_dword = get_unaligned_le24(&msg->buf[xfer_cnt]); ++ break; ++ default: ++ wbuf_dword = get_unaligned_le32(&msg->buf[xfer_cnt]); ++ break; ++ } ++ writel(wbuf_dword, i2c_bus->buf_base + i); ++ } ++ if (readl(i2c_bus->reg_base + AST2600_I2CS_ISR)) ++ return -EBUSY; ++ writel(AST2600_I2CC_SET_TX_BUF_LEN(xfer_len), ++ i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ } ++ ++ if (readl(i2c_bus->reg_base + AST2600_I2CS_ISR)) ++ return -EBUSY; ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2600_i2c_setup_byte_tx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len; ++ ++ xfer_len = msg->len - i2c_bus->controller_xfer_cnt; ++ ++ cmd |= AST2600_I2CM_PKT_EN; ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ if ((i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) && ++ ((i2c_bus->controller_xfer_cnt + 1) == msg->len)) ++ cmd |= AST2600_I2CM_STOP_CMD; ++ ++ if (xfer_len) { ++ cmd |= AST2600_I2CM_TX_CMD; ++ writel(msg->buf[i2c_bus->controller_xfer_cnt], ++ i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); ++ } ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2600_i2c_setup_dma_rx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len = msg->len - i2c_bus->controller_xfer_cnt; ++ ++ cmd |= AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_DMA_EN | AST2600_I2CM_RX_CMD; ++ ++ if (msg->flags & I2C_M_RECV_LEN) { ++ dev_dbg(i2c_bus->dev, "smbus read\n"); ++ xfer_len = 1; ++ } else if (xfer_len > AST2600_I2C_DMA_SIZE) { ++ xfer_len = AST2600_I2C_DMA_SIZE; ++ } else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) { ++ cmd |= CONTROLLER_TRIGGER_LAST_STOP; ++ } ++ ++ writel(AST2600_I2CM_SET_RX_DMA_LEN(xfer_len - 1), i2c_bus->reg_base + AST2600_I2CM_DMA_LEN); ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2700_i2c_setup_buff_rx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len = msg->len - i2c_bus->controller_xfer_cnt; ++ ++ cmd |= AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_DMA_EN | AST2600_I2CM_RX_CMD; ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ if (msg->flags & I2C_M_RECV_LEN) { ++ dev_dbg(i2c_bus->dev, "smbus read\n"); ++ xfer_len = 1; ++ } else if (xfer_len > i2c_bus->buf_size) { ++ xfer_len = i2c_bus->buf_size; ++ } else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) { ++ cmd |= CONTROLLER_TRIGGER_LAST_STOP; ++ } ++ ++ writel(AST2600_I2CM_SET_RX_DMA_LEN(xfer_len - 1), ++ i2c_bus->reg_base + AST2600_I2CM_DMA_LEN); ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2600_i2c_setup_buff_rx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len = msg->len - i2c_bus->controller_xfer_cnt; ++ ++ cmd |= AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_BUFF_EN | AST2600_I2CM_RX_CMD; ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ if (msg->flags & I2C_M_RECV_LEN) { ++ dev_dbg(i2c_bus->dev, "smbus read\n"); ++ xfer_len = 1; ++ } else if (xfer_len > i2c_bus->buf_size) { ++ xfer_len = i2c_bus->buf_size; ++ } else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) { ++ cmd |= CONTROLLER_TRIGGER_LAST_STOP; ++ } ++ ++ writel(AST2600_I2CC_SET_RX_BUF_LEN(xfer_len), i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2600_i2c_setup_byte_rx(u32 cmd, struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ ++ cmd |= AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_CMD; ++ ++ if (cmd & AST2600_I2CM_START_CMD) ++ cmd |= AST2600_I2CM_PKT_ADDR(msg->addr); ++ ++ if (msg->flags & I2C_M_RECV_LEN) { ++ dev_dbg(i2c_bus->dev, "smbus read\n"); ++ } else if ((i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) && ++ ((i2c_bus->controller_xfer_cnt + 1) == msg->len)) { ++ cmd |= CONTROLLER_TRIGGER_LAST_STOP; ++ } ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); ++ ++ return 0; ++} ++ ++static int ast2600_i2c_do_start(struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ ++ /* send start */ ++ dev_dbg(i2c_bus->dev, "[%d] %s %d byte%s %s 0x%02x\n", ++ i2c_bus->msgs_index, str_read_write(msg->flags & I2C_M_RD), ++ msg->len, str_plural(msg->len), ++ msg->flags & I2C_M_RD ? "from" : "to", msg->addr); ++ ++ if (!i2c_bus->setup_rx || !i2c_bus->setup_tx) ++ return -EINVAL; ++ ++ i2c_bus->controller_xfer_cnt = 0; ++ i2c_bus->buf_index = 0; ++ ++ if (msg->flags & I2C_M_RD) ++ return i2c_bus->setup_rx(AST2600_I2CM_START_CMD, i2c_bus); ++ ++ return i2c_bus->setup_tx(AST2600_I2CM_START_CMD, i2c_bus); ++} ++ ++static int ast2700_i2c_irq_err_to_errno(u32 irq_status) ++{ ++ if (irq_status & AST2700_I2CM_ABNORMAL_ACTION) ++ return -EAGAIN; ++ if (irq_status & (AST2600_I2CM_SDA_DL_TO | AST2600_I2CM_SCL_LOW_TO)) ++ return -EBUSY; ++ if (irq_status & (AST2600_I2CM_ABNORMAL)) ++ return -EPROTO; ++ ++ return 0; ++} ++ ++static void ast2700_i2c_irq_clear(struct ast2600_i2c_bus *i2c_bus, u32 sts) ++{ ++ writel(sts, i2c_bus->reg_base + AST2600_I2CM_ISR); ++} ++ ++static int ast2600_i2c_irq_err_to_errno(u32 irq_status) ++{ ++ if (irq_status & AST2600_I2CM_ARBIT_LOSS) ++ return -EAGAIN; ++ if (irq_status & (AST2600_I2CM_SDA_DL_TO | AST2600_I2CM_SCL_LOW_TO)) ++ return -ETIMEDOUT; ++ if (irq_status & (AST2600_I2CM_ABNORMAL)) ++ return -EPROTO; ++ ++ return 0; ++} ++ ++static void ast2600_i2c_irq_clear(struct ast2600_i2c_bus *i2c_bus, u32 sts) ++{ ++ writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR); ++} ++ ++static void ast2600_i2c_controller_packet_irq(struct ast2600_i2c_bus *i2c_bus, u32 sts) ++{ ++ struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index]; ++ int xfer_len; ++ int i; ++ ++ i2c_bus->irq_clear(i2c_bus, sts); ++ ++ sts &= ~(AST2600_I2CM_PKT_DONE | AST2600_I2CM_SW_ISR_MASK); ++ ++ switch (sts) { ++ case AST2600_I2CM_PKT_ERROR: ++ i2c_bus->cmd_err = -EAGAIN; ++ complete(&i2c_bus->cmd_complete); ++ break; ++ case AST2600_I2CM_PKT_ERROR | AST2600_I2CM_TX_NAK: /* a0 fix for issue */ ++ fallthrough; ++ case AST2600_I2CM_PKT_ERROR | AST2600_I2CM_TX_NAK | AST2600_I2CM_NORMAL_STOP: ++ i2c_bus->cmd_err = -ENXIO; ++ complete(&i2c_bus->cmd_complete); ++ break; ++ case AST2600_I2CM_NORMAL_STOP: ++ /* write 0 byte only have stop isr */ ++ i2c_bus->msgs_index++; ++ if (i2c_bus->msgs_index < i2c_bus->msgs_count) { ++ if (ast2600_i2c_do_start(i2c_bus)) { ++ i2c_bus->cmd_err = -EBUSY; ++ complete(&i2c_bus->cmd_complete); ++ } ++ } else { ++ i2c_bus->cmd_err = i2c_bus->msgs_index; ++ complete(&i2c_bus->cmd_complete); ++ } ++ break; ++ case AST2600_I2CM_TX_ACK: ++ case AST2600_I2CM_TX_ACK | AST2600_I2CM_NORMAL_STOP: ++ if (i2c_bus->mode == DMA_MODE) { ++ xfer_len = AST2600_I2C_GET_TX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CM_DMA_LEN_STS)); ++ } else if (i2c_bus->mode == BUFF_MODE) { ++ if (i2c_bus->version == AST2600) ++ xfer_len = AST2600_I2CC_GET_TX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ else ++ xfer_len = AST2600_I2C_GET_TX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CM_DMA_LEN_STS)); ++ } else { ++ xfer_len = 1; ++ } ++ ++ i2c_bus->controller_xfer_cnt += xfer_len; ++ ++ if (i2c_bus->controller_xfer_cnt == msg->len) { ++ i2c_bus->msgs_index++; ++ if (i2c_bus->msgs_index == i2c_bus->msgs_count) { ++ i2c_bus->cmd_err = i2c_bus->msgs_index; ++ complete(&i2c_bus->cmd_complete); ++ } else { ++ if (ast2600_i2c_do_start(i2c_bus)) { ++ i2c_bus->cmd_err = -EBUSY; ++ complete(&i2c_bus->cmd_complete); ++ } ++ } ++ } else { ++ i2c_bus->setup_tx(0, i2c_bus); ++ } ++ break; ++ case AST2600_I2CM_RX_DONE: ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ /* ++ * Workaround for controller/target packet mode enable rx done stuck issue ++ * When controller go for first read (RX_DONE), target mode will also effect ++ * Then controller will send nack, not operate anymore. ++ */ ++ if (readl(i2c_bus->reg_base + AST2600_I2CS_CMD_STS) & AST2600_I2CS_PKT_MODE_EN) { ++ u32 target_cmd = readl(i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ ++ writel(0, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ writel(target_cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ } ++ fallthrough; ++#endif ++ case AST2600_I2CM_RX_DONE | AST2600_I2CM_NORMAL_STOP: ++ /* do next rx */ ++ if (i2c_bus->mode == DMA_MODE) { ++ xfer_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CM_DMA_LEN_STS)); ++ memcpy(&msg->buf[i2c_bus->controller_xfer_cnt], ++ i2c_bus->controller_dma_buf, xfer_len); ++ } else if (i2c_bus->mode == BUFF_MODE) { ++ if (i2c_bus->version == AST2600) { ++ xfer_len = AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CC_BUFF_CTRL)); ++ } else { ++ xfer_len = AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + ++ AST2600_I2CM_DMA_LEN_STS)); ++ } ++ for (i = 0; i < xfer_len; i++) ++ msg->buf[i2c_bus->controller_xfer_cnt + i] = ++ readb(i2c_bus->buf_base + 0x10 + i); ++ } else { ++ xfer_len = 1; ++ msg->buf[i2c_bus->controller_xfer_cnt] = ++ AST2600_I2CC_GET_RX_BUFF(readl(i2c_bus->reg_base + ++ AST2600_I2CC_STS_AND_BUFF)); ++ } ++ ++ if (msg->flags & I2C_M_RECV_LEN) { ++ u8 recv_len = 0; ++ ++ if (i2c_bus->version == AST2700) { ++ recv_len = AST2700_I2CC_GET_BUFF(readl(i2c_bus->reg_base ++ + BYTE_DATA_LOG)); ++ } else { ++ recv_len = AST2600_I2CC_GET_RX_BUFF(readl(i2c_bus->reg_base ++ + AST2600_I2CC_STS_AND_BUFF)); ++ } ++ ++ msg->len = min_t(unsigned int, recv_len, I2C_SMBUS_BLOCK_MAX); ++ msg->len += ((msg->flags & I2C_CLIENT_PEC) ? 2 : 1); ++ msg->flags &= ~I2C_M_RECV_LEN; ++ if (!recv_len) ++ i2c_bus->controller_xfer_cnt = 0; ++ else ++ i2c_bus->controller_xfer_cnt = 1; ++ } else { ++ i2c_bus->controller_xfer_cnt += xfer_len; ++ } ++ ++ if (i2c_bus->controller_xfer_cnt == msg->len) { ++ i2c_bus->msgs_index++; ++ if (i2c_bus->msgs_index == i2c_bus->msgs_count) { ++ i2c_bus->cmd_err = i2c_bus->msgs_index; ++ complete(&i2c_bus->cmd_complete); ++ } else { ++ if (ast2600_i2c_do_start(i2c_bus)) { ++ i2c_bus->cmd_err = -EBUSY; ++ complete(&i2c_bus->cmd_complete); ++ } ++ } ++ } else { ++ i2c_bus->setup_rx(0, i2c_bus); ++ } ++ break; ++ default: ++ dev_dbg(i2c_bus->dev, "unhandled sts %x\n", sts); ++ break; ++ } ++} ++ ++static int ast2600_i2c_controller_irq(struct ast2600_i2c_bus *i2c_bus) ++{ ++ u32 sts = readl(i2c_bus->reg_base + AST2600_I2CM_ISR); ++ u32 ier = readl(i2c_bus->reg_base + AST2600_I2CM_IER); ++ u32 ctrl; ++ ++ /* mask un-used isr bits */ ++ sts &= ~AST2600_I2CM_ISR_MASK; ++ ++ if (!i2c_bus->alert_enable) ++ sts &= ~AST2600_I2CM_SMBUS_ALERT; ++ ++ if (sts & AST2600_I2CM_BUS_RECOVER_FAIL) { ++ writel(AST2600_I2CM_BUS_RECOVER_FAIL, i2c_bus->reg_base + AST2600_I2CM_ISR); ++ ctrl = readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ i2c_bus->cmd_err = -EPROTO; ++ complete(&i2c_bus->cmd_complete); ++ return 1; ++ } ++ ++ if (sts & AST2600_I2CM_BUS_RECOVER) { ++ writel(AST2600_I2CM_BUS_RECOVER, i2c_bus->reg_base + AST2600_I2CM_ISR); ++ i2c_bus->cmd_err = 0; ++ complete(&i2c_bus->cmd_complete); ++ return 1; ++ } ++ ++ if (sts & AST2600_I2CM_SMBUS_ALERT) { ++ if (ier & AST2600_I2CM_SMBUS_ALERT) { ++ /* Disable ALT INT */ ++ writel(ier & ~AST2600_I2CM_SMBUS_ALERT, i2c_bus->reg_base + AST2600_I2CM_IER); ++ i2c_handle_smbus_alert(i2c_bus->ara); ++ writel(AST2600_I2CM_SMBUS_ALERT, i2c_bus->reg_base + AST2600_I2CM_ISR); ++ dev_err(i2c_bus->dev, ++ "ast2600_controller_alert_recv bus id %d, Disable Alt, Please Imple\n", ++ i2c_bus->adap.nr); ++ return 1; ++ } ++ } ++ ++ /* handle controller abnormal condition */ ++ if (i2c_bus->irq_err_to_errno && i2c_bus->irq_clear) { ++ i2c_bus->cmd_err = i2c_bus->irq_err_to_errno(sts); ++ if (i2c_bus->cmd_err) { ++ i2c_bus->irq_clear(i2c_bus, sts); ++ complete(&i2c_bus->cmd_complete); ++ return 1; ++ } ++ } ++ ++ if (sts & AST2600_I2CM_PKT_DONE) { ++ ast2600_i2c_controller_packet_irq(i2c_bus, sts); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static irqreturn_t ast2600_i2c_bus_irq(int irq, void *dev_id) ++{ ++ struct ast2600_i2c_bus *i2c_bus = dev_id; ++ ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ if (readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL) & AST2600_I2CC_SLAVE_EN) { ++ if (ast2600_i2c_target_irq(i2c_bus)) ++ return IRQ_HANDLED; ++ } ++#endif ++ return IRQ_RETVAL(ast2600_i2c_controller_irq(i2c_bus)); ++} ++ ++static int ast2600_i2c_controller_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) ++{ ++ struct ast2600_i2c_bus *i2c_bus = i2c_get_adapdata(adap); ++ unsigned long timeout; ++ int ret; ++ ++ if (!i2c_bus->multi_master && ++ (readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF) & AST2600_I2CC_BUS_BUSY_STS)) { ++ ret = ast2600_i2c_recover_bus(i2c_bus); ++ if (ret) ++ return ret; ++ } ++ ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ if (i2c_bus->mode == BUFF_MODE) { ++ if (i2c_bus->target_operate) ++ return -EBUSY; ++ /* disable target isr */ ++ writel(0, i2c_bus->reg_base + AST2600_I2CS_IER); ++ if (readl(i2c_bus->reg_base + AST2600_I2CS_ISR) || i2c_bus->target_operate) { ++ writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_IER); ++ return -EBUSY; ++ } ++ } ++#endif ++ ++ i2c_bus->cmd_err = 0; ++ i2c_bus->msgs = msgs; ++ i2c_bus->msgs_index = 0; ++ i2c_bus->msgs_count = num; ++ reinit_completion(&i2c_bus->cmd_complete); ++ ret = ast2600_i2c_do_start(i2c_bus); ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ /* avoid race condication target is wait and controller wait 1st target operate */ ++ if (i2c_bus->mode == BUFF_MODE) ++ writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_IER); ++#endif ++ if (ret) ++ goto controller_out; ++ timeout = wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->adap.timeout); ++ if (timeout == 0) { ++ u32 ctrl = readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ ++ dev_dbg(i2c_bus->dev, "timeout isr[%x], sts[%x]\n", ++ readl(i2c_bus->reg_base + AST2600_I2CM_ISR), ++ readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); ++ writel(ctrl & ~AST2600_I2CC_MASTER_EN, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ ++ /* ++ * A slave holding SCL low can stall the transfer and trigger ++ * a master timeout. In multi-master mode, attempt bus recovery ++ * if the bus is still busy. ++ */ ++ if (i2c_bus->multi_master && !i2c_bus->target_operate && ++ (readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF) & ++ AST2600_I2CC_BUS_BUSY_STS)) ++ ast2600_i2c_recover_bus(i2c_bus); ++ ret = -ETIMEDOUT; ++ } else { ++ ret = i2c_bus->cmd_err; ++ } ++ ++ dev_dbg(i2c_bus->dev, "bus%d-m: %d end\n", i2c_bus->adap.nr, i2c_bus->cmd_err); ++ ++controller_out: ++ return ret; ++} ++ ++static int ast2600_i2c_init(struct ast2600_i2c_bus *i2c_bus) ++{ ++ struct platform_device *pdev = to_platform_device(i2c_bus->dev); ++ u32 fun_ctrl = AST2600_I2CC_BUS_AUTO_RELEASE | AST2600_I2CC_MASTER_EN; ++ ++ /* I2C Reset */ ++ writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ ++ if (!i2c_bus->multi_master) ++ fun_ctrl |= AST2600_I2CC_MULTI_MASTER_DIS; ++ ++ /* I2C manual minimum SCL high */ ++ if (device_property_read_u32(&pdev->dev, "manual-min-high", ++ &i2c_bus->manual_min_high)) ++ i2c_bus->manual_min_high = 0; ++ ++ /* I2C manual data hold */ ++ if (device_property_read_u32(&pdev->dev, "manual-data-hold", ++ &i2c_bus->manual_data_hold)) ++ i2c_bus->manual_data_hold = 0; ++ ++ /* I2C Debounce level */ ++ if (i2c_bus->version != AST2600) { ++ if (!device_property_read_u32(&pdev->dev, "debounce-level", ++ &i2c_bus->debounce_level)) { ++ u32 debounce_level = 0; ++ ++ /* AST2700 support manual debounce setting */ ++ if (i2c_bus->version == AST2700) { ++ if (i2c_bus->debounce_level > AST2700_DEBOUNCE_LEVEL_MAX) ++ i2c_bus->debounce_level = AST2700_DEBOUNCE_LEVEL_MAX; ++ if (i2c_bus->debounce_level < AST2700_DEBOUNCE_LEVEL_MIN) ++ i2c_bus->debounce_level = AST2700_DEBOUNCE_LEVEL_MIN; ++ } ++ ++ debounce_level = readl(i2c_bus->reg_base + MSIC2_CONFIG) ++ & ~(AST2700_DEBOUNCE_MASK); ++ ++ debounce_level |= i2c_bus->debounce_level; ++ writel(debounce_level, i2c_bus->reg_base + MSIC2_CONFIG); ++ ++ fun_ctrl |= AST2700_I2CC_MANUAL_DEBOUNCE; ++ } ++ } ++ ++ /* Enable Controller Mode */ ++ writel(fun_ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ /* disable target address */ ++ writel(0, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); ++ ++ if (i2c_bus->version == AST2700 && i2c_bus->mode == BUFF_MODE) { ++ writel(0x00, i2c_bus->reg_base + AST2600_I2CM_TX_DMA); ++ writel(0x00, i2c_bus->reg_base + AST2600_I2CM_TX_DMA_H); ++ writel(0x10, i2c_bus->reg_base + AST2600_I2CM_RX_DMA); ++ writel(0x00, i2c_bus->reg_base + AST2600_I2CM_RX_DMA_H); ++ writel(0x20, i2c_bus->reg_base + AST2600_I2CS_TX_DMA); ++ writel(0x00, i2c_bus->reg_base + AST2600_I2CS_TX_DMA_H); ++ writel(0x30, i2c_bus->reg_base + AST2600_I2CS_RX_DMA); ++ writel(0x00, i2c_bus->reg_base + AST2600_I2CS_RX_DMA_H); ++ } ++ ++ /* Set AC Timing */ ++ i2c_bus->ac_timing_config(i2c_bus); ++ ++ if (i2c_bus->mode == DMA_MODE) { ++ i2c_bus->controller_dma_buf = ++ dmam_alloc_coherent(i2c_bus->dev, AST2600_I2C_DMA_SIZE, ++ &i2c_bus->controller_dma_addr, GFP_KERNEL); ++ if (!i2c_bus->controller_dma_buf) ++ return -ENOMEM; ++ writel(lower_32_bits(i2c_bus->controller_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CM_TX_DMA); ++ writel(upper_32_bits(i2c_bus->controller_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CM_TX_DMA_H); ++ writel(lower_32_bits(i2c_bus->controller_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CM_RX_DMA); ++ writel(upper_32_bits(i2c_bus->controller_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CM_RX_DMA_H); ++ } ++ ++ /* Clear Interrupt */ ++ writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CM_ISR); ++ ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ /* for memory buffer initial */ ++ if (i2c_bus->mode == DMA_MODE) { ++ i2c_bus->target_dma_buf = ++ dmam_alloc_coherent(i2c_bus->dev, I2C_TARGET_MSG_BUF_SIZE, ++ &i2c_bus->target_dma_addr, GFP_KERNEL); ++ if (!i2c_bus->target_dma_buf) ++ return -ENOMEM; ++ } ++ ++ writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CS_ISR); ++ ++ if (i2c_bus->mode == BYTE_MODE) ++ writel(GENMASK(15, 0), i2c_bus->reg_base + AST2600_I2CS_IER); ++ else ++ writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_IER); ++#endif ++ ++ return 0; ++} ++ ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++static int ast2600_i2c_reg_target(struct i2c_client *client) ++{ ++ struct ast2600_i2c_bus *i2c_bus = i2c_get_adapdata(client->adapter); ++ u32 cmd = TARGET_TRIGGER_CMD; ++ u32 target_addr = readl(i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); ++ u32 ctrl = readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ bool target_reg = false; ++ u8 i = 0; ++ ++ /* check target client input and target counts*/ ++ if (!client || i2c_bus->target_attached == AST2600_I2C_TARGET_COUNT) ++ return -EINVAL; ++ ++ /* check duplicate address */ ++ for (i = 0; i < AST2600_I2C_TARGET_COUNT; i++) { ++ if (i2c_bus->multi_target[i]) { ++ if (i2c_bus->multi_target[i]->addr == client->addr) { ++ dev_dbg(i2c_bus->dev, "duplicate address [%x] on %d\n", ++ client->addr, i); ++ return -EINVAL; ++ } ++ } ++ } ++ ++ /* assign the target address into array */ ++ for (i = 0; i < AST2600_I2C_TARGET_COUNT; i++) { ++ if (!i2c_bus->multi_target[i]) { ++ i2c_bus->multi_target[i] = client; ++ target_reg = true; ++ dev_dbg(i2c_bus->dev, "reg [%x] on %d\n", client->addr, i); ++ ++ /* set target addr by index */ ++ switch (i) { ++ case 0: ++ target_addr &= ~(AST2600_I2CS_ADDR1_MASK); ++ target_addr |= (AST2600_I2CS_ADDR1(client->addr) ++ | AST2600_I2CS_ADDR1_ENABLE); ++ break; ++ case 1: ++ target_addr &= ~(AST2600_I2CS_ADDR2_MASK); ++ target_addr |= (AST2600_I2CS_ADDR2(client->addr) ++ | AST2600_I2CS_ADDR2_ENABLE); ++ break; ++ case 2: ++ target_addr &= ~(AST2600_I2CS_ADDR3_MASK); ++ target_addr |= (AST2600_I2CS_ADDR3(client->addr) ++ | AST2600_I2CS_ADDR3_ENABLE); ++ break; ++ } ++ ++ /* Set target addr. */ ++ writel(target_addr, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); ++ break; ++ } ++ } ++ ++ /* don't reg target */ ++ if (!target_reg) ++ return -EINVAL; ++ ++ dev_dbg(i2c_bus->dev, "target addr %x\n", client->addr); ++ ++ /* turn on target mode */ ++ if (!(ctrl & AST2600_I2CC_SLAVE_EN)) { ++ writel(ctrl | AST2600_I2CC_SLAVE_EN, ++ i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ } ++ ++ /* trigger rx buffer */ ++ if (i2c_bus->mode == DMA_MODE) { ++ cmd |= AST2600_I2CS_RX_DMA_EN; ++ writel(lower_32_bits(i2c_bus->target_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CS_RX_DMA); ++ writel(upper_32_bits(i2c_bus->target_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CS_RX_DMA_H); ++ writel(lower_32_bits(i2c_bus->target_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CS_TX_DMA); ++ writel(upper_32_bits(i2c_bus->target_dma_addr), ++ i2c_bus->reg_base + AST2600_I2CS_TX_DMA_H); ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ } else if (i2c_bus->mode == BUFF_MODE) { ++ cmd = TARGET_TRIGGER_CMD; ++ if (i2c_bus->version == AST2700) { ++ writel(AST2600_I2CS_SET_RX_DMA_LEN(i2c_bus->buf_size), ++ i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); ++ cmd |= AST2600_I2CS_RX_DMA_EN; ++ } ++ } else { ++ cmd &= ~AST2600_I2CS_PKT_MODE_EN; ++ } ++ ++ writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); ++ ++ i2c_bus->target_attached++; ++ return 0; ++} ++ ++static int ast2600_i2c_unreg_target(struct i2c_client *client) ++{ ++ struct ast2600_i2c_bus *i2c_bus = i2c_get_adapdata(client->adapter); ++ u32 target_addr = readl(i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); ++ bool target_unreg = false; ++ u8 i = 0; ++ ++ /* check target client input and salve counts*/ ++ if (!client || i2c_bus->target_attached == 0) ++ return -EINVAL; ++ ++ /* remove the target call back from array */ ++ for (i = 0; i < AST2600_I2C_TARGET_COUNT; i++) { ++ if (i2c_bus->multi_target[i]) { ++ if (i2c_bus->multi_target[i]->addr == client->addr) { ++ i2c_bus->multi_target[i] = NULL; ++ target_unreg = true; ++ dev_dbg(i2c_bus->dev, "un-reg [%x] from %d\n", client->addr, i); ++ /* remove target addr by index */ ++ target_addr &= ~(0xff << 8 * i); ++ writel(target_addr, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); ++ break; ++ } ++ } ++ } ++ ++ /* don't un-reg target */ ++ if (!target_unreg) ++ return -EINVAL; ++ ++ i2c_bus->target_attached--; ++ ++ /* Turn off target mode */ ++ if (i2c_bus->target_attached == 0x0) { ++ writel(~AST2600_I2CC_SLAVE_EN & readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL), ++ i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ } ++ ++ return 0; ++} ++#endif ++ ++static u32 ast2600_i2c_functionality(struct i2c_adapter *adap) ++{ ++ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static const struct i2c_algorithm i2c_ast2600_algorithm = { ++ .xfer = ast2600_i2c_controller_xfer, ++ .functionality = ast2600_i2c_functionality, ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ .reg_target = ast2600_i2c_reg_target, ++ .unreg_target = ast2600_i2c_unreg_target, ++#endif ++}; ++ ++static const struct i2c_adapter_quirks ast2600_i2c_quirks = { ++ .flags = I2C_AQ_NO_ZERO_LEN_READ, ++}; ++ ++static int ast2600_i2c_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct ast2600_i2c_bus *i2c_bus; ++ struct reset_control *rst; ++ const char *xfer_mode; ++ struct resource *res; ++ u32 global_ctrl; ++ int ret; ++ ++ if (!device_property_present(dev, "aspeed,global-regs")) ++ return -ENODEV; ++ ++ i2c_bus = devm_kzalloc(dev, sizeof(*i2c_bus), GFP_KERNEL); ++ if (!i2c_bus) ++ return -ENOMEM; ++ ++ i2c_bus->reg_base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(i2c_bus->reg_base)) ++ return PTR_ERR(i2c_bus->reg_base); ++ ++ rst = devm_reset_control_get_shared(dev, NULL); ++ if (IS_ERR(rst)) ++ return dev_err_probe(dev, PTR_ERR(rst), "Missing reset ctrl\n"); ++ ++ reset_control_deassert(rst); ++ ++ i2c_bus->version = (enum i2c_version)device_get_match_data(dev); ++ ++ i2c_bus->global_regs = ++ syscon_regmap_lookup_by_phandle(dev_of_node(dev), "aspeed,global-regs"); ++ if (IS_ERR(i2c_bus->global_regs)) ++ return PTR_ERR(i2c_bus->global_regs); ++ ++ regmap_read(i2c_bus->global_regs, AST2600_I2CG_CTRL, &global_ctrl); ++ if ((global_ctrl & AST2600_GLOBAL_INIT) != AST2600_GLOBAL_INIT) { ++ regmap_write(i2c_bus->global_regs, AST2600_I2CG_CTRL, AST2600_GLOBAL_INIT); ++ if (i2c_bus->version == AST2600) ++ regmap_write(i2c_bus->global_regs, ++ AST2600_I2CG_CLK_DIV_CTRL, AST2600_I2CCG_DIV_CTRL); ++ else ++ regmap_write(i2c_bus->global_regs, ++ AST2600_I2CG_CLK_DIV_CTRL, AST2700_I2CCG_DIV_CTRL); ++ } ++ ++ if (i2c_bus->version == AST2700) { ++ i2c_bus->ac_timing_config = ast2700_i2c_ac_timing_config; ++ i2c_bus->irq_err_to_errno = ast2700_i2c_irq_err_to_errno; ++ i2c_bus->irq_clear = ast2700_i2c_irq_clear; ++ } else { ++ i2c_bus->ac_timing_config = ast2600_i2c_ac_timing_config; ++ i2c_bus->irq_err_to_errno = ast2600_i2c_irq_err_to_errno; ++ i2c_bus->irq_clear = ast2600_i2c_irq_clear; ++ } ++ ++ /* Default BUFF mode, allow override */ ++ i2c_bus->mode = BUFF_MODE; ++ ++ /* override the transfer mode */ ++ if (!device_property_read_string(dev, "aspeed,transfer-mode", &xfer_mode)) { ++ if (!strcmp(xfer_mode, "dma")) ++ i2c_bus->mode = DMA_MODE; ++ else if (!strcmp(xfer_mode, "byte")) ++ i2c_bus->mode = BYTE_MODE; ++ else ++ i2c_bus->mode = BUFF_MODE; ++ } ++ ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ i2c_bus->target_operate = 0; ++ i2c_bus->target_attached = 0; ++ for (int i = 0; i < AST2600_I2C_TARGET_COUNT; i++) ++ i2c_bus->multi_target[i] = NULL; ++#endif ++ i2c_bus->dev = dev; ++ i2c_bus->multi_master = device_property_read_bool(dev, "multi-master"); ++ ++ if (i2c_bus->version == AST2700) { ++ /* select the transfer method*/ ++ u32 xfer_type = readl(i2c_bus->reg_base + I2CC_VERSION_CTRL); ++ ++ /* AST2700 would not allow byte mode */ ++ if (i2c_bus->mode == BYTE_MODE) ++ i2c_bus->mode = DMA_MODE; ++ ++ if (i2c_bus->mode == DMA_MODE) { ++ /* ast2700 cfg enable dma */ ++ xfer_type |= FUNC_CFG_DMA_EN; ++ } else if (i2c_bus->mode == BUFF_MODE) { ++ i2c_bus->buf_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (IS_ERR(i2c_bus->buf_base)) { ++ i2c_bus->mode = DMA_MODE; ++ } else { ++ i2c_bus->buf_size = resource_size(res) / 4; ++ ++ /* ast2700 cfg disable dma */ ++ xfer_type &= ~FUNC_CFG_DMA_EN; ++ } ++ } ++ writel(xfer_type, i2c_bus->reg_base + I2CC_VERSION_CTRL); ++ } else { ++ if (i2c_bus->mode == BUFF_MODE) { ++ i2c_bus->buf_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (IS_ERR(i2c_bus->buf_base)) ++ i2c_bus->mode = BYTE_MODE; ++ else ++ i2c_bus->buf_size = resource_size(res) / 2; ++ } ++ } ++ ++ switch (i2c_bus->mode) { ++ case DMA_MODE: ++ i2c_bus->setup_tx = ast2600_i2c_setup_dma_tx; ++ i2c_bus->setup_rx = ast2600_i2c_setup_dma_rx; ++ break; ++ case BUFF_MODE: ++ default: ++ if (i2c_bus->version == AST2700) { ++ i2c_bus->setup_tx = ast2700_i2c_setup_buff_tx; ++ i2c_bus->setup_rx = ast2700_i2c_setup_buff_rx; ++ } else { ++ i2c_bus->setup_tx = ast2600_i2c_setup_buff_tx; ++ i2c_bus->setup_rx = ast2600_i2c_setup_buff_rx; ++ } ++ break; ++ case BYTE_MODE: ++ i2c_bus->setup_tx = ast2600_i2c_setup_byte_tx; ++ i2c_bus->setup_rx = ast2600_i2c_setup_byte_rx; ++ break; ++ } ++ ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ switch (i2c_bus->mode) { ++ case DMA_MODE: ++ if (i2c_bus->version == AST2700) ++ i2c_bus->target_packet_irq = ast2700_i2c_target_packet_dma_irq; ++ else ++ i2c_bus->target_packet_irq = ast2600_i2c_target_packet_dma_irq; ++ break; ++ case BUFF_MODE: ++ default: ++ if (i2c_bus->version == AST2700) ++ i2c_bus->target_packet_irq = ast2700_i2c_target_packet_buff_irq; ++ else ++ i2c_bus->target_packet_irq = ast2600_i2c_target_packet_buff_irq; ++ break; ++ case BYTE_MODE: ++ i2c_bus->target_byte_irq = ast2600_i2c_target_byte_irq; ++ break; ++ } ++#endif ++ ++ /* ++ * i2c timeout counter: use base clk4 1Mhz, ++ * per unit: 1/(1000/1024) = 1024us ++ * lower than 1024 us will be set 1 ms. ++ */ ++ ret = device_property_read_u32(dev, "i2c-scl-clk-low-timeout-us", &i2c_bus->timeout); ++ if (!ret) { ++ i2c_bus->timeout = DIV_ROUND_UP(i2c_bus->timeout, 1024); ++ } ++ ++ init_completion(&i2c_bus->cmd_complete); ++ ++ i2c_bus->irq = platform_get_irq(pdev, 0); ++ if (i2c_bus->irq < 0) ++ return i2c_bus->irq; ++ ++ platform_set_drvdata(pdev, i2c_bus); ++ ++ i2c_bus->clk = devm_clk_get(i2c_bus->dev, NULL); ++ if (IS_ERR(i2c_bus->clk)) ++ return dev_err_probe(i2c_bus->dev, PTR_ERR(i2c_bus->clk), "Can't get clock\n"); ++ ++ i2c_bus->apb_clk = clk_get_rate(i2c_bus->clk); ++ ++ i2c_parse_fw_timings(i2c_bus->dev, &i2c_bus->timing_info, true); ++ ++ /* Initialize the I2C adapter */ ++ i2c_bus->adap.owner = THIS_MODULE; ++ i2c_bus->adap.algo = &i2c_ast2600_algorithm; ++ i2c_bus->adap.quirks = &ast2600_i2c_quirks; ++ i2c_bus->adap.retries = 0; ++ i2c_bus->adap.dev.parent = i2c_bus->dev; ++ device_set_node(&i2c_bus->adap.dev, dev_fwnode(dev)); ++ i2c_bus->adap.algo_data = i2c_bus; ++ strscpy(i2c_bus->adap.name, pdev->name); ++ i2c_set_adapdata(&i2c_bus->adap, i2c_bus); ++ dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); ++ ++ ret = ast2600_i2c_init(i2c_bus); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "Unable to initial i2c %d\n", ret); ++ ++ ret = devm_request_irq(dev, i2c_bus->irq, ast2600_i2c_bus_irq, 0, ++ dev_name(dev), i2c_bus); ++ if (ret < 0) { ++ ret = dev_err_probe(dev, ret, "Unable to request irq %d\n", ++ i2c_bus->irq); ++ goto err; ++ } ++ ++ writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER, ++ i2c_bus->reg_base + AST2600_I2CM_IER); ++ ++ ret = devm_i2c_add_adapter(dev, &i2c_bus->adap); ++ if (ret) ++ goto err; ++ ++ i2c_bus->alert_enable = device_property_read_bool(dev, "smbus-alert"); ++ if (i2c_bus->alert_enable) { ++ i2c_bus->ara = i2c_new_smbus_alert_device(&i2c_bus->adap, &i2c_bus->alert_data); ++ if (!i2c_bus->ara) ++ dev_warn(dev, "Failed to register ARA client\n"); ++ else ++ writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER | ++ AST2600_I2CM_SMBUS_ALERT, ++ i2c_bus->reg_base + AST2600_I2CM_IER); ++ } else { ++ i2c_bus->alert_enable = false; ++ } ++ ++ dev_info(dev, "%s [%d]: adapter [%d KHz] mode [%d] version [%d]\n", ++ dev->of_node->name, i2c_bus->adap.nr, i2c_bus->timing_info.bus_freq_hz / 1000, ++ i2c_bus->mode, i2c_bus->version); ++ ++ return 0; ++ ++err: ++ writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); ++ return ret; ++} ++ ++static void ast2600_i2c_remove(struct platform_device *pdev) ++{ ++ struct ast2600_i2c_bus *i2c_bus = platform_get_drvdata(pdev); ++ ++ /* Disable everything. */ ++ writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); ++ writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); ++} ++ ++static const struct of_device_id ast2600_i2c_of_match[] = { ++ { .compatible = "aspeed,ast2600-i2cv2", .data = (const void *)AST2600, }, ++ { .compatible = "aspeed,ast2700-i2c", .data = (const void *)AST2700, }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, ast2600_i2c_of_match); ++ ++static struct platform_driver ast2600_i2c_driver = { ++ .probe = ast2600_i2c_probe, ++ .remove = ast2600_i2c_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = ast2600_i2c_of_match, ++ }, ++}; ++module_platform_driver(ast2600_i2c_driver); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_DESCRIPTION("ASPEED AST2600 I2C Controller Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/i3c/master/ast2600-i3c-master.c b/drivers/i3c/master/ast2600-i3c-master.c +--- a/drivers/i3c/master/ast2600-i3c-master.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/i3c/master/ast2600-i3c-master.c 2026-09-09 15:51:44.672238767 +0000 +@@ -10,6 +10,8 @@ + #include + #include + #include ++#include ++#include + + #include "dw-i3c-master.h" + +@@ -33,18 +35,97 @@ + #define AST2600_I3CG_REG1_SA_EN BIT(15) + #define AST2600_I3CG_REG1_INST_ID_MASK GENMASK(19, 16) + #define AST2600_I3CG_REG1_INST_ID(x) (((x) << 16) & AST2600_I3CG_REG1_INST_ID_MASK) ++#define SCL_SW_MODE_OE BIT(20) ++#define SCL_OUT_SW_MODE_VAL BIT(21) ++#define SCL_IN_SW_MODE_VAL BIT(23) ++#define SDA_SW_MODE_OE BIT(24) ++#define SDA_OUT_SW_MODE_VAL BIT(25) ++#define SDA_IN_SW_MODE_VAL BIT(27) ++#define SCL_IN_SW_MODE_EN BIT(28) ++#define SDA_IN_SW_MODE_EN BIT(29) ++#define SCL_OUT_SW_MODE_EN BIT(30) ++#define SDA_OUT_SW_MODE_EN BIT(31) + + #define AST2600_DEFAULT_SDA_PULLUP_OHMS 2000 + ++#define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31) ++#define DEV_ADDR_TABLE_DYNAMIC_ADDR GENMASK(23, 16) ++#define DEV_ADDR_TABLE_IBI_ADDR_MASK GENMASK(25, 24) ++#define IBI_ADDR_MASK_OFF 0b00 ++#define IBI_ADDR_MASK_LAST_3BITS 0b01 ++#define IBI_ADDR_MASK_LAST_4BITS 0b10 ++#define DEV_ADDR_TABLE_DA_PARITY BIT(23) ++#define DEV_ADDR_TABLE_MR_REJECT BIT(14) ++#define DEV_ADDR_TABLE_SIR_REJECT BIT(13) ++#define DEV_ADDR_TABLE_IBI_MDB BIT(12) + #define DEV_ADDR_TABLE_IBI_PEC BIT(11) ++#define DEV_ADDR_TABLE_STATIC_ADDR GENMASK(6, 0) ++ ++#define DEV_ADDR_TABLE_LOC(start, idx) ((start) + ((idx) << 2)) ++ ++#define DEVICE_CTRL 0x0 ++#define DEV_CTRL_SLAVE_MDB GENMASK(23, 16) ++#define DEV_CTRL_HOT_JOIN_NACK BIT(8) ++ ++#define NUM_OF_SWDATS_IN_GROUP 8 ++#define ALL_DATS_IN_GROUP_ARE_FREE ((1 << NUM_OF_SWDATS_IN_GROUP) - 1) ++#define NUM_OF_SWDAT_GROUP 16 ++ ++#define ADDR_GRP_MASK GENMASK(6, 3) ++#define ADDR_GRP(x) (((x) & ADDR_GRP_MASK) >> 3) ++#define ADDR_HID_MASK GENMASK(2, 0) ++#define ADDR_HID(x) ((x) & ADDR_HID_MASK) ++ ++#define IBI_QUEUE_STATUS 0x18 ++#define IBI_QUEUE_STATUS_DATA_LEN(x) ((x) & GENMASK(7, 0)) ++ ++#define IBI_SIR_REQ_REJECT 0x30 ++#define INTR_STATUS_EN 0x40 ++#define INTR_SIGNAL_EN 0x44 ++#define INTR_IBI_THLD_STAT BIT(2) ++ ++#define QUEUE_STATUS_LEVEL 0x4c ++#define QUEUE_STATUS_IBI_STATUS_CNT(x) (((x) & GENMASK(28, 24)) >> 24) ++ ++#define PRESENT_STATE 0x54 ++#define CM_TFR_STS GENMASK(13, 8) ++#define CM_TFR_STS_MASTER_SERV_IBI 0xe ++#define SDA_LINE_SIGNAL_LEVEL BIT(1) ++#define SCL_LINE_SIGNAL_LEVEL BIT(0) ++ ++struct ast2600_i3c_swdat_group { ++ u32 dat[NUM_OF_SWDATS_IN_GROUP]; ++ u32 free_pos; ++ int hw_index; ++ struct { ++ u32 set; ++ u32 clr; ++ } mask; ++}; + + struct ast2600_i3c { + struct dw_i3c_master dw; + struct regmap *global_regs; + unsigned int global_idx; + unsigned int sda_pullup; ++ ++ struct ast2600_i3c_swdat_group dat_group[NUM_OF_SWDAT_GROUP]; + }; + ++static u8 even_parity(u8 p) ++{ ++ p ^= p >> 4; ++ p &= 0xf; ++ ++ return (0x9669 >> p) & 1; ++} ++ ++static inline struct dw_i3c_master * ++to_dw_i3c_master(struct i3c_master_controller *master) ++{ ++ return container_of(master, struct dw_i3c_master, base); ++} ++ + static struct ast2600_i3c *to_ast2600_i3c(struct dw_i3c_master *dw) + { + return container_of(dw, struct ast2600_i3c, dw); +@@ -117,9 +198,559 @@ + } + } + ++static void ast2600_i3c_enter_sw_mode(struct dw_i3c_master *dw) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_IN_SW_MODE_VAL | SDA_IN_SW_MODE_VAL, ++ SCL_IN_SW_MODE_VAL | SDA_IN_SW_MODE_VAL); ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_IN_SW_MODE_EN | SDA_IN_SW_MODE_EN, ++ SCL_IN_SW_MODE_EN | SDA_IN_SW_MODE_EN); ++} ++ ++static void ast2600_i3c_exit_sw_mode(struct dw_i3c_master *dw) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_IN_SW_MODE_EN | SDA_IN_SW_MODE_EN, 0); ++} ++ ++static void ast2600_i3c_toggle_scl_in(struct dw_i3c_master *dw, int count) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ ++ for (; count; count--) { ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_IN_SW_MODE_VAL, 0); ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_IN_SW_MODE_VAL, SCL_IN_SW_MODE_VAL); ++ } ++} ++ ++static void ast2600_i3c_gen_internal_stop(struct dw_i3c_master *dw) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_IN_SW_MODE_VAL, 0); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_VAL, 0); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_IN_SW_MODE_VAL, SCL_IN_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_VAL, SDA_IN_SW_MODE_VAL); ++} ++ ++static int aspeed_i3c_bus_recovery(struct dw_i3c_master *dw) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ int i, ret = -1; ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_SW_MODE_OE, SCL_SW_MODE_OE); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_EN, SCL_OUT_SW_MODE_EN); ++ ++ for (i = 0; i < 19; i++) { ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, 0); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL); ++ if (readl(dw->regs + PRESENT_STATE) & SDA_LINE_SIGNAL_LEVEL) { ++ ret = 0; ++ break; ++ } ++ } ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_EN, 0); ++ if (ret) ++ dev_err(&dw->base.dev, "Failed to recover the bus\n"); ++ ++ return ret; ++} ++ ++static void ast2600_i3c_gen_target_reset_pattern(struct dw_i3c_master *dw) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ int i; ++ ++ if (dw->base.bus.context == I3C_BUS_CONTEXT_JESD403) { ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_SW_MODE_OE, SCL_SW_MODE_OE); ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_EN, SCL_OUT_SW_MODE_EN); ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, 0); ++ mdelay(DIV_ROUND_UP(dw->timing.timed_reset_scl_low_ns, ++ 1000000)); ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_EN, 0); ++ return; ++ } ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_OUT_SW_MODE_VAL | SCL_OUT_SW_MODE_VAL, ++ SDA_OUT_SW_MODE_VAL | SCL_OUT_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_SW_MODE_OE | SCL_SW_MODE_OE, ++ SDA_SW_MODE_OE | SCL_SW_MODE_OE); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_OUT_SW_MODE_EN | SCL_OUT_SW_MODE_EN, ++ SDA_OUT_SW_MODE_EN | SCL_OUT_SW_MODE_EN); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_VAL | SCL_IN_SW_MODE_VAL, ++ SDA_IN_SW_MODE_VAL | SCL_IN_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_EN | SCL_IN_SW_MODE_EN, ++ SDA_IN_SW_MODE_EN | SCL_IN_SW_MODE_EN); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, 0); ++ for (i = 0; i < 7; i++) { ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_OUT_SW_MODE_VAL, 0); ++ regmap_write_bits(i3c->global_regs, ++ AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_OUT_SW_MODE_VAL, SDA_OUT_SW_MODE_VAL); ++ } ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_OUT_SW_MODE_VAL, 0); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_OUT_SW_MODE_VAL, SDA_OUT_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_OUT_SW_MODE_EN | SCL_OUT_SW_MODE_EN, 0); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_EN | SCL_IN_SW_MODE_EN, 0); ++} ++ ++static void aspeed_i3c_drain_ibi_queue(struct dw_i3c_master *dw) ++{ ++ /* ++ * Clear the IBI queue to avoid any stale IBI data when ++ * re-enabling the controller. ++ */ ++ u32 ibi_status = readl(dw->regs + IBI_QUEUE_STATUS); ++ u8 length = IBI_QUEUE_STATUS_DATA_LEN(ibi_status); ++ int i, nwords = (length + 3) >> 2; ++ ++ for (i = 0; i < nwords; i++) ++ readl(dw->regs + IBI_QUEUE_STATUS); ++} ++ ++static bool ast2600_i3c_fsm_exit_serv_ibi(struct dw_i3c_master *dw) ++{ ++ u32 state; ++ ++ /* ++ * Clear the IBI queue to enable the hardware to generate SCL and ++ * begin detecting the T-bit low to stop reading IBI data. ++ */ ++ aspeed_i3c_drain_ibi_queue(dw); ++ state = FIELD_GET(CM_TFR_STS, readl(dw->regs + PRESENT_STATE)); ++ if (state == CM_TFR_STS_MASTER_SERV_IBI) ++ return false; ++ ++ return true; ++} ++ ++static void ast2600_i3c_gen_tbits_in(struct dw_i3c_master *dw) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ bool is_halted; ++ u32 nibi, i; ++ int ret; ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_VAL, SDA_IN_SW_MODE_VAL); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_EN, SDA_IN_SW_MODE_EN); ++ ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_VAL, 0); ++ ret = readx_poll_timeout_atomic(ast2600_i3c_fsm_exit_serv_ibi, dw, ++ is_halted, is_halted, 0, 2000000); ++ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx), ++ SDA_IN_SW_MODE_EN, 0); ++ if (ret) { ++ dev_err(&dw->base.dev, ++ "Failed to exit the I3C fsm from %lx(MASTER_SERV_IBI): %d", ++ FIELD_GET(CM_TFR_STS, readl(dw->regs + PRESENT_STATE)), ++ ret); ++ } else { ++ /* Clear the dummy data generated in this recovery process */ ++ nibi = readl(dw->regs + QUEUE_STATUS_LEVEL); ++ nibi = QUEUE_STATUS_IBI_STATUS_CNT(nibi); ++ for (i = 0; i < nibi; i++) ++ aspeed_i3c_drain_ibi_queue(dw); ++ } ++} ++ ++static void ast2600_i3c_set_ibi_mdb(struct dw_i3c_master *dw, u8 mdb) ++{ ++ u32 reg; ++ ++ reg = readl(dw->regs + DEVICE_CTRL); ++ reg &= ~DEV_CTRL_SLAVE_MDB; ++ reg |= FIELD_PREP(DEV_CTRL_SLAVE_MDB, mdb); ++ writel(reg, dw->regs + DEVICE_CTRL); ++} ++ ++static int ast2600_i3c_get_free_hw_pos(struct dw_i3c_master *dw) ++{ ++ if (!(dw->free_pos & GENMASK(dw->maxdevs - 1, 0))) ++ return -ENOSPC; ++ ++ return ffs(dw->free_pos) - 1; ++} ++ ++static void ast2600_i3c_init_swdat(struct dw_i3c_master *dw) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ struct ast2600_i3c_swdat_group *gp; ++ int i, j; ++ u32 def_set, def_clr; ++ ++ def_clr = DEV_ADDR_TABLE_IBI_ADDR_MASK; ++ def_set = DEV_ADDR_TABLE_MR_REJECT | DEV_ADDR_TABLE_SIR_REJECT; ++ ++ for (i = 0; i < NUM_OF_SWDAT_GROUP; i++) { ++ gp = &i3c->dat_group[i]; ++ gp->hw_index = -1; ++ gp->free_pos = ALL_DATS_IN_GROUP_ARE_FREE; ++ gp->mask.clr = def_clr; ++ gp->mask.set = def_set; ++ ++ for (j = 0; j < NUM_OF_SWDATS_IN_GROUP; j++) ++ gp->dat[j] = 0; ++ } ++ ++ for (i = 0; i < dw->maxdevs; i++) ++ writel(def_set, ++ dw->regs + DEV_ADDR_TABLE_LOC(dw->datstartaddr, i)); ++} ++ ++static int ast2600_i3c_set_swdat(struct dw_i3c_master *dw, u8 addr, u32 val) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ struct ast2600_i3c_swdat_group *gp = &i3c->dat_group[ADDR_GRP(addr)]; ++ int pos = ADDR_HID(addr); ++ ++ if (!(val & DEV_ADDR_TABLE_LEGACY_I2C_DEV)) { ++ /* Calculate DA parity for I3C devices */ ++ val &= ~DEV_ADDR_TABLE_DA_PARITY; ++ val |= FIELD_PREP(DEV_ADDR_TABLE_DA_PARITY, even_parity(addr)); ++ } ++ gp->dat[pos] = val; ++ ++ if (val) { ++ gp->free_pos &= ~BIT(pos); ++ ++ /* ++ * reserve the hw dat resource for the first member of the ++ * group. all the members in the group share the same hw dat. ++ */ ++ if (gp->hw_index == -1) { ++ gp->hw_index = ast2600_i3c_get_free_hw_pos(dw); ++ if (gp->hw_index < 0) ++ goto out; ++ ++ dw->free_pos &= ~BIT(gp->hw_index); ++ val &= ~gp->mask.clr; ++ val |= gp->mask.set; ++ writel(val, ++ dw->regs + DEV_ADDR_TABLE_LOC(dw->datstartaddr, ++ gp->hw_index)); ++ } ++ } else { ++ gp->free_pos |= BIT(pos); ++ ++ /* ++ * release the hw dat resource if all the members in the group ++ * are free. ++ */ ++ if (gp->free_pos == ALL_DATS_IN_GROUP_ARE_FREE) { ++ writel(gp->mask.set, ++ dw->regs + DEV_ADDR_TABLE_LOC(dw->datstartaddr, ++ gp->hw_index)); ++ dw->free_pos |= BIT(gp->hw_index); ++ gp->hw_index = -1; ++ } ++ } ++out: ++ return gp->hw_index; ++} ++ ++static u32 ast2600_i3c_get_swdat(struct dw_i3c_master *dw, u8 addr) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ struct ast2600_i3c_swdat_group *gp = &i3c->dat_group[ADDR_GRP(addr)]; ++ ++ return gp->dat[ADDR_HID(addr)]; ++} ++ ++static int ast2600_i3c_flush_swdat(struct dw_i3c_master *dw, u8 addr) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ struct ast2600_i3c_swdat_group *gp = &i3c->dat_group[ADDR_GRP(addr)]; ++ u32 dat = gp->dat[ADDR_HID(addr)]; ++ int hw_index = gp->hw_index; ++ ++ if (!dat || hw_index < 0) ++ return -1; ++ ++ dat &= ~gp->mask.clr; ++ dat |= gp->mask.set; ++ writel(dat, dw->regs + DEV_ADDR_TABLE_LOC(dw->datstartaddr, hw_index)); ++ ++ return 0; ++} ++ ++static int ast2600_i3c_get_swdat_hw_pos(struct dw_i3c_master *dw, u8 addr) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ struct ast2600_i3c_swdat_group *gp = &i3c->dat_group[ADDR_GRP(addr)]; ++ ++ return gp->hw_index; ++} ++ ++static int ast2600_i3c_reattach_i3c_dev(struct i3c_dev_desc *dev, ++ u8 old_dyn_addr) ++{ ++ struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); ++ struct i3c_master_controller *m = i3c_dev_get_master(dev); ++ struct dw_i3c_master *master = to_dw_i3c_master(m); ++ u32 dat = FIELD_PREP(DEV_ADDR_TABLE_DYNAMIC_ADDR, dev->info.dyn_addr); ++ ++ if (old_dyn_addr != dev->info.dyn_addr) ++ ast2600_i3c_set_swdat(master, old_dyn_addr, 0); ++ ++ ast2600_i3c_set_swdat(master, dev->info.dyn_addr, dat); ++ data->index = ast2600_i3c_get_swdat_hw_pos(master, dev->info.dyn_addr); ++ master->devs[dev->info.dyn_addr].addr = dev->info.dyn_addr; ++ ++ return 0; ++} ++ ++static int ast2600_i3c_attach_i3c_dev(struct i3c_dev_desc *dev) ++{ ++ struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); ++ struct i3c_master_controller *m = i3c_dev_get_master(dev); ++ struct dw_i3c_master *master = to_dw_i3c_master(m); ++ int pos; ++ u8 addr = dev->info.dyn_addr ?: dev->info.static_addr; ++ ++ pos = ast2600_i3c_set_swdat(master, addr, ++ FIELD_PREP(DEV_ADDR_TABLE_DYNAMIC_ADDR, addr)); ++ if (pos < 0) ++ return pos; ++ ++ data = kzalloc(sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->index = ast2600_i3c_get_swdat_hw_pos(master, addr); ++ master->devs[addr].addr = addr; ++ i3c_dev_set_master_data(dev, data); ++ ++ if (master->base.bus.context == I3C_BUS_CONTEXT_JESD403) { ++ dev->info.max_write_ds = 0; ++ dev->info.max_read_ds = 0; ++ } ++ ++ return 0; ++} ++ ++static void ast2600_i3c_detach_i3c_dev(struct i3c_dev_desc *dev) ++{ ++ struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); ++ struct i3c_master_controller *m = i3c_dev_get_master(dev); ++ struct dw_i3c_master *master = to_dw_i3c_master(m); ++ u8 addr = dev->info.dyn_addr ?: dev->info.static_addr; ++ ++ ast2600_i3c_set_swdat(master, addr, 0); ++ ++ i3c_dev_set_master_data(dev, NULL); ++ master->devs[addr].addr = 0; ++ kfree(data); ++} ++ ++static int ast2600_i3c_attach_i2c_dev(struct i2c_dev_desc *dev) ++{ ++ struct dw_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev); ++ struct i3c_master_controller *m = i2c_dev_get_master(dev); ++ struct dw_i3c_master *master = to_dw_i3c_master(m); ++ int pos; ++ ++ pos = ast2600_i3c_set_swdat(master, dev->addr, ++ DEV_ADDR_TABLE_LEGACY_I2C_DEV | ++ FIELD_PREP(DEV_ADDR_TABLE_STATIC_ADDR, dev->addr)); ++ if (pos < 0) ++ return pos; ++ ++ data = kzalloc(sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->index = ast2600_i3c_get_swdat_hw_pos(master, dev->addr); ++ master->devs[dev->addr].addr = dev->addr; ++ i2c_dev_set_master_data(dev, data); ++ ++ return 0; ++} ++ ++static void ast2600_i3c_detach_i2c_dev(struct i2c_dev_desc *dev) ++{ ++ struct dw_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev); ++ struct i3c_master_controller *m = i2c_dev_get_master(dev); ++ struct dw_i3c_master *master = to_dw_i3c_master(m); ++ ++ ast2600_i3c_set_swdat(master, dev->addr, 0); ++ ++ i2c_dev_set_master_data(dev, NULL); ++ master->devs[dev->addr].addr = 0; ++ kfree(data); ++} ++ ++static void ast2600_i3c_set_sir_enabled(struct dw_i3c_master *dw, ++ struct i3c_dev_desc *dev, u8 idx, ++ bool enable) ++{ ++ struct ast2600_i3c *i3c = to_ast2600_i3c(dw); ++ struct ast2600_i3c_swdat_group *gp = ++ &i3c->dat_group[ADDR_GRP(dev->info.dyn_addr)]; ++ unsigned long flags; ++ u32 reg; ++ bool global; ++ ++ spin_lock_irqsave(&dw->devs_lock, flags); ++ if (enable) { ++ gp->mask.clr |= DEV_ADDR_TABLE_SIR_REJECT | ++ DEV_ADDR_TABLE_IBI_ADDR_MASK; ++ ++ gp->mask.set &= ~DEV_ADDR_TABLE_SIR_REJECT; ++ gp->mask.set |= FIELD_PREP(DEV_ADDR_TABLE_IBI_ADDR_MASK, ++ IBI_ADDR_MASK_LAST_3BITS); ++ /* ++ * The ast2600 i3c controller will lock up on receiving 4n+1-byte IBIs ++ * if the PEC is disabled. We have no way to restrict the length of ++ * IBIs sent to the controller, so we need to unconditionally enable ++ * PEC checking, which means we drop a byte of payload data ++ */ ++ gp->mask.set |= DEV_ADDR_TABLE_IBI_PEC; ++ if (dev->info.bcr & I3C_BCR_IBI_PAYLOAD) ++ gp->mask.set |= DEV_ADDR_TABLE_IBI_MDB; ++ } else { ++ reg = ast2600_i3c_get_swdat(dw, dev->info.dyn_addr); ++ reg |= DEV_ADDR_TABLE_SIR_REJECT; ++ ast2600_i3c_set_swdat(dw, dev->info.dyn_addr, reg); ++ } ++ ++ reg = readl(dw->regs + IBI_SIR_REQ_REJECT); ++ if (enable) { ++ global = reg == 0xffffffff; ++ reg &= ~BIT(gp->hw_index); ++ } else { ++ int i; ++ bool hj_rejected = !!(readl(dw->regs + DEVICE_CTRL) & ++ DEV_CTRL_HOT_JOIN_NACK); ++ bool ibi_enable = false; ++ ++ for (i = 0; i < NUM_OF_SWDATS_IN_GROUP; i++) { ++ if (!(gp->dat[i] & DEV_ADDR_TABLE_SIR_REJECT)) { ++ ibi_enable = true; ++ break; ++ } ++ } ++ ++ if (!ibi_enable) { ++ reg |= BIT(gp->hw_index); ++ global = (reg == 0xffffffff) && hj_rejected; ++ ++ gp->mask.set = DEV_ADDR_TABLE_SIR_REJECT; ++ } ++ } ++ writel(reg, dw->regs + IBI_SIR_REQ_REJECT); ++ ++ if (global) { ++ reg = readl(dw->regs + INTR_STATUS_EN); ++ reg &= ~INTR_IBI_THLD_STAT; ++ if (enable) ++ reg |= INTR_IBI_THLD_STAT; ++ writel(reg, dw->regs + INTR_STATUS_EN); ++ ++ reg = readl(dw->regs + INTR_SIGNAL_EN); ++ reg &= ~INTR_IBI_THLD_STAT; ++ if (enable) ++ reg |= INTR_IBI_THLD_STAT; ++ writel(reg, dw->regs + INTR_SIGNAL_EN); ++ } ++ ++ ast2600_i3c_flush_swdat(dw, dev->info.dyn_addr); ++ ++ spin_unlock_irqrestore(&dw->devs_lock, flags); ++} ++ ++static void ast2600_i3c_set_ibi_dev(struct dw_i3c_master *dw, ++ struct i3c_dev_desc *dev) ++{ ++ dw->devs[dev->info.dyn_addr].ibi_dev = dev; ++} ++ ++static void ast2600_i3c_unset_ibi_dev(struct dw_i3c_master *dw, ++ struct i3c_dev_desc *dev) ++{ ++ dw->devs[dev->info.dyn_addr].ibi_dev = NULL; ++} ++ ++static struct i3c_dev_desc *ast2600_i3c_get_ibi_dev(struct dw_i3c_master *dw, ++ u8 addr) ++{ ++ return dw->devs[addr].ibi_dev; ++} ++ + static const struct dw_i3c_platform_ops ast2600_i3c_ops = { + .init = ast2600_i3c_init, + .set_dat_ibi = ast2600_i3c_set_dat_ibi, ++ .enter_sw_mode = ast2600_i3c_enter_sw_mode, ++ .exit_sw_mode = ast2600_i3c_exit_sw_mode, ++ .toggle_scl_in = ast2600_i3c_toggle_scl_in, ++ .gen_internal_stop = ast2600_i3c_gen_internal_stop, ++ .gen_target_reset_pattern = ast2600_i3c_gen_target_reset_pattern, ++ .gen_tbits_in = ast2600_i3c_gen_tbits_in, ++ .bus_recovery = aspeed_i3c_bus_recovery, ++ .set_ibi_mdb = ast2600_i3c_set_ibi_mdb, ++ .reattach_i3c_dev = ast2600_i3c_reattach_i3c_dev, ++ .attach_i3c_dev = ast2600_i3c_attach_i3c_dev, ++ .detach_i3c_dev = ast2600_i3c_detach_i3c_dev, ++ .attach_i2c_dev = ast2600_i3c_attach_i2c_dev, ++ .detach_i2c_dev = ast2600_i3c_detach_i2c_dev, ++ .get_addr_pos = ast2600_i3c_get_swdat_hw_pos, ++ .flush_dat = ast2600_i3c_flush_swdat, ++ .set_sir_enabled = ast2600_i3c_set_sir_enabled, ++ .set_ibi_dev = ast2600_i3c_set_ibi_dev, ++ .unset_ibi_dev = ast2600_i3c_unset_ibi_dev, ++ .get_ibi_dev = ast2600_i3c_get_ibi_dev, + }; + + static int ast2600_i3c_probe(struct platform_device *pdev) +@@ -156,6 +787,10 @@ + i3c->sda_pullup); + + i3c->dw.platform_ops = &ast2600_i3c_ops; ++ i3c->dw.base.pec_supported = true; ++ ++ ast2600_i3c_init_swdat(&i3c->dw); ++ + return dw_i3c_common_probe(&i3c->dw, pdev); + } + +diff --git a/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h b/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h +--- a/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h 2026-09-09 15:51:44.665238872 +0000 +@@ -0,0 +1,453 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++#ifndef VENDOR_ASPEED_H ++#define VENDOR_ASPEED_H ++ ++/* Aspeed in-house register */ ++#include "linux/bitfield.h" ++#define ast_inhouse_read(r) readl(hci->INHOUSE_regs + (r)) ++#define ast_inhouse_write(r, v) writel(v, hci->INHOUSE_regs + (r)) ++ ++#define ASPEED_I3C_CTRL 0x0 ++#define ASPEED_I3C_CTRL_STOP_QUEUE_PT BIT(31) //Stop the queue read pointer. ++#define ASPEED_I3C_CTRL_CLOCK_STALL_EN BIT(14) ++#define ASPEED_I3C_CTRL_INIT BIT(4) ++#define ASPEED_I3C_CTRL_INIT_MODE GENMASK(1, 0) ++#define INIT_MST_MODE 0 ++#define INIT_SEC_MST_MODE 1 ++#define INIT_SLV_MODE 2 ++ ++#define ASPEED_I3C_STS 0x4 ++#define ASPEED_I3C_STS_SLV_DYNAMIC_ADDRESS_VALID BIT(23) ++#define ASPEED_I3C_STS_SLV_DYNAMIC_ADDRESS GENMASK(22, 16) ++#define ASPEED_I3C_STS_MODE_PURE_SLV BIT(8) ++#define ASPEED_I3C_STS_MODE_SECONDARY_SLV_TO_MST BIT(7) ++#define ASPEED_I3C_STS_MODE_SECONDARY_MST_TO_SLV BIT(6) ++#define ASPEED_I3C_STS_MODE_SECONDARY_SLV BIT(5) ++#define ASPEED_I3C_STS_MODE_SECONDARY_MST BIT(4) ++#define ASPEED_I3C_STS_MODE_PRIMARY_SLV_TO_MST BIT(3) ++#define ASPEED_I3C_STS_MODE_PRIMARY_MST_TO_SLV BIT(2) ++#define ASPEED_I3C_STS_MODE_PRIMARY_SLV BIT(1) ++#define ASPEED_I3C_STS_MODE_PRIMARY_MST BIT(0) ++ ++#define ASPEED_I3C_MST_MRL 0x8 ++#define ASPEED_I3C_IBI_TERMINATE_EN BIT(16) ++#define ASPEED_I3C_IBI_TERMINATE_LEN GENMASK(15, 0) ++ ++#define ASPEED_I3C_DAA_INDEX0 0x10 ++#define ASPEED_I3C_DAA_INDEX1 0x14 ++#define ASPEED_I3C_DAA_INDEX2 0x18 ++#define ASPEED_I3C_DAA_INDEX3 0x1C ++ ++#define ASPEED_I3C_AUTOCMD_0 0x20 ++#define ASPEED_I3C_AUTOCMD_1 0x24 ++#define ASPEED_I3C_AUTOCMD_2 0x28 ++#define ASPEED_I3C_AUTOCMD_3 0x2C ++#define ASPEED_I3C_AUTOCMD_4 0x30 ++#define ASPEED_I3C_AUTOCMD_5 0x34 ++#define ASPEED_I3C_AUTOCMD_6 0x38 ++#define ASPEED_I3C_AUTOCMD_7 0x3C ++ ++#define ASPEED_I3C_AUTOCMD_SEL_0_7 0x40 ++#define ASPEED_I3C_AUTOCMD_SEL_8_15 0x44 ++#define ASPEED_I3C_AUTOCMD_SEL_16_23 0x48 ++#define ASPEED_I3C_AUTOCMD_SEL_24_31 0x4C ++#define ASPEED_I3C_AUTOCMD_SEL_32_39 0x50 ++#define ASPEED_I3C_AUTOCMD_SEL_40_47 0x54 ++#define ASPEED_I3C_AUTOCMD_SEL_48_55 0x58 ++#define ASPEED_I3C_AUTOCMD_SEL_56_63 0x5C ++#define ASPEED_I3C_AUTOCMD_SEL_64_71 0x60 ++#define ASPEED_I3C_AUTOCMD_SEL_72_79 0x64 ++#define ASPEED_I3C_AUTOCMD_SEL_80_87 0x68 ++#define ASPEED_I3C_AUTOCMD_SEL_88_95 0x6C ++#define ASPEED_I3C_AUTOCMD_SEL_96_103 0x70 ++#define ASPEED_I3C_AUTOCMD_SEL_104_111 0x74 ++#define ASPEED_I3C_AUTOCMD_SEL_112_119 0x78 ++#define ASPEED_I3C_AUTOCMD_SEL_120_127 0x7C ++ ++#define ASPEED_I3C_WDMA_CTRL 0x80 ++#define ASPEED_I3C_WDMA_DBG_LO 0x84 ++#define ASPEED_I3C_WDMA_DBG_HI 0x88 ++#define ASPEED_I3C_RDMA_CTRL 0x90 ++#define ASPEED_I3C_RDMA_DBG_LO 0x94 ++#define ASPEED_I3C_RDMA_DBG_HI 0x98 ++#define I3C_DMA_DBG_LO_ABORT BIT(0) ++#define I3C_DMA_DBG_LO_BUSY BIT(1) ++#define I3C_DMA_DBG_LO_DONE BIT(2) ++#define I3C_DMA_DBG_LO_START BIT(3) ++#define I3C_DMA_DBG_LO_READY BIT(4) ++#define I3C_DMA_DBG_LO_VALID BIT(5) ++#define I3C_DMA_DBG_LO_EN BIT(10) ++ ++#define ASPEED_I3C_RING_STATUS 0x9C ++#define I3C_RING_IDLE 0 ++#define I3C_RING_GET_TRANSFER 1 ++#define I3C_RING_GET_TX_DATA 2 ++#define I3C_RING_CMD_PROCESS 3 ++#define I3C_RING_WRITE_RX_DATA 4 ++#define I3C_RING_WRITE_RESPONSE 5 ++#define I3C_RING_UPDATE_CR_PTR 6 ++#define I3C_RING_WRITE_IBI_STS 7 ++#define I3C_RING_WRITE_IBI_DAT 8 ++#define I3C_RING_UPDATE_IBI_PTR 9 ++#define I3C_RING_ABORT 10 ++#define I3C_RING_SLV_WRITE_DATA 11 ++#define I3C_RING_SLV_WRITE_RESPONSE 12 ++#define I3C_RING_SLV_UPDATE_PT 13 ++ ++#define ASPEED_I3C_SLV_CHAR_CTRL 0xA0 ++#define ASPEED_I3C_SLV_CHAR_CTRL_DCR GENMASK(23, 16) ++#define ASPEED_I3C_SLV_CHAR_CTRL_BCR GENMASK(15, 8) ++#define SLV_BCR_DEVICE_ROLE GENMASK(7, 6) ++#define ASPEED_I3C_SLV_CHAR_CTRL_STATIC_ADDR_EN BIT(7) ++#define ASPEED_I3C_SLV_CHAR_CTRL_STATIC_ADDR GENMASK(6, 0) ++#define SLV_PID_HI(x) (((x) >> 32) & GENMASK(15, 0)) ++#define SLV_PID_LO(x) ((x) & GENMASK(31, 0)) ++#define ASPEED_I3C_SLV_PID_LO 0xA4 ++#define ASPEED_I3C_SLV_PID_HI 0xA8 ++#define ASPEED_I3C_SLV_FSM 0xAC ++#define ASPEED_I3C_SLV_CAP_CTRL 0xB0 ++#define ASPEED_I3C_SLV_CAP_CTRL_PEC_EN BIT(31) ++#define ASPEED_I3C_SLV_CAP_CTRL_HAIT_IF_IBI_ERR BIT(30) ++#define ASPEED_I3C_SLV_CAP_CTRL_ACCEPT_CR BIT(16) ++#define ASPEED_I3C_SLV_CAP_CTRL_HJ_REQ BIT(10) ++#define ASPEED_I3C_SLV_CAP_CTRL_MR_REQ BIT(9) ++#define ASPEED_I3C_SLV_CAP_CTRL_IBI_REQ BIT(8) ++#define ASPEED_I3C_SLV_CAP_CTRL_HJ_WAIT BIT(6) ++#define ASPEED_I3C_SLV_CAP_CTRL_MR_WAIT BIT(5) ++#define ASPEED_I3C_SLV_CAP_CTRL_IBI_WAIT BIT(4) ++#define ASPEED_I3C_SLV_CAP_CTRL_NOTSUP_DEF_BYTE BIT(1) ++#define ASPEED_I3C_SLV_CAP_CTRL_I2C_DEV BIT(0) ++/* CCC related registers */ ++#define ASPEED_I3C_SLV_STS1 0xB4 ++#define ASPEED_I3C_SLV_STS1_IBI_PAYLOAD_SIZE GENMASK(31, 24) ++#define ASPEED_I3C_SLV_STS1_RSTACT GENMASK(22, 16) ++/* the parameters for the HDR-DDR Data Transfer Early Termination procedure*/ ++#define ASPEED_I3C_SLV_STS1_ETP_ACK_CAP BIT(15) ++#define ASPEED_I3C_SLV_STS1_ETP_W_REQ BIT(14) ++#define ASPEED_I3C_SLV_STS1_ETP_CRC GENMASK(13, 12) ++#define ASPEED_I3C_SLV_STS1_ENDXFER_CONFIRM BIT(11) ++#define ASPEED_I3C_SLV_STS1_ENTER_TEST_MDOE BIT(8) ++#define ASPEED_I3C_SLV_STS1_HJ_EN BIT(6) ++#define ASPEED_I3C_SLV_STS1_CR_EN BIT(5) ++#define ASPEED_I3C_SLV_STS1_IBI_EN BIT(4) ++#define ASPEED_I3C_SLV_STS1_HJ_DONE BIT(2) ++#define ASPEED_I3C_SLV_STS1_CR_DONE BIT(1) ++#define ASPEED_I3C_SLV_STS1_IBI_DONE BIT(0) ++#define ASPEED_I3C_SLV_STS2 0xB8 ++#define ASPEED_I3C_SLV_STS2_MWL GENMASK(31, 16) ++#define ASPEED_I3C_SLV_STS2_MRL GENMASK(15, 0) ++#define ASPEED_I3C_SLV_STS3_GROUP_ADDR 0xBC ++#define ASPEED_I3C_SLV_STS3_GROUP3_VALID BIT(31) ++#define ASPEED_I3C_SLV_STS3_GROUP3_ADDR GENMASK(30, 24) ++#define ASPEED_I3C_SLV_STS3_GROUP2_VALID BIT(23) ++#define ASPEED_I3C_SLV_STS3_GROUP2_ADDR GENMASK(22, 16) ++#define ASPEED_I3C_SLV_STS3_GROUP1_VALID BIT(15) ++#define ASPEED_I3C_SLV_STS3_GROUP1_ADDR GENMASK(14, 8) ++#define ASPEED_I3C_SLV_STS3_GROUP0_VALID BIT(7) ++#define ASPEED_I3C_SLV_STS3_GROUP0_ADDR GENMASK(6, 0) ++#define ASPEED_I3C_SLV_STS4_RSTACT_TIME 0xC0 ++#define ASPEED_I3C_SLV_STS4_DBG_NET GENMASK(23, 16) ++#define ASPEED_I3C_SLV_STS4_WHOLE_CHIP GENMASK(15, 8) ++#define ASPEED_I3C_SLV_STS4_I3C GENMASK(7, 0) ++#define ASPEED_I3C_SLV_STS5_GETMXDS_RW 0xC4 ++#define ASPEED_I3C_SLV_STS5_MAXWR GENMASK(15, 8) ++#define ASPEED_I3C_SLV_STS5_MAXRD GENMASK(7, 0) ++#define ASPEED_I3C_SLV_STS6_GETMXDS 0xC8 ++#define ASPEED_I3C_SLV_STS6_FORMAT BIT(24) ++#define ASPEED_I3C_SLV_STS6_MAXRD_TURN_H GENMASK(23, 16) ++#define ASPEED_I3C_SLV_STS6_MAXRD_TURN_M GENMASK(15, 8) ++#define ASPEED_I3C_SLV_STS6_MAXRD_TURN_L GENMASK(7, 0) ++#define ASPEED_I3C_SLV_STS7_GETSTATUS 0xCC ++#define ASPEED_I3C_SLV_STS7_PRECR GENMASK(31, 16) ++#define ASPEED_I3C_SLV_STS7_TGT GENMASK(15, 0) ++#define ASPEED_I3C_SLV_STS8_GETCAPS_TGT 0xD0 ++#define ASPEED_I3C_SLV_STS9_GETCAPS_VT_CR 0xD4 ++#define ASPEED_I3C_SLV_STS7_VT GENMASK(31, 16) ++#define ASPEED_I3C_SLV_STS7_CR GENMASK(15, 0) ++ ++#define ASPEED_I3C_QUEUE_PTR0 0xD8 ++#define QUEUE_PTR0_TX_R(q) FIELD_GET(GENMASK(24, 20), q) ++#define QUEUE_PTR0_TX_W(q) FIELD_GET(GENMASK(16, 12), q) ++#define QUEUE_PTR0_IBI_R(q) FIELD_GET(GENMASK(11, 10), q) ++#define QUEUE_PTR0_IBI_W(q) FIELD_GET(GENMASK(9, 8), q) ++#define QUEUE_PTR0_RESP_R(q) FIELD_GET(GENMASK(7, 6), q) ++#define QUEUE_PTR0_RESP_W(q) FIELD_GET(GENMASK(5, 4), q) ++#define QUEUE_PTR0_CMD_R(q) FIELD_GET(GENMASK(3, 2), q) ++#define QUEUE_PTR0_CMD_W(q) FIELD_GET(GENMASK(1, 0), q) ++ ++#define ASPEED_I3C_QUEUE_PTR1 0xDC ++#define QUEUE_PTR1_IBI_DATA_R(q) FIELD_GET(GENMASK(28, 24), q) ++#define QUEUE_PTR1_IBI_DATA_W(q) FIELD_GET(GENMASK(20, 16), q) ++#define QUEUE_PTR1_RX_R(q) FIELD_GET(GENMASK(12, 8), q) ++#define QUEUE_PTR1_RX_W(q) FIELD_GET(GENMASK(4, 0), q) ++ ++#define ASPEED_I3C_INTR_STATUS 0xE0 ++#define ASPEED_I3C_INTR_STATUS_ENABLE 0xE4 ++#define ASPEED_I3C_INTR_SIGNAL_ENABLE 0xE8 ++#define ASPEED_I3C_INTR_FORCE 0xEC ++#define ASPEED_I3C_INTR_I2C_SDA_STUCK_LOW BIT(14) ++#define ASPEED_I3C_INTR_I3C_SDA_STUCK_HIGH BIT(13) ++#define ASPEED_I3C_INTR_I3C_SDA_STUCK_LOW BIT(12) ++#define ASPEED_I3C_INTR_MST_INTERNAL_DONE BIT(10) ++#define ASPEED_I3C_INTR_MST_DDR_READ_DONE BIT(9) ++#define ASPEED_I3C_INTR_MST_DDR_WRITE_DONE BIT(8) ++#define ASPEED_I3C_INTR_MST_IBI_DONE BIT(7) ++#define ASPEED_I3C_INTR_MST_READ_DONE BIT(6) ++#define ASPEED_I3C_INTR_MST_WRITE_DONE BIT(5) ++#define ASPEED_I3C_INTR_MST_DAA_DONE BIT(4) ++#define ASPEED_I3C_INTR_SLV_SCL_STUCK BIT(1) ++#define ASPEED_I3C_INTR_TGRST BIT(0) ++ ++#define ASPEED_I3C_INTR_SUM_STATUS 0xF0 ++#define ASPEED_INTR_SUM_INHOUSE BIT(3) ++#define ASPEED_INTR_SUM_RHS BIT(2) ++#define ASPEED_INTR_SUM_PIO BIT(1) ++#define ASPEED_INTR_SUM_CAP BIT(0) ++ ++#define ASPEED_I3C_INTR_RENEW 0xF4 ++ ++/* Aspeed Phy register */ ++#define ast_phy_read(r) readl(hci->PHY_regs + (r)) ++#define ast_phy_write(r, v) writel(v, hci->PHY_regs + (r)) ++ ++#define PHY_SW_FORCE_CTRL 0x4 ++#define PHY_SW_FORCE_CTRL_SCL_IN_EN BIT(31) ++#define PHY_SW_FORCE_CTRL_SCL_OUT_EN BIT(30) ++#define PHY_SW_FORCE_CTRL_SCL_OE_EN BIT(29) ++#define PHY_SW_FORCE_CTRL_SCL_PU_EN BIT(28) ++#define PHY_SW_FORCE_CTRL_SDA_IN_EN BIT(27) ++#define PHY_SW_FORCE_CTRL_SDA_OUT_EN BIT(26) ++#define PHY_SW_FORCE_CTRL_SDA_OE_EN BIT(25) ++#define PHY_SW_FORCE_CTRL_SDA_PU_EN BIT(24) ++#define PHY_SW_FORCE_CTRL_SCL_IN_VAL BIT(13) ++#define PHY_SW_FORCE_CTRL_SCL_OUT_VAL BIT(12) ++#define PHY_SW_FORCE_CTRL_SCL_OE_VAL BIT(11) ++#define PHY_SW_FORCE_CTRL_SCL_PU_VAL GENMASK(10, 8) ++#define PHY_SW_FORCE_CTRL_SDA_IN_VAL BIT(5) ++#define PHY_SW_FORCE_CTRL_SDA_OUT_VAL BIT(4) ++#define PHY_SW_FORCE_CTRL_SDA_OE_VAL BIT(3) ++#define PHY_SW_FORCE_CTRL_SDA_PU_VAL GENMASK(2, 0) ++ ++/* I2C FM: 400K */ ++#define PHY_I2C_FM_CTRL0 0x8 ++#define PHY_I2C_FM_CTRL0_CAS GENMASK(25, 16) ++#define PHY_I2C_FM_CTRL0_SU_STO GENMASK(9, 0) ++#define PHY_I2C_FM_CTRL1 0xC ++#define PHY_I2C_FM_CTRL1_SCL_H GENMASK(25, 16) ++#define PHY_I2C_FM_CTRL1_SCL_L GENMASK(9, 0) ++#define PHY_I2C_FM_CTRL2 0x10 ++#define PHY_I2C_FM_CTRL2_ACK_H GENMASK(25, 16) ++#define PHY_I2C_FM_CTRL2_ACK_L GENMASK(9, 0) ++#define PHY_I2C_FM_CTRL3 0x14 ++#define PHY_I2C_FM_CTRL3_HD_DAT GENMASK(25, 16) ++#define PHY_I2C_FM_CTRL3_AHD_DAT GENMASK(9, 0) ++ ++#define PHY_I2C_FM_DEFAULT_CAS_NS 1130 ++#define PHY_I2C_FM_DEFAULT_SU_STO_NS 1370 ++#define PHY_I2C_FM_DEFAULT_SCL_H_NS 1130 ++#define PHY_I2C_FM_DEFAULT_SCL_L_NS 1370 ++#define PHY_I2C_FM_DEFAULT_HD_DAT 10 ++#define PHY_I2C_FM_DEFAULT_AHD_DAT 10 ++ ++/* I2C FMP: 1M */ ++#define PHY_I2C_FMP_CTRL0 0x18 ++#define PHY_I2C_FMP_CTRL0_CAS GENMASK(25, 16) ++#define PHY_I2C_FMP_CTRL0_SU_STO GENMASK(9, 0) ++#define PHY_I2C_FMP_CTRL1 0x1C ++#define PHY_I2C_FMP_CTRL1_SCL_H GENMASK(25, 16) ++#define PHY_I2C_FMP_CTRL1_SCL_L GENMASK(9, 0) ++#define PHY_I2C_FMP_CTRL2 0x20 ++#define PHY_I2C_FMP_CTRL2_ACK_H GENMASK(25, 16) ++#define PHY_I2C_FMP_CTRL2_ACK_L GENMASK(9, 0) ++#define PHY_I2C_FMP_CTRL3 0x24 ++#define PHY_I2C_FMP_CTRL3_HD_DAT GENMASK(25, 16) ++#define PHY_I2C_FMP_CTRL3_AHD_DAT GENMASK(9, 0) ++ ++#define PHY_I2C_FMP_DEFAULT_CAS_NS 380 ++#define PHY_I2C_FMP_DEFAULT_SU_STO_NS 620 ++#define PHY_I2C_FMP_DEFAULT_SCL_H_NS 380 ++#define PHY_I2C_FMP_DEFAULT_SCL_L_NS 620 ++#define PHY_I2C_FMP_DEFAULT_HD_DAT 10 ++#define PHY_I2C_FMP_DEFAULT_AHD_DAT 10 ++ ++/* I3C OD */ ++#define PHY_I3C_OD_CTRL0 0x28 ++#define PHY_I3C_OD_CTRL0_CAS GENMASK(25, 16) ++#define PHY_I3C_OD_CTRL0_CBP GENMASK(9, 0) ++#define PHY_I3C_OD_CTRL1 0x2C ++#define PHY_I3C_OD_CTRL1_SCL_H GENMASK(25, 16) ++#define PHY_I3C_OD_CTRL1_SCL_L GENMASK(9, 0) ++#define PHY_I3C_OD_CTRL2 0x30 ++#define PHY_I3C_OD_CTRL2_ACK_H GENMASK(25, 16) ++#define PHY_I3C_OD_CTRL2_ACK_L GENMASK(9, 0) ++#define PHY_I3C_OD_CTRL3 0x34 ++#define PHY_I3C_OD_CTRL3_HD_DAT GENMASK(25, 16) ++#define PHY_I3C_OD_CTRL3_AHD_DAT GENMASK(9, 0) ++ ++/* ++ * MIPI I3C minimum timing for the Open-Drain start/stop, expressed in tenths ++ * of a nanosecond to keep the fractional spec values (tCAS >= 38.4ns, ++ * tCBP >= 19.2ns) in integer arithmetic. ++ */ ++#define PHY_I3C_OD_MIN_CAS_NS_X10 384 ++#define PHY_I3C_OD_MIN_CBP_NS_X10 192 ++#define PHY_I3C_OD_DEFAULT_SCL_H_NS 380 ++#define PHY_I3C_OD_DEFAULT_SCL_L_NS 620 ++#define PHY_I3C_OD_DEFAULT_HD_DAT 10 ++#define PHY_I3C_OD_DEFAULT_AHD_DAT 10 ++ ++/* I3C PP SDR0 */ ++#define PHY_I3C_SDR0_CTRL0 0x38 ++#define PHY_I3C_SDR0_CTRL0_SCL_H GENMASK(25, 16) ++#define PHY_I3C_SDR0_CTRL0_SCL_L GENMASK(9, 0) ++#define PHY_I3C_SDR0_CTRL1 0x3C ++#define PHY_I3C_SDR0_CTRL1_TBIT_H GENMASK(25, 16) ++#define PHY_I3C_SDR0_CTRL1_TBIT_L GENMASK(9, 0) ++#define PHY_I3C_SDR0_CTRL2 0x40 ++#define PHY_I3C_SDR0_CTRL2_HD_PP GENMASK(25, 16) ++#define PHY_I3C_SDR0_CTRL2_TBIT_HD_PP GENMASK(9, 0) ++ ++/* 1MHz */ ++#define PHY_I3C_SDR0_DEFAULT_SCL_H_NS 380 ++#define PHY_I3C_SDR0_DEFAULT_SCL_L_NS 620 ++#define PHY_I3C_SDR0_DEFAULT_TBIT_H_NS 380 ++#define PHY_I3C_SDR0_DEFAULT_TBIT_L_NS 620 ++#define PHY_I3C_SDR0_DEFAULT_HD_PP_NS 10 ++#define PHY_I3C_SDR0_DEFAULT_TBIT_HD_PP_NS 10 ++ ++#define PHY_I3C_CTRL0_OFFSET 0x0 ++#define PHY_I3C_CTRL1_OFFSET 0x4 ++#define PHY_I3C_CTRL2_OFFSET 0x8 ++/* I3C PP SDR1 */ ++#define PHY_I3C_SDR1_CTRL0 0x44 ++#define PHY_I3C_SDR1_CTRL0_SCL_H GENMASK(25, 16) ++#define PHY_I3C_SDR1_CTRL0_SCL_L GENMASK(9, 0) ++#define PHY_I3C_SDR1_CTRL1 0x48 ++#define PHY_I3C_SDR1_CTRL1_TBIT_H GENMASK(25, 16) ++#define PHY_I3C_SDR1_CTRL1_TBIT_L GENMASK(9, 0) ++#define PHY_I3C_SDR1_CTRL2 0x4C ++#define PHY_I3C_SDR1_CTRL2_HD_PP GENMASK(25, 16) ++#define PHY_I3C_SDR1_CTRL2_TBIT_HD_PP GENMASK(9, 0) ++/* I3C PP SDR2 */ ++#define PHY_I3C_SDR2_CTRL0 0x50 ++#define PHY_I3C_SDR2_CTRL0_SCL_H GENMASK(25, 16) ++#define PHY_I3C_SDR2_CTRL0_SCL_L GENMASK(9, 0) ++#define PHY_I3C_SDR2_CTRL1 0x54 ++#define PHY_I3C_SDR2_CTRL1_TBIT_H GENMASK(25, 16) ++#define PHY_I3C_SDR2_CTRL1_TBIT_L GENMASK(9, 0) ++#define PHY_I3C_SDR2_CTRL2 0x58 ++#define PHY_I3C_SDR2_CTRL2_HD_PP GENMASK(25, 16) ++#define PHY_I3C_SDR2_CTRL2_TBIT_HD_PP GENMASK(9, 0) ++/* I3C PP SDR3 */ ++#define PHY_I3C_SDR3_CTRL0 0x5C ++#define PHY_I3C_SDR3_CTRL0_SCL_H GENMASK(25, 16) ++#define PHY_I3C_SDR3_CTRL0_SCL_L GENMASK(9, 0) ++#define PHY_I3C_SDR3_CTRL1 0x60 ++#define PHY_I3C_SDR3_CTRL1_TBIT_H GENMASK(25, 16) ++#define PHY_I3C_SDR3_CTRL1_TBIT_L GENMASK(9, 0) ++#define PHY_I3C_SDR3_CTRL2 0x64 ++#define PHY_I3C_SDR3_CTRL2_HD_PP GENMASK(25, 16) ++#define PHY_I3C_SDR3_CTRL2_TBIT_HD_PP GENMASK(9, 0) ++/* I3C PP SDR4 */ ++#define PHY_I3C_SDR4_CTRL0 0x68 ++#define PHY_I3C_SDR4_CTRL0_SCL_H GENMASK(25, 16) ++#define PHY_I3C_SDR4_CTRL0_SCL_L GENMASK(9, 0) ++#define PHY_I3C_SDR4_CTRL1 0x6C ++#define PHY_I3C_SDR4_CTRL1_TBIT_H GENMASK(25, 16) ++#define PHY_I3C_SDR4_CTRL1_TBIT_L GENMASK(9, 0) ++#define PHY_I3C_SDR4_CTRL2 0x70 ++#define PHY_I3C_SDR4_CTRL2_HD_PP GENMASK(25, 16) ++#define PHY_I3C_SDR4_CTRL2_TBIT_HD_PP GENMASK(9, 0) ++/* I3C PP DDR */ ++#define PHY_I3C_DDR_CTRL0 0x74 ++#define PHY_I3C_DDR_CTRL0_SCL_H GENMASK(25, 16) ++#define PHY_I3C_DDR_CTRL0_SCL_L GENMASK(9, 0) ++#define PHY_I3C_DDR_CTRL1 0x78 ++#define PHY_I3C_DDR_CTRL1_TBIT_H GENMASK(25, 16) ++#define PHY_I3C_DDR_CTRL1_TBIT_L GENMASK(9, 0) ++#define PHY_I3C_DDR_CTRL2 0x7C ++#define PHY_I3C_DDR_CTRL2_HD_PP GENMASK(25, 16) ++#define PHY_I3C_DDR_CTRL2_TBIT_HD_PP GENMASK(9, 0) ++ ++/* 1MHz */ ++#define PHY_I3C_DDR_DEFAULT_SCL_H_NS 380 ++#define PHY_I3C_DDR_DEFAULT_SCL_L_NS 620 ++#define PHY_I3C_DDR_DEFAULT_TBIT_H_NS 380 ++#define PHY_I3C_DDR_DEFAULT_TBIT_L_NS 620 ++#define PHY_I3C_DDR_DEFAULT_HD_PP_NS 10 ++#define PHY_I3C_DDR_DEFAULT_TBIT_HD_PP_NS 10 ++ ++#define PHY_I3C_SR_P_PREPARE_CTRL 0x80 ++#define PHY_I3C_SR_P_PREPARE_CTRL_HD GENMASK(25, 16) ++#define PHY_I3C_SR_P_PREPARE_CTRL_SCL_L GENMASK(9, 0) ++#define PHY_I3C_SR_P_DEFAULT_HD_NS 16 ++#define PHY_I3C_SR_P_DEFAULT_SCL_L_NS 40 ++ ++#define PHY_PULLUP_EN 0x98 ++#define PHY_PULLUP_EN_SCL GENMASK(14, 12) ++#define PHY_PULLUP_EN_SDA GENMASK(10, 8) ++#define PHY_PULLUP_EN_DDR_SCL GENMASK(6, 4) ++#define PHY_PULLUP_EN_DDR_SDA GENMASK(2, 0) ++ ++#define PHY_I3C_OD_CTRL4 0xD8 ++// SDA drive high (push-pull) time After tCBP ++#define PHY_I3C_OD_CTRL4_DAP GENMASK(26, 16) ++#define PHY_I3C_OD_DEFAULT_DAP_NS 12 ++ ++static inline unsigned int aspeed_get_avail_tx_entries(struct i3c_hci *hci) ++{ ++ unsigned int queue_ptr, entries; ++ ++ queue_ptr = ast_inhouse_read(ASPEED_I3C_QUEUE_PTR0); ++ if (QUEUE_PTR0_TX_W(queue_ptr) >= QUEUE_PTR0_TX_R(queue_ptr)) ++ entries = 0x20 - (QUEUE_PTR0_TX_W(queue_ptr) - ++ QUEUE_PTR0_TX_R(queue_ptr)); ++ else ++ entries = QUEUE_PTR0_TX_R(queue_ptr) - QUEUE_PTR0_TX_W(queue_ptr); ++ ++ return entries; ++} ++ ++static inline unsigned int aspeed_get_received_rx_entries(struct i3c_hci *hci) ++{ ++ unsigned int queue_ptr, entries; ++ ++ queue_ptr = ast_inhouse_read(ASPEED_I3C_QUEUE_PTR1); ++ if (QUEUE_PTR1_RX_W(queue_ptr) >= QUEUE_PTR1_RX_R(queue_ptr)) ++ entries = QUEUE_PTR1_RX_W(queue_ptr) - QUEUE_PTR1_RX_R(queue_ptr); ++ else ++ entries = 0x20 - (QUEUE_PTR1_RX_R(queue_ptr) - ++ QUEUE_PTR1_RX_W(queue_ptr)); ++ ++ return entries; ++} ++ ++static inline unsigned int aspeed_get_i3c_revision_id(struct i3c_hci *hci) ++{ ++ return FIELD_GET(GENMASK(23, 16), hci->vendor_product_id); ++} ++ ++static inline void aspeed_i3c_ccc_handler(struct i3c_hci *hci, u8 ccc) ++{ ++ u32 reg; ++ u8 dynamic_addr; ++ ++ switch (ccc) { ++ case I3C_CCC_RSTDAA(true): ++ case I3C_CCC_RSTDAA(false): ++ hci->master.this->info.dyn_addr = 0; ++ break; ++ case I3C_CCC_ENTDAA: ++ case I3C_CCC_SETDASA: ++ case I3C_CCC_SETNEWDA: ++ case I3C_CCC_SETAASA: ++ reg = ast_inhouse_read(ASPEED_I3C_STS); ++ if (reg & ASPEED_I3C_STS_SLV_DYNAMIC_ADDRESS_VALID) { ++ dynamic_addr = FIELD_GET(ASPEED_I3C_STS_SLV_DYNAMIC_ADDRESS, reg); ++ hci->master.this->info.dyn_addr = dynamic_addr; ++ } ++ break; ++ } ++} ++ ++#endif +diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c +--- a/drivers/iio/adc/aspeed_adc.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/iio/adc/aspeed_adc.c 2026-09-09 15:51:44.513241169 +0000 +@@ -72,6 +72,8 @@ + #define ASPEED_ADC_BAT_SENSING_ENABLE BIT(13) + #define ASPEED_ADC_CTRL_CHANNEL GENMASK(31, 16) + #define ASPEED_ADC_CTRL_CHANNEL_ENABLE(ch) FIELD_PREP(ASPEED_ADC_CTRL_CHANNEL, BIT(ch)) ++#define ADC_MASK(n) ((n) < 16 ? ((1U << (n)) - 1) : 0xFFFF) ++#define ASPEED_ADC_CTRL_CHANNELS_ENABLE(chs) FIELD_PREP(ASPEED_ADC_CTRL_CHANNEL, ADC_MASK(chs)) + + #define ASPEED_ADC_INIT_POLLING_TIME 500 + #define ASPEED_ADC_INIT_TIMEOUT 500000 +@@ -82,6 +84,8 @@ + */ + #define ASPEED_ADC_DEF_SAMPLING_RATE 65000 + ++static DEFINE_IDA(aspeed_adc_ida); ++ + struct aspeed_adc_trim_locate { + const unsigned int offset; + const unsigned int field; +@@ -95,6 +99,7 @@ + bool wait_init_sequence; + bool need_prescaler; + bool bat_sense_sup; ++ bool require_extra_eoc; + u8 scaler_bit_width; + unsigned int num_channels; + const struct aspeed_adc_trim_locate *trim_locate; +@@ -107,6 +112,7 @@ + + struct aspeed_adc_data { + struct device *dev; ++ int id; + const struct aspeed_adc_model_data *model_data; + void __iomem *base; + spinlock_t clk_lock; +@@ -119,6 +125,26 @@ + int cv; + bool battery_sensing; + struct adc_gain battery_mode_gain; ++ unsigned int required_eoc_num; ++ u16 *upper_bound; ++ u16 *lower_bound; ++ bool *upper_en; ++ bool *lower_en; ++}; ++ ++static const struct iio_event_spec aspeed_adc_events[] = { ++ { ++ .type = IIO_EV_TYPE_THRESH, ++ .dir = IIO_EV_DIR_RISING, ++ .mask_separate = ++ BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), ++ }, ++ { ++ .type = IIO_EV_TYPE_THRESH, ++ .dir = IIO_EV_DIR_FALLING, ++ .mask_separate = ++ BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), ++ }, + }; + + #define ASPEED_CHAN(_idx, _data_reg_addr) { \ +@@ -130,6 +156,8 @@ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ ++ .event_spec = aspeed_adc_events, \ ++ .num_event_specs = ARRAY_SIZE(aspeed_adc_events), \ + } + + static const struct iio_chan_spec aspeed_adc_iio_channels[] = { +@@ -174,18 +202,11 @@ + + static int aspeed_adc_set_trim_data(struct iio_dev *indio_dev) + { +- struct device_node *syscon; + struct regmap *scu; + u32 scu_otp, trimming_val; + struct aspeed_adc_data *data = iio_priv(indio_dev); + +- syscon = of_find_node_by_name(NULL, "syscon"); +- if (syscon == NULL) { +- dev_warn(data->dev, "Couldn't find syscon node\n"); +- return -EOPNOTSUPP; +- } +- scu = syscon_node_to_regmap(syscon); +- of_node_put(syscon); ++ scu = syscon_regmap_lookup_by_phandle(data->dev->of_node, "aspeed,scu"); + if (IS_ERR(scu)) { + dev_warn(data->dev, "Failed to get syscon regmap\n"); + return -EOPNOTSUPP; +@@ -276,36 +297,68 @@ + return 0; + } + ++static int aspeed_adc_get_voltage_raw(struct aspeed_adc_data *data, struct iio_chan_spec const *chan) ++{ ++ int val; ++ ++ val = readw(data->base + chan->address); ++ dev_dbg(data->dev, ++ "%d upper_bound: %d %x, lower_bound: %d %x, delay: %d * %d ns", ++ chan->channel, data->upper_en[chan->channel], ++ data->upper_bound[chan->channel], data->lower_en[chan->channel], ++ data->lower_bound[chan->channel], data->sample_period_ns, ++ data->required_eoc_num); ++ if (data->upper_en[chan->channel]) { ++ if (val >= data->upper_bound[chan->channel]) { ++ ndelay(data->sample_period_ns * ++ data->required_eoc_num); ++ val = readw(data->base + chan->address); ++ } ++ } ++ if (data->lower_en[chan->channel]) { ++ if (val <= data->lower_bound[chan->channel]) { ++ ndelay(data->sample_period_ns * ++ data->required_eoc_num); ++ val = readw(data->base + chan->address); ++ } ++ } ++ return val; ++} ++ + static int aspeed_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) + { + struct aspeed_adc_data *data = iio_priv(indio_dev); +- u32 adc_engine_control_reg_val; ++ u32 engine_ctrl_tmp_val, reg_val; + + switch (mask) { + case IIO_CHAN_INFO_RAW: +- if (data->battery_sensing && chan->channel == 7) { +- adc_engine_control_reg_val = +- readl(data->base + ASPEED_REG_ENGINE_CONTROL); +- writel(adc_engine_control_reg_val | +- FIELD_PREP(ASPEED_ADC_CH7_MODE, +- ASPEED_ADC_CH7_BAT) | +- ASPEED_ADC_BAT_SENSING_ENABLE, +- data->base + ASPEED_REG_ENGINE_CONTROL); ++ if (data->model_data->bat_sense_sup && ++ chan->channel == data->model_data->num_channels - 1) { ++ engine_ctrl_tmp_val = readl(data->base + ASPEED_REG_ENGINE_CONTROL); ++ reg_val = engine_ctrl_tmp_val & ++ ~ASPEED_ADC_CTRL_CHANNELS_ENABLE(data->model_data->num_channels); ++ reg_val |= ASPEED_ADC_CTRL_CHANNEL_ENABLE(chan->channel); ++ if (data->battery_sensing) ++ reg_val |= FIELD_PREP(ASPEED_ADC_CH7_MODE, ASPEED_ADC_CH7_BAT) | ++ ASPEED_ADC_BAT_SENSING_ENABLE; ++ writel(reg_val, data->base + ASPEED_REG_ENGINE_CONTROL); + /* + * After enable battery sensing mode need to wait some time for adc stable + * Experiment result is 1ms. + */ + mdelay(1); +- *val = readw(data->base + chan->address); +- *val = (*val * data->battery_mode_gain.mult) / +- data->battery_mode_gain.div; ++ *val = aspeed_adc_get_voltage_raw(data, chan); ++ if (data->battery_sensing) ++ *val = (*val * data->battery_mode_gain.mult) / ++ data->battery_mode_gain.div; + /* Restore control register value */ +- writel(adc_engine_control_reg_val, ++ writel(engine_ctrl_tmp_val, + data->base + ASPEED_REG_ENGINE_CONTROL); +- } else +- *val = readw(data->base + chan->address); ++ } else { ++ *val = aspeed_adc_get_voltage_raw(data, chan); ++ } + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OFFSET: +@@ -368,9 +421,106 @@ + return 0; + } + ++static int aspeed_adc_read_event_config(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir) ++{ ++ struct aspeed_adc_data *data = iio_priv(indio_dev); ++ ++ switch (dir) { ++ case IIO_EV_DIR_RISING: ++ return data->upper_en[chan->channel]; ++ case IIO_EV_DIR_FALLING: ++ return data->lower_en[chan->channel]; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int aspeed_adc_write_event_config(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir, ++ int state) ++{ ++ struct aspeed_adc_data *data = iio_priv(indio_dev); ++ ++ switch (dir) { ++ case IIO_EV_DIR_RISING: ++ data->upper_en[chan->channel] = state ? 1 : 0; ++ break; ++ case IIO_EV_DIR_FALLING: ++ data->lower_en[chan->channel] = state ? 1 : 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_adc_write_event_value(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir, ++ enum iio_event_info info, int val, ++ int val2) ++{ ++ struct aspeed_adc_data *data = iio_priv(indio_dev); ++ ++ if (info != IIO_EV_INFO_VALUE) ++ return -EINVAL; ++ ++ switch (dir) { ++ case IIO_EV_DIR_RISING: ++ if (val >= BIT(ASPEED_RESOLUTION_BITS)) ++ return -EINVAL; ++ data->upper_bound[chan->channel] = val; ++ break; ++ case IIO_EV_DIR_FALLING: ++ data->lower_bound[chan->channel] = val; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_adc_read_event_value(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir, ++ enum iio_event_info info, int *val, ++ int *val2) ++{ ++ struct aspeed_adc_data *data = iio_priv(indio_dev); ++ ++ if (info != IIO_EV_INFO_VALUE) ++ return -EINVAL; ++ ++ switch (dir) { ++ case IIO_EV_DIR_RISING: ++ *val = data->upper_bound[chan->channel]; ++ break; ++ case IIO_EV_DIR_FALLING: ++ *val = data->lower_bound[chan->channel]; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return IIO_VAL_INT; ++} ++ + static const struct iio_info aspeed_adc_iio_info = { + .read_raw = aspeed_adc_read_raw, + .write_raw = aspeed_adc_write_raw, ++ .read_event_config = &aspeed_adc_read_event_config, ++ .write_event_config = &aspeed_adc_write_event_config, ++ .read_event_value = &aspeed_adc_read_event_value, ++ .write_event_value = &aspeed_adc_write_event_value, + .debugfs_reg_access = aspeed_adc_reg_access, + }; + +@@ -381,6 +531,13 @@ + clk_hw_unregister_fixed_factor(clk); + } + ++static void aspeed_adc_ida_remove(void *data) ++{ ++ struct aspeed_adc_data *priv_data = data; ++ ++ ida_simple_remove(&aspeed_adc_ida, priv_data->id); ++} ++ + static void aspeed_adc_reset_assert(void *data) + { + struct reset_control *rst = data; +@@ -475,6 +632,7 @@ + u32 adc_engine_control_reg_val; + unsigned long scaler_flags = 0; + char clk_name[32], clk_parent_name[32]; ++ const char *model_name; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data)); + if (!indio_dev) +@@ -489,12 +647,44 @@ + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + ++ data->upper_bound = devm_kzalloc(&pdev->dev, ++ sizeof(data->upper_bound) * ++ data->model_data->num_channels, ++ GFP_KERNEL); ++ if (!data->upper_bound) ++ return -ENOMEM; ++ data->upper_en = devm_kzalloc(&pdev->dev, ++ sizeof(data->upper_en) * ++ data->model_data->num_channels, ++ GFP_KERNEL); ++ if (!data->upper_en) ++ return -ENOMEM; ++ data->lower_bound = devm_kzalloc(&pdev->dev, ++ sizeof(data->lower_bound) * ++ data->model_data->num_channels, ++ GFP_KERNEL); ++ if (!data->lower_bound) ++ return -ENOMEM; ++ data->lower_en = devm_kzalloc(&pdev->dev, ++ sizeof(data->lower_en) * ++ data->model_data->num_channels, ++ GFP_KERNEL); ++ if (!data->lower_en) ++ return -ENOMEM; ++ data->id = ida_simple_get(&aspeed_adc_ida, 0, 0, GFP_KERNEL); ++ if (data->id < 0) ++ return data->id; ++ ret = devm_add_action_or_reset(data->dev, aspeed_adc_ida_remove, data); ++ if (ret) ++ return ret; ++ model_name = kasprintf(GFP_KERNEL, "%s-%d", ++ data->model_data->model_name, data->id); + /* Register ADC clock prescaler with source specified by device tree. */ + spin_lock_init(&data->clk_lock); + snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), "%s", + of_clk_get_parent_name(pdev->dev.of_node, 0)); + snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-fixed-div", +- data->model_data->model_name); ++ model_name); + data->fixed_div_clk = clk_hw_register_fixed_factor( + &pdev->dev, clk_name, clk_parent_name, 0, 1, 2); + if (IS_ERR(data->fixed_div_clk)) +@@ -509,7 +699,7 @@ + + if (data->model_data->need_prescaler) { + snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-prescaler", +- data->model_data->model_name); ++ model_name); + data->clk_prescaler = devm_clk_hw_register_divider( + &pdev->dev, clk_name, clk_parent_name, 0, + data->base + ASPEED_REG_CLOCK_CONTROL, 17, 15, 0, +@@ -525,7 +715,7 @@ + * setting to adjust the prescaler as well. + */ + snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-scaler", +- data->model_data->model_name); ++ model_name); + data->clk_scaler = devm_clk_hw_register_divider( + &pdev->dev, clk_name, clk_parent_name, scaler_flags, + data->base + ASPEED_REG_CLOCK_CONTROL, 0, +@@ -613,13 +803,25 @@ + + aspeed_adc_compensation(indio_dev); + /* Start all channels in normal mode. */ +- adc_engine_control_reg_val = +- readl(data->base + ASPEED_REG_ENGINE_CONTROL); +- adc_engine_control_reg_val |= ASPEED_ADC_CTRL_CHANNEL; ++ adc_engine_control_reg_val = readl(data->base + ASPEED_REG_ENGINE_CONTROL); ++ /* Disable the last channel when the controller supports battery sensing */ ++ if (data->model_data->bat_sense_sup) ++ adc_engine_control_reg_val |= ++ ASPEED_ADC_CTRL_CHANNELS_ENABLE(data->model_data->num_channels - 1); ++ else ++ adc_engine_control_reg_val |= ++ ASPEED_ADC_CTRL_CHANNELS_ENABLE(data->model_data->num_channels); + writel(adc_engine_control_reg_val, + data->base + ASPEED_REG_ENGINE_CONTROL); +- +- indio_dev->name = data->model_data->model_name; ++ adc_engine_control_reg_val = ++ FIELD_GET(ASPEED_ADC_CTRL_CHANNEL, ++ readl(data->base + ASPEED_REG_ENGINE_CONTROL)); ++ data->required_eoc_num = hweight_long(adc_engine_control_reg_val); ++ if (data->model_data->require_extra_eoc && ++ (adc_engine_control_reg_val & ++ BIT(data->model_data->num_channels - 1))) ++ data->required_eoc_num += 12; ++ indio_dev->name = model_name; + indio_dev->info = &aspeed_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = data->battery_sensing ? +@@ -646,6 +848,16 @@ + .field = GENMASK(7, 4), + }; + ++static const struct aspeed_adc_trim_locate ast2700_adc0_trim = { ++ .offset = 0x820, ++ .field = GENMASK(3, 0), ++}; ++ ++static const struct aspeed_adc_trim_locate ast2700_adc1_trim = { ++ .offset = 0x820, ++ .field = GENMASK(7, 4), ++}; ++ + static const struct aspeed_adc_model_data ast2400_model_data = { + .model_name = "ast2400-adc", + .vref_fixed_mv = 2500, +@@ -654,6 +866,7 @@ + .need_prescaler = true, + .scaler_bit_width = 10, + .num_channels = 16, ++ .require_extra_eoc = 0, + }; + + static const struct aspeed_adc_model_data ast2500_model_data = { +@@ -666,6 +879,7 @@ + .scaler_bit_width = 10, + .num_channels = 16, + .trim_locate = &ast2500_adc_trim, ++ .require_extra_eoc = 0, + }; + + static const struct aspeed_adc_model_data ast2600_adc0_model_data = { +@@ -677,6 +891,7 @@ + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2600_adc0_trim, ++ .require_extra_eoc = 1, + }; + + static const struct aspeed_adc_model_data ast2600_adc1_model_data = { +@@ -688,6 +903,29 @@ + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2600_adc1_trim, ++ .require_extra_eoc = 1, ++}; ++ ++static const struct aspeed_adc_model_data ast2700_adc0_model_data = { ++ .model_name = "ast2700-adc0", ++ .min_sampling_rate = 10000, ++ .max_sampling_rate = 500000, ++ .wait_init_sequence = true, ++ .bat_sense_sup = true, ++ .scaler_bit_width = 16, ++ .num_channels = 8, ++ .trim_locate = &ast2700_adc0_trim, ++}; ++ ++static const struct aspeed_adc_model_data ast2700_adc1_model_data = { ++ .model_name = "ast2700-adc1", ++ .min_sampling_rate = 10000, ++ .max_sampling_rate = 500000, ++ .wait_init_sequence = true, ++ .bat_sense_sup = true, ++ .scaler_bit_width = 16, ++ .num_channels = 8, ++ .trim_locate = &ast2700_adc1_trim, + }; + + static const struct of_device_id aspeed_adc_matches[] = { +@@ -695,6 +933,8 @@ + { .compatible = "aspeed,ast2500-adc", .data = &ast2500_model_data }, + { .compatible = "aspeed,ast2600-adc0", .data = &ast2600_adc0_model_data }, + { .compatible = "aspeed,ast2600-adc1", .data = &ast2600_adc1_model_data }, ++ { .compatible = "aspeed,ast2700-adc0", .data = &ast2700_adc0_model_data }, ++ { .compatible = "aspeed,ast2700-adc1", .data = &ast2700_adc1_model_data }, + { } + }; + MODULE_DEVICE_TABLE(of, aspeed_adc_matches); +diff --git a/drivers/irqchip/irq-aspeed-e2m-ic.c b/drivers/irqchip/irq-aspeed-e2m-ic.c +--- a/drivers/irqchip/irq-aspeed-e2m-ic.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/irqchip/irq-aspeed-e2m-ic.c 2026-09-09 15:51:44.753237543 +0000 +@@ -0,0 +1,178 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Aspeed AST27XX E2M Interrupt Controller ++ * Copyright (C) 2023 ASPEED Technology Inc. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ASPEED_AST2700_E2M_IC_SHIFT 0 ++#define ASPEED_AST2700_E2M_IC_ENABLE \ ++ GENMASK(7, ASPEED_AST2700_E2M_IC_SHIFT) ++#define ASPEED_AST2700_E2M_IC_NUM_IRQS 8 ++#define ASPEED_AST2700_E2M_IC_EN_REG 0x14 ++#define ASPEED_AST2700_E2M_IC_STS_REG 0x18 ++ ++struct aspeed_e2m_ic { ++ unsigned long irq_enable; ++ unsigned long irq_shift; ++ unsigned int num_irqs; ++ unsigned int reg; ++ unsigned int en_reg; ++ unsigned int sts_reg; ++ struct regmap *e2m; ++ struct irq_domain *irq_domain; ++}; ++ ++static void aspeed_e2m_ic_irq_handler(struct irq_desc *desc) ++{ ++ unsigned int val; ++ unsigned long bit; ++ unsigned long enabled; ++ unsigned long max; ++ unsigned long status; ++ struct aspeed_e2m_ic *e2m_ic = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ unsigned int mask; ++ ++ chained_irq_enter(chip, desc); ++ ++ mask = e2m_ic->irq_enable; ++ regmap_read(e2m_ic->e2m, e2m_ic->en_reg, &val); ++ enabled = val & e2m_ic->irq_enable; ++ regmap_read(e2m_ic->e2m, e2m_ic->sts_reg, &val); ++ status = val & enabled; ++ ++ bit = e2m_ic->irq_shift; ++ max = e2m_ic->num_irqs + bit; ++ ++ for_each_set_bit_from(bit, &status, max) { ++ generic_handle_domain_irq(e2m_ic->irq_domain, bit - e2m_ic->irq_shift); ++ ++ regmap_write_bits(e2m_ic->e2m, e2m_ic->sts_reg, mask, BIT(bit)); ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void aspeed_e2m_ic_irq_mask(struct irq_data *data) ++{ ++ struct aspeed_e2m_ic *e2m_ic = irq_data_get_irq_chip_data(data); ++ unsigned int mask; ++ ++ mask = BIT(data->hwirq + e2m_ic->irq_shift); ++ regmap_update_bits(e2m_ic->e2m, e2m_ic->en_reg, mask, 0); ++} ++ ++static void aspeed_e2m_ic_irq_unmask(struct irq_data *data) ++{ ++ struct aspeed_e2m_ic *e2m_ic = irq_data_get_irq_chip_data(data); ++ unsigned int bit = BIT(data->hwirq + e2m_ic->irq_shift); ++ unsigned int mask; ++ ++ mask = bit; ++ regmap_update_bits(e2m_ic->e2m, e2m_ic->en_reg, mask, bit); ++} ++ ++static int aspeed_e2m_ic_irq_set_affinity(struct irq_data *data, ++ const struct cpumask *dest, ++ bool force) ++{ ++ return -EINVAL; ++} ++ ++static struct irq_chip aspeed_scu_ic_chip = { ++ .name = "aspeed-e2m-ic", ++ .irq_mask = aspeed_e2m_ic_irq_mask, ++ .irq_unmask = aspeed_e2m_ic_irq_unmask, ++ .irq_set_affinity = aspeed_e2m_ic_irq_set_affinity, ++}; ++ ++static int aspeed_e2m_ic_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops aspeed_e2m_ic_domain_ops = { ++ .map = aspeed_e2m_ic_map, ++}; ++ ++static int aspeed_e2m_ic_of_init_common(struct aspeed_e2m_ic *e2m_ic, ++ struct device_node *node) ++{ ++ int irq; ++ int rc = 0; ++ ++ if (!node->parent) { ++ rc = -ENODEV; ++ goto err; ++ } ++ ++ e2m_ic->e2m = syscon_node_to_regmap(node->parent); ++ if (IS_ERR(e2m_ic->e2m)) { ++ rc = PTR_ERR(e2m_ic->e2m); ++ goto err; ++ } ++ ++ /* Clear status and disable all interrupt */ ++ regmap_write_bits(e2m_ic->e2m, e2m_ic->sts_reg, ++ e2m_ic->irq_enable, e2m_ic->irq_enable); ++ regmap_write_bits(e2m_ic->e2m, e2m_ic->en_reg, ++ e2m_ic->irq_enable, 0); ++ ++ irq = irq_of_parse_and_map(node, 0); ++ if (!irq) { ++ rc = -EINVAL; ++ goto err; ++ } ++ ++ e2m_ic->irq_domain = irq_domain_add_linear(node, e2m_ic->num_irqs, ++ &aspeed_e2m_ic_domain_ops, ++ e2m_ic); ++ if (!e2m_ic->irq_domain) { ++ rc = -ENOMEM; ++ goto err; ++ } ++ ++ irq_set_chained_handler_and_data(irq, aspeed_e2m_ic_irq_handler, ++ e2m_ic); ++ ++ return 0; ++ ++err: ++ kfree(e2m_ic); ++ ++ return rc; ++} ++ ++static int __init aspeed_ast2700_e2m_ic_of_init(struct device_node *node, ++ struct device_node *parent) ++{ ++ struct aspeed_e2m_ic *e2m_ic = kzalloc(sizeof(*e2m_ic), GFP_KERNEL); ++ ++ if (!e2m_ic) ++ return -ENOMEM; ++ ++ e2m_ic->irq_enable = ASPEED_AST2700_E2M_IC_ENABLE; ++ e2m_ic->irq_shift = ASPEED_AST2700_E2M_IC_SHIFT; ++ e2m_ic->num_irqs = ASPEED_AST2700_E2M_IC_NUM_IRQS; ++ e2m_ic->en_reg = ASPEED_AST2700_E2M_IC_EN_REG; ++ e2m_ic->sts_reg = ASPEED_AST2700_E2M_IC_STS_REG; ++ ++ return aspeed_e2m_ic_of_init_common(e2m_ic, node); ++} ++ ++IRQCHIP_DECLARE(ast2700_e2m_ic, "aspeed,ast2700-e2m-ic", ++ aspeed_ast2700_e2m_ic_of_init); +diff --git a/drivers/irqchip/irq-aspeed-intc.c b/drivers/irqchip/irq-aspeed-intc.c +--- a/drivers/irqchip/irq-aspeed-intc.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/irqchip/irq-aspeed-intc.c 2026-09-09 15:51:44.760237437 +0000 +@@ -0,0 +1,327 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Aspeed Interrupt Controller. ++ * ++ * Copyright (C) 2023 ASPEED Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define INTC_INT_ENABLE_REG 0x00 ++#define INTC_INT_STATUS_REG 0x04 ++#define INTC_IRQS_PER_WORD 32 ++#define INTC_IRQ_BASE 192 ++ ++struct aspeed_intc_ic { ++ void __iomem *base; ++ raw_spinlock_t intc_lock; ++ struct irq_domain *irq_domain; ++ struct device_node *node; ++ unsigned int parent_irqs[INTC_IRQS_PER_WORD]; ++ unsigned int parent_irq_count; ++ struct kobject kobj; ++ struct list_head list; ++}; ++ ++static LIST_HEAD(aspeed_intc_instances); ++ ++/* ++ * INTC0 uses a 1:1 mapping between each parent GIC SPI line and one leaf ++ * interrupt bit. The leaf hwirq is derived directly from the parent GIC ++ * hwirq number, so no status register scan is needed (contrast with intc1 ++ * which aggregates multiple sources behind a single parent line). ++ */ ++static irqreturn_t aspeed_intc0_ic_irq_handler(int irq, void *dev_id) ++{ ++ struct aspeed_intc_ic *intc_ic = dev_id; ++ struct irq_data *irq_data = irq_get_irq_data(irq); ++ unsigned long hwirq; ++ ++ if (!irq_data || !intc_ic) { ++ pr_err("Invalid irq_data or intc_ic\n"); ++ return IRQ_NONE; ++ } ++ ++ if (irq_data->hwirq < INTC_IRQ_BASE + 32) { ++ pr_err("Invalid hwirq: %lu\n", irq_data->hwirq); ++ return IRQ_NONE; ++ } ++ hwirq = irq_data->hwirq - INTC_IRQ_BASE - 32; /* 32 is SPI offset */ ++ ++ generic_handle_domain_irq(intc_ic->irq_domain, hwirq); ++ ++ /* ++ * Serialize the status-clear to prevent potential race conditions ++ * when multiple interrupts are processed in a multi-core environment. ++ */ ++ raw_spin_lock(&intc_ic->intc_lock); ++ writel(BIT(hwirq), intc_ic->base + INTC_INT_STATUS_REG); ++ raw_spin_unlock(&intc_ic->intc_lock); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t aspeed_intc1_ic_irq_handler(int irq, void *dev_id) ++{ ++ struct aspeed_intc_ic *intc_ic = dev_id; ++ unsigned long bit, status; ++ ++ if (!intc_ic) { ++ pr_err("Invalid intc_ic\n"); ++ return IRQ_NONE; ++ } ++ ++ status = readl(intc_ic->base + INTC_INT_STATUS_REG); ++ if (!status) ++ return IRQ_NONE; ++ ++ for_each_set_bit(bit, &status, INTC_IRQS_PER_WORD) { ++ generic_handle_domain_irq(intc_ic->irq_domain, bit); ++ writel(BIT(bit), intc_ic->base + INTC_INT_STATUS_REG); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static void aspeed_intc_irq_mask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ unsigned int mask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ mask = readl(intc_ic->base + INTC_INT_ENABLE_REG) & ~BIT(data->hwirq); ++ writel(mask, intc_ic->base + INTC_INT_ENABLE_REG); ++} ++ ++static void aspeed_intc_irq_unmask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ unsigned int unmask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ unmask = readl(intc_ic->base + INTC_INT_ENABLE_REG) | BIT(data->hwirq); ++ writel(unmask, intc_ic->base + INTC_INT_ENABLE_REG); ++} ++ ++static void aspeed_intc_irq_print_chip(struct irq_data *data, struct seq_file *p) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ ++ if (intc_ic && intc_ic->node) ++ seq_printf(p, " %pOF", intc_ic->node); ++ else ++ seq_puts(p, " ASPEED INTC"); ++} ++ ++static struct irq_chip aspeed_intc_chip = { ++ .name = "ASPEED INTC", ++ .irq_mask = aspeed_intc_irq_mask, ++ .irq_unmask = aspeed_intc_irq_unmask, ++ .irq_print_chip = aspeed_intc_irq_print_chip, ++}; ++ ++static int aspeed_intc_ic_map_irq_domain(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &aspeed_intc_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops aspeed_intc_ic_irq_domain_ops = { ++ .map = aspeed_intc_ic_map_irq_domain, ++}; ++ ++static ssize_t irq_groups_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ struct aspeed_intc_ic *intc = ++ container_of(kobj, struct aspeed_intc_ic, kobj); ++ ssize_t len = 0; ++ ++ if (intc->parent_irq_count == 1) { ++ /* Aggregated: one parent line serves all children. */ ++ len += sysfs_emit_at(buf, len, "group 0: parent_irq=%u members=", ++ intc->parent_irqs[0]); ++ for (int hwirq = 0; hwirq < INTC_IRQS_PER_WORD; hwirq++) { ++ unsigned int virq = irq_find_mapping(intc->irq_domain, hwirq); ++ ++ if (virq) ++ len += sysfs_emit_at(buf, len, "%u ", virq); ++ } ++ len += sysfs_emit_at(buf, len, "\n"); ++ } else { ++ /* 1:1: each parent serves one child (derived from GIC hwirq). */ ++ for (unsigned int i = 0; i < intc->parent_irq_count; i++) { ++ struct irq_data *pdata = irq_get_irq_data(intc->parent_irqs[i]); ++ unsigned int virq; ++ unsigned long phw; ++ ++ if (!pdata) ++ continue; ++ phw = pdata->hwirq; ++ if (phw < INTC_IRQ_BASE + 32) ++ continue; ++ virq = irq_find_mapping(intc->irq_domain, ++ phw - INTC_IRQ_BASE - 32); ++ if (!virq) ++ continue; ++ len += sysfs_emit_at(buf, len, ++ "group %u: parent_irq=%u members=%u\n", ++ i, intc->parent_irqs[i], virq); ++ } ++ } ++ return len; ++} ++ ++static struct kobj_attribute aspeed_intc_irq_groups_attr = __ATTR_RO(irq_groups); ++ ++static struct attribute *aspeed_intc_attrs[] = { ++ &aspeed_intc_irq_groups_attr.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(aspeed_intc); ++ ++static const struct kobj_type aspeed_intc_kobj_type = { ++ .sysfs_ops = &kobj_sysfs_ops, ++ .default_groups = aspeed_intc_groups, ++}; ++ ++static int __init aspeed_intc_ic_of_init(struct device_node *node, ++ struct device_node *parent) ++{ ++ struct aspeed_intc_ic *intc_ic; ++ irq_handler_t handler; ++ int ret = 0; ++ int irq, irq_count = 0, i; ++ ++ intc_ic = kzalloc(sizeof(*intc_ic), GFP_KERNEL); ++ if (!intc_ic) ++ return -ENOMEM; ++ ++ intc_ic->base = of_iomap(node, 0); ++ if (!intc_ic->base) { ++ pr_err("Failed to iomap intc_ic base\n"); ++ ret = -ENOMEM; ++ goto err_free_ic; ++ } ++ intc_ic->node = node; ++ writel(0xffffffff, intc_ic->base + INTC_INT_STATUS_REG); ++ writel(0x0, intc_ic->base + INTC_INT_ENABLE_REG); ++ ++ irq_count = of_irq_count(node); ++ if (irq_count == 0) { ++ pr_err("Failed to get irq count\n"); ++ ret = -EINVAL; ++ goto err_iounmap; ++ } ++ ++ if (irq_count > INTC_IRQS_PER_WORD) { ++ pr_err("Too many parent IRQs: %d\n", irq_count); ++ ret = -EINVAL; ++ goto err_iounmap; ++ } ++ ++ intc_ic->irq_domain = ++ irq_domain_create_linear(of_fwnode_handle(node), ++ INTC_IRQS_PER_WORD, ++ &aspeed_intc_ic_irq_domain_ops, ++ intc_ic); ++ if (!intc_ic->irq_domain) { ++ ret = -ENOMEM; ++ goto err_iounmap; ++ } ++ ++ raw_spin_lock_init(&intc_ic->intc_lock); ++ ++ if (irq_count > 1) ++ handler = aspeed_intc0_ic_irq_handler; ++ else ++ handler = aspeed_intc1_ic_irq_handler; ++ ++ for (i = 0; i < irq_count; i++) { ++ irq = irq_of_parse_and_map(node, i); ++ if (!irq) { ++ pr_err("Failed to get irq number\n"); ++ ret = -EINVAL; ++ goto err_iounmap; ++ } else { ++ ret = request_irq(irq, handler, IRQF_NO_THREAD, ++ "aspeed-intc", intc_ic); ++ if (ret) { ++ pr_err("Failed to request IRQ %d\n", irq); ++ irq_dispose_mapping(irq); ++ goto err_iounmap; ++ } ++ ++ intc_ic->parent_irqs[intc_ic->parent_irq_count++] = irq; ++ } ++ } ++ ++ list_add_tail(&intc_ic->list, &aspeed_intc_instances); ++ ++ return 0; ++ ++err_iounmap: ++ for (i = 0; i < intc_ic->parent_irq_count; i++) { ++ free_irq(intc_ic->parent_irqs[i], intc_ic); ++ irq_dispose_mapping(intc_ic->parent_irqs[i]); ++ } ++ if (intc_ic->irq_domain) ++ irq_domain_remove(intc_ic->irq_domain); ++ iounmap(intc_ic->base); ++err_free_ic: ++ kfree(intc_ic); ++ return ret; ++} ++ ++/* ++ * IRQCHIP_DECLARE callbacks fire from init_IRQ(), which runs before ++ * kernel_kobj is created in core_initcall(ksysfs_init). Defer the sysfs ++ * setup until kernel_kobj is available so /sys/kernel/aspeed-intc-* can be ++ * created. The physical base address is appended to disambiguate multiple ++ * controllers that share a basename (e.g. nested LTPI controllers). ++ */ ++static int __init aspeed_intc_sysfs_init(void) ++{ ++ struct aspeed_intc_ic *intc; ++ struct resource res; ++ int ret; ++ ++ list_for_each_entry(intc, &aspeed_intc_instances, list) { ++ ret = of_address_to_resource(intc->node, 0, &res); ++ if (ret) { ++ pr_warn("Failed to get resource for %pOF: %d\n", ++ intc->node, ret); ++ continue; ++ } ++ ++ ret = kobject_init_and_add(&intc->kobj, &aspeed_intc_kobj_type, ++ kernel_kobj, "aspeed-intc-%pOFn@%llx", ++ intc->node, ++ (unsigned long long)res.start); ++ if (ret) { ++ pr_warn("Failed to create sysfs entry for %pOF: %d\n", ++ intc->node, ret); ++ kobject_put(&intc->kobj); ++ } ++ } ++ return 0; ++} ++late_initcall(aspeed_intc_sysfs_init); ++ ++IRQCHIP_DECLARE(ast2700_intc_ic, "aspeed,ast2700-intc-ic", aspeed_intc_ic_of_init); +diff --git a/drivers/irqchip/irq-aspeed-scu-ic.c b/drivers/irqchip/irq-aspeed-scu-ic.c +--- a/drivers/irqchip/irq-aspeed-scu-ic.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/irqchip/irq-aspeed-scu-ic.c 2026-09-09 15:51:44.764237376 +0000 +@@ -1,61 +1,76 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + /* +- * Aspeed AST24XX, AST25XX, and AST26XX SCU Interrupt Controller ++ * Aspeed AST24XX, AST25XX, AST26XX, and AST27XX SCU Interrupt Controller + * Copyright 2019 IBM Corporation + * + * Eddie James + */ + + #include ++#include + #include + #include + #include + #include +-#include ++#include + #include +-#include + +-#define ASPEED_SCU_IC_REG 0x018 +-#define ASPEED_SCU_IC_SHIFT 0 +-#define ASPEED_SCU_IC_ENABLE GENMASK(15, ASPEED_SCU_IC_SHIFT) +-#define ASPEED_SCU_IC_NUM_IRQS 7 + #define ASPEED_SCU_IC_STATUS GENMASK(28, 16) + #define ASPEED_SCU_IC_STATUS_SHIFT 16 ++#define AST2700_SCU_IC_STATUS GENMASK(15, 0) + +-#define ASPEED_AST2600_SCU_IC0_REG 0x560 +-#define ASPEED_AST2600_SCU_IC0_SHIFT 0 +-#define ASPEED_AST2600_SCU_IC0_ENABLE \ +- GENMASK(5, ASPEED_AST2600_SCU_IC0_SHIFT) +-#define ASPEED_AST2600_SCU_IC0_NUM_IRQS 6 +- +-#define ASPEED_AST2600_SCU_IC1_REG 0x570 +-#define ASPEED_AST2600_SCU_IC1_SHIFT 4 +-#define ASPEED_AST2600_SCU_IC1_ENABLE \ +- GENMASK(5, ASPEED_AST2600_SCU_IC1_SHIFT) +-#define ASPEED_AST2600_SCU_IC1_NUM_IRQS 2 ++struct aspeed_scu_ic_variant { ++ const char *compatible; ++ unsigned long irq_enable; ++ unsigned long irq_shift; ++ unsigned int num_irqs; ++ bool split_ier_isr; ++ unsigned long ier; ++ unsigned long isr; ++}; ++ ++#define SCU_VARIANT(_compat, _shift, _enable, _num, _split, _ier, _isr) { \ ++ .compatible = _compat, \ ++ .irq_shift = _shift, \ ++ .irq_enable = _enable, \ ++ .num_irqs = _num, \ ++ .split_ier_isr = _split, \ ++ .ier = _ier, \ ++ .isr = _isr, \ ++} ++ ++static const struct aspeed_scu_ic_variant scu_ic_variants[] __initconst = { ++ SCU_VARIANT("aspeed,ast2400-scu-ic", 0, GENMASK(15, 0), 7, false, 0, 0), ++ SCU_VARIANT("aspeed,ast2500-scu-ic", 0, GENMASK(15, 0), 7, false, 0, 0), ++ SCU_VARIANT("aspeed,ast2600-scu-ic0", 0, GENMASK(5, 0), 6, false, 0, 0), ++ SCU_VARIANT("aspeed,ast2600-scu-ic1", 4, GENMASK(5, 4), 2, false, 0, 0), ++ SCU_VARIANT("aspeed,ast2700-scu-ic0", 0, GENMASK(3, 0), 4, true, 0x00, 0x04), ++ SCU_VARIANT("aspeed,ast2700-scu-ic1", 0, GENMASK(3, 0), 4, true, 0x00, 0x04), ++ SCU_VARIANT("aspeed,ast2700-scu-ic2", 0, GENMASK(3, 0), 4, true, 0x04, 0x00), ++ SCU_VARIANT("aspeed,ast2700-scu-ic3", 0, GENMASK(1, 0), 2, true, 0x04, 0x00), ++}; + + struct aspeed_scu_ic { +- unsigned long irq_enable; +- unsigned long irq_shift; +- unsigned int num_irqs; +- unsigned int reg; +- struct regmap *scu; +- struct irq_domain *irq_domain; ++ unsigned long irq_enable; ++ unsigned long irq_shift; ++ unsigned int num_irqs; ++ void __iomem *base; ++ struct irq_domain *irq_domain; ++ bool split_ier_isr; ++ unsigned long ier; ++ unsigned long isr; + }; + +-static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) +-{ +- unsigned int sts; +- unsigned long bit; +- unsigned long enabled; +- unsigned long max; +- unsigned long status; ++static void aspeed_scu_ic_irq_handler_combined(struct irq_desc *desc) ++{ + struct aspeed_scu_ic *scu_ic = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); +- unsigned int mask = scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT; ++ unsigned long bit, enabled, max, status; ++ unsigned int sts, mask; + + chained_irq_enter(chip, desc); + ++ mask = scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT; + /* + * The SCU IC has just one register to control its operation and read + * status. The interrupt enable bits occupy the lower 16 bits of the +@@ -66,7 +81,7 @@ + * shifting the status down to get the mapping and then back up to + * clear the bit. + */ +- regmap_read(scu_ic->scu, scu_ic->reg, &sts); ++ sts = readl(scu_ic->base); + enabled = sts & scu_ic->irq_enable; + status = (sts >> ASPEED_SCU_IC_STATUS_SHIFT) & enabled; + +@@ -76,15 +91,41 @@ + for_each_set_bit_from(bit, &status, max) { + generic_handle_domain_irq(scu_ic->irq_domain, + bit - scu_ic->irq_shift); ++ writel((readl(scu_ic->base) & ~mask) | ++ BIT(bit + ASPEED_SCU_IC_STATUS_SHIFT), ++ scu_ic->base); ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void aspeed_scu_ic_irq_handler_split(struct irq_desc *desc) ++{ ++ struct aspeed_scu_ic *scu_ic = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ unsigned long bit, enabled, max, status; ++ unsigned int sts, mask; ++ ++ chained_irq_enter(chip, desc); + +- regmap_write_bits(scu_ic->scu, scu_ic->reg, mask, +- BIT(bit + ASPEED_SCU_IC_STATUS_SHIFT)); ++ mask = scu_ic->irq_enable; ++ sts = readl(scu_ic->base + scu_ic->isr); ++ enabled = sts & scu_ic->irq_enable; ++ sts = readl(scu_ic->base + scu_ic->isr); ++ status = sts & enabled; ++ ++ bit = scu_ic->irq_shift; ++ max = scu_ic->num_irqs + bit; ++ ++ for_each_set_bit_from(bit, &status, max) { ++ generic_handle_domain_irq(scu_ic->irq_domain, bit - scu_ic->irq_shift); ++ writel(BIT(bit), scu_ic->base + scu_ic->isr); // clear interrupt + } + + chained_irq_exit(chip, desc); + } + +-static void aspeed_scu_ic_irq_mask(struct irq_data *data) ++static void aspeed_scu_ic_irq_mask_combined(struct irq_data *data) + { + struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); + unsigned int mask = BIT(data->hwirq + scu_ic->irq_shift) | +@@ -95,10 +136,10 @@ + * operation from clearing the status bits, they should be under the + * mask and written with 0. + */ +- regmap_update_bits(scu_ic->scu, scu_ic->reg, mask, 0); ++ writel(readl(scu_ic->base) & ~mask, scu_ic->base); + } + +-static void aspeed_scu_ic_irq_unmask(struct irq_data *data) ++static void aspeed_scu_ic_irq_unmask_combined(struct irq_data *data) + { + struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); + unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); +@@ -110,7 +151,23 @@ + * operation from clearing the status bits, they should be under the + * mask and written with 0. + */ +- regmap_update_bits(scu_ic->scu, scu_ic->reg, mask, bit); ++ writel((readl(scu_ic->base) & ~mask) | bit, scu_ic->base); ++} ++ ++static void aspeed_scu_ic_irq_mask_split(struct irq_data *data) ++{ ++ struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); ++ ++ writel(readl(scu_ic->base) & ~BIT(data->hwirq + scu_ic->irq_shift), ++ scu_ic->base + scu_ic->ier); ++} ++ ++static void aspeed_scu_ic_irq_unmask_split(struct irq_data *data) ++{ ++ struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); ++ unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); ++ ++ writel(readl(scu_ic->base) | bit, scu_ic->base + scu_ic->ier); + } + + static int aspeed_scu_ic_irq_set_affinity(struct irq_data *data, +@@ -120,17 +177,29 @@ + return -EINVAL; + } + +-static struct irq_chip aspeed_scu_ic_chip = { +- .name = "aspeed-scu-ic", +- .irq_mask = aspeed_scu_ic_irq_mask, +- .irq_unmask = aspeed_scu_ic_irq_unmask, +- .irq_set_affinity = aspeed_scu_ic_irq_set_affinity, ++static struct irq_chip aspeed_scu_ic_chip_combined = { ++ .name = "aspeed-scu-ic", ++ .irq_mask = aspeed_scu_ic_irq_mask_combined, ++ .irq_unmask = aspeed_scu_ic_irq_unmask_combined, ++ .irq_set_affinity = aspeed_scu_ic_irq_set_affinity, ++}; ++ ++static struct irq_chip aspeed_scu_ic_chip_split = { ++ .name = "ast2700-scu-ic", ++ .irq_mask = aspeed_scu_ic_irq_mask_split, ++ .irq_unmask = aspeed_scu_ic_irq_unmask_split, ++ .irq_set_affinity = aspeed_scu_ic_irq_set_affinity, + }; + + static int aspeed_scu_ic_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) + { +- irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip, handle_level_irq); ++ struct aspeed_scu_ic *scu_ic = domain->host_data; ++ ++ if (scu_ic->split_ier_isr) ++ irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip_split, handle_level_irq); ++ else ++ irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip_combined, handle_level_irq); + irq_set_chip_data(irq, domain->host_data); + + return 0; +@@ -146,18 +215,19 @@ + int irq; + int rc = 0; + +- if (!node->parent) { +- rc = -ENODEV; ++ scu_ic->base = of_iomap(node, 0); ++ if (IS_ERR(scu_ic->base)) { ++ rc = PTR_ERR(scu_ic->base); + goto err; + } + +- scu_ic->scu = syscon_node_to_regmap(node->parent); +- if (IS_ERR(scu_ic->scu)) { +- rc = PTR_ERR(scu_ic->scu); +- goto err; ++ if (scu_ic->split_ier_isr) { ++ writel(AST2700_SCU_IC_STATUS, scu_ic->base + scu_ic->isr); ++ writel(0, scu_ic->base + scu_ic->ier); ++ } else { ++ writel(ASPEED_SCU_IC_STATUS, scu_ic->base); ++ writel(0, scu_ic->base); + } +- regmap_write_bits(scu_ic->scu, scu_ic->reg, ASPEED_SCU_IC_STATUS, ASPEED_SCU_IC_STATUS); +- regmap_write_bits(scu_ic->scu, scu_ic->reg, ASPEED_SCU_IC_ENABLE, 0); + + irq = irq_of_parse_and_map(node, 0); + if (!irq) { +@@ -166,14 +236,15 @@ + } + + scu_ic->irq_domain = irq_domain_add_linear(node, scu_ic->num_irqs, +- &aspeed_scu_ic_domain_ops, +- scu_ic); ++ &aspeed_scu_ic_domain_ops, scu_ic); + if (!scu_ic->irq_domain) { + rc = -ENOMEM; + goto err; + } + +- irq_set_chained_handler_and_data(irq, aspeed_scu_ic_irq_handler, ++ irq_set_chained_handler_and_data(irq, scu_ic->split_ier_isr ? ++ aspeed_scu_ic_irq_handler_split : ++ aspeed_scu_ic_irq_handler_combined, + scu_ic); + + return 0; +@@ -184,57 +255,45 @@ + return rc; + } + +-static int __init aspeed_scu_ic_of_init(struct device_node *node, +- struct device_node *parent) ++static const struct aspeed_scu_ic_variant * ++aspeed_scu_ic_find_variant(struct device_node *np) + { +- struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); +- +- if (!scu_ic) +- return -ENOMEM; +- +- scu_ic->irq_enable = ASPEED_SCU_IC_ENABLE; +- scu_ic->irq_shift = ASPEED_SCU_IC_SHIFT; +- scu_ic->num_irqs = ASPEED_SCU_IC_NUM_IRQS; +- scu_ic->reg = ASPEED_SCU_IC_REG; ++ for (int i = 0; i < ARRAY_SIZE(scu_ic_variants); i++) { ++ if (of_device_is_compatible(np, scu_ic_variants[i].compatible)) ++ return &scu_ic_variants[i]; ++ } + +- return aspeed_scu_ic_of_init_common(scu_ic, node); ++ return NULL; + } + +-static int __init aspeed_ast2600_scu_ic0_of_init(struct device_node *node, +- struct device_node *parent) ++static int __init aspeed_scu_ic_of_init(struct device_node *node, struct device_node *parent) + { +- struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); +- +- if (!scu_ic) +- return -ENOMEM; +- +- scu_ic->irq_enable = ASPEED_AST2600_SCU_IC0_ENABLE; +- scu_ic->irq_shift = ASPEED_AST2600_SCU_IC0_SHIFT; +- scu_ic->num_irqs = ASPEED_AST2600_SCU_IC0_NUM_IRQS; +- scu_ic->reg = ASPEED_AST2600_SCU_IC0_REG; +- +- return aspeed_scu_ic_of_init_common(scu_ic, node); +-} ++ const struct aspeed_scu_ic_variant *variant; ++ struct aspeed_scu_ic *scu_ic; + +-static int __init aspeed_ast2600_scu_ic1_of_init(struct device_node *node, +- struct device_node *parent) +-{ +- struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); ++ variant = aspeed_scu_ic_find_variant(node); ++ if (!variant) ++ return -ENODEV; + ++ scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); + if (!scu_ic) + return -ENOMEM; + +- scu_ic->irq_enable = ASPEED_AST2600_SCU_IC1_ENABLE; +- scu_ic->irq_shift = ASPEED_AST2600_SCU_IC1_SHIFT; +- scu_ic->num_irqs = ASPEED_AST2600_SCU_IC1_NUM_IRQS; +- scu_ic->reg = ASPEED_AST2600_SCU_IC1_REG; ++ scu_ic->irq_enable = variant->irq_enable; ++ scu_ic->irq_shift = variant->irq_shift; ++ scu_ic->num_irqs = variant->num_irqs; ++ scu_ic->split_ier_isr = variant->split_ier_isr; ++ scu_ic->ier = variant->ier; ++ scu_ic->isr = variant->isr; + + return aspeed_scu_ic_of_init_common(scu_ic, node); + } + + IRQCHIP_DECLARE(ast2400_scu_ic, "aspeed,ast2400-scu-ic", aspeed_scu_ic_of_init); + IRQCHIP_DECLARE(ast2500_scu_ic, "aspeed,ast2500-scu-ic", aspeed_scu_ic_of_init); +-IRQCHIP_DECLARE(ast2600_scu_ic0, "aspeed,ast2600-scu-ic0", +- aspeed_ast2600_scu_ic0_of_init); +-IRQCHIP_DECLARE(ast2600_scu_ic1, "aspeed,ast2600-scu-ic1", +- aspeed_ast2600_scu_ic1_of_init); ++IRQCHIP_DECLARE(ast2600_scu_ic0, "aspeed,ast2600-scu-ic0", aspeed_scu_ic_of_init); ++IRQCHIP_DECLARE(ast2600_scu_ic1, "aspeed,ast2600-scu-ic1", aspeed_scu_ic_of_init); ++IRQCHIP_DECLARE(ast2700_scu_ic0, "aspeed,ast2700-scu-ic0", aspeed_scu_ic_of_init); ++IRQCHIP_DECLARE(ast2700_scu_ic1, "aspeed,ast2700-scu-ic1", aspeed_scu_ic_of_init); ++IRQCHIP_DECLARE(ast2700_scu_ic2, "aspeed,ast2700-scu-ic2", aspeed_scu_ic_of_init); ++IRQCHIP_DECLARE(ast2700_scu_ic3, "aspeed,ast2700-scu-ic3", aspeed_scu_ic_of_init); +diff --git a/drivers/irqchip/irq-ast2700-intc0.c b/drivers/irqchip/irq-ast2700-intc0.c +--- a/drivers/irqchip/irq-ast2700-intc0.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/irqchip/irq-ast2700-intc0.c 2026-09-09 15:51:44.603239809 +0000 +@@ -0,0 +1,509 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Aspeed Interrupt Controller. ++ * ++ * Copyright (C) 2023 ASPEED Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define INT_NUM 480 ++#define SWINT_NUM 16 ++#define INTM_NUM 50 ++ ++#define SWINT_BASE (INT_NUM) ++#define INTM_BASE (INT_NUM + SWINT_NUM) ++#define INT0_NUM (INT_NUM + SWINT_NUM + INTM_NUM) ++ ++#define GIC_P2P_SPI_END 128 ++#define GIC_SWINT_SPI_BASE 144 ++#define GIC_SWINT_SPI_NUM 16 ++#define GIC_INTM_SPI_BASE 192 ++ ++#define INTC0_SWINT_IER 0x10 ++#define INTC0_SWINT_ISR 0x14 ++#define INTC0_INTBANKX_IER 0x1000 ++#define INTC0_INTBANK_GROUPS 11 ++#define INTC0_INTBANKS_PER_GRP 3 ++#define INTC0_IMTMX_IER 0x1b00 ++#define INTC0_IMTMX_ISR 0x1b04 ++#define INTC0_IMTM_BANK_NUM 3 ++#define INTM_IRQS_PER_BANK 10 ++ ++struct intc0_pin_region { ++ u32 int_base; ++ u32 gic_base; ++ u32 cnt; ++}; ++ ++struct aspeed_intc_ic { ++ void __iomem *base; ++ raw_spinlock_t intc_lock; ++ struct irq_domain *irq_domain; ++ struct intc0_pin_region *pin_region; ++ int pin_region_cnt; ++}; ++ ++static void aspeed_swint_irq_mask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bit = data->hwirq - SWINT_BASE; ++ unsigned int mask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ mask = readl(intc_ic->base + INTC0_SWINT_IER) & ~BIT(bit); ++ writel(mask, intc_ic->base + INTC0_SWINT_IER); ++ irq_chip_mask_parent(data); ++} ++ ++static void aspeed_swint_irq_unmask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bit = data->hwirq - SWINT_BASE; ++ unsigned int unmask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ unmask = readl(intc_ic->base + INTC0_SWINT_IER) | BIT(bit); ++ writel(unmask, intc_ic->base + INTC0_SWINT_IER); ++ irq_chip_unmask_parent(data); ++} ++ ++static void aspeed_swint_irq_eoi(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bit = data->hwirq - SWINT_BASE; ++ ++ writel(BIT(bit), intc_ic->base + INTC0_SWINT_ISR); ++ irq_chip_eoi_parent(data); ++} ++ ++static struct irq_chip aspeed_swint_chip = { ++ .name = "ast2700-swint", ++ .irq_eoi = aspeed_swint_irq_eoi, ++ .irq_mask = aspeed_swint_irq_mask, ++ .irq_unmask = aspeed_swint_irq_unmask, ++ .irq_set_affinity = irq_chip_set_affinity_parent, ++ .flags = IRQCHIP_SET_TYPE_MASKED, ++}; ++ ++static void aspeed_intc0_irq_mask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; ++ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; ++ unsigned int mask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ mask = readl(intc_ic->base + INTC0_IMTMX_IER + bank * 0x10) & ~BIT(bit); ++ writel(mask, intc_ic->base + INTC0_IMTMX_IER + bank * 0x10); ++ irq_chip_mask_parent(data); ++} ++ ++static void aspeed_intc0_irq_unmask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; ++ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; ++ unsigned int unmask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ unmask = readl(intc_ic->base + INTC0_IMTMX_IER + bank * 0x10) | BIT(bit); ++ writel(unmask, intc_ic->base + INTC0_IMTMX_IER + bank * 0x10); ++ irq_chip_unmask_parent(data); ++} ++ ++static void aspeed_intc0_irq_eoi(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; ++ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; ++ ++ /* ++ * TODO: This a WA to prevnet potential race conditions when ++ * multiple interrupts are processed in multi-core environment. ++ */ ++ raw_spin_lock(&intc_ic->intc_lock); ++ writel(BIT(bit), intc_ic->base + INTC0_IMTMX_ISR + bank * 0x10); ++ raw_spin_unlock(&intc_ic->intc_lock); ++ ++ irq_chip_eoi_parent(data); ++} ++ ++static struct irq_chip aspeed_intm_chip = { ++ .name = "ast2700-intmerge", ++ .irq_eoi = aspeed_intc0_irq_eoi, ++ .irq_mask = aspeed_intc0_irq_mask, ++ .irq_unmask = aspeed_intc0_irq_unmask, ++ .irq_set_affinity = irq_chip_set_affinity_parent, ++ .flags = IRQCHIP_SET_TYPE_MASKED, ++ ++}; ++ ++static struct irq_chip linear_intr_irq_chip = { ++ .name = "ast2700-int", ++ .irq_eoi = irq_chip_eoi_parent, ++ .irq_mask = irq_chip_mask_parent, ++ .irq_unmask = irq_chip_unmask_parent, ++ .irq_set_affinity = irq_chip_set_affinity_parent, ++ .flags = IRQCHIP_SET_TYPE_MASKED, ++}; ++ ++static int aspeed_intc_ic0_map_irq_domain(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ if (hwirq < GIC_P2P_SPI_END) ++ irq_set_chip_and_handler(irq, &linear_intr_irq_chip, handle_level_irq); ++ else if (hwirq < SWINT_BASE) ++ return -EINVAL; ++ else if (hwirq < INTM_BASE) ++ irq_set_chip_and_handler(irq, &aspeed_swint_chip, handle_level_irq); ++ else if (hwirq < INT0_NUM) ++ irq_set_chip_and_handler(irq, &aspeed_intm_chip, handle_level_irq); ++ else ++ return -EINVAL; ++ ++ irq_set_chip_data(irq, domain->host_data); ++ return 0; ++} ++ ++static int aspeed_intc0_irq_domain_translate(struct irq_domain *domain, ++ struct irq_fwspec *fwspec, ++ unsigned long *hwirq, ++ unsigned int *type) ++{ ++ if (fwspec->param_count != 1) ++ return -EINVAL; ++ ++ *hwirq = fwspec->param[0]; ++ *type = IRQ_TYPE_NONE; ++ return 0; ++} ++ ++static int get_parent_hwirq(struct aspeed_intc_ic *intc_ic, ++ u32 local_hwirq, ++ irq_hw_number_t *parent_hwirq) ++{ ++ int i; ++ ++ for (i = 0; i < intc_ic->pin_region_cnt; i++) { ++ u32 int_base = intc_ic->pin_region[i].int_base; ++ u32 cnt = intc_ic->pin_region[i].cnt; ++ ++ if (local_hwirq >= int_base && local_hwirq < int_base + cnt) { ++ *parent_hwirq = intc_ic->pin_region[i].gic_base + ++ (local_hwirq - int_base); ++ return 0; ++ } ++ } ++ ++ return -EINVAL; ++} ++ ++static int aspeed_intc0_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *data) ++{ ++ struct aspeed_intc_ic *intc_ic = domain->host_data; ++ struct irq_fwspec *fwspec = data; ++ struct irq_fwspec parent_fwspec; ++ irq_hw_number_t parent_hwirq; ++ struct irq_chip *chip; ++ unsigned long hwirq; ++ unsigned int type; ++ int ret; ++ ++ ret = aspeed_intc0_irq_domain_translate(domain, fwspec, &hwirq, &type); ++ if (ret) ++ return ret; ++ ++ if (hwirq >= GIC_P2P_SPI_END && hwirq < INT_NUM) ++ return -EINVAL; ++ ++ if (hwirq < SWINT_BASE) ++ chip = &linear_intr_irq_chip; ++ else if (hwirq < INTM_BASE) ++ chip = &aspeed_swint_chip; ++ else ++ chip = &aspeed_intm_chip; ++ ++ ret = get_parent_hwirq(intc_ic, (u32)hwirq, &parent_hwirq); ++ if (ret) ++ return irq_domain_disconnect_hierarchy(domain->parent, virq); ++ ++ parent_fwspec.fwnode = domain->parent->fwnode; ++ parent_fwspec.param_count = 3; ++ parent_fwspec.param[0] = GIC_SPI; ++ parent_fwspec.param[1] = parent_hwirq; ++ parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH; ++ ++ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); ++ if (ret) ++ return ret; ++ ++ for (int i = 0; i < nr_irqs; ++i, ++hwirq, ++virq) { ++ ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, ++ chip, ++ domain->host_data); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_intc0_irq_domain_activate(struct irq_domain *domain, ++ struct irq_data *data, bool reserve) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ ++ if (data->hwirq < GIC_P2P_SPI_END) { ++ int bank = data->hwirq / 32; ++ int bit = data->hwirq % 32; ++ u32 mask = BIT(bit); ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ for (int i = 0; i < 3; i++) { ++ void __iomem *sel = intc_ic->base + 0x200 + bank * 4 + 0x100 * i; ++ ++ if (readl(sel) & mask) { ++ writel(readl(sel) & ~mask, sel); ++ if (readl(sel) & mask) ++ return -EACCES; ++ } ++ } ++ } else if (data->hwirq < INT_NUM) { ++ return -EINVAL; ++ } else if (data->hwirq < INT0_NUM) { ++ return 0; ++ } else { ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops aspeed_intc0_ic_irq_domain_ops = { ++ .translate = aspeed_intc0_irq_domain_translate, ++ .alloc = aspeed_intc0_irq_domain_alloc, ++ .free = irq_domain_free_irqs_common, ++ .map = aspeed_intc_ic0_map_irq_domain, ++ .activate = aspeed_intc0_irq_domain_activate, ++}; ++ ++static int aspeed_intc0_init_gic_ranges(struct aspeed_intc_ic *intc_ic, ++ struct device_node *node, ++ struct device_node *parent_node) ++{ ++ struct intc0_pin_region *pin_region; ++ int region_cnt = 0; ++ int i, n, ret; ++ ++ if (!of_device_is_compatible(parent_node, "arm,gic-v3")) ++ return -ENOENT; ++ ++ n = of_property_count_elems_of_size(node, "aspeed,interrupt-ranges", sizeof(u32)); ++ if (n <= 0 || n % 6) ++ return -EINVAL; ++ ++ /* ++ * Each range is described as: ++ * ++ * and we only care about ranges whose phandle matches ++ * this controller's interrupt-parent (&gic). ++ */ ++ for (i = 0; i < n / 6; i++) { ++ struct device_node *target; ++ phandle parent_handle; ++ u32 gic_type; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 2, &parent_handle); ++ if (ret) ++ return ret; ++ ++ target = of_find_node_by_phandle(parent_handle); ++ if (!target) ++ continue; ++ ++ if (target != parent_node) { ++ of_node_put(target); ++ continue; ++ } ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 3, &gic_type); ++ of_node_put(target); ++ if (ret) ++ return ret; ++ ++ if (gic_type != GIC_SPI) ++ continue; ++ ++ region_cnt++; ++ } ++ ++ if (!region_cnt) ++ return -EINVAL; ++ ++ pin_region = kcalloc(region_cnt, sizeof(*pin_region), GFP_KERNEL); ++ if (!pin_region) ++ return -ENOMEM; ++ ++ intc_ic->pin_region_cnt = region_cnt; ++ intc_ic->pin_region = pin_region; ++ ++ region_cnt = 0; ++ for (i = 0; i < n / 6; i++) { ++ struct device_node *target; ++ phandle parent_handle; ++ u32 gic_flags; ++ u32 gic_type; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 2, &parent_handle); ++ if (ret) ++ return ret; ++ ++ target = of_find_node_by_phandle(parent_handle); ++ if (!target) ++ continue; ++ ++ if (target != parent_node) { ++ of_node_put(target); ++ continue; ++ } ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 0, ++ &pin_region[region_cnt].int_base); ++ if (ret) ++ goto out_put; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 1, ++ &pin_region[region_cnt].cnt); ++ if (ret) ++ goto out_put; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 3, &gic_type); ++ if (ret) ++ goto out_put; ++ ++ if (gic_type != GIC_SPI) ++ goto out_put; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 4, ++ &pin_region[region_cnt].gic_base); ++ if (ret) ++ goto out_put; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 6 + 5, &gic_flags); ++ if (ret) ++ goto out_put; ++ ++ if (gic_flags != IRQ_TYPE_LEVEL_HIGH) ++ goto out_put; ++ ++ region_cnt++; ++out_put: ++ of_node_put(target); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void aspeed_intc0_disable_swint(struct aspeed_intc_ic *intc_ic) ++{ ++ writel(0, intc_ic->base + INTC0_SWINT_IER); ++} ++ ++static void aspeed_intc0_disable_intbank(struct aspeed_intc_ic *intc_ic) ++{ ++ int i, j; ++ ++ for (i = 0; i < INTC0_INTBANK_GROUPS; i++) { ++ for (j = 0; j < INTC0_INTBANKS_PER_GRP; j++) { ++ u32 base = INTC0_INTBANKX_IER + (0x100 * i) + (0x10 * j); ++ ++ writel(0, intc_ic->base + base); ++ } ++ } ++} ++ ++static void aspeed_intc0_disable_intm(struct aspeed_intc_ic *intc_ic) ++{ ++ int i; ++ ++ for (i = 0; i < INTC0_IMTM_BANK_NUM; i++) ++ writel(0, intc_ic->base + INTC0_IMTMX_IER + (0x10 * i)); ++} ++ ++static int aspeed_intc0_ic_of_init(struct device_node *node, struct device_node *parent) ++{ ++ struct irq_domain *parent_domain; ++ struct aspeed_intc_ic *intc_ic; ++ int ret; ++ ++ if (!parent) { ++ pr_err("missing parent interrupt node\n"); ++ return -ENODEV; ++ } ++ ++ intc_ic = kzalloc(sizeof(*intc_ic), GFP_KERNEL); ++ if (!intc_ic) ++ return -ENOMEM; ++ ++ intc_ic->base = of_iomap(node, 0); ++ if (!intc_ic->base) { ++ ret = -ENOMEM; ++ goto err_free_ic; ++ } ++ ++ aspeed_intc0_disable_swint(intc_ic); ++ aspeed_intc0_disable_intbank(intc_ic); ++ aspeed_intc0_disable_intm(intc_ic); ++ ++ raw_spin_lock_init(&intc_ic->intc_lock); ++ ++ parent_domain = irq_find_host(parent); ++ if (!parent_domain) { ++ pr_err("unable to obtain parent domain\n"); ++ return -ENODEV; ++ } ++ ++ intc_ic->irq_domain = irq_domain_create_hierarchy(parent_domain, 0, INT0_NUM, ++ of_fwnode_handle(node), ++ &aspeed_intc0_ic_irq_domain_ops, ++ intc_ic); ++ if (!intc_ic->irq_domain) { ++ ret = -ENOMEM; ++ goto err_ioumap; ++ } ++ ++ ret = aspeed_intc0_init_gic_ranges(intc_ic, node, parent); ++ if (ret < 0) ++ goto err_ioumap; ++ ++ return 0; ++ ++err_ioumap: ++ iounmap(intc_ic->base); ++err_free_ic: ++ kfree(intc_ic); ++ return ret; ++} ++ ++IRQCHIP_DECLARE(ast2700_intc0_ic, "aspeed,ast2700-intc0-ic", aspeed_intc0_ic_of_init); +diff --git a/drivers/irqchip/irq-ast2700-intc1.c b/drivers/irqchip/irq-ast2700-intc1.c +--- a/drivers/irqchip/irq-ast2700-intc1.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/irqchip/irq-ast2700-intc1.c 2026-09-09 15:51:44.596239915 +0000 +@@ -0,0 +1,254 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Aspeed Interrupt Controller. ++ * ++ * Copyright (C) 2023 ASPEED Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define INTC1_IER 0x100 ++#define INTC1_ISR 0x104 ++#define INTC1_IRQS_PER_BANK 32 ++#define INTC1_BANK_NUM 6 ++ ++struct aspeed_intc_ic { ++ void __iomem *base; ++ raw_spinlock_t intc_lock; ++ struct irq_domain *irq_domain; ++}; ++ ++static void aspeed_intc1_ic_irq_handler(struct irq_desc *desc) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ unsigned long bit, status; ++ ++ chained_irq_enter(chip, desc); ++ ++ for (int bank = 0; bank < INTC1_BANK_NUM; bank++) { ++ status = readl(intc_ic->base + INTC1_ISR + (0x10 * bank)); ++ if (!status) ++ continue; ++ ++ for_each_set_bit(bit, &status, INTC1_IRQS_PER_BANK) { ++ generic_handle_domain_irq(intc_ic->irq_domain, ++ (bank * INTC1_IRQS_PER_BANK) + bit); ++ writel(BIT(bit), intc_ic->base + INTC1_ISR + (0x10 * bank)); ++ } ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void aspeed_intc1_irq_mask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bit = data->hwirq % INTC1_IRQS_PER_BANK; ++ int bank = data->hwirq / INTC1_IRQS_PER_BANK; ++ unsigned int mask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ mask = readl(intc_ic->base + INTC1_IER + (0x10 * bank)) & ~BIT(bit); ++ writel(mask, intc_ic->base + INTC1_IER + (0x10 * bank)); ++} ++ ++static void aspeed_intc1_irq_unmask(struct irq_data *data) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bit = data->hwirq % INTC1_IRQS_PER_BANK; ++ int bank = data->hwirq / INTC1_IRQS_PER_BANK; ++ unsigned int unmask; ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ unmask = readl(intc_ic->base + INTC1_IER + (0x10 * bank)) | BIT(bit); ++ writel(unmask, intc_ic->base + INTC1_IER + (0x10 * bank)); ++} ++ ++static struct irq_chip aspeed_intc_chip = { ++ .name = "ASPEED INTC1", ++ .irq_mask = aspeed_intc1_irq_mask, ++ .irq_unmask = aspeed_intc1_irq_unmask, ++}; ++ ++static int aspeed_intc1_irq_domain_translate(struct irq_domain *domain, struct irq_fwspec *fwspec, ++ unsigned long *hwirq, unsigned int *type) ++{ ++ if (fwspec->param_count != 1) ++ return -EINVAL; ++ ++ *hwirq = fwspec->param[0]; ++ *type = IRQ_TYPE_LEVEL_HIGH; ++ return 0; ++} ++ ++static int aspeed_intc1_ic_map_irq_domain(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_domain_set_info(domain, irq, hwirq, &aspeed_intc_chip, ++ domain->host_data, handle_level_irq, NULL, NULL); ++ return 0; ++} ++ ++static int aspeed_intc1_irq_domain_activate(struct irq_domain *domain, ++ struct irq_data *data, bool reserve) ++{ ++ struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data); ++ int bank = data->hwirq / INTC1_IRQS_PER_BANK; ++ int bit = data->hwirq % INTC1_IRQS_PER_BANK; ++ u32 mask = BIT(bit); ++ ++ guard(raw_spinlock_irqsave)(&intc_ic->intc_lock); ++ for (int i = 0; i < 3; i++) { ++ void __iomem *sel = intc_ic->base + 0x80 + bank * 4 + 0x20 * i; ++ ++ if (readl(sel) & mask) { ++ writel(readl(sel) & ~mask, sel); ++ if (readl(sel) & mask) ++ return -EACCES; ++ } ++ } ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops aspeed_intc1_ic_irq_domain_ops = { ++ .map = aspeed_intc1_ic_map_irq_domain, ++ .translate = aspeed_intc1_irq_domain_translate, ++ .activate = aspeed_intc1_irq_domain_activate, ++}; ++ ++static int aspeed_intc1_interrupt_ranges(struct aspeed_intc_ic *intc_ic, ++ struct device_node *node, ++ struct device_node *parent_node) ++{ ++ struct of_phandle_args parent_irq; ++ int i, j, n, ret; ++ ++ if (!of_device_is_compatible(parent_node, "aspeed,ast2700-intc0-ic")) ++ return -ENOENT; ++ ++ n = of_property_count_elems_of_size(node, "aspeed,interrupt-ranges", sizeof(u32)); ++ if (n <= 0 || n % 4) ++ return -EINVAL; ++ ++ /* ++ * Each range is described as: ++ * ++ * and we only care about ranges whose phandle matches ++ * this controller's interrupt-parent (&intc0). ++ */ ++ for (i = 0; i < n / 4; i++) { ++ struct device_node *target; ++ phandle parent_handle; ++ u32 base_irq; ++ u32 count; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 4 + 2, &parent_handle); ++ if (ret) ++ return ret; ++ ++ target = of_find_node_by_phandle(parent_handle); ++ if (!target) ++ continue; ++ ++ if (target != parent_node) { ++ of_node_put(target); ++ continue; ++ } ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 4 + 1, &count); ++ if (ret) ++ return ret; ++ ++ ret = of_property_read_u32_index(node, "aspeed,interrupt-ranges", ++ i * 4 + 3, &base_irq); ++ if (ret) ++ return ret; ++ ++ for (j = 0; j < count; j++) { ++ int irq; ++ ++ parent_irq.np = parent_node; ++ parent_irq.args_count = 1; ++ parent_irq.args[0] = base_irq + j; ++ irq = irq_create_of_mapping(&parent_irq); ++ if (!irq) ++ continue; ++ ++ irq_set_chained_handler_and_data(irq, ++ aspeed_intc1_ic_irq_handler, ++ intc_ic); ++ } ++ ++ of_node_put(target); ++ } ++ ++ return 0; ++} ++ ++static void aspeed_intc1_disable_int(struct aspeed_intc_ic *intc_ic) ++{ ++ for (int i = 0; i < INTC1_BANK_NUM; i++) ++ writel(0x0, intc_ic->base + INTC1_IER + (0x10 * i)); ++} ++ ++static int aspeed_intc1_ic_of_init(struct device_node *node, struct device_node *parent) ++{ ++ struct aspeed_intc_ic *intc_ic; ++ int ret = 0; ++ ++ if (!parent) { ++ pr_err("missing parent interrupt node\n"); ++ return -ENODEV; ++ } ++ ++ if (!irq_find_host(parent)) ++ return -ENODEV; ++ ++ intc_ic = kzalloc(sizeof(*intc_ic), GFP_KERNEL); ++ ++ if (!intc_ic) ++ return -ENOMEM; ++ ++ intc_ic->base = of_iomap(node, 0); ++ if (!intc_ic->base) { ++ pr_err("Failed to iomap intc_ic base\n"); ++ ret = -ENOMEM; ++ goto err_free_ic; ++ } ++ ++ raw_spin_lock_init(&intc_ic->intc_lock); ++ ++ aspeed_intc1_disable_int(intc_ic); ++ intc_ic->irq_domain = irq_domain_create_linear(of_fwnode_handle(node), ++ INTC1_BANK_NUM * INTC1_IRQS_PER_BANK, ++ &aspeed_intc1_ic_irq_domain_ops, intc_ic); ++ if (!intc_ic->irq_domain) { ++ ret = -ENOMEM; ++ goto err_iounmap; ++ } ++ ++ ret = aspeed_intc1_interrupt_ranges(intc_ic, node, parent); ++ if (ret < 0) ++ goto err_iounmap; ++ ++ return 0; ++ ++err_iounmap: ++ iounmap(intc_ic->base); ++err_free_ic: ++ kfree(intc_ic); ++ return ret; ++} ++ ++IRQCHIP_DECLARE(ast2700_intc1_ic, "aspeed,ast2700-intc1-ic", aspeed_intc1_ic_of_init); +diff --git a/drivers/jtag/jtag-aspeed.c b/drivers/jtag/jtag-aspeed.c +--- a/drivers/jtag/jtag-aspeed.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/jtag/jtag-aspeed.c 2026-09-09 15:51:44.571240293 +0000 +@@ -0,0 +1,1692 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2018 Mellanox Technologies. All rights reserved. ++// Copyright (c) 2018 Oleksandr Shamray ++// Copyright (c) 2019 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ASPEED_JTAG_DATA 0x00 ++#define ASPEED_JTAG_INST 0x04 ++#define ASPEED_JTAG_CTRL 0x08 ++#define ASPEED_JTAG_ISR 0x0C ++#define ASPEED_JTAG_SW 0x10 ++#define ASPEED_JTAG_TCK 0x14 ++#define ASPEED_JTAG_EC 0x18 ++ ++#define ASPEED_JTAG_DATA_MSB 0x01 ++#define ASPEED_JTAG_DATA_CHUNK_SIZE 0x20 ++#define ASPEED_JTAG_HW2_DATA_CHUNK_SIZE 512 ++ ++/* ASPEED_JTAG_CTRL: Engine Control 24xx and 25xx series*/ ++#define ASPEED_JTAG_CTL_ENG_EN BIT(31) ++#define ASPEED_JTAG_CTL_ENG_OUT_EN BIT(30) ++#define ASPEED_JTAG_CTL_FORCE_TMS BIT(29) ++#define ASPEED_JTAG_CTL_IR_UPDATE BIT(26) ++#define ASPEED_JTAG_CTL_INST_LEN(x) ((x) << 20) ++#define ASPEED_JTAG_CTL_LASPEED_INST BIT(17) ++#define ASPEED_JTAG_CTL_INST_EN BIT(16) ++#define ASPEED_JTAG_CTL_DR_UPDATE BIT(10) ++#define ASPEED_JTAG_CTL_DATA_LEN(x) ((x) << 4) ++#define ASPEED_JTAG_CTL_LASPEED_DATA BIT(1) ++#define ASPEED_JTAG_CTL_DATA_EN BIT(0) ++ ++/* ASPEED_JTAG_CTRL: Engine Control 26xx series*/ ++#define ASPEED_JTAG_CTL_26XX_RESET_FIFO BIT(21) ++#define ASPEED_JTAG_CTL_26XX_FIFO_MODE_CTRL BIT(20) ++#define ASPEED_JTAG_CTL_26XX_TRANS_LEN(x) ((x) << 8) ++#define ASPEED_JTAG_CTL_26XX_TRANS_MASK GENMASK(17, 8) ++#define ASPEED_JTAG_CTL_26XX_MSB_FIRST BIT(6) ++#define ASPEED_JTAG_CTL_26XX_TERM_TRANS BIT(5) ++#define ASPEED_JTAG_CTL_26XX_LASPEED_TRANS BIT(4) ++#define ASPEED_JTAG_CTL_26XX_INST_EN BIT(1) ++ ++/* ASPEED_JTAG_ISR : Interrupt status and enable */ ++#define ASPEED_JTAG_ISR_INST_PAUSE BIT(19) ++#define ASPEED_JTAG_ISR_INST_COMPLETE BIT(18) ++#define ASPEED_JTAG_ISR_DATA_PAUSE BIT(17) ++#define ASPEED_JTAG_ISR_DATA_COMPLETE BIT(16) ++#define ASPEED_JTAG_ISR_INST_PAUSE_EN BIT(3) ++#define ASPEED_JTAG_ISR_INST_COMPLETE_EN BIT(2) ++#define ASPEED_JTAG_ISR_DATA_PAUSE_EN BIT(1) ++#define ASPEED_JTAG_ISR_DATA_COMPLETE_EN BIT(0) ++#define ASPEED_JTAG_ISR_INT_EN_MASK GENMASK(3, 0) ++#define ASPEED_JTAG_ISR_INT_MASK GENMASK(19, 16) ++ ++/* ASPEED_JTAG_SW : Software Mode and Status */ ++#define ASPEED_JTAG_SW_MODE_EN BIT(19) ++#define ASPEED_JTAG_SW_MODE_TCK BIT(18) ++#define ASPEED_JTAG_SW_MODE_TMS BIT(17) ++#define ASPEED_JTAG_SW_MODE_TDIO BIT(16) ++ ++/* ASPEED_JTAG_TCK : TCK Control */ ++#define ASPEED_JTAG_TCK_DIVISOR_MASK GENMASK(11, 0) ++#define ASPEED_JTAG_TCK_GET_DIV(x) ((x) & ASPEED_JTAG_TCK_DIVISOR_MASK) ++ ++/* ASPEED_JTAG_EC : Controller set for go to IDLE */ ++#define ASPEED_JTAG_EC_TRSTn_HIGH BIT(31) ++#define ASPEED_JTAG_EC_GO_IDLE BIT(0) ++ ++#define ASPEED_JTAG_IOUT_LEN(len) \ ++ (ASPEED_JTAG_CTL_ENG_EN | \ ++ ASPEED_JTAG_CTL_ENG_OUT_EN | \ ++ ASPEED_JTAG_CTL_INST_LEN(len)) ++ ++#define ASPEED_JTAG_DOUT_LEN(len) \ ++ (ASPEED_JTAG_CTL_ENG_EN | \ ++ ASPEED_JTAG_CTL_ENG_OUT_EN | \ ++ ASPEED_JTAG_CTL_DATA_LEN(len)) ++ ++#define ASPEED_JTAG_TRANS_LEN(len) \ ++ (ASPEED_JTAG_CTL_ENG_EN | \ ++ ASPEED_JTAG_CTL_ENG_OUT_EN | \ ++ ASPEED_JTAG_CTL_26XX_TRANS_LEN(len)) ++ ++#define ASPEED_JTAG_SW_TDIO (ASPEED_JTAG_SW_MODE_EN | ASPEED_JTAG_SW_MODE_TDIO) ++ ++#define ASPEED_JTAG_GET_TDI(direction, byte) \ ++ (((direction) & JTAG_WRITE_XFER) ? byte : UINT_MAX) ++ ++#define ASPEED_JTAG_TCK_WAIT 10 ++#define ASPEED_JTAG_RESET_CNTR 10 ++#define WAIT_ITERATIONS 300 ++ ++/* Use this macro to switch between HW mode 1(comment out) and 2(defined) */ ++#define ASPEED_JTAG_HW_MODE_2_ENABLE 1 ++ ++/* ASPEED JTAG HW MODE 2 (Only supported in AST26xx series) */ ++#define ASPEED_JTAG_SHDATA 0x20 ++#define ASPEED_JTAG_SHINST 0x24 ++#define ASPEED_JTAG_PADCTRL0 0x28 ++#define ASPEED_JTAG_PADCTRL1 0x2C ++#define ASPEED_JTAG_SHCTRL 0x30 ++#define ASPEED_JTAG_GBLCTRL 0x34 ++#define ASPEED_JTAG_INTCTRL 0x38 ++#define ASPEED_JTAG_STAT 0x3C ++ ++/* ASPEED_JTAG_PADCTRLx : Padding control 0 and 1 */ ++#define ASPEED_JTAG_PADCTRL_PAD_DATA BIT(24) ++#define ASPEED_JTAG_PADCTRL_POSTPAD(x) (((x) & GENMASK(8, 0)) << 12) ++#define ASPEED_JTAG_PADCTRL_PREPAD(x) (((x) & GENMASK(8, 0)) << 0) ++ ++/* ASPEED_JTAG_SHCTRL: Shift Control */ ++#define ASPEED_JTAG_SHCTRL_FRUN_TCK_EN BIT(31) ++#define ASPEED_JTAG_SHCTRL_STSHIFT_EN BIT(30) ++#define ASPEED_JTAG_SHCTRL_TMS(x) (((x) & GENMASK(13, 0)) << 16) ++#define ASPEED_JTAG_SHCTRL_POST_TMS(x) (((x) & GENMASK(2, 0)) << 13) ++#define ASPEED_JTAG_SHCTRL_PRE_TMS(x) (((x) & GENMASK(2, 0)) << 10) ++#define ASPEED_JTAG_SHCTRL_PAD_SEL0 (0) ++#define ASPEED_JTAG_SHCTRL_PAD_SEL1 BIT(9) ++#define ASPEED_JTAG_SHCTRL_END_SHIFT BIT(8) ++#define ASPEED_JTAG_SHCTRL_START_SHIFT BIT(7) ++#define ASPEED_JTAG_SHCTRL_LWRDT_SHIFT(x) ((x) & GENMASK(6, 0)) ++ ++#define ASPEED_JTAG_END_SHIFT_DISABLED 0 ++ ++/* ASPEED_JTAG_GBLCTRL : Global Control */ ++#define ASPEED_JTAG_GBLCTRL_ENG_MODE_EN BIT(31) ++#define ASPEED_JTAG_GBLCTRL_ENG_OUT_EN BIT(30) ++#define ASPEED_JTAG_GBLCTRL_FORCE_TMS BIT(29) ++#define ASPEED_JTAG_GBLCTRL_SHIFT_COMPLETE BIT(28) ++#define ASPEED_JTAG_GBLCTRL_RESET_FIFO BIT(25) ++#define ASPEED_JTAG_GBLCTRL_FIFO_CTRL_MODE BIT(24) ++#define ASPEED_JTAG_GBLCTRL_UPDT_SHIFT(x) (((x) & GENMASK(9, 7)) << 13) ++#define ASPEED_JTAG_GBLCTRL_STSHIFT(x) (((x) & GENMASK(0, 0)) << 16) ++#define ASPEED_JTAG_GBLCTRL_TRST BIT(15) ++#define ASPEED_JTAG_CLK_DIVISOR_MASK GENMASK(11, 0) ++#define ASPEED_JTAG_CLK_GET_DIV(x) ((x) & ASPEED_JTAG_CLK_DIVISOR_MASK) ++ ++/* ASPEED_JTAG_INTCTRL: Interrupt Control */ ++#define ASPEED_JTAG_INTCTRL_SHCPL_IRQ_EN BIT(16) ++#define ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT BIT(0) ++ ++/* ASPEED_JTAG_STAT: JTAG HW mode 2 status */ ++#define ASPEED_JTAG_STAT_ENG_IDLE BIT(0) ++ ++#define ASPEED_JTAG_MAX_PAD_SIZE 512 ++ ++/* Use this macro to set us delay to WA the intensive R/W FIFO usage issue */ ++#define AST26XX_FIFO_UDELAY 2 ++ ++/* Use this macro to set us delay for JTAG Master Controller to be programmed */ ++#define AST26XX_JTAG_CTRL_UDELAY 2 ++ ++/* Timeout (us) for a GBLCTRL FIFO mode/reset state change to settle */ ++#define ASPEED_JTAG_FIFO_POLL_TIMEOUT_US 1000 ++ ++#define DEBUG_JTAG ++ ++static const char * const regnames[] = { ++ [ASPEED_JTAG_DATA] = "ASPEED_JTAG_DATA", ++ [ASPEED_JTAG_INST] = "ASPEED_JTAG_INST", ++ [ASPEED_JTAG_CTRL] = "ASPEED_JTAG_CTRL", ++ [ASPEED_JTAG_ISR] = "ASPEED_JTAG_ISR", ++ [ASPEED_JTAG_SW] = "ASPEED_JTAG_SW", ++ [ASPEED_JTAG_TCK] = "ASPEED_JTAG_TCK", ++ [ASPEED_JTAG_EC] = "ASPEED_JTAG_EC", ++ [ASPEED_JTAG_SHDATA] = "ASPEED_JTAG_SHDATA", ++ [ASPEED_JTAG_SHINST] = "ASPEED_JTAG_SHINST", ++ [ASPEED_JTAG_PADCTRL0] = "ASPEED_JTAG_PADCTRL0", ++ [ASPEED_JTAG_PADCTRL1] = "ASPEED_JTAG_PADCTRL1", ++ [ASPEED_JTAG_SHCTRL] = "ASPEED_JTAG_SHCTRL", ++ [ASPEED_JTAG_GBLCTRL] = "ASPEED_JTAG_GBLCTRL", ++ [ASPEED_JTAG_INTCTRL] = "ASPEED_JTAG_INTCTRL", ++ [ASPEED_JTAG_STAT] = "ASPEED_JTAG_STAT", ++}; ++ ++#define ASPEED_JTAG_NAME "jtag-aspeed" ++ ++struct aspeed_jtag { ++ void __iomem *reg_base; ++ struct device *dev; ++ struct clk *pclk; ++ enum jtag_tapstate status; ++ int irq; ++ struct reset_control *rst; ++ u32 flag; ++ wait_queue_head_t jtag_wq; ++ u32 mode; ++ enum jtag_tapstate current_state; ++ u32 tck_period; ++ const struct jtag_low_level_functions *llops; ++ u32 pad_data_one[ASPEED_JTAG_MAX_PAD_SIZE / 32]; ++ u32 pad_data_zero[ASPEED_JTAG_MAX_PAD_SIZE / 32]; ++}; ++ ++/* ++ * Multi generation support is enabled by fops and low level assped function ++ * mapping using asped_jtag_functions struct as config mechanism. ++ */ ++ ++struct jtag_low_level_functions { ++ void (*output_disable)(struct aspeed_jtag *aspeed_jtag); ++ void (*master_enable)(struct aspeed_jtag *aspeed_jtag); ++ int (*xfer_push_data)(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, u32 bits_len); ++ int (*xfer_push_data_last)(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, u32 bits_len); ++ void (*xfer_sw)(struct aspeed_jtag *aspeed_jtag, struct jtag_xfer *xfer, ++ u32 *data); ++ int (*xfer_hw)(struct aspeed_jtag *aspeed_jtag, struct jtag_xfer *xfer, ++ u32 *data); ++ int (*trst_set)(struct aspeed_jtag *aspeed_jtag, u32 active); ++ void (*xfer_hw_fifo_delay)(void); ++ void (*xfer_sw_delay)(struct aspeed_jtag *aspeed_jtag); ++ irqreturn_t (*jtag_interrupt)(s32 this_irq, void *dev_id); ++}; ++ ++struct aspeed_jtag_functions { ++ const struct jtag_ops *aspeed_jtag_ops; ++ const struct jtag_low_level_functions *aspeed_jtag_llops; ++}; ++ ++#ifdef DEBUG_JTAG ++static char *end_status_str[] = { "tlr", "idle", "selDR", "capDR", "sDR", ++ "ex1DR", "pDR", "ex2DR", "updDR", "selIR", ++ "capIR", "sIR", "ex1IR", "pIR", "ex2IR", ++ "updIR", "current" }; ++#endif ++ ++static u32 aspeed_jtag_read(struct aspeed_jtag *aspeed_jtag, u32 reg) ++{ ++ u32 val = readl(aspeed_jtag->reg_base + reg); ++ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "read:%s val = 0x%08x\n", regnames[reg], val); ++#endif ++ return val; ++} ++ ++static void aspeed_jtag_write(struct aspeed_jtag *aspeed_jtag, u32 val, u32 reg) ++{ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "write:%s val = 0x%08x\n", regnames[reg], ++ val); ++#endif ++ writel(val, aspeed_jtag->reg_base + reg); ++} ++ ++static int aspeed_jtag_freq_set(struct jtag *jtag, u32 freq) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ unsigned long apb_frq; ++ u32 tck_val; ++ u16 div; ++ ++ if (!freq) ++ return -EINVAL; ++ ++ apb_frq = clk_get_rate(aspeed_jtag->pclk); ++ if (!apb_frq) ++ return -EOPNOTSUPP; ++ ++ div = (apb_frq - 1) / freq; ++ if (div > ASPEED_JTAG_TCK_DIVISOR_MASK) ++ div = ASPEED_JTAG_TCK_DIVISOR_MASK; ++ tck_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK); ++ aspeed_jtag_write(aspeed_jtag, ++ (tck_val & ~ASPEED_JTAG_TCK_DIVISOR_MASK) | div, ++ ASPEED_JTAG_TCK); ++ aspeed_jtag->tck_period = ++ DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (div + 1), apb_frq); ++ return 0; ++} ++ ++static int aspeed_jtag_freq_set_26xx(struct jtag *jtag, u32 freq) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ unsigned long apb_frq; ++ u32 tck_val; ++ u16 div; ++ ++ if (!freq) ++ return -EINVAL; ++ ++ apb_frq = clk_get_rate(aspeed_jtag->pclk); ++ if (!apb_frq) ++ return -EOPNOTSUPP; ++ ++ div = (apb_frq - 1) / freq; ++ tck_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ (tck_val & ~ASPEED_JTAG_CLK_DIVISOR_MASK) | div, ++ ASPEED_JTAG_GBLCTRL); ++ aspeed_jtag->tck_period = ++ DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (div + 1), apb_frq); ++ return 0; ++} ++ ++static int aspeed_jtag_freq_get(struct jtag *jtag, u32 *frq) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ u32 pclk; ++ u32 tck; ++ ++ pclk = clk_get_rate(aspeed_jtag->pclk); ++ tck = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK); ++ *frq = pclk / (ASPEED_JTAG_TCK_GET_DIV(tck) + 1); ++ ++ return 0; ++} ++ ++static int aspeed_jtag_freq_get_26xx(struct jtag *jtag, u32 *frq) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ u32 pclk; ++ u32 tck; ++ ++ pclk = clk_get_rate(aspeed_jtag->pclk); ++ tck = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ *frq = pclk / (ASPEED_JTAG_CLK_GET_DIV(tck) + 1); ++ ++ return 0; ++} ++ ++static inline void aspeed_jtag_output_disable(struct aspeed_jtag *aspeed_jtag) ++{ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); ++} ++ ++static inline void ++aspeed_jtag_output_disable_26xx(struct aspeed_jtag *aspeed_jtag) ++{ ++ u32 reg_val; ++ ++ reg_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL) & ++ ASPEED_JTAG_CLK_DIVISOR_MASK; ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, reg_val, ASPEED_JTAG_GBLCTRL); ++} ++ ++static inline void aspeed_jtag_master(struct aspeed_jtag *aspeed_jtag) ++{ ++ aspeed_jtag_write(aspeed_jtag, ++ (ASPEED_JTAG_CTL_ENG_EN | ASPEED_JTAG_CTL_ENG_OUT_EN), ++ ASPEED_JTAG_CTRL); ++ ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_SW_MODE_EN | ASPEED_JTAG_SW_MODE_TDIO, ++ ASPEED_JTAG_SW); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_ISR_INST_PAUSE | ++ ASPEED_JTAG_ISR_INST_COMPLETE | ++ ASPEED_JTAG_ISR_DATA_PAUSE | ++ ASPEED_JTAG_ISR_DATA_COMPLETE | ++ ASPEED_JTAG_ISR_INST_PAUSE_EN | ++ ASPEED_JTAG_ISR_INST_COMPLETE_EN | ++ ASPEED_JTAG_ISR_DATA_PAUSE_EN | ++ ASPEED_JTAG_ISR_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); /* Enable Interrupt */ ++} ++ ++static inline void aspeed_jtag_master_26xx(struct aspeed_jtag *aspeed_jtag) ++{ ++ u32 reg_val; ++ ++ reg_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL) & ++ ASPEED_JTAG_CLK_DIVISOR_MASK; ++ if (aspeed_jtag->mode & JTAG_XFER_HW_MODE) { ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_SW_MODE_EN | ++ ASPEED_JTAG_SW_MODE_TDIO, ++ ASPEED_JTAG_SW); ++ } ++ /* ++ * For the software mode, it's still necessary to enable out_en and ++ * select the out_en in the hw2 register to maintain control of the ++ * TRST bit same as hw2. ++ */ ++ aspeed_jtag_write(aspeed_jtag, ++ reg_val | ASPEED_JTAG_GBLCTRL_ENG_MODE_EN | ++ ASPEED_JTAG_GBLCTRL_ENG_OUT_EN | ++ ASPEED_JTAG_GBLCTRL_TRST, ++ ASPEED_JTAG_GBLCTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_INTCTRL_SHCPL_IRQ_EN | ++ ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT, ++ ASPEED_JTAG_INTCTRL); /* Enable HW2 IRQ */ ++ ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_ISR_INST_PAUSE | ++ ASPEED_JTAG_ISR_INST_COMPLETE | ++ ASPEED_JTAG_ISR_DATA_PAUSE | ++ ASPEED_JTAG_ISR_DATA_COMPLETE | ++ ASPEED_JTAG_ISR_INST_PAUSE_EN | ++ ASPEED_JTAG_ISR_INST_COMPLETE_EN | ++ ASPEED_JTAG_ISR_DATA_PAUSE_EN | ++ ASPEED_JTAG_ISR_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); /* Enable HW1 Interrupts */ ++} ++ ++static int aspeed_jtag_mode_set(struct jtag *jtag, struct jtag_mode *jtag_mode) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ switch (jtag_mode->feature) { ++ case JTAG_XFER_MODE: ++ aspeed_jtag->mode = jtag_mode->mode; ++ aspeed_jtag->llops->master_enable(aspeed_jtag); ++ break; ++ case JTAG_CONTROL_MODE: ++ if (jtag_mode->mode == JTAG_CONTROLLER_OUTPUT_DISABLE) ++ aspeed_jtag->llops->output_disable(aspeed_jtag); ++ else if (jtag_mode->mode == JTAG_CONTROLLER_MODE) ++ aspeed_jtag->llops->master_enable(aspeed_jtag); ++ break; ++ default: ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++/* ++ * We read and write from an unused JTAG Master controller register in SW ++ * mode to create a delay in xfers. ++ * We found this mechanism better than any udelay or usleep option. ++ */ ++static inline void aspeed_jtag_sw_delay_26xx(struct aspeed_jtag *aspeed_jtag) ++{ ++ u32 read_reg = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_PADCTRL1); ++ ++ aspeed_jtag_write(aspeed_jtag, read_reg, ASPEED_JTAG_PADCTRL1); ++} ++ ++static char aspeed_jtag_tck_cycle(struct aspeed_jtag *aspeed_jtag, u8 tms, ++ u8 tdi) ++{ ++ char tdo = 0; ++ ++ /* TCK = 0 */ ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_SW_MODE_EN | ++ (tms * ASPEED_JTAG_SW_MODE_TMS) | ++ (tdi * ASPEED_JTAG_SW_MODE_TDIO), ++ ASPEED_JTAG_SW); ++ ++ /* Wait until JTAG Master controller finishes the operation */ ++ if (aspeed_jtag->llops->xfer_sw_delay) ++ aspeed_jtag->llops->xfer_sw_delay(aspeed_jtag); ++ else ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); ++ ++ ndelay(aspeed_jtag->tck_period >> 1); ++ ++ /* TCK = 1 */ ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_SW_MODE_EN | ASPEED_JTAG_SW_MODE_TCK | ++ (tms * ASPEED_JTAG_SW_MODE_TMS) | ++ (tdi * ASPEED_JTAG_SW_MODE_TDIO), ++ ASPEED_JTAG_SW); ++ ++ /* Wait until JTAG Master controller finishes the operation */ ++ if (aspeed_jtag->llops->xfer_sw_delay) ++ aspeed_jtag->llops->xfer_sw_delay(aspeed_jtag); ++ ++ ndelay(aspeed_jtag->tck_period >> 1); ++ ++ if (aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW) & ++ ASPEED_JTAG_SW_MODE_TDIO) ++ tdo = 1; ++ ++ return tdo; ++} ++ ++static int aspeed_jtag_bitbang(struct jtag *jtag, ++ struct bitbang_packet *bitbang, ++ struct tck_bitbang *bitbang_data) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ int i = 0; ++ ++ for (i = 0; i < bitbang->length; i++) { ++ bitbang_data[i].tdo = ++ aspeed_jtag_tck_cycle(aspeed_jtag, bitbang_data[i].tms, ++ bitbang_data[i].tdi); ++ } ++ return 0; ++} ++ ++static inline void aspeed_jtag_xfer_hw_fifo_delay_26xx(void) ++{ ++ udelay(AST26XX_FIFO_UDELAY); ++} ++ ++static int aspeed_jtag_isr_wait(struct aspeed_jtag *aspeed_jtag, u32 bit) ++{ ++ int res = 0; ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ if (!aspeed_jtag->irq) { ++ res = wait_event_interruptible(aspeed_jtag->jtag_wq, ++ aspeed_jtag->flag & bit); ++ aspeed_jtag->flag &= ~bit; ++ } else { ++ while ((status & bit) == 0) { ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, ++ status); ++#endif ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(aspeed_jtag->dev, ++ "%s %d in ASPEED_JTAG_ISR\n", ++ "aspeed_jtag driver timed out waiting for bit", ++ bit); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ aspeed_jtag_write(aspeed_jtag, bit | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ } ++ return res; ++} ++ ++static int aspeed_jtag_wait_shift_complete(struct aspeed_jtag *aspeed_jtag) ++{ ++ int res = 0; ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ if (!aspeed_jtag->irq) { ++ res = wait_event_interruptible(aspeed_jtag->jtag_wq, ++ aspeed_jtag->flag & ++ ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT); ++ aspeed_jtag->flag &= ~ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT; ++ } else { ++ while ((status & ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT) == 0) { ++ status = aspeed_jtag_read(aspeed_jtag, ++ ASPEED_JTAG_INTCTRL); ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, ++ status); ++#endif ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(aspeed_jtag->dev, ++ "aspeed_jtag driver timed out waiting for shift completed\n"); ++ res = -EFAULT; ++ break; ++ } ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT | ++ ASPEED_JTAG_INTCTRL_SHCPL_IRQ_EN, ++ ASPEED_JTAG_INTCTRL); ++ } ++ ++ return res; ++} ++ ++static void aspeed_jtag_set_tap_state(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_tapstate from_state, ++ enum jtag_tapstate end_state) ++{ ++ int i = 0; ++ enum jtag_tapstate from, to; ++ ++ from = from_state; ++ to = end_state; ++ ++ for (i = 0; i < _tms_cycle_lookup[from][to].count; i++) ++ aspeed_jtag_tck_cycle(aspeed_jtag, ++ ((_tms_cycle_lookup[from][to].tmsbits ++ >> i) & 0x1), 0); ++ aspeed_jtag->current_state = end_state; ++} ++ ++static void aspeed_jtag_set_tap_state_sw(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ int i; ++ ++ /* SW mode from curent tap state -> to end_state */ ++ if (tapstate->reset || tapstate->endstate == JTAG_STATE_TLRESET) { ++ for (i = 0; i < ASPEED_JTAG_RESET_CNTR; i++) ++ aspeed_jtag_tck_cycle(aspeed_jtag, 1, 0); ++ aspeed_jtag->current_state = JTAG_STATE_TLRESET; ++ } ++ ++ aspeed_jtag_set_tap_state(aspeed_jtag, tapstate->from, ++ tapstate->endstate); ++ if (tapstate->endstate == JTAG_STATE_TLRESET || ++ tapstate->endstate == JTAG_STATE_IDLE || ++ tapstate->endstate == JTAG_STATE_PAUSEDR || ++ tapstate->endstate == JTAG_STATE_PAUSEIR) ++ for (i = 0; i < tapstate->tck; i++) ++ aspeed_jtag_tck_cycle(aspeed_jtag, 0, 0); ++} ++ ++static int aspeed_jtag_status_set(struct jtag *jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ int i; ++ ++ if (tapstate->from == JTAG_STATE_CURRENT) ++ tapstate->from = aspeed_jtag->current_state; ++ if (tapstate->endstate == JTAG_STATE_CURRENT) ++ tapstate->endstate = aspeed_jtag->current_state; ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "Set TAP state: %s\n", ++ end_status_str[tapstate->endstate]); ++#endif ++ ++ if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { ++ aspeed_jtag_set_tap_state_sw(aspeed_jtag, tapstate); ++ return 0; ++ } ++ ++ /* x TMS high + 1 TMS low */ ++ if (tapstate->reset) { ++ /* Disable sw mode */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ mdelay(1); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_CTL_ENG_EN | ++ ASPEED_JTAG_CTL_ENG_OUT_EN | ++ ASPEED_JTAG_CTL_FORCE_TMS, ++ ASPEED_JTAG_CTRL); ++ mdelay(1); ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_TDIO, ++ ASPEED_JTAG_SW); ++ aspeed_jtag->current_state = JTAG_STATE_TLRESET; ++ } ++ for (i = 0; i < tapstate->tck; i++) ++ ndelay(aspeed_jtag->tck_period); ++ ++ return 0; ++} ++ ++static int aspeed_jtag_shctrl_tms_mask(enum jtag_tapstate from, ++ enum jtag_tapstate to, ++ enum jtag_tapstate there, ++ enum jtag_tapstate endstate, ++ u32 start_shift, u32 end_shift, ++ u32 *tms_mask) ++{ ++ u32 pre_tms = start_shift ? _tms_cycle_lookup[from][to].count : 0; ++ u32 post_tms = end_shift ? _tms_cycle_lookup[there][endstate].count : 0; ++ u32 tms_value = start_shift ? _tms_cycle_lookup[from][to].tmsbits : 0; ++ ++ tms_value |= end_shift ? _tms_cycle_lookup[there][endstate].tmsbits ++ << pre_tms : ++ 0; ++ if (pre_tms > GENMASK(2, 0) || post_tms > GENMASK(2, 0)) { ++ pr_err("pre/port tms count is greater than hw limit"); ++ return -EINVAL; ++ } ++ *tms_mask = start_shift | ASPEED_JTAG_SHCTRL_PRE_TMS(pre_tms) | ++ end_shift | ASPEED_JTAG_SHCTRL_POST_TMS(post_tms) | ++ ASPEED_JTAG_SHCTRL_TMS(tms_value); ++ return 0; ++} ++ ++static void aspeed_jtag_set_tap_state_hw2(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ u32 reg_val, execute_tck; ++ u32 tck = tapstate->tck; ++ ++ /* x TMS high + 1 TMS low */ ++ if (tapstate->reset || tapstate->endstate == JTAG_STATE_TLRESET) { ++ /* Disable sw mode */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ udelay(AST26XX_JTAG_CTRL_UDELAY); ++ reg_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ reg_val | ASPEED_JTAG_GBLCTRL_ENG_MODE_EN | ++ ASPEED_JTAG_GBLCTRL_ENG_OUT_EN | ++ ASPEED_JTAG_GBLCTRL_RESET_FIFO | ++ ASPEED_JTAG_GBLCTRL_FORCE_TMS, ++ ASPEED_JTAG_GBLCTRL); ++ udelay(AST26XX_JTAG_CTRL_UDELAY); ++ while (aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL) & ASPEED_JTAG_GBLCTRL_FORCE_TMS) ++ ; ++ aspeed_jtag->current_state = JTAG_STATE_TLRESET; ++ } else { ++ aspeed_jtag_set_tap_state(aspeed_jtag, ++ aspeed_jtag->current_state, ++ tapstate->endstate); ++ } ++ /* Run TCK */ ++ while (tck) { ++ execute_tck = tck > GENMASK(9, 0) ? GENMASK(9, 0) : tck; ++ /* Disable sw mode */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_PADCTRL0); ++ reg_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ reg_val = reg_val & ~(GENMASK(22, 20)); ++ aspeed_jtag_write(aspeed_jtag, ++ reg_val | ASPEED_JTAG_GBLCTRL_FIFO_CTRL_MODE | ++ ASPEED_JTAG_GBLCTRL_STSHIFT(0) | ++ ASPEED_JTAG_GBLCTRL_UPDT_SHIFT(execute_tck), ++ ASPEED_JTAG_GBLCTRL); ++ ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_SHCTRL_STSHIFT_EN | ++ ASPEED_JTAG_SHCTRL_LWRDT_SHIFT(execute_tck), ++ ASPEED_JTAG_SHCTRL); ++ aspeed_jtag_wait_shift_complete(aspeed_jtag); ++ tck -= execute_tck; ++ } ++} ++ ++static int aspeed_jtag_status_set_26xx(struct jtag *jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ if (tapstate->from == JTAG_STATE_CURRENT) ++ tapstate->from = aspeed_jtag->current_state; ++ if (tapstate->endstate == JTAG_STATE_CURRENT) ++ tapstate->endstate = aspeed_jtag->current_state; ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "Set TAP state: status %s from %s to %s\n", ++ end_status_str[aspeed_jtag->current_state], ++ end_status_str[tapstate->from], ++ end_status_str[tapstate->endstate]); ++#endif ++ ++ if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { ++ aspeed_jtag_set_tap_state_sw(aspeed_jtag, tapstate); ++ return 0; ++ } ++ ++ aspeed_jtag_set_tap_state_hw2(aspeed_jtag, tapstate); ++ return 0; ++} ++ ++static void aspeed_jtag_xfer_sw(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_xfer *xfer, u32 *data) ++{ ++ unsigned long remain_xfer = xfer->length; ++ unsigned long shift_bits = 0; ++ unsigned long index = 0; ++ unsigned long tdi; ++ char tdo; ++ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "SW JTAG SHIFT %s, length = %d\n", ++ (xfer->type == JTAG_SIR_XFER) ? "IR" : "DR", xfer->length); ++#endif ++ ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_jtag_set_tap_state(aspeed_jtag, xfer->from, ++ JTAG_STATE_SHIFTIR); ++ else ++ aspeed_jtag_set_tap_state(aspeed_jtag, xfer->from, ++ JTAG_STATE_SHIFTDR); ++ ++ tdi = ASPEED_JTAG_GET_TDI(xfer->direction, data[index]); ++ data[index] = 0; ++ while (remain_xfer > 1) { ++ tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 0, ++ tdi & ASPEED_JTAG_DATA_MSB); ++ data[index] |= tdo ++ << (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE); ++ tdi >>= 1; ++ shift_bits++; ++ remain_xfer--; ++ ++ if (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE == 0) { ++ tdo = 0; ++ index++; ++ tdi = ASPEED_JTAG_GET_TDI(xfer->direction, data[index]); ++ data[index] = 0; ++ } ++ } ++ ++ if ((xfer->endstate == (xfer->type == JTAG_SIR_XFER ? ++ JTAG_STATE_SHIFTIR : ++ JTAG_STATE_SHIFTDR))) { ++ /* Stay in Shift IR/DR*/ ++ tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 0, ++ tdi & ASPEED_JTAG_DATA_MSB); ++ data[index] |= tdo ++ << (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE); ++ } else { ++ /* Goto end state */ ++ tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 1, ++ tdi & ASPEED_JTAG_DATA_MSB); ++ data[index] |= tdo ++ << (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE); ++ aspeed_jtag->status = (xfer->type == JTAG_SIR_XFER) ? ++ JTAG_STATE_EXIT1IR : ++ JTAG_STATE_EXIT1DR; ++ aspeed_jtag_set_tap_state(aspeed_jtag, aspeed_jtag->status, ++ xfer->endstate); ++ } ++} ++ ++static int aspeed_jtag_xfer_push_data_26xx(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, ++ u32 bits_len) ++{ ++ int res = 0; ++ ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_TRANS_LEN(bits_len), ++ ASPEED_JTAG_CTRL); ++ if (type == JTAG_SIR_XFER) { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_TRANS_LEN(bits_len) | ++ ASPEED_JTAG_CTL_26XX_INST_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_INST_PAUSE); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_TRANS_LEN(bits_len) | ++ ASPEED_JTAG_CTL_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_DATA_PAUSE); ++ } ++ return res; ++} ++ ++static int aspeed_jtag_xfer_push_data(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, u32 bits_len) ++{ ++ int res = 0; ++ ++ if (type == JTAG_SIR_XFER) { ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_IOUT_LEN(bits_len), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_IOUT_LEN(bits_len) | ++ ASPEED_JTAG_CTL_INST_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_INST_PAUSE); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_DOUT_LEN(bits_len) | ++ ASPEED_JTAG_CTL_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_DATA_PAUSE); ++ } ++ return res; ++} ++ ++static int aspeed_jtag_xfer_push_data_last_26xx(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, ++ u32 shift_bits) ++{ ++ int res = 0; ++ ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_TRANS_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_26XX_LASPEED_TRANS, ++ ASPEED_JTAG_CTRL); ++ if (type == JTAG_SIR_XFER) { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_TRANS_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_26XX_LASPEED_TRANS | ++ ASPEED_JTAG_CTL_26XX_INST_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_INST_COMPLETE); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_TRANS_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_26XX_LASPEED_TRANS | ++ ASPEED_JTAG_CTL_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_DATA_COMPLETE); ++ } ++ return res; ++} ++ ++static int aspeed_jtag_xfer_push_data_last(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, ++ u32 shift_bits) ++{ ++ int res = 0; ++ ++ if (type == JTAG_SIR_XFER) { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_IOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_INST, ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_IOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_INST | ++ ASPEED_JTAG_CTL_INST_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_INST_COMPLETE); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_DOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_DATA, ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_DOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_DATA | ++ ASPEED_JTAG_CTL_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_isr_wait(aspeed_jtag, ++ ASPEED_JTAG_ISR_DATA_COMPLETE); ++ } ++ return res; ++} ++ ++static int aspeed_jtag_xfer_hw(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_xfer *xfer, u32 *data) ++{ ++ unsigned long remain_xfer = xfer->length; ++ unsigned long index = 0; ++ char shift_bits; ++ u32 data_reg; ++ u32 scan_end; ++ union pad_config padding; ++ int retval = 0; ++ ++ padding.int_value = xfer->padding; ++ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "HW JTAG SHIFT %s, length = %d pad = 0x%x\n", ++ (xfer->type == JTAG_SIR_XFER) ? "IR" : "DR", xfer->length, ++ xfer->padding); ++#endif ++ data_reg = xfer->type == JTAG_SIR_XFER ? ASPEED_JTAG_INST : ++ ASPEED_JTAG_DATA; ++ if (xfer->endstate == JTAG_STATE_SHIFTIR || ++ xfer->endstate == JTAG_STATE_SHIFTDR || ++ xfer->endstate == JTAG_STATE_PAUSEIR || ++ xfer->endstate == JTAG_STATE_PAUSEDR) { ++ scan_end = 0; ++ } else { ++ if (padding.post_pad_number) ++ scan_end = 0; ++ else ++ scan_end = 1; ++ } ++ ++ /* Perform pre padding */ ++ if (padding.pre_pad_number) { ++ struct jtag_xfer pre_xfer = { ++ .type = xfer->type, ++ .direction = JTAG_WRITE_XFER, ++ .from = xfer->from, ++ .endstate = xfer->type == JTAG_SIR_XFER ? ++ JTAG_STATE_SHIFTIR : JTAG_STATE_SHIFTDR, ++ .padding = 0, ++ .length = padding.pre_pad_number, ++ }; ++ if (padding.pre_pad_number > ASPEED_JTAG_MAX_PAD_SIZE) ++ return -EINVAL; ++ retval = aspeed_jtag_xfer_hw(aspeed_jtag, &pre_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ if (retval) ++ return retval; ++ } ++ ++ while (remain_xfer) { ++ if (xfer->direction & JTAG_WRITE_XFER) ++ aspeed_jtag_write(aspeed_jtag, data[index], data_reg); ++ else ++ aspeed_jtag_write(aspeed_jtag, 0, data_reg); ++ if (aspeed_jtag->llops->xfer_hw_fifo_delay) ++ aspeed_jtag->llops->xfer_hw_fifo_delay(); ++ ++ if (remain_xfer > ASPEED_JTAG_DATA_CHUNK_SIZE) { ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, ++ "Chunk len=%d chunk_size=%d remain_xfer=%lu\n", ++ xfer->length, ASPEED_JTAG_DATA_CHUNK_SIZE, ++ remain_xfer); ++#endif ++ shift_bits = ASPEED_JTAG_DATA_CHUNK_SIZE; ++ ++ /* ++ * Transmit bytes that were not equals to column length ++ * and after the transfer go to Pause IR/DR. ++ */ ++ if (aspeed_jtag->llops->xfer_push_data(aspeed_jtag, ++ xfer->type, ++ shift_bits) ++ != 0) { ++ return -EFAULT; ++ } ++ } else { ++ /* ++ * Read bytes equals to column length ++ */ ++ shift_bits = remain_xfer; ++ if (scan_end) { ++ /* ++ * If this data is the end of the transmission ++ * send remaining bits and go to endstate ++ */ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, ++ "Last len=%d chunk_size=%d remain_xfer=%lu\n", ++ xfer->length, ++ ASPEED_JTAG_DATA_CHUNK_SIZE, ++ remain_xfer); ++#endif ++ if (aspeed_jtag->llops->xfer_push_data_last( ++ aspeed_jtag, xfer->type, ++ shift_bits) != 0) { ++ return -EFAULT; ++ } ++ } else { ++ /* ++ * If transmission is waiting for additional ++ * data send remaining bits and then go to ++ * Pause IR/DR. ++ */ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, ++ "Tail len=%d chunk_size=%d remain_xfer=%lu\n", ++ xfer->length, ++ ASPEED_JTAG_DATA_CHUNK_SIZE, ++ remain_xfer); ++#endif ++ if (aspeed_jtag->llops->xfer_push_data( ++ aspeed_jtag, xfer->type, ++ shift_bits) != 0) { ++ return -EFAULT; ++ } ++ } ++ } ++ ++ if (xfer->direction & JTAG_READ_XFER) { ++ if (shift_bits < ASPEED_JTAG_DATA_CHUNK_SIZE) { ++ data[index] = ++ aspeed_jtag_read(aspeed_jtag, data_reg); ++ ++ data[index] >>= ASPEED_JTAG_DATA_CHUNK_SIZE - ++ shift_bits; ++ } else { ++ data[index] = ++ aspeed_jtag_read(aspeed_jtag, data_reg); ++ } ++ if (aspeed_jtag->llops->xfer_hw_fifo_delay) ++ aspeed_jtag->llops->xfer_hw_fifo_delay(); ++ } ++ ++ remain_xfer = remain_xfer - shift_bits; ++ index++; ++ } ++ ++ /* Perform post padding */ ++ if (padding.post_pad_number) { ++ struct jtag_xfer post_xfer = { ++ .type = xfer->type, ++ .direction = JTAG_WRITE_XFER, ++ .from = xfer->from, ++ .endstate = xfer->endstate, ++ .padding = 0, ++ .length = padding.post_pad_number, ++ }; ++ if (padding.post_pad_number > ASPEED_JTAG_MAX_PAD_SIZE) ++ return -EINVAL; ++ retval = aspeed_jtag_xfer_hw(aspeed_jtag, &post_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ if (retval) ++ return retval; ++ } ++ return 0; ++} ++ ++static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer, ++ u8 *xfer_data) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { ++ /* SW mode */ ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_TDIO, ++ ASPEED_JTAG_SW); ++ ++ aspeed_jtag->llops->xfer_sw(aspeed_jtag, xfer, ++ (u32 *)xfer_data); ++ } else { ++ /* HW mode */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ if (aspeed_jtag->llops->xfer_hw(aspeed_jtag, xfer, ++ (u32 *)xfer_data) != 0) ++ return -EFAULT; ++ } ++ ++ aspeed_jtag->status = xfer->endstate; ++ return 0; ++} ++ ++static int aspeed_jtag_xfer_hw2(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_xfer *xfer, u32 *data) ++{ ++ unsigned long remain_xfer = xfer->length; ++ unsigned long partial_xfer_size = 0; ++ unsigned long index = 0; ++ u32 shift_bits; ++ u32 data_reg; ++ u32 reg_val; ++ u32 poll_val; ++ enum jtag_tapstate shift; ++ enum jtag_tapstate exit; ++ enum jtag_tapstate exitx; ++ enum jtag_tapstate pause; ++ enum jtag_tapstate endstate; ++ u32 start_shift; ++ u32 end_shift; ++ u32 tms_mask; ++ int ret; ++ ++ if (xfer->type == JTAG_SIR_XFER) { ++ data_reg = ASPEED_JTAG_SHDATA; ++ shift = JTAG_STATE_SHIFTIR; ++ pause = JTAG_STATE_PAUSEIR; ++ exit = JTAG_STATE_EXIT1IR; ++ exitx = JTAG_STATE_EXIT1DR; ++ } else { ++ data_reg = ASPEED_JTAG_SHDATA; ++ shift = JTAG_STATE_SHIFTDR; ++ pause = JTAG_STATE_PAUSEDR; ++ exit = JTAG_STATE_EXIT1DR; ++ exitx = JTAG_STATE_EXIT1IR; ++ } ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, ++ "HW2 JTAG SHIFT %s, length %d status %s from %s to %s then %s pad 0x%x\n", ++ (xfer->type == JTAG_SIR_XFER) ? "IR" : "DR", xfer->length, ++ end_status_str[aspeed_jtag->current_state], ++ end_status_str[xfer->from], ++ end_status_str[shift], ++ end_status_str[xfer->endstate], xfer->padding); ++#endif ++ ++ if (aspeed_jtag->current_state == shift) { ++ start_shift = 0; ++ } else { ++ start_shift = ASPEED_JTAG_SHCTRL_START_SHIFT; ++ } ++ ++ if (xfer->endstate == shift) { ++ /* ++ * In the case of shifting 1 bit of data and attempting to stay ++ * in the SHIFT state, the AST2600 JTAG Master Controller in ++ * Hardware mode 2 has been observed to go to EXIT1 IR/DR ++ * instead of staying in the SHIFT IR/DR state. The following ++ * code special cases this one bit shift and directs the state ++ * machine to go to the PAUSE IR/DR state instead. ++ * Alternatively, the application making driver calls can avoid ++ * this situation as follows: ++ * 1.) Bundle all of the shift bits together into one call ++ * AND/OR ++ * 2.) Direct all partial shifts to move to the PAUSE-IR/DR ++ * state. ++ */ ++ if (xfer->length == 1) { ++#ifdef DEBUG_JTAG ++ dev_warn(aspeed_jtag->dev, "JTAG Silicon WA: going to pause instead of shift"); ++#endif ++ end_shift = ASPEED_JTAG_SHCTRL_END_SHIFT; ++ endstate = pause; ++ } else { ++ end_shift = 0; ++ endstate = shift; ++ } ++ } else { ++ endstate = xfer->endstate; ++ end_shift = ASPEED_JTAG_SHCTRL_END_SHIFT; ++ } ++ ++ aspeed_jtag_write(aspeed_jtag, xfer->padding, ASPEED_JTAG_PADCTRL0); ++ ++ while (remain_xfer) { ++ unsigned long partial_xfer; ++ unsigned long partial_index; ++ ++ if (remain_xfer > ASPEED_JTAG_HW2_DATA_CHUNK_SIZE) ++ partial_xfer_size = ASPEED_JTAG_HW2_DATA_CHUNK_SIZE; ++ else ++ partial_xfer_size = remain_xfer; ++ ++ partial_index = index; ++ partial_xfer = partial_xfer_size; ++ ++ reg_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ ++ /* ++ * The FIFO must only be reset while it is in CPU mode; ++ * asserting RESET_FIFO in controller mode corrupts the data ++ * of subsequent transfers. Whether the FIFO is in CPU or ++ * controller mode is decided by the hardware itself and ++ * FIFO_CTRL_MODE is read-only, so wait for the hardware to ++ * report CPU mode (0) before asserting RESET_FIFO. ++ */ ++ ret = read_poll_timeout(aspeed_jtag_read, poll_val, ++ !(poll_val & ASPEED_JTAG_GBLCTRL_FIFO_CTRL_MODE), ++ 0, ASPEED_JTAG_FIFO_POLL_TIMEOUT_US, false, ++ aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ if (ret) { ++ dev_err(aspeed_jtag->dev, ++ "timed out waiting for FIFO to return to CPU mode\n"); ++ return ret; ++ } ++ ++ /* CPU mode confirmed; it is now safe to reset the FIFO. */ ++ aspeed_jtag_write(aspeed_jtag, reg_val | ++ ASPEED_JTAG_GBLCTRL_RESET_FIFO, ++ ASPEED_JTAG_GBLCTRL); ++ ++ /* ++ * RESET_FIFO is self-clearing; wait for it to read back as 0 ++ * so the FIFO is guaranteed flushed before we start loading ++ * data into it. ++ */ ++ ret = read_poll_timeout(aspeed_jtag_read, poll_val, ++ !(poll_val & ASPEED_JTAG_GBLCTRL_RESET_FIFO), ++ 0, ASPEED_JTAG_FIFO_POLL_TIMEOUT_US, false, ++ aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ if (ret) { ++ dev_err(aspeed_jtag->dev, ++ "timed out waiting for FIFO reset to clear\n"); ++ return ret; ++ } ++ ++ while (partial_xfer) { ++ if (partial_xfer > ASPEED_JTAG_DATA_CHUNK_SIZE) ++ shift_bits = ASPEED_JTAG_DATA_CHUNK_SIZE; ++ else ++ shift_bits = partial_xfer; ++ ++ if (xfer->direction & JTAG_WRITE_XFER) ++ aspeed_jtag_write(aspeed_jtag, ++ data[partial_index++], ++ data_reg); ++ else ++ aspeed_jtag_write(aspeed_jtag, 0, data_reg); ++ if (aspeed_jtag->llops->xfer_hw_fifo_delay) ++ aspeed_jtag->llops->xfer_hw_fifo_delay(); ++ partial_xfer = partial_xfer - shift_bits; ++ } ++ if (remain_xfer > ASPEED_JTAG_HW2_DATA_CHUNK_SIZE) { ++ shift_bits = ASPEED_JTAG_HW2_DATA_CHUNK_SIZE; ++ ++ /* ++ * Transmit bytes that were not equals to column length ++ * and after the transfer go to Pause IR/DR. ++ */ ++ ++ ret = aspeed_jtag_shctrl_tms_mask(aspeed_jtag->current_state, shift, exit, ++ endstate, start_shift, 0, &tms_mask); ++ if (ret) ++ return ret; ++ ++ reg_val = aspeed_jtag_read(aspeed_jtag, ++ ASPEED_JTAG_GBLCTRL); ++ reg_val = reg_val & ~(GENMASK(22, 20)); ++ aspeed_jtag_write(aspeed_jtag, reg_val | ++ ASPEED_JTAG_GBLCTRL_FIFO_CTRL_MODE | ++ ASPEED_JTAG_GBLCTRL_UPDT_SHIFT( ++ shift_bits), ++ ASPEED_JTAG_GBLCTRL); ++ ++ aspeed_jtag_write(aspeed_jtag, tms_mask | ++ ASPEED_JTAG_SHCTRL_LWRDT_SHIFT(shift_bits), ++ ASPEED_JTAG_SHCTRL); ++ aspeed_jtag_wait_shift_complete(aspeed_jtag); ++ } else { ++ /* ++ * Read bytes equals to column length ++ */ ++ shift_bits = remain_xfer; ++ ret = aspeed_jtag_shctrl_tms_mask(aspeed_jtag->current_state, shift, exit, ++ endstate, start_shift, end_shift, ++ &tms_mask); ++ if (ret) ++ return ret; ++ ++ reg_val = aspeed_jtag_read(aspeed_jtag, ++ ASPEED_JTAG_GBLCTRL); ++ reg_val = reg_val & ~(GENMASK(22, 20)); ++ aspeed_jtag_write(aspeed_jtag, reg_val | ++ ASPEED_JTAG_GBLCTRL_FIFO_CTRL_MODE | ++ ASPEED_JTAG_GBLCTRL_UPDT_SHIFT( ++ shift_bits), ++ ASPEED_JTAG_GBLCTRL); ++ ++ aspeed_jtag_write(aspeed_jtag, tms_mask | ++ ASPEED_JTAG_SHCTRL_LWRDT_SHIFT( ++ shift_bits), ++ ASPEED_JTAG_SHCTRL); ++ ++ aspeed_jtag_wait_shift_complete(aspeed_jtag); ++ } ++ ++ partial_index = index; ++ partial_xfer = partial_xfer_size; ++ while (partial_xfer) { ++ if (partial_xfer > ++ ASPEED_JTAG_DATA_CHUNK_SIZE) { ++ shift_bits = ++ ASPEED_JTAG_DATA_CHUNK_SIZE; ++ data[partial_index++] = ++ aspeed_jtag_read(aspeed_jtag, ++ data_reg); ++ ++ } else { ++ shift_bits = partial_xfer; ++ data[partial_index++] = ++ aspeed_jtag_read(aspeed_jtag, ++ data_reg); ++ } ++ if (aspeed_jtag->llops->xfer_hw_fifo_delay) ++ aspeed_jtag->llops->xfer_hw_fifo_delay(); ++ partial_xfer = partial_xfer - shift_bits; ++ } ++ ++ remain_xfer = remain_xfer - partial_xfer_size; ++ index = partial_index; ++ start_shift = 0; ++ } ++ aspeed_jtag->current_state = endstate; ++ return 0; ++} ++ ++static int aspeed_jtag_status_get(struct jtag *jtag, u32 *status) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ *status = aspeed_jtag->current_state; ++ return 0; ++} ++ ++static irqreturn_t aspeed_jtag_interrupt(s32 this_irq, void *dev_id) ++{ ++ struct aspeed_jtag *aspeed_jtag = dev_id; ++ irqreturn_t ret; ++ u32 status; ++ ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++ ++ if (status & ASPEED_JTAG_ISR_INT_MASK) { ++ aspeed_jtag_write(aspeed_jtag, ++ (status & ASPEED_JTAG_ISR_INT_MASK) | ++ (status & ++ ASPEED_JTAG_ISR_INT_EN_MASK), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= status & ASPEED_JTAG_ISR_INT_MASK; ++ } ++ ++ if (aspeed_jtag->flag) { ++ wake_up_interruptible(&aspeed_jtag->jtag_wq); ++ ret = IRQ_HANDLED; ++ } else { ++ dev_err(aspeed_jtag->dev, "irq status:%x\n", status); ++ ret = IRQ_NONE; ++ } ++ return ret; ++} ++ ++static irqreturn_t aspeed_jtag_interrupt_hw2(s32 this_irq, void *dev_id) ++{ ++ struct aspeed_jtag *aspeed_jtag = dev_id; ++ irqreturn_t ret; ++ u32 status; ++ ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_INTCTRL); ++ ++ if (status & ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT) { ++ aspeed_jtag_write(aspeed_jtag, ++ status | ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT, ++ ASPEED_JTAG_INTCTRL); ++ aspeed_jtag->flag |= status & ASPEED_JTAG_INTCTRL_SHCPL_IRQ_STAT; ++ } ++ ++ if (aspeed_jtag->flag) { ++ wake_up_interruptible(&aspeed_jtag->jtag_wq); ++ ret = IRQ_HANDLED; ++ } else { ++ dev_err(aspeed_jtag->dev, "irq status:%x\n", status); ++ ret = IRQ_NONE; ++ } ++ return ret; ++} ++ ++static int aspeed_jtag_enable(struct jtag *jtag) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ aspeed_jtag->llops->master_enable(aspeed_jtag); ++ return 0; ++} ++ ++static int aspeed_jtag_disable(struct jtag *jtag) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ aspeed_jtag->llops->output_disable(aspeed_jtag); ++ return 0; ++} ++ ++static int aspeed_jtag_init(struct platform_device *pdev, ++ struct aspeed_jtag *aspeed_jtag) ++{ ++ struct resource *res; ++ ++ memset(aspeed_jtag->pad_data_one, ~0, ++ sizeof(aspeed_jtag->pad_data_one)); ++ memset(aspeed_jtag->pad_data_zero, 0, ++ sizeof(aspeed_jtag->pad_data_zero)); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ aspeed_jtag->reg_base = devm_ioremap_resource(aspeed_jtag->dev, res); ++ if (IS_ERR(aspeed_jtag->reg_base)) ++ return -ENOMEM; ++ ++ aspeed_jtag->pclk = devm_clk_get(aspeed_jtag->dev, NULL); ++ if (IS_ERR(aspeed_jtag->pclk)) { ++ dev_err(aspeed_jtag->dev, "devm_clk_get failed\n"); ++ return PTR_ERR(aspeed_jtag->pclk); ++ } ++ ++ aspeed_jtag->irq = platform_get_irq(pdev, 0); ++ if (aspeed_jtag->irq < 0) ++ dev_warn(aspeed_jtag->dev, ++ "no irq specified, using polling mode"); ++ ++ if (clk_prepare_enable(aspeed_jtag->pclk)) { ++ dev_err(aspeed_jtag->dev, "no irq specified\n"); ++ return -ENOENT; ++ } ++ ++ aspeed_jtag->rst = devm_reset_control_get_shared(&pdev->dev, NULL); ++ if (IS_ERR(aspeed_jtag->rst)) { ++ dev_err(aspeed_jtag->dev, ++ "missing or invalid reset controller device tree entry"); ++ return PTR_ERR(aspeed_jtag->rst); ++ } ++ reset_control_deassert(aspeed_jtag->rst); ++ ++ if (aspeed_jtag->irq >= 0) { ++ aspeed_jtag->irq = ++ devm_request_irq(aspeed_jtag->dev, aspeed_jtag->irq, ++ aspeed_jtag->llops->jtag_interrupt, 0, ++ "aspeed-jtag", aspeed_jtag); ++ if (aspeed_jtag->irq) { ++ dev_warn(aspeed_jtag->dev, ++ "unable to request IRQ, using polling mode"); ++ } ++ } ++ ++ aspeed_jtag->llops->output_disable(aspeed_jtag); ++ ++ aspeed_jtag->flag = 0; ++ aspeed_jtag->mode = 0; ++ init_waitqueue_head(&aspeed_jtag->jtag_wq); ++ return 0; ++} ++ ++static int aspeed_jtag_deinit(struct platform_device *pdev, ++ struct aspeed_jtag *aspeed_jtag) ++{ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_ISR); ++ /* Disable clock */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); ++ reset_control_assert(aspeed_jtag->rst); ++ clk_disable_unprepare(aspeed_jtag->pclk); ++ return 0; ++} ++ ++static int aspeed_jtag_trst_set_hw1(struct aspeed_jtag *aspeed_jtag, u32 active) ++{ ++ aspeed_jtag_write(aspeed_jtag, active ? 0 : ASPEED_JTAG_EC_TRSTn_HIGH, ++ ASPEED_JTAG_EC); ++ return 0; ++} ++ ++static int aspeed_jtag_trst_set_hw2(struct aspeed_jtag *aspeed_jtag, u32 active) ++{ ++ u32 reg_val; ++ ++ reg_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_GBLCTRL); ++ if (active) ++ reg_val |= ASPEED_JTAG_GBLCTRL_TRST; ++ else ++ reg_val &= ~ASPEED_JTAG_GBLCTRL_TRST; ++ aspeed_jtag_write(aspeed_jtag, reg_val, ASPEED_JTAG_GBLCTRL); ++ return 0; ++} ++ ++static int aspeed_jtag_trst_set(struct jtag *jtag, u32 active) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ return aspeed_jtag->llops->trst_set(aspeed_jtag, active); ++} ++ ++static const struct jtag_ops aspeed_jtag_ops = { ++ .freq_get = aspeed_jtag_freq_get, ++ .freq_set = aspeed_jtag_freq_set, ++ .status_get = aspeed_jtag_status_get, ++ .status_set = aspeed_jtag_status_set, ++ .xfer = aspeed_jtag_xfer, ++ .mode_set = aspeed_jtag_mode_set, ++ .bitbang = aspeed_jtag_bitbang, ++ .enable = aspeed_jtag_enable, ++ .disable = aspeed_jtag_disable ++}; ++ ++static const struct jtag_ops aspeed_jtag_ops_26xx = { ++#ifdef ASPEED_JTAG_HW_MODE_2_ENABLE ++ .freq_get = aspeed_jtag_freq_get_26xx, ++ .freq_set = aspeed_jtag_freq_set_26xx, ++ .status_get = aspeed_jtag_status_get, ++ .status_set = aspeed_jtag_status_set_26xx, ++#else ++ .freq_get = aspeed_jtag_freq_get, ++ .freq_set = aspeed_jtag_freq_set, ++ .status_get = aspeed_jtag_status_get, ++ .status_set = aspeed_jtag_status_set, ++#endif ++ .xfer = aspeed_jtag_xfer, ++ .mode_set = aspeed_jtag_mode_set, ++ .trst_set = aspeed_jtag_trst_set, ++ .bitbang = aspeed_jtag_bitbang, ++ .enable = aspeed_jtag_enable, ++ .disable = aspeed_jtag_disable ++}; ++ ++static const struct jtag_low_level_functions ast25xx_llops = { ++ .master_enable = aspeed_jtag_master, ++ .output_disable = aspeed_jtag_output_disable, ++ .xfer_push_data = aspeed_jtag_xfer_push_data, ++ .xfer_push_data_last = aspeed_jtag_xfer_push_data_last, ++ .xfer_sw = aspeed_jtag_xfer_sw, ++ .xfer_hw = aspeed_jtag_xfer_hw, ++ .xfer_hw_fifo_delay = NULL, ++ .xfer_sw_delay = NULL, ++ .jtag_interrupt = aspeed_jtag_interrupt, ++ .trst_set = aspeed_jtag_trst_set_hw1 ++}; ++ ++static const struct aspeed_jtag_functions ast25xx_functions = { ++ .aspeed_jtag_ops = &aspeed_jtag_ops, ++ .aspeed_jtag_llops = &ast25xx_llops ++}; ++ ++static const struct jtag_low_level_functions ast26xx_llops = { ++#ifdef ASPEED_JTAG_HW_MODE_2_ENABLE ++ .master_enable = aspeed_jtag_master_26xx, ++ .output_disable = aspeed_jtag_output_disable_26xx, ++ .xfer_push_data = aspeed_jtag_xfer_push_data_26xx, ++ .xfer_push_data_last = aspeed_jtag_xfer_push_data_last_26xx, ++ .xfer_sw = aspeed_jtag_xfer_sw, ++ .xfer_hw = aspeed_jtag_xfer_hw2, ++ .xfer_hw_fifo_delay = aspeed_jtag_xfer_hw_fifo_delay_26xx, ++ .xfer_sw_delay = aspeed_jtag_sw_delay_26xx, ++ .jtag_interrupt = aspeed_jtag_interrupt_hw2, ++ .trst_set = aspeed_jtag_trst_set_hw2 ++#else ++ .master_enable = aspeed_jtag_master, ++ .output_disable = aspeed_jtag_output_disable, ++ .xfer_push_data = aspeed_jtag_xfer_push_data_26xx, ++ .xfer_push_data_last = aspeed_jtag_xfer_push_data_last_26xx, ++ .xfer_sw = aspeed_jtag_xfer_sw, ++ .xfer_hw = aspeed_jtag_xfer_hw, ++ .xfer_hw_fifo_delay = aspeed_jtag_xfer_hw_fifo_delay_26xx, ++ .xfer_sw_delay = aspeed_jtag_sw_delay_26xx, ++ .jtag_interrupt = aspeed_jtag_interrupt, ++ .trst_set = aspeed_jtag_trst_set_hw1 ++#endif ++}; ++ ++static const struct aspeed_jtag_functions ast26xx_functions = { ++ .aspeed_jtag_ops = &aspeed_jtag_ops_26xx, ++ .aspeed_jtag_llops = &ast26xx_llops ++}; ++ ++static const struct of_device_id aspeed_jtag_of_match[] = { ++ { .compatible = "aspeed,ast2400-jtag", .data = &ast25xx_functions }, ++ { .compatible = "aspeed,ast2500-jtag", .data = &ast25xx_functions }, ++ { .compatible = "aspeed,ast2600-jtag", .data = &ast26xx_functions }, ++ { .compatible = "aspeed,ast2700-jtag", .data = &ast26xx_functions }, ++ {} ++}; ++ ++static int aspeed_jtag_probe(struct platform_device *pdev) ++{ ++ struct aspeed_jtag *aspeed_jtag; ++ struct jtag *jtag; ++ const struct of_device_id *match; ++ const struct aspeed_jtag_functions *jtag_functions; ++ int err; ++ ++ match = of_match_node(aspeed_jtag_of_match, pdev->dev.of_node); ++ if (!match) ++ return -ENODEV; ++ jtag_functions = match->data; ++ ++ jtag = jtag_alloc(&pdev->dev, sizeof(*aspeed_jtag), ++ jtag_functions->aspeed_jtag_ops); ++ if (!jtag) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, jtag); ++ aspeed_jtag = jtag_priv(jtag); ++ aspeed_jtag->dev = &pdev->dev; ++ ++ aspeed_jtag->llops = jtag_functions->aspeed_jtag_llops; ++ ++ /* Initialize device*/ ++ err = aspeed_jtag_init(pdev, aspeed_jtag); ++ if (err) ++ goto err_jtag_init; ++ ++ /* Initialize JTAG core structure*/ ++ err = devm_jtag_register(aspeed_jtag->dev, jtag); ++ if (err) ++ goto err_jtag_register; ++ ++ jtag_functions->aspeed_jtag_ops->freq_set(jtag, 1000000); ++ ++ return 0; ++ ++err_jtag_register: ++ aspeed_jtag_deinit(pdev, aspeed_jtag); ++err_jtag_init: ++ jtag_free(jtag); ++ return err; ++} ++ ++static void aspeed_jtag_remove(struct platform_device *pdev) ++{ ++ struct jtag *jtag = platform_get_drvdata(pdev); ++ ++ aspeed_jtag_deinit(pdev, jtag_priv(jtag)); ++} ++ ++static struct platform_driver aspeed_jtag_driver = { ++ .probe = aspeed_jtag_probe, ++ .remove = aspeed_jtag_remove, ++ .driver = { ++ .name = ASPEED_JTAG_NAME, ++ .of_match_table = aspeed_jtag_of_match, ++ }, ++}; ++module_platform_driver(aspeed_jtag_driver); ++ ++MODULE_AUTHOR("Oleksandr Shamray "); ++MODULE_DESCRIPTION("ASPEED JTAG driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/mailbox/ast2700-mailbox.c b/drivers/mailbox/ast2700-mailbox.c +--- a/drivers/mailbox/ast2700-mailbox.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/mailbox/ast2700-mailbox.c 2026-09-09 15:51:44.624239492 +0000 +@@ -0,0 +1,235 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright Aspeed Technology Inc. (C) 2025. All rights reserved ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Each bit in the register represents an IPC ID */ ++#define IPCR_TX_TRIG 0x00 ++#define IPCR_ENABLE 0x04 ++#define IPCR_STATUS 0x08 ++#define RX_IRQ(n) BIT(n) ++#define RX_IRQ_MASK 0xf ++#define IPCR_DATA 0x10 ++ ++struct ast2700_mbox_data { ++ u8 num_chans; ++ u8 msg_size; ++}; ++ ++struct ast2700_mbox { ++ struct mbox_controller mbox; ++ u8 msg_size; ++ void __iomem *tx_regs; ++ void __iomem *rx_regs; ++ spinlock_t lock; /* control register lock */ ++}; ++ ++static inline int ch_num(struct mbox_chan *chan) ++{ ++ return chan - chan->mbox->chans; ++} ++ ++static inline bool ast2700_mbox_tx_done(struct ast2700_mbox *mb, int idx) ++{ ++ return !(readl(mb->tx_regs + IPCR_STATUS) & BIT(idx)); ++} ++ ++static irqreturn_t ast2700_mbox_irq(int irq, void *p) ++{ ++ struct ast2700_mbox *mb = p; ++ void __iomem *data_reg; ++ int num_words = mb->msg_size / sizeof(u32); ++ u32 *word_data; ++ u32 status; ++ int n, i; ++ ++ /* Only examine channels that are currently enabled. */ ++ status = readl(mb->rx_regs + IPCR_ENABLE) & ++ readl(mb->rx_regs + IPCR_STATUS); ++ ++ if (!(status & RX_IRQ_MASK)) ++ return IRQ_NONE; ++ ++ for (n = 0; n < mb->mbox.num_chans; ++n) { ++ struct mbox_chan *chan = &mb->mbox.chans[n]; ++ ++ if (!(status & RX_IRQ(n))) ++ continue; ++ ++ data_reg = mb->rx_regs + IPCR_DATA + mb->msg_size * n; ++ word_data = chan->con_priv; ++ /* Read the message data */ ++ for (i = 0; i < num_words; i++) ++ word_data[i] = readl(data_reg + i * sizeof(u32)); ++ ++ mbox_chan_received_data(chan, chan->con_priv); ++ ++ /* The IRQ can be cleared only once the FIFO is empty. */ ++ writel(RX_IRQ(n), mb->rx_regs + IPCR_STATUS); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int ast2700_mbox_send_data(struct mbox_chan *chan, void *data) ++{ ++ struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); ++ int idx = ch_num(chan); ++ void __iomem *data_reg = mb->tx_regs + IPCR_DATA + mb->msg_size * idx; ++ u32 *word_data = data; ++ int num_words = mb->msg_size / sizeof(u32); ++ int i; ++ ++ if (!(readl(mb->tx_regs + IPCR_ENABLE) & BIT(idx))) { ++ dev_warn(mb->mbox.dev, "%s: Ch-%d not enabled yet\n", __func__, idx); ++ return -ENODEV; ++ } ++ ++ if (!(ast2700_mbox_tx_done(mb, idx))) { ++ dev_warn(mb->mbox.dev, "%s: Ch-%d last data has not finished\n", __func__, idx); ++ return -EBUSY; ++ } ++ ++ /* Write the message data */ ++ for (i = 0 ; i < num_words; i++) ++ writel(word_data[i], data_reg + i * sizeof(u32)); ++ ++ writel(BIT(idx), mb->tx_regs + IPCR_TX_TRIG); ++ dev_dbg(mb->mbox.dev, "%s: Ch-%d sent\n", __func__, idx); ++ ++ return 0; ++} ++ ++static int ast2700_mbox_startup(struct mbox_chan *chan) ++{ ++ struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); ++ int idx = ch_num(chan); ++ void __iomem *reg = mb->rx_regs + IPCR_ENABLE; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&mb->lock, flags); ++ writel(readl(reg) | BIT(idx), reg); ++ spin_unlock_irqrestore(&mb->lock, flags); ++ ++ return 0; ++} ++ ++static void ast2700_mbox_shutdown(struct mbox_chan *chan) ++{ ++ struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); ++ int idx = ch_num(chan); ++ void __iomem *reg = mb->rx_regs + IPCR_ENABLE; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&mb->lock, flags); ++ writel(readl(reg) & ~BIT(idx), reg); ++ spin_unlock_irqrestore(&mb->lock, flags); ++} ++ ++static bool ast2700_mbox_last_tx_done(struct mbox_chan *chan) ++{ ++ struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); ++ int idx = ch_num(chan); ++ ++ return ast2700_mbox_tx_done(mb, idx); ++} ++ ++static const struct mbox_chan_ops ast2700_mbox_chan_ops = { ++ .send_data = ast2700_mbox_send_data, ++ .startup = ast2700_mbox_startup, ++ .shutdown = ast2700_mbox_shutdown, ++ .last_tx_done = ast2700_mbox_last_tx_done, ++}; ++ ++static int ast2700_mbox_probe(struct platform_device *pdev) ++{ ++ struct ast2700_mbox *mb; ++ const struct ast2700_mbox_data *dev_data; ++ struct device *dev = &pdev->dev; ++ int irq, ret; ++ ++ if (!pdev->dev.of_node) ++ return -ENODEV; ++ ++ dev_data = device_get_match_data(&pdev->dev); ++ ++ mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL); ++ if (!mb) ++ return -ENOMEM; ++ ++ mb->mbox.chans = devm_kcalloc(&pdev->dev, dev_data->num_chans, ++ sizeof(*mb->mbox.chans), GFP_KERNEL); ++ if (!mb->mbox.chans) ++ return -ENOMEM; ++ ++ /* con_priv of each channel is used to store the message received */ ++ for (int i = 0; i < dev_data->num_chans; i++) { ++ mb->mbox.chans[i].con_priv = devm_kcalloc(dev, dev_data->msg_size, ++ sizeof(u8), GFP_KERNEL); ++ if (!mb->mbox.chans[i].con_priv) ++ return -ENOMEM; ++ } ++ ++ platform_set_drvdata(pdev, mb); ++ ++ mb->tx_regs = devm_platform_ioremap_resource_byname(pdev, "tx"); ++ if (IS_ERR(mb->tx_regs)) ++ return PTR_ERR(mb->tx_regs); ++ ++ mb->rx_regs = devm_platform_ioremap_resource_byname(pdev, "rx"); ++ if (IS_ERR(mb->rx_regs)) ++ return PTR_ERR(mb->rx_regs); ++ ++ mb->msg_size = dev_data->msg_size; ++ mb->mbox.dev = dev; ++ mb->mbox.num_chans = dev_data->num_chans; ++ mb->mbox.ops = &ast2700_mbox_chan_ops; ++ mb->mbox.txdone_irq = false; ++ mb->mbox.txdone_poll = true; ++ mb->mbox.txpoll_period = 5; ++ spin_lock_init(&mb->lock); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ ret = devm_request_irq(dev, irq, ast2700_mbox_irq, 0, dev_name(dev), mb); ++ if (ret) ++ return ret; ++ ++ return devm_mbox_controller_register(dev, &mb->mbox); ++} ++ ++static const struct ast2700_mbox_data ast2700_dev_data = { ++ .num_chans = 4, ++ .msg_size = 0x20, ++}; ++ ++static const struct of_device_id ast2700_mbox_of_match[] = { ++ { .compatible = "aspeed,ast2700-mailbox", .data = &ast2700_dev_data }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, ast2700_mbox_of_match); ++ ++static struct platform_driver ast2700_mbox_driver = { ++ .driver = { ++ .name = "ast2700-mailbox", ++ .of_match_table = ast2700_mbox_of_match, ++ }, ++ .probe = ast2700_mbox_probe, ++}; ++module_platform_driver(ast2700_mbox_driver); ++ ++MODULE_AUTHOR("Jammy Huang "); ++MODULE_DESCRIPTION("ASPEED AST2700 IPC driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/platform/aspeed/aspeed-video.c b/drivers/media/platform/aspeed/aspeed-video.c +--- a/drivers/media/platform/aspeed/aspeed-video.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/media/platform/aspeed/aspeed-video.c 2026-09-09 15:51:44.727237936 +0000 +@@ -4,6 +4,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -25,6 +26,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -96,6 +100,7 @@ + #define VE_CTRL_INTERLACE BIT(14) + #define VE_CTRL_HSYNC_POL_CTRL BIT(15) + #define VE_CTRL_FRC GENMASK(23, 16) ++#define AST2600_VE_CTRL_EN_COMPARE_ONLY BIT(31) + + #define VE_TGS_0 0x00c + #define VE_TGS_1 0x010 +@@ -149,6 +154,8 @@ + #define AST2400_VE_COMP_SIZE_READ_BACK 0x078 + #define AST2600_VE_COMP_SIZE_READ_BACK 0x084 + ++#define VE_COMP_FRAME_COUNT_READ_BACK 0x07C ++ + #define VE_SRC_LR_EDGE_DET 0x090 + #define VE_SRC_LR_EDGE_DET_LEFT GENMASK(11, 0) + #define VE_SRC_LR_EDGE_DET_NO_V BIT(12) +@@ -203,6 +210,51 @@ + #define VE_MEM_RESTRICT_START 0x310 + #define VE_MEM_RESTRICT_END 0x314 + ++/* SCU's registers */ ++#define SCU_MISC_CTRL 0xC0 ++#define SCU_DPLL_SOURCE BIT(20) ++ ++#define SCU_CLK_SEL 0x288 ++#define SCU_SOC_DISPLAY_SEL BIT(15) ++ ++#define SCU_CLK_SEL2 0x304 ++#define SCU_VIDEO_OUTPUT_DELAY GENMASK(5, 0) ++ ++#define SCU_CRT2CLK 0x350 ++#define SCU_CRT2CLK_N GENMASK(31, 16) ++#define SCU_CRT2CLK_R GENMASK(15, 0) ++ ++#define SCU_MULTI_FUNC_12 0x440 ++#define SCU_MULTI_FUNC_CPU_SLI_DIR BIT(5) ++#define SCU_MULTI_FUNC_15 0x454 ++#define SCU_MULTI_FUNC_IO_SLI_DIR BIT(21) ++ ++/* GFX's registers */ ++#define GFX_CTRL 0x60 ++#define GFX_CTRL_ENABLE BIT(0) ++#define GFX_CTRL_FMT GENMASK(9, 7) ++ ++#define GFX_H_DISPLAY 0x70 ++#define GFX_H_DISPLAY_DE GENMASK(28, 16) ++#define GFX_H_DISPLAY_TOTAL GENMASK(12, 0) ++ ++#define GFX_V_DISPLAY 0x78 ++#define GFX_V_DISPLAY_DE GENMASK(27, 16) ++#define GFX_V_DISPLAY_TOTAL GENMASK(11, 0) ++ ++#define GFX_DISPLAY_ADDR 0x80 ++ ++enum { ++ VIDEO_CLK_25MHz = 0, ++ VIDEO_CLK_D1, ++ VIDEO_CLK_D2, ++ VIDEO_CLK_CRT1, ++ VIDEO_CLK_CRT2, ++ VIDEO_CLK_HPLL, ++ VIDEO_CLK_MPLL, ++ VIDEO_CLK_48MHz, ++}; ++ + /* + * VIDEO_MODE_DETECT_DONE: a flag raised if signal lock + * VIDEO_RES_CHANGE: a flag raised if res_change work on-going +@@ -211,6 +263,7 @@ + * VIDEO_FRAME_INPRG: a flag raised if hw working on a frame + * VIDEO_STOPPED: a flag raised if device release + * VIDEO_CLOCKS_ON: a flag raised if clk is on ++ * VIDEO_BOUNDING_BOX: a flag raised if box-finding for partial-jpeg + */ + enum { + VIDEO_MODE_DETECT_DONE, +@@ -220,12 +273,14 @@ + VIDEO_FRAME_INPRG, + VIDEO_STOPPED, + VIDEO_CLOCKS_ON, ++ VIDEO_BOUNDING_BOX, + }; + + enum aspeed_video_format { + VIDEO_FMT_STANDARD = 0, + VIDEO_FMT_ASPEED, +- VIDEO_FMT_MAX = VIDEO_FMT_ASPEED ++ VIDEO_FMT_PARTIAL, ++ VIDEO_FMT_MAX = VIDEO_FMT_PARTIAL + }; + + // for VE_CTRL_CAPTURE_FMT +@@ -243,6 +298,11 @@ + void *virt; + }; + ++struct aspeed_video_box { ++ struct v4l2_rect box; ++ struct list_head link; ++}; ++ + struct aspeed_video_buffer { + struct vb2_v4l2_buffer vb; + struct list_head link; +@@ -262,6 +322,9 @@ + /* + * struct aspeed_video - driver data + * ++ * version: holds the version of aspeed SoC ++ * base: holds the base address of video engine ++ * dvi_base: holds the base address of DVI engine. For 2700 dvi support. + * res_work: holds the delayed_work for res-detection if unlock + * buffers: holds the list of buffer queued from user + * flags: holds the state of video +@@ -270,9 +333,11 @@ + * srcs: holds the buffer information for srcs + * jpeg: holds the buffer information for jpeg header + * bcd: holds the buffer information for bcd work ++ * dbg_src: holds the buffer information for debug input + * yuv420: a flag raised if JPEG subsampling is 420 + * format: holds the video format + * hq_mode: a flag raised if HQ is enabled. Only for VIDEO_FMT_ASPEED ++ * input: holds the video input + * frame_rate: holds the frame_rate + * jpeg_quality: holds jpeq's quality (0~11) + * jpeg_hq_quality: holds hq's quality (1~12) only if hq_mode enabled +@@ -281,11 +346,16 @@ + * frame_right: end position of video data in horizontal direction + * frame_top: start position of video data in vertical direction + * perf: holds the statistics primary for debugfs ++ * bounding_box: holds the video rect for partial-jpeg ++ * boxes: holds the list of video-rect info for each partial-jpeg + */ + struct aspeed_video { + void __iomem *base; ++ void __iomem *dvi_base; + struct clk *eclk; + struct clk *vclk; ++ struct clk *crt2clk; ++ struct reset_control *reset; + + struct device *dev; + struct v4l2_ctrl_handler ctrl_handler; +@@ -297,9 +367,15 @@ + struct vb2_queue queue; + struct video_device vdev; + struct mutex video_lock; /* v4l2 and videobuf2 lock */ ++ struct dentry *debugfs_entry; ++ int id; + ++ struct regmap *scu; ++ struct regmap *gfx; ++ u32 version; + u32 jpeg_mode; + u32 comp_size_read; ++ u32 compare_only; + + wait_queue_head_t wait; + spinlock_t lock; /* buffer list lock */ +@@ -307,15 +383,20 @@ + struct list_head buffers; + unsigned long flags; + unsigned int sequence; ++ struct workqueue_struct *rst_wq; ++ struct work_struct rst_work; + + unsigned int max_compressed_size; ++ struct aspeed_video_addr pool; + struct aspeed_video_addr srcs[2]; + struct aspeed_video_addr jpeg; + struct aspeed_video_addr bcd; ++ struct aspeed_video_addr dbg_src; + + bool yuv420; + enum aspeed_video_format format; + bool hq_mode; ++ enum aspeed_video_input input; + unsigned int frame_rate; + unsigned int jpeg_quality; + unsigned int jpeg_hq_quality; +@@ -326,28 +407,45 @@ + unsigned int frame_top; + + struct aspeed_video_perf perf; ++ struct v4l2_rect bounding_box; ++ struct list_head boxes; + }; + + #define to_aspeed_video(x) container_of((x), struct aspeed_video, v4l2_dev) + + struct aspeed_video_config { ++ u32 version; + u32 jpeg_mode; + u32 comp_size_read; ++ u32 compare_only; + }; + + static const struct aspeed_video_config ast2400_config = { ++ .version = 4, + .jpeg_mode = AST2400_VE_SEQ_CTRL_JPEG_MODE, + .comp_size_read = AST2400_VE_COMP_SIZE_READ_BACK, ++ .compare_only = 0, + }; + + static const struct aspeed_video_config ast2500_config = { ++ .version = 5, + .jpeg_mode = AST2500_VE_SEQ_CTRL_JPEG_MODE, + .comp_size_read = AST2400_VE_COMP_SIZE_READ_BACK, ++ .compare_only = 0, + }; + + static const struct aspeed_video_config ast2600_config = { ++ .version = 6, + .jpeg_mode = AST2500_VE_SEQ_CTRL_JPEG_MODE, + .comp_size_read = AST2600_VE_COMP_SIZE_READ_BACK, ++ .compare_only = AST2600_VE_CTRL_EN_COMPARE_ONLY, ++}; ++ ++static const struct aspeed_video_config ast2700_config = { ++ .version = 7, ++ .jpeg_mode = AST2500_VE_SEQ_CTRL_JPEG_MODE, ++ .comp_size_read = AST2600_VE_COMP_SIZE_READ_BACK, ++ .compare_only = AST2600_VE_CTRL_EN_COMPARE_ONLY, + }; + + static const u32 aspeed_video_jpeg_header[ASPEED_VIDEO_JPEG_HEADER_SIZE] = { +@@ -484,9 +582,12 @@ + }; + + static const char * const format_str[] = {"Standard JPEG", +- "Aspeed JPEG"}; ++ "Aspeed JPEG", "Partial JPEG"}; ++static const char * const input_str[] = {"HOST VGA", "BMC GFX", "MEMORY", "DVI"}; + + static unsigned int debug; ++static unsigned int dual_flag; ++DECLARE_WAIT_QUEUE_HEAD(waitq); + + static bool aspeed_video_alloc_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr, +@@ -495,6 +596,54 @@ + static void aspeed_video_free_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr); + ++/** ++ * _make_addr - make address fit for ast2700 ++ * @addr: dma address for hardware to work ++ * ++ * Return: 32bit format of address ++ */ ++static inline u32 _make_addr(dma_addr_t addr) ++{ ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ // In ast2700, it store higt byte[35:32] in low byte[3:0] ++ return (addr >> 32) | (u32)(addr); ++#else ++ return addr; ++#endif ++} ++ ++static void aspeed_video_support_vga_ctrl(struct aspeed_video *v, bool yes) ++{ ++ u32 val = yes ? BIT(0) : 0; ++ u32 reg; ++ ++ if (!v || v->version < 6) ++ return; ++ ++ if (v->version == 7) ++ reg = (v->id == 1) ? 0x914 : 0x904; ++ else ++ reg = 0x104; ++ ++ regmap_update_bits(v->scu, reg, BIT(0), val); ++} ++ ++static void aspeed_video_set_vga_on(struct aspeed_video *v, bool on) ++{ ++ u32 val = on ? BIT(1) : 0; ++ u32 reg; ++ ++ if (!v || v->version < 6) ++ return; ++ ++ if (v->version == 7) ++ reg = (v->id == 1) ? 0x914 : 0x904; ++ else ++ reg = 0x104; ++ ++ regmap_update_bits(v->scu, reg, BIT(1), val); ++} ++ + static void aspeed_video_init_jpeg_table(u32 *table, bool yuv420) + { + int i; +@@ -576,13 +725,58 @@ + p->duration); + } + ++static void aspeed_video_partial_jpeg_update_regs(struct aspeed_video *v) ++{ ++ if (test_bit(VIDEO_BOUNDING_BOX, &v->flags)) { ++ aspeed_video_update(v, VE_SEQ_CTRL, ++ v->jpeg_mode, ++ VE_SEQ_CTRL_AUTO_COMP); ++ aspeed_video_update(v, VE_BCD_CTRL, 0, ++ VE_BCD_CTRL_EN_BCD); ++ aspeed_video_write(v, VE_COMP_WINDOW, ++ v->pix_fmt.width << 16 | ++ v->pix_fmt.height); ++ v4l2_dbg(1, debug, &v->v4l2_dev, ++ "%s: BCD enabled\n", __func__); ++ } else { ++ u32 scan_lines = aspeed_video_read(v, VE_SRC_SCANLINE_OFFSET); ++ u32 frame_count = aspeed_video_read(v, VE_COMP_FRAME_COUNT_READ_BACK); ++ u32 old_src_addr, new_src_addr; ++ dma_addr_t addr; ++ u32 offset; ++ ++ if (v->version >= 7) { ++ old_src_addr = (frame_count & 0x01) ? VE_SRC0_ADDR : VE_SRC1_ADDR; ++ new_src_addr = (frame_count & 0x01) ? VE_SRC1_ADDR : VE_SRC0_ADDR; ++ } else { ++ old_src_addr = VE_SRC0_ADDR; ++ new_src_addr = VE_SRC0_ADDR; ++ } ++ addr = aspeed_video_read(v, old_src_addr); ++ ++ aspeed_video_update(v, VE_SEQ_CTRL, ++ VE_SEQ_CTRL_AUTO_COMP, ++ v->jpeg_mode); ++ aspeed_video_update(v, VE_BCD_CTRL, ++ VE_BCD_CTRL_EN_BCD, 0); ++ aspeed_video_write(v, VE_COMP_WINDOW, ++ v->bounding_box.width << 16 | ++ v->bounding_box.height); ++ ++ offset = (scan_lines * v->bounding_box.top) + ++ ((256 * v->bounding_box.left) >> (v->yuv420 ? 4 : 3)); ++ aspeed_video_write(v, new_src_addr, addr + offset); ++ v4l2_dbg(1, debug, &v->v4l2_dev, ++ "%s: BCD disabled, frame#(%d) offset(0x%x)\n", __func__, frame_count, offset); ++ } ++} ++ + static int aspeed_video_start_frame(struct aspeed_video *video) + { + dma_addr_t addr; + unsigned long flags; + struct aspeed_video_buffer *buf; + u32 seq_ctrl = aspeed_video_read(video, VE_SEQ_CTRL); +- bool bcd_buf_need = (video->format != VIDEO_FMT_STANDARD); + + if (video->v4l2_input_status) { + v4l2_dbg(1, debug, &video->v4l2_dev, "No signal; don't start frame\n"); +@@ -595,18 +789,14 @@ + return -EBUSY; + } + +- if (bcd_buf_need && !video->bcd.size) { +- if (!aspeed_video_alloc_buf(video, &video->bcd, +- VE_BCD_BUFF_SIZE)) { +- dev_err(video->dev, "Failed to allocate BCD buffer\n"); +- dev_err(video->dev, "don't start frame\n"); +- return -ENOMEM; +- } +- aspeed_video_write(video, VE_BCD_ADDR, video->bcd.dma); +- v4l2_dbg(1, debug, &video->v4l2_dev, "bcd addr(%pad) size(%d)\n", +- &video->bcd.dma, video->bcd.size); +- } else if (!bcd_buf_need && video->bcd.size) { +- aspeed_video_free_buf(video, &video->bcd); ++ if (video->input == VIDEO_INPUT_GFX) { ++ u32 val; ++ ++ // update input buffer address as gfx's ++ regmap_read(video->gfx, GFX_DISPLAY_ADDR, &val); ++ aspeed_video_write(video, VE_TGS_0, val); ++ } else if (video->input == VIDEO_INPUT_MEM) { ++ aspeed_video_write(video, VE_TGS_0, _make_addr(video->dbg_src.dma)); + } + + spin_lock_irqsave(&video->lock, flags); +@@ -624,15 +814,26 @@ + + aspeed_video_write(video, VE_COMP_PROC_OFFSET, 0); + aspeed_video_write(video, VE_COMP_OFFSET, 0); +- aspeed_video_write(video, VE_COMP_ADDR, addr); ++ aspeed_video_write(video, VE_COMP_ADDR, _make_addr(addr)); + + aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, + VE_INTERRUPT_COMP_COMPLETE); + +- video->perf.last_sample = ktime_get(); +- +- aspeed_video_update(video, VE_SEQ_CTRL, 0, +- VE_SEQ_CTRL_TRIG_CAPTURE | VE_SEQ_CTRL_TRIG_COMP); ++ if (video->format == VIDEO_FMT_PARTIAL) { ++ aspeed_video_partial_jpeg_update_regs(video); ++ if (test_bit(VIDEO_BOUNDING_BOX, &video->flags)) { ++ video->perf.last_sample = ktime_get(); ++ seq_ctrl = VE_SEQ_CTRL_TRIG_CAPTURE | VE_SEQ_CTRL_TRIG_COMP; ++ aspeed_video_update(video, VE_CTRL, video->compare_only, video->compare_only); ++ } else { ++ seq_ctrl = VE_SEQ_CTRL_TRIG_COMP; ++ aspeed_video_update(video, VE_CTRL, video->compare_only, 0); ++ } ++ } else { ++ video->perf.last_sample = ktime_get(); ++ seq_ctrl = VE_SEQ_CTRL_TRIG_CAPTURE | VE_SEQ_CTRL_TRIG_COMP; ++ } ++ aspeed_video_update(video, VE_SEQ_CTRL, 0, seq_ctrl); + + return 0; + } +@@ -660,6 +861,9 @@ + aspeed_video_write(video, VE_INTERRUPT_CTRL, 0); + aspeed_video_write(video, VE_INTERRUPT_STATUS, 0xffffffff); + ++ reset_control_assert(video->reset); ++ usleep_range(100, 200); ++ + /* Turn off the relevant clocks */ + clk_disable(video->eclk); + clk_disable(video->vclk); +@@ -676,7 +880,68 @@ + clk_enable(video->vclk); + clk_enable(video->eclk); + ++ mdelay(10); ++ reset_control_deassert(video->reset); ++ + set_bit(VIDEO_CLOCKS_ON, &video->flags); ++ ++ if (video->version >= 7) ++ queue_work(video->rst_wq, &video->rst_work); ++} ++ ++static void aspeed_video_reset(struct aspeed_video *v) ++{ ++ int rc; ++ u32 val; ++ ++ reset_control_assert(v->reset); ++ rc = reset_control_status(v->reset); ++ if (rc == 0) { ++ /* 2700 has 2 VE, but only 1 reset. To have reset work, we need ++ * to notify the other VE if reset is not asserted. ++ */ ++ val = 1 << (v->id ^ 1); ++ dual_flag |= val; ++ v4l2_dbg(2, debug, &v->v4l2_dev, "%s: reset not asserted, needs another VE(%x)\n", __func__, val); ++ wake_up_all(&waitq); ++ rc = wait_event_interruptible(waitq, (dual_flag & val) != val); ++ if (rc) ++ v4l2_dbg(2, debug, &v->v4l2_dev, "%s: another VE done, dual_flag(%d)\n", __func__, dual_flag); ++ } ++ ++ usleep_range(100, 200); ++ reset_control_deassert(v->reset); ++ udelay(1); ++} ++ ++/* ++ * aspeed_video_rst_worker: This is a work to wait event from the other VE to ++ * do full function reset because 2700's 2 VE share 1 reset line. When there ++ * is one VE wants reset, both VE needs to do it. ++ * ++ */ ++static void aspeed_video_rst_worker(struct work_struct *work) ++{ ++ struct aspeed_video *v = ++ container_of(work, struct aspeed_video, rst_work); ++ int rc; ++ ++ rc = wait_event_timeout(waitq, ++ (dual_flag & (1 << v->id)), ++ INVALID_RESOLUTION_DELAY); ++ if (rc) { ++ v4l2_dbg(2, debug, &v->v4l2_dev, "%s: dual_flag(%x)\n", __func__, dual_flag); ++ dual_flag = 0; ++ set_bit(VIDEO_RES_CHANGE, &v->flags); ++ clear_bit(VIDEO_FRAME_INPRG, &v->flags); ++ schedule_delayed_work(&v->res_work, 0); ++ wait_event_interruptible(v->wait, ++ !test_bit(VIDEO_RES_CHANGE, &v->flags)); ++ v4l2_dbg(2, debug, &v->v4l2_dev, "%s: rst and clear, %d\n", __func__, rc); ++ } ++ ++ if (test_bit(VIDEO_CLOCKS_ON, &v->flags)) ++ queue_work(v->rst_wq, &v->rst_work); + } + + static void aspeed_video_bufs_done(struct aspeed_video *video, +@@ -684,11 +949,18 @@ + { + unsigned long flags; + struct aspeed_video_buffer *buf; ++ struct aspeed_video_box *box, *tmp; + + spin_lock_irqsave(&video->lock, flags); + list_for_each_entry(buf, &video->buffers, link) + vb2_buffer_done(&buf->vb.vb2_buf, state); + INIT_LIST_HEAD(&video->buffers); ++ ++ list_for_each_entry_safe(box, tmp, &video->boxes, link) { ++ list_del(&box->link); ++ kfree(box); ++ } ++ INIT_LIST_HEAD(&video->boxes); + spin_unlock_irqrestore(&video->lock, flags); + } + +@@ -701,12 +973,96 @@ + + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + +- aspeed_video_off(video); ++ aspeed_video_write(video, VE_INTERRUPT_CTRL, 0); ++ aspeed_video_write(video, VE_INTERRUPT_STATUS, 0xffffffff); + aspeed_video_bufs_done(video, VB2_BUF_STATE_ERROR); + + schedule_delayed_work(&video->res_work, delay); + } + ++static inline bool _box_data_changed(struct aspeed_video *v, u8 data) ++{ ++ if (v->version >= 6) ++ return ((data & 0xf) != 0xf); ++ ++ return ((data & 0xf) == 0xf); ++} ++ ++static void aspeed_video_get_bounding_box(struct aspeed_video *v, ++ struct v4l2_rect *box) ++{ ++ u16 min_x, min_y, max_x, max_y; ++ u16 w, h, i, j; ++ u32 bytesperline; ++ u8 mb_shift = v->yuv420 ? 4 : 3; ++ u8 *bcd_buf = v->bcd.virt; ++ ++ if (!bcd_buf) { ++ box->left = 0; ++ box->top = 0; ++ box->width = v->pix_fmt.width; ++ box->height = v->pix_fmt.width; ++ v4l2_dbg(1, debug, &v->v4l2_dev, "%s: bcd buf not ready yet\n", __func__); ++ return; ++ } ++ ++ w = v->pix_fmt.width >> mb_shift; ++ h = v->pix_fmt.height >> mb_shift; ++ v4l2_dbg(1, debug, &v->v4l2_dev, "%s: macrobox_shift(%d) size (%d * %d)\n", ++ __func__, mb_shift, w, h); ++ ++ min_x = 0x3ff; ++ min_y = 0x3ff; ++ max_x = 0; ++ max_y = 0; ++ ++ for (j = 0; j < h; j++) { ++ bytesperline = w * j; ++ for (i = 0; i < w; i++) { ++ if (_box_data_changed(v, *(bcd_buf + bytesperline + i))) { ++ min_x = min(i, min_x); ++ max_x = max(i, max_x); ++ min_y = min(j, min_y); ++ max_y = max(j, max_y); ++ ++ // skip line if max_x can't be bigger ++ if (max_x == w) ++ i = w; ++ // skip the pixels between min_x ~ max_x ++ if (max_x > min_x && i > min_x && i < max_x) ++ i = max_x; ++ } ++ } ++ } ++ v4l2_dbg(1, debug, &v->v4l2_dev, ++ "%s: left %d right %d top %d bottom %d\n", __func__, ++ min_x, max_x, min_y, max_y); ++ ++ // clear bcd flag ++ if (v->version < 6) ++ memset(bcd_buf, 0x01, (w * h)); ++ ++ // use full size every 8 frames ++ if (IS_ALIGNED(v->sequence, 8)) { ++ min_x = 0; ++ max_x = w - 1; ++ min_y = 0; ++ max_y = h - 1; ++ } else if (min_x > max_x || min_y > max_y || max_x > w || max_y > h) { ++ memset(box, 0, sizeof(*box)); ++ v4l2_dbg(1, debug, &v->v4l2_dev, "box not found\n"); ++ return; ++ } ++ ++ box->left = min_x << mb_shift; ++ box->top = min_y << mb_shift; ++ box->width = (max_x + 1 - min_x) << mb_shift; ++ box->height = (max_y + 1 - min_y) << mb_shift; ++ v4l2_dbg(1, debug, &v->v4l2_dev, ++ "%s: x: %d, y: %d, w: %d , h: %d\n", __func__, ++ box->left, box->top, box->width, box->height); ++} ++ + static void aspeed_video_swap_src_buf(struct aspeed_video *v) + { + if (v->format == VIDEO_FMT_STANDARD) +@@ -716,24 +1072,100 @@ + if (IS_ALIGNED(v->sequence, 8)) + memset((u8 *)v->bcd.virt, 0x00, VE_BCD_BUFF_SIZE); + ++ // 2700's new design will automatically swap src at each operation ++ if (v->version > 6 && v->format == VIDEO_FMT_ASPEED) ++ return; ++ + if (v->sequence & 0x01) { +- aspeed_video_write(v, VE_SRC0_ADDR, v->srcs[1].dma); +- aspeed_video_write(v, VE_SRC1_ADDR, v->srcs[0].dma); ++ aspeed_video_write(v, VE_SRC0_ADDR, _make_addr(v->srcs[1].dma)); ++ aspeed_video_write(v, VE_SRC1_ADDR, _make_addr(v->srcs[0].dma)); + } else { +- aspeed_video_write(v, VE_SRC0_ADDR, v->srcs[0].dma); +- aspeed_video_write(v, VE_SRC1_ADDR, v->srcs[1].dma); ++ aspeed_video_write(v, VE_SRC0_ADDR, _make_addr(v->srcs[0].dma)); ++ aspeed_video_write(v, VE_SRC1_ADDR, _make_addr(v->srcs[1].dma)); ++ } ++} ++ ++static void aspeed_video_frame_done_handler(struct aspeed_video *video, ++ bool buf_done) ++{ ++ struct aspeed_video_buffer *buf; ++ bool empty = true; ++ u32 frame_size; ++ ++ if (!buf_done) ++ return; ++ ++ spin_lock(&video->lock); ++ clear_bit(VIDEO_FRAME_INPRG, &video->flags); ++ buf = list_first_entry_or_null(&video->buffers, ++ struct aspeed_video_buffer, ++ link); ++ if (buf) { ++ frame_size = aspeed_video_read(video, ++ video->comp_size_read); ++ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size); ++ ++ /* ++ * VIDEO_FMT_ASPEED requires continuous update. ++ * On the contrary, standard jpeg can keep last buffer ++ * to always have the latest result. ++ */ ++ if (video->format != VIDEO_FMT_ASPEED && ++ list_is_last(&buf->link, &video->buffers)) { ++ empty = false; ++ v4l2_dbg(1, debug, &video->v4l2_dev, "skip to keep last frame updated\n"); ++ } else { ++ buf->vb.vb2_buf.timestamp = ktime_get_ns(); ++ buf->vb.sequence = video->sequence++; ++ buf->vb.field = V4L2_FIELD_NONE; ++ vb2_buffer_done(&buf->vb.vb2_buf, ++ VB2_BUF_STATE_DONE); ++ list_del(&buf->link); ++ empty = list_empty(&video->buffers); ++ if (video->format == VIDEO_FMT_PARTIAL) { ++ struct aspeed_video_box *box = ++ kmalloc(sizeof(struct aspeed_video_box), ++ GFP_KERNEL); ++ ++ box->box = video->bounding_box; ++ list_add_tail(&box->link, &video->boxes); ++ } ++ } + } ++ spin_unlock(&video->lock); ++ ++ aspeed_video_swap_src_buf(video); ++ ++ if (test_bit(VIDEO_STREAMING, &video->flags) && !empty && ++ video->input != VIDEO_INPUT_MEM) { ++ set_bit(VIDEO_BOUNDING_BOX, &video->flags); ++ aspeed_video_start_frame(video); ++ } ++} ++ ++static irqreturn_t aspeed_video_thread_irq(int irq, void *arg) ++{ ++ struct aspeed_video *v = arg; ++ ++ aspeed_video_get_bounding_box(v, &v->bounding_box); ++ ++ if (v->bounding_box.width && v->bounding_box.height) ++ clear_bit(VIDEO_BOUNDING_BOX, &v->flags); ++ else ++ set_bit(VIDEO_BOUNDING_BOX, &v->flags); ++ ++ aspeed_video_start_frame(v); ++ ++ return IRQ_HANDLED; + } + + static irqreturn_t aspeed_video_irq(int irq, void *arg) + { + struct aspeed_video *video = arg; + u32 sts = aspeed_video_read(video, VE_INTERRUPT_STATUS); ++ bool get_box = false; + +- /* +- * Hardware sometimes asserts interrupts that we haven't actually +- * enabled; ignore them if so. +- */ ++ aspeed_video_write(video, VE_INTERRUPT_STATUS, sts); + sts &= aspeed_video_read(video, VE_INTERRUPT_CTRL); + + v4l2_dbg(2, debug, &video->v4l2_dev, "irq sts=%#x %s%s%s%s\n", sts, +@@ -755,8 +1187,6 @@ + if (test_bit(VIDEO_RES_DETECT, &video->flags)) { + aspeed_video_update(video, VE_INTERRUPT_CTRL, + VE_INTERRUPT_MODE_DETECT, 0); +- aspeed_video_write(video, VE_INTERRUPT_STATUS, +- VE_INTERRUPT_MODE_DETECT); + sts &= ~VE_INTERRUPT_MODE_DETECT; + set_bit(VIDEO_MODE_DETECT_DONE, &video->flags); + wake_up_interruptible_all(&video->wait); +@@ -772,41 +1202,13 @@ + } + + if (sts & VE_INTERRUPT_COMP_COMPLETE) { +- struct aspeed_video_buffer *buf; +- bool empty = true; +- u32 frame_size = aspeed_video_read(video, +- video->comp_size_read); +- +- update_perf(&video->perf); +- +- spin_lock(&video->lock); +- clear_bit(VIDEO_FRAME_INPRG, &video->flags); +- buf = list_first_entry_or_null(&video->buffers, +- struct aspeed_video_buffer, +- link); +- if (buf) { +- vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size); ++ bool frame_done = false; + +- /* +- * aspeed_jpeg requires continuous update. +- * On the contrary, standard jpeg can keep last buffer +- * to always have the latest result. +- */ +- if (video->format == VIDEO_FMT_STANDARD && +- list_is_last(&buf->link, &video->buffers)) { +- empty = false; +- v4l2_dbg(1, debug, &video->v4l2_dev, "skip to keep last frame updated\n"); +- } else { +- buf->vb.vb2_buf.timestamp = ktime_get_ns(); +- buf->vb.sequence = video->sequence++; +- buf->vb.field = V4L2_FIELD_NONE; +- vb2_buffer_done(&buf->vb.vb2_buf, +- VB2_BUF_STATE_DONE); +- list_del(&buf->link); +- empty = list_empty(&video->buffers); +- } +- } +- spin_unlock(&video->lock); ++ if (video->format != VIDEO_FMT_PARTIAL) ++ frame_done = true; ++ else if (!test_bit(VIDEO_BOUNDING_BOX, &video->flags) && ++ video->bounding_box.width && video->bounding_box.height) ++ frame_done = true; + + aspeed_video_update(video, VE_SEQ_CTRL, + VE_SEQ_CTRL_TRIG_CAPTURE | +@@ -814,17 +1216,60 @@ + VE_SEQ_CTRL_TRIG_COMP, 0); + aspeed_video_update(video, VE_INTERRUPT_CTRL, + VE_INTERRUPT_COMP_COMPLETE, 0); +- aspeed_video_write(video, VE_INTERRUPT_STATUS, +- VE_INTERRUPT_COMP_COMPLETE); + sts &= ~VE_INTERRUPT_COMP_COMPLETE; + +- aspeed_video_swap_src_buf(video); ++ if (frame_done) { ++ update_perf(&video->perf); ++ aspeed_video_frame_done_handler(video, frame_done); ++ } else { ++ get_box = true; ++ } ++ } ++ ++ return get_box ? IRQ_WAKE_THREAD : IRQ_HANDLED; ++} + +- if (test_bit(VIDEO_STREAMING, &video->flags) && !empty) +- aspeed_video_start_frame(video); ++static irqreturn_t aspeed_video_md_irq(int irq, void *arg) ++{ ++ struct aspeed_video *video = arg; ++ u32 sts; ++ ++ sts = readl(video->dvi_base + VE_INTERRUPT_STATUS); ++ writel(sts, video->dvi_base + VE_INTERRUPT_STATUS); ++ sts &= readl(video->dvi_base + VE_INTERRUPT_CTRL); ++ ++ v4l2_dbg(2, debug, &video->v4l2_dev, "dvi irq sts=%#x %s%s\n", sts, ++ sts & VE_INTERRUPT_MODE_DETECT_WD ? ", unlock" : "", ++ sts & VE_INTERRUPT_MODE_DETECT ? ", lock" : ""); ++ ++ if (sts & VE_INTERRUPT_MODE_DETECT_WD) { ++ writel(0, video->dvi_base + VE_INTERRUPT_CTRL); ++ writel(0xffffffff, video->dvi_base + VE_INTERRUPT_STATUS); ++ aspeed_video_irq_res_change(video, 0); ++ return IRQ_HANDLED; + } + +- return sts ? IRQ_NONE : IRQ_HANDLED; ++ if (sts & VE_INTERRUPT_MODE_DETECT) { ++ if (test_bit(VIDEO_RES_DETECT, &video->flags)) { ++ aspeed_video_update(video, VE_INTERRUPT_CTRL, ++ VE_INTERRUPT_MODE_DETECT, 0); ++ sts &= ~VE_INTERRUPT_MODE_DETECT; ++ set_bit(VIDEO_MODE_DETECT_DONE, &video->flags); ++ wake_up_interruptible_all(&video->wait); ++ } else { ++ /* ++ * Signal acquired while NOT doing resolution ++ * detection; reset the engine and re-initialize ++ */ ++ writel(0, video->dvi_base + VE_INTERRUPT_CTRL); ++ writel(0xffffffff, video->dvi_base + VE_INTERRUPT_STATUS); ++ aspeed_video_irq_res_change(video, ++ RESOLUTION_CHANGE_DELAY); ++ return IRQ_HANDLED; ++ } ++ } ++ ++ return IRQ_HANDLED; + } + + static void aspeed_video_check_and_set_polarity(struct aspeed_video *video) +@@ -896,7 +1341,7 @@ + + /* + * Get the minimum HW-supported compression buffer size for the frame size. +- * Assume worst-case JPEG compression size is 1/8 raw size. This should be ++ * Assume worst-case JPEG compression size is 1/4 raw size. This should be + * plenty even for maximum quality; any worse and the engine will simply return + * incomplete JPEGs. + */ +@@ -908,7 +1353,7 @@ + unsigned int size; + const unsigned int num_compression_packets = 4; + const unsigned int compression_packet_size = 1024; +- const unsigned int max_compressed_size = frame_size / 2; /* 4bpp / 8 */ ++ const unsigned int max_compressed_size = frame_size; /* 4bpp / 4 */ + + video->max_compressed_size = UINT_MAX; + +@@ -929,6 +1374,7 @@ + aspeed_video_write(video, VE_STREAM_BUF_SIZE, + compression_buffer_size_reg); + ++ video->max_compressed_size = round_up(max_compressed_size, 0x10000); + v4l2_dbg(1, debug, &video->v4l2_dev, "Max compressed size: %#x\n", + video->max_compressed_size); + } +@@ -1026,9 +1472,23 @@ + } + } + ++static void aspeed_video_get_resolution_gfx(struct aspeed_video *video, ++ struct v4l2_bt_timings *det) ++{ ++ u32 h_val, v_val; ++ ++ regmap_read(video->gfx, GFX_H_DISPLAY, &h_val); ++ regmap_read(video->gfx, GFX_V_DISPLAY, &v_val); ++ ++ det->width = FIELD_GET(GFX_H_DISPLAY_DE, h_val) + 1; ++ det->height = FIELD_GET(GFX_V_DISPLAY_DE, v_val) + 1; ++ video->v4l2_input_status = 0; ++} ++ + #define res_check(v) test_and_clear_bit(VIDEO_MODE_DETECT_DONE, &(v)->flags) + +-static void aspeed_video_get_resolution(struct aspeed_video *video) ++static void aspeed_video_get_resolution_vga(struct aspeed_video *video, ++ struct v4l2_bt_timings *det) + { + bool invalid_resolution = true; + int rc; +@@ -1036,7 +1496,6 @@ + u32 mds; + u32 src_lr_edge; + u32 src_tb_edge; +- struct v4l2_bt_timings *det = &video->detected_timings; + + det->width = MIN_WIDTH; + det->height = MIN_HEIGHT; +@@ -1107,20 +1566,51 @@ + return; + } + ++ if (video->input == VIDEO_INPUT_DVI && video->version == 6) ++ video->frame_right -= 1; ++ + det->height = (video->frame_bottom - video->frame_top) + 1; + det->width = (video->frame_right - video->frame_left) + 1; + video->v4l2_input_status = 0; + + aspeed_video_get_timings(video, det); + +- /* +- * Enable mode-detect watchdog, resolution-change watchdog and +- * automatic compression after frame capture. +- */ ++ /* Enable mode-detect watchdog, resolution-change watchdog */ + aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, + VE_INTERRUPT_MODE_DETECT_WD); +- aspeed_video_update(video, VE_SEQ_CTRL, 0, +- VE_SEQ_CTRL_AUTO_COMP | VE_SEQ_CTRL_EN_WATCHDOG); ++ aspeed_video_update(video, VE_SEQ_CTRL, 0, VE_SEQ_CTRL_EN_WATCHDOG); ++} ++ ++/* ++ * For ast2700 only. Due to hw design, the timing detection of DVI is ++ * in io-die. Thus, we need to use another hw to do this job. ++ */ ++static void aspeed_video_get_resolution_dvi(struct aspeed_video *video, ++ struct v4l2_bt_timings *det) ++{ ++ void *base = video->base; ++ ++ video->base = video->dvi_base; ++ aspeed_video_get_resolution_vga(video, det); ++ video->base = base; ++} ++ ++static void aspeed_video_get_resolution(struct aspeed_video *video) ++{ ++ struct v4l2_bt_timings *det = &video->detected_timings; ++ ++ // if input is MEM, leave resolution decided by user through set_dv_timings ++ if (video->input == VIDEO_INPUT_MEM) { ++ video->v4l2_input_status = 0; ++ return; ++ } ++ ++ if (video->input == VIDEO_INPUT_GFX) ++ aspeed_video_get_resolution_gfx(video, det); ++ else if (video->input == VIDEO_INPUT_DVI && video->version == 7) ++ aspeed_video_get_resolution_dvi(video, det); ++ else ++ aspeed_video_get_resolution_vga(video, det); + + v4l2_dbg(1, debug, &video->v4l2_dev, "Got resolution: %dx%d\n", + det->width, det->height); +@@ -1130,6 +1620,8 @@ + { + struct v4l2_bt_timings *act = &video->active_timings; + unsigned int size = act->width * ALIGN(act->height, 8); ++ bool is_sync_mode_ok = (video->version != 7) || ++ (video->version == 7 && video->id == 0); + + /* Set capture/compression frame sizes */ + aspeed_video_calc_compressed_size(video, size); +@@ -1156,7 +1648,8 @@ + aspeed_video_write(video, VE_SRC_SCANLINE_OFFSET, act->width * 4); + + /* Don't use direct mode below 1024 x 768 (irqs don't fire) */ +- if (size < DIRECT_FETCH_THRESHOLD) { ++ if (video->input == VIDEO_INPUT_VGA && size < DIRECT_FETCH_THRESHOLD && ++ is_sync_mode_ok) { + v4l2_dbg(1, debug, &video->v4l2_dev, "Capture: Sync Mode\n"); + aspeed_video_write(video, VE_TGS_0, + FIELD_PREP(VE_TGS_FIRST, +@@ -1170,41 +1663,50 @@ + aspeed_video_update(video, VE_CTRL, + VE_CTRL_INT_DE | VE_CTRL_DIRECT_FETCH, + VE_CTRL_INT_DE); ++ } else if (video->input == VIDEO_INPUT_DVI) { ++ v4l2_dbg(1, debug, &video->v4l2_dev, "Capture: Sync Mode for external source\n"); ++ aspeed_video_update(video, VE_CTRL, ++ VE_CTRL_INT_DE | VE_CTRL_DIRECT_FETCH, ++ 0); + } else { ++ u32 ctrl, val, bpp; ++ + v4l2_dbg(1, debug, &video->v4l2_dev, "Capture: Direct Mode\n"); ++ ctrl = VE_CTRL_DIRECT_FETCH; ++ if (video->input == VIDEO_INPUT_GFX) { ++ regmap_read(video->gfx, GFX_CTRL, &val); ++ bpp = FIELD_GET(GFX_CTRL_FMT, val) ? 32 : 16; ++ if (bpp == 16) ++ ctrl |= VE_CTRL_INT_DE; ++ aspeed_video_write(video, VE_TGS_1, act->width * (bpp >> 3)); ++ } else { ++ // stride should be the same with capture window width ++ val = aspeed_video_read(video, VE_CAP_WINDOW) >> 16; ++ aspeed_video_write(video, VE_TGS_1, val * 4); ++ } + aspeed_video_update(video, VE_CTRL, + VE_CTRL_INT_DE | VE_CTRL_DIRECT_FETCH, +- VE_CTRL_DIRECT_FETCH); ++ ctrl); + } + +- size *= 4; +- +- if (size != video->srcs[0].size) { +- if (video->srcs[0].size) +- aspeed_video_free_buf(video, &video->srcs[0]); +- if (video->srcs[1].size) +- aspeed_video_free_buf(video, &video->srcs[1]); +- +- if (!aspeed_video_alloc_buf(video, &video->srcs[0], size)) +- goto err_mem; +- if (!aspeed_video_alloc_buf(video, &video->srcs[1], size)) +- goto err_mem; +- +- v4l2_dbg(1, debug, &video->v4l2_dev, "src buf0 addr(%pad) size(%d)\n", +- &video->srcs[0].dma, video->srcs[0].size); +- v4l2_dbg(1, debug, &video->v4l2_dev, "src buf1 addr(%pad) size(%d)\n", +- &video->srcs[1].dma, video->srcs[1].size); +- aspeed_video_write(video, VE_SRC0_ADDR, video->srcs[0].dma); +- aspeed_video_write(video, VE_SRC1_ADDR, video->srcs[1].dma); +- } +- +- return; ++ aspeed_video_write(video, VE_SRC0_ADDR, _make_addr(video->srcs[0].dma)); ++ aspeed_video_write(video, VE_SRC1_ADDR, _make_addr(video->srcs[1].dma)); ++} + +-err_mem: +- dev_err(video->dev, "Failed to allocate source buffers\n"); ++/* ++ * Update relative parameters when timing changed. ++ * ++ * @video: the struct of aspeed_video ++ * @timings: the new timings ++ */ ++static void aspeed_video_update_timings(struct aspeed_video *video, struct v4l2_bt_timings *timings) ++{ ++ video->active_timings = *timings; ++ aspeed_video_set_resolution(video); + +- if (video->srcs[0].size) +- aspeed_video_free_buf(video, &video->srcs[0]); ++ video->pix_fmt.width = timings->width; ++ video->pix_fmt.height = timings->height; ++ video->pix_fmt.sizeimage = video->max_compressed_size; + } + + static void aspeed_video_update_regs(struct aspeed_video *video) +@@ -1219,6 +1721,8 @@ + u32 ctrl = 0; + u32 seq_ctrl = 0; + ++ v4l2_dbg(1, debug, &video->v4l2_dev, "input(%s)\n", ++ input_str[video->input]); + v4l2_dbg(1, debug, &video->v4l2_dev, "framerate(%d)\n", + video->frame_rate); + v4l2_dbg(1, debug, &video->v4l2_dev, "jpeg format(%s) subsample(%s)\n", +@@ -1234,14 +1738,35 @@ + else + aspeed_video_update(video, VE_BCD_CTRL, VE_BCD_CTRL_EN_BCD, 0); + ++ if (video->input == VIDEO_INPUT_VGA) ++ ctrl |= VE_CTRL_AUTO_OR_CURSOR; ++ ++ if (video->input == VIDEO_INPUT_DVI) ++ ctrl |= VE_CTRL_SOURCE; ++ ++ if (video->version == 7) { ++ if (video->input == VIDEO_INPUT_DVI) ++ ctrl |= FIELD_PREP(VE_CTRL_CLK_DELAY, VIDEO_CLK_CRT2); ++ else if (video->input == VIDEO_INPUT_VGA && video->id == 0) ++ ctrl |= FIELD_PREP(VE_CTRL_CLK_DELAY, VIDEO_CLK_D1); ++ else ++ ctrl |= FIELD_PREP(VE_CTRL_CLK_DELAY, VIDEO_CLK_48MHz); ++ } ++ + if (video->frame_rate) + ctrl |= FIELD_PREP(VE_CTRL_FRC, video->frame_rate); + ++ if (video->format == VIDEO_FMT_PARTIAL) ++ ctrl |= video->compare_only; ++ + if (video->format == VIDEO_FMT_STANDARD) { + comp_ctrl &= ~FIELD_PREP(VE_COMP_CTRL_EN_HQ, video->hq_mode); + seq_ctrl |= video->jpeg_mode; + } + ++ if (video->format != VIDEO_FMT_PARTIAL) ++ seq_ctrl |= VE_SEQ_CTRL_AUTO_COMP; ++ + if (video->yuv420) + seq_ctrl |= VE_SEQ_CTRL_YUV420; + +@@ -1252,7 +1777,9 @@ + aspeed_video_update(video, VE_SEQ_CTRL, + video->jpeg_mode | VE_SEQ_CTRL_YUV420, + seq_ctrl); +- aspeed_video_update(video, VE_CTRL, VE_CTRL_FRC, ctrl); ++ aspeed_video_update(video, VE_CTRL, ++ VE_CTRL_FRC | VE_CTRL_AUTO_OR_CURSOR | ++ VE_CTRL_SOURCE | VE_CTRL_CLK_DELAY, ctrl); + aspeed_video_update(video, VE_COMP_CTRL, + VE_COMP_CTRL_DCT_LUM | VE_COMP_CTRL_DCT_CHR | + VE_COMP_CTRL_EN_HQ | VE_COMP_CTRL_HQ_DCT_LUM | +@@ -1278,8 +1805,18 @@ + aspeed_video_write(video, VE_COMP_OFFSET, 0); + + aspeed_video_write(video, VE_JPEG_ADDR, video->jpeg.dma); ++ aspeed_video_write(video, VE_BCD_ADDR, _make_addr(video->bcd.dma)); + + /* Set control registers */ ++ aspeed_video_write(video, VE_SEQ_CTRL, VE_SEQ_CTRL_AUTO_COMP); ++ if (video->version == 7) { ++ if (video->input == VIDEO_INPUT_DVI) ++ ctrl |= FIELD_PREP(VE_CTRL_CLK_DELAY, VIDEO_CLK_CRT2); ++ else if (video->input == VIDEO_INPUT_VGA && video->id == 0) ++ ctrl |= FIELD_PREP(VE_CTRL_CLK_DELAY, VIDEO_CLK_D1); ++ else ++ ctrl |= FIELD_PREP(VE_CTRL_CLK_DELAY, VIDEO_CLK_48MHz); ++ } + aspeed_video_write(video, VE_CTRL, ctrl); + aspeed_video_write(video, VE_COMP_CTRL, VE_COMP_CTRL_RSVD); + +@@ -1303,6 +1840,8 @@ + + static void aspeed_video_start(struct aspeed_video *video) + { ++ aspeed_video_set_vga_on(video, true); ++ + aspeed_video_on(video); + + aspeed_video_init_regs(video); +@@ -1311,30 +1850,18 @@ + aspeed_video_get_resolution(video); + + /* Set timings since the device is being opened for the first time */ +- video->active_timings = video->detected_timings; +- aspeed_video_set_resolution(video); +- +- video->pix_fmt.width = video->active_timings.width; +- video->pix_fmt.height = video->active_timings.height; +- video->pix_fmt.sizeimage = video->max_compressed_size; ++ aspeed_video_update_timings(video, &video->detected_timings); + } + + static void aspeed_video_stop(struct aspeed_video *video) + { ++ aspeed_video_set_vga_on(video, false); ++ + set_bit(VIDEO_STOPPED, &video->flags); + cancel_delayed_work_sync(&video->res_work); + + aspeed_video_off(video); + +- if (video->srcs[0].size) +- aspeed_video_free_buf(video, &video->srcs[0]); +- +- if (video->srcs[1].size) +- aspeed_video_free_buf(video, &video->srcs[1]); +- +- if (video->bcd.size) +- aspeed_video_free_buf(video, &video->bcd); +- + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + video->flags = 0; + } +@@ -1342,10 +1869,14 @@ + static int aspeed_video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) + { ++ struct aspeed_video *video = video_drvdata(file); ++ + strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver)); + strscpy(cap->card, "Aspeed Video Engine", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + DEVICE_NAME); ++ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform: %s%d", ++ DEVICE_NAME, video->id); + + return 0; + } +@@ -1383,7 +1914,8 @@ + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_JPEG: +- video->format = VIDEO_FMT_STANDARD; ++ video->format = (f->fmt.pix.flags == V4L2_PIX_FMT_FLAG_PARTIAL_JPG) ++ ? VIDEO_FMT_PARTIAL : VIDEO_FMT_STANDARD; + break; + case V4L2_PIX_FMT_AJPG: + video->format = VIDEO_FMT_ASPEED; +@@ -1401,10 +1933,10 @@ + { + struct aspeed_video *video = video_drvdata(file); + +- if (inp->index) ++ if (inp->index >= VIDEO_INPUT_MAX) + return -EINVAL; + +- strscpy(inp->name, "Host VGA capture", sizeof(inp->name)); ++ sprintf(inp->name, "%s capture", input_str[inp->index]); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; + inp->status = video->v4l2_input_status; +@@ -1414,15 +1946,110 @@ + + static int aspeed_video_get_input(struct file *file, void *fh, unsigned int *i) + { +- *i = 0; ++ struct aspeed_video *video = video_drvdata(file); ++ ++ *i = video->input; + + return 0; + } + + static int aspeed_video_set_input(struct file *file, void *fh, unsigned int i) + { +- if (i) ++ struct aspeed_video *video = video_drvdata(file); ++ ++ if (i >= VIDEO_INPUT_MAX) ++ return -EINVAL; ++ ++ if (IS_ERR(video->scu)) { ++ v4l2_dbg(1, debug, &video->v4l2_dev, "%s: scu isn't ready for input-control\n", __func__); ++ return -EINVAL; ++ } ++ ++ if (IS_ERR(video->gfx) && i == VIDEO_INPUT_GFX) { ++ v4l2_dbg(1, debug, &video->v4l2_dev, "%s: gfx isn't ready for GFX input\n", __func__); + return -EINVAL; ++ } ++ ++ // prepare memory space for user to put test batch ++ if (i == VIDEO_INPUT_MEM && !video->dbg_src.size) { ++ if (!aspeed_video_alloc_buf(video, &video->dbg_src, VE_MAX_SRC_BUFFER_SIZE)) { ++ v4l2_err(&video->v4l2_dev, "Failed to allocate buffer for debug input\n"); ++ return -EINVAL; ++ } ++ v4l2_dbg(1, debug, &video->v4l2_dev, "dbg src addr(%pad) size(%d)\n", ++ &video->dbg_src.dma, video->dbg_src.size); ++ } ++ if (i != VIDEO_INPUT_MEM && video->dbg_src.size) ++ aspeed_video_free_buf(video, &video->dbg_src); ++ ++ if (i == VIDEO_INPUT_DVI && video->version == 7) { ++ if (IS_ERR(video->dvi_base)) { ++ v4l2_err(&video->v4l2_dev, "%s: dvi isn't ready for DVI input\n", __func__); ++ return -EINVAL; ++ } ++ ++ /* Set DVI mode detection defaults */ ++ writel(FIELD_PREP(VE_MODE_DT_HOR_TOLER, 2) | ++ FIELD_PREP(VE_MODE_DT_VER_TOLER, 2) | ++ FIELD_PREP(VE_MODE_DT_HOR_STABLE, 6) | ++ FIELD_PREP(VE_MODE_DT_VER_STABLE, 6) | ++ FIELD_PREP(VE_MODE_DT_EDG_THROD, 0x65), ++ video->dvi_base + VE_MODE_DETECT); ++ } ++ ++ video->input = i; ++ ++ if (video->version == 6) { ++ /* modify dpll source per current input */ ++ if (video->input == VIDEO_INPUT_VGA) ++ regmap_update_bits(video->scu, SCU_MISC_CTRL, SCU_DPLL_SOURCE, 0); ++ else ++ regmap_update_bits(video->scu, SCU_MISC_CTRL, SCU_DPLL_SOURCE, SCU_DPLL_SOURCE); ++ ++ // SLI direction: inverse if DVI ++ if (video->input == VIDEO_INPUT_DVI) { ++ regmap_update_bits(video->scu, SCU_MULTI_FUNC_12, ++ SCU_MULTI_FUNC_CPU_SLI_DIR, ++ SCU_MULTI_FUNC_CPU_SLI_DIR); ++ regmap_update_bits(video->scu, SCU_MULTI_FUNC_15, ++ SCU_MULTI_FUNC_IO_SLI_DIR, ++ SCU_MULTI_FUNC_IO_SLI_DIR); ++ regmap_update_bits(video->scu, SCU_CLK_SEL2, ++ SCU_VIDEO_OUTPUT_DELAY, ++ 4); ++ } else { ++ regmap_update_bits(video->scu, SCU_MULTI_FUNC_12, ++ SCU_MULTI_FUNC_CPU_SLI_DIR, ++ 0); ++ regmap_update_bits(video->scu, SCU_MULTI_FUNC_15, ++ SCU_MULTI_FUNC_IO_SLI_DIR, ++ 0); ++ } ++ } else if (video->version == 7) { ++ if (video->input == VIDEO_INPUT_DVI) { ++ // CRT2CLK = 500 * R / N ++ regmap_write(video->scu, SCU_CRT2CLK, ++ FIELD_PREP(SCU_CRT2CLK_N, 50) | FIELD_PREP(SCU_CRT2CLK_R, 15)); ++ ++ regmap_write(video->scu, SCU_CLK_SEL, FIELD_PREP(SCU_SOC_DISPLAY_SEL, 1)); ++ } else { ++ regmap_write(video->scu, SCU_CLK_SEL, FIELD_PREP(SCU_SOC_DISPLAY_SEL, 0)); ++ } ++ } ++ ++ aspeed_video_update_regs(video); ++ ++ // update signal status ++ if (video->input == VIDEO_INPUT_MEM) { ++ video->v4l2_input_status = 0; ++ } else { ++ aspeed_video_get_resolution(video); ++ if (!video->v4l2_input_status) ++ aspeed_video_update_timings(video, &video->detected_timings); ++ } ++ ++ if (video->input == VIDEO_INPUT_MEM) ++ aspeed_video_start_frame(video); + + return 0; + } +@@ -1520,6 +2147,12 @@ + { + struct aspeed_video *video = video_drvdata(file); + ++ // if input is MEM, resolution decided by user ++ if (video->input == VIDEO_INPUT_MEM) { ++ video->detected_timings.width = timings->bt.width; ++ video->detected_timings.height = timings->bt.height; ++ } ++ + if (timings->bt.width == video->active_timings.width && + timings->bt.height == video->active_timings.height) + return 0; +@@ -1527,13 +2160,7 @@ + if (vb2_is_busy(&video->queue)) + return -EBUSY; + +- video->active_timings = timings->bt; +- +- aspeed_video_set_resolution(video); +- +- video->pix_fmt.width = timings->bt.width; +- video->pix_fmt.height = timings->bt.height; +- video->pix_fmt.sizeimage = video->max_compressed_size; ++ aspeed_video_update_timings(video, &timings->bt); + + timings->type = V4L2_DV_BT_656_1120; + +@@ -1589,6 +2216,37 @@ + NULL, NULL); + } + ++static int aspeed_video_g_selection(struct file *file, void *fh, ++ struct v4l2_selection *s) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ struct aspeed_video_box *box; ++ unsigned long flags; ++ ++ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && ++ s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ++ return -EINVAL; ++ ++ switch (s->target) { ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ spin_lock_irqsave(&video->lock, flags); ++ box = list_first_entry_or_null(&video->boxes, ++ struct aspeed_video_box, ++ link); ++ if (box) { ++ s->r = box->box; ++ list_del(&box->link); ++ kfree(box); ++ } else { ++ memset(&s->r, 0, sizeof(s->r)); ++ } ++ spin_unlock_irqrestore(&video->lock, flags); ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ + static int aspeed_video_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) + { +@@ -1641,6 +2299,8 @@ + .vidioc_enum_dv_timings = aspeed_video_enum_dv_timings, + .vidioc_dv_timings_cap = aspeed_video_dv_timings_cap, + ++ .vidioc_g_selection = aspeed_video_g_selection, ++ + .vidioc_subscribe_event = aspeed_video_sub_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + }; +@@ -1710,6 +2370,9 @@ + struct delayed_work *dwork = to_delayed_work(work); + struct aspeed_video *video = container_of(dwork, struct aspeed_video, + res_work); ++ bool is_res_chg = false; ++ ++ aspeed_video_reset(video); + + aspeed_video_on(video); + +@@ -1723,8 +2386,14 @@ + + aspeed_video_get_resolution(video); + +- if (video->detected_timings.width != video->active_timings.width || +- video->detected_timings.height != video->active_timings.height) { ++ if (video->v4l2_input_status) ++ goto done; ++ ++ is_res_chg = (video->detected_timings.width != video->active_timings.width || ++ video->detected_timings.height != video->active_timings.height); ++ aspeed_video_update_timings(video, &video->detected_timings); ++ ++ if (is_res_chg) { + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, +@@ -1740,6 +2409,32 @@ + done: + clear_bit(VIDEO_RES_CHANGE, &video->flags); + wake_up_interruptible_all(&video->wait); ++ wake_up_all(&waitq); ++} ++ ++/* ++ * To mmap source memory for test from memory usage. ++ * test from memory input mode requires much bigger size because it is ++ * uncompressed BGRA format. Thus, We use VM_READ to tell it is for test ++ * or v4l2 now. ++ */ ++static int aspeed_video_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ int rc; ++ struct aspeed_video *v = video_drvdata(file); ++ const size_t size = vma->vm_end - vma->vm_start; ++ const unsigned long pfn = __phys_to_pfn(v->dbg_src.dma); ++ ++ if (v->input != VIDEO_INPUT_MEM || vma->vm_flags & VM_READ) ++ return vb2_fop_mmap(file, vma); ++ ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ rc = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); ++ if (rc) { ++ v4l2_err(&v->v4l2_dev, "remap_pfn_range failed(%d)\n", rc); ++ return -EAGAIN; ++ } ++ return 0; + } + + static int aspeed_video_open(struct file *file) +@@ -1785,7 +2480,7 @@ + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, +- .mmap = vb2_fop_mmap, ++ .mmap = aspeed_video_mmap, + .open = aspeed_video_open, + .release = aspeed_video_release, + }; +@@ -1830,13 +2525,17 @@ + video->sequence = 0; + video->perf.duration_max = 0; + video->perf.duration_min = 0xffffffff; ++ set_bit(VIDEO_BOUNDING_BOX, &video->flags); + + aspeed_video_update_regs(video); + +- rc = aspeed_video_start_frame(video); +- if (rc) { +- aspeed_video_bufs_done(video, VB2_BUF_STATE_QUEUED); +- return rc; ++ // if input is MEM, don't start capture until user acquire ++ if (video->input != VIDEO_INPUT_MEM) { ++ rc = aspeed_video_start_frame(video); ++ if (rc) { ++ aspeed_video_bufs_done(video, VB2_BUF_STATE_QUEUED); ++ return rc; ++ } + } + + set_bit(VIDEO_STREAMING, &video->flags); +@@ -1860,8 +2559,7 @@ + * Need to force stop any DMA and try and get HW into a good + * state for future calls to start streaming again. + */ +- aspeed_video_off(video); +- aspeed_video_on(video); ++ aspeed_video_reset(video); + + aspeed_video_init_regs(video); + +@@ -1885,7 +2583,8 @@ + spin_unlock_irqrestore(&video->lock, flags); + + if (test_bit(VIDEO_STREAMING, &video->flags) && +- !test_bit(VIDEO_FRAME_INPRG, &video->flags) && empty) ++ !test_bit(VIDEO_FRAME_INPRG, &video->flags) && empty && ++ (video->input != VIDEO_INPUT_MEM)) + aspeed_video_start_frame(video); + } + +@@ -1911,6 +2610,7 @@ + val08 = aspeed_video_read(v, VE_CTRL); + if (FIELD_GET(VE_CTRL_DIRECT_FETCH, val08)) { + seq_printf(s, " %-20s:\tDirect fetch\n", "Mode"); ++ seq_printf(s, " %-20s:\t%s\n", "Input", input_str[v->input]); + seq_printf(s, " %-20s:\t%s\n", "VGA bpp mode", + FIELD_GET(VE_CTRL_INT_DE, val08) ? "16" : "32"); + } else { +@@ -1962,19 +2662,19 @@ + } + DEFINE_SHOW_ATTRIBUTE(aspeed_video_debugfs); + +-static struct dentry *debugfs_entry; +- + static void aspeed_video_debugfs_remove(struct aspeed_video *video) + { +- debugfs_remove_recursive(debugfs_entry); +- debugfs_entry = NULL; ++ debugfs_remove_recursive(video->debugfs_entry); + } + + static void aspeed_video_debugfs_create(struct aspeed_video *video) + { +- debugfs_entry = debugfs_create_file(DEVICE_NAME, 0444, NULL, +- video, +- &aspeed_video_debugfs_fops); ++ char filename[16]; ++ ++ snprintf(filename, sizeof(filename), "%s%d", DEVICE_NAME, video->id); ++ video->debugfs_entry = debugfs_create_file(filename, 0444, ++ video->debugfs_entry, video, ++ &aspeed_video_debugfs_fops); + } + #else + static void aspeed_video_debugfs_remove(struct aspeed_video *video) { } +@@ -2028,6 +2728,8 @@ + vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbq->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + vbq->dev = v4l2_dev->dev; ++ snprintf(vdev->name, sizeof(vdev->name), "%s%d", ++ DEVICE_NAME, video->id); + vbq->lock = &video->video_lock; + vbq->ops = &aspeed_video_vb2_ops; + vbq->mem_ops = &vb2_dma_contig_memops; +@@ -2070,11 +2772,30 @@ + return 0; + } + ++/* ++ * Get regmap without checking res, such as clk/reset, that could lead to ++ * conflict. ++ */ ++static struct regmap *aspeed_regmap_lookup(struct device_node *np, const char *property) ++{ ++ struct device_node *syscon_np __free(device_node) = of_parse_phandle(np, property, 0); ++ ++ if (!syscon_np) ++ return ERR_PTR(-ENODEV); ++ ++ return device_node_to_regmap(syscon_np); ++} ++ + static int aspeed_video_init(struct aspeed_video *video) + { + int irq; + int rc; + struct device *dev = video->dev; ++ unsigned int mask_size = (video->version >= 7) ? 64 : 32; ++ u32 resv_size = VE_MAX_SRC_BUFFER_SIZE * 2 + VE_JPEG_HEADER_SIZE + VE_BCD_BUFF_SIZE; ++ ++ video->scu = aspeed_regmap_lookup(dev->of_node, "aspeed,scu"); ++ video->gfx = aspeed_regmap_lookup(dev->of_node, "aspeed,gfx"); + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) { +@@ -2082,14 +2803,36 @@ + return -ENODEV; + } + +- rc = devm_request_threaded_irq(dev, irq, NULL, aspeed_video_irq, +- IRQF_ONESHOT, DEVICE_NAME, video); ++ rc = devm_request_threaded_irq(dev, irq, aspeed_video_irq, ++ aspeed_video_thread_irq, ++ IRQF_ONESHOT, dev_name(dev), video); + if (rc < 0) { + dev_err(dev, "Unable to request IRQ %d\n", irq); + return rc; + } + dev_info(video->dev, "irq %d\n", irq); + ++ if (!IS_ERR(video->dvi_base)) { ++ irq = irq_of_parse_and_map(dev->of_node, 1); ++ if (!irq) { ++ dev_err(dev, "Unable to find DVI IRQ\n"); ++ return -ENODEV; ++ } ++ ++ rc = devm_request_irq(dev, irq, aspeed_video_md_irq, 0, dev_name(dev), video); ++ if (rc < 0) { ++ dev_err(dev, "Unable to request DVI IRQ %d\n", irq); ++ return rc; ++ } ++ dev_info(video->dev, "dvi mode-detection irq %d\n", irq); ++ } ++ ++ video->reset = devm_reset_control_get_shared(dev, NULL); ++ if (IS_ERR(video->reset)) { ++ dev_err(dev, "Unable to get reset\n"); ++ return PTR_ERR(video->reset); ++ } ++ + video->eclk = devm_clk_get(dev, "eclk"); + if (IS_ERR(video->eclk)) { + dev_err(dev, "Unable to get ECLK\n"); +@@ -2111,22 +2854,53 @@ + if (rc) + goto err_unprepare_eclk; + ++ if (video->version > 6) { ++ video->crt2clk = devm_clk_get(dev, "crt2clk"); ++ if (IS_ERR(video->crt2clk)) { ++ dev_err(dev, "Unable to get CRT2CLK\n"); ++ rc = PTR_ERR(video->crt2clk); ++ goto err_unprepare_vclk; ++ } ++ ++ rc = clk_prepare_enable(video->crt2clk); ++ if (rc) ++ goto err_unprepare_vclk; ++ } ++ + of_reserved_mem_device_init(dev); + +- rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(mask_size)); + if (rc) { + dev_err(dev, "Failed to set DMA mask\n"); + goto err_release_reserved_mem; + } + +- if (!aspeed_video_alloc_buf(video, &video->jpeg, +- VE_JPEG_HEADER_SIZE)) { +- dev_err(dev, "Failed to allocate DMA for JPEG header\n"); ++ if (!aspeed_video_alloc_buf(video, &video->pool, resv_size)) { ++ dev_err(dev, "Failed to allocate DMA pool\n"); + rc = -ENOMEM; + goto err_release_reserved_mem; + } +- dev_info(video->dev, "alloc mem size(%d) at %pad for jpeg header\n", +- VE_JPEG_HEADER_SIZE, &video->jpeg.dma); ++ video->jpeg.size = VE_JPEG_HEADER_SIZE; ++ video->jpeg.virt = video->pool.virt; ++ video->jpeg.dma = video->pool.dma; ++ video->bcd.size = VE_BCD_BUFF_SIZE; ++ video->bcd.virt = video->jpeg.virt + video->jpeg.size; ++ video->bcd.dma = video->jpeg.dma + video->jpeg.size; ++ video->srcs[0].size = VE_MAX_SRC_BUFFER_SIZE; ++ video->srcs[0].dma = video->bcd.dma + video->bcd.size; ++ video->srcs[1].size = VE_MAX_SRC_BUFFER_SIZE; ++ video->srcs[1].dma = video->srcs[0].dma + video->srcs[0].size; ++ ++ dev_info(video->dev, "alloc mem size(%d) at %pad for pool\n", ++ resv_size, &video->pool.dma); ++ v4l2_dbg(1, debug, &video->v4l2_dev, "jpeg header addr(%pad) size(%d)\n", ++ &video->jpeg.dma, video->jpeg.size); ++ v4l2_dbg(1, debug, &video->v4l2_dev, "bcd addr(%pad) size(%d)\n", ++ &video->bcd.dma, video->bcd.size); ++ v4l2_dbg(1, debug, &video->v4l2_dev, "src buf0 addr(%pad) size(%d)\n", ++ &video->srcs[0].dma, video->srcs[0].size); ++ v4l2_dbg(1, debug, &video->v4l2_dev, "src buf1 addr(%pad) size(%d)\n", ++ &video->srcs[1].dma, video->srcs[1].size); + + aspeed_video_init_jpeg_table(video->jpeg.virt, video->yuv420); + +@@ -2134,6 +2908,9 @@ + + err_release_reserved_mem: + of_reserved_mem_device_release(dev); ++ if (!IS_ERR(video->crt2clk)) ++ clk_disable_unprepare(video->crt2clk); ++err_unprepare_vclk: + clk_unprepare(video->vclk); + err_unprepare_eclk: + clk_unprepare(video->eclk); +@@ -2145,6 +2922,7 @@ + { .compatible = "aspeed,ast2400-video-engine", .data = &ast2400_config }, + { .compatible = "aspeed,ast2500-video-engine", .data = &ast2500_config }, + { .compatible = "aspeed,ast2600-video-engine", .data = &ast2600_config }, ++ { .compatible = "aspeed,ast2700-video-engine", .data = &ast2700_config }, + {} + }; + MODULE_DEVICE_TABLE(of, aspeed_video_of_match); +@@ -2154,11 +2932,16 @@ + const struct aspeed_video_config *config; + struct aspeed_video *video; + int rc; ++ bool vga_ctrl; + + video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL); + if (!video) + return -ENOMEM; + ++ video->id = of_alias_get_id(pdev->dev.of_node, "video"); ++ if (video->id < 0) ++ video->id = 0; ++ + video->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(video->base)) + return PTR_ERR(video->base); +@@ -2167,8 +2950,10 @@ + if (!config) + return -ENODEV; + ++ video->version = config->version; + video->jpeg_mode = config->jpeg_mode; + video->comp_size_read = config->comp_size_read; ++ video->compare_only = config->compare_only; + + video->frame_rate = 30; + video->jpeg_hq_quality = 1; +@@ -2178,14 +2963,31 @@ + init_waitqueue_head(&video->wait); + INIT_DELAYED_WORK(&video->res_work, aspeed_video_resolution_work); + INIT_LIST_HEAD(&video->buffers); ++ INIT_LIST_HEAD(&video->boxes); ++ ++ video->rst_wq = create_singlethread_workqueue("video_rst_wq"); ++ if (!video->rst_wq) { ++ dev_err(&pdev->dev, "unable to alloc rst workqueue\n"); ++ return -ENOMEM; ++ } ++ INIT_WORK(&video->rst_work, aspeed_video_rst_worker); ++ ++ if (video->version == 7 && video->id == 0) ++ video->dvi_base = devm_platform_ioremap_resource(pdev, 1); ++ else ++ video->dvi_base = ERR_PTR(-ENODEV); + + rc = aspeed_video_init(video); + if (rc) + return rc; + ++ /* vga_ctrl is a property to say if VGA will be on/off with KVM */ ++ vga_ctrl = of_find_property(pdev->dev.of_node, "vga_ctrl", NULL) ? 1 : 0; ++ aspeed_video_support_vga_ctrl(video, vga_ctrl); ++ aspeed_video_set_vga_on(video, false); ++ + rc = aspeed_video_setup_video(video); + if (rc) { +- aspeed_video_free_buf(video, &video->jpeg); + clk_unprepare(video->vclk); + clk_unprepare(video->eclk); + return rc; +@@ -2193,6 +2995,9 @@ + + aspeed_video_debugfs_create(video); + ++ dev_info(video->dev, "%s%d registered as /dev/video%d\n", DEVICE_NAME, ++ video->id, video->vdev.num); ++ + return 0; + } + +@@ -2204,8 +3009,12 @@ + + aspeed_video_off(video); + ++ destroy_workqueue(video->rst_wq); ++ + aspeed_video_debugfs_remove(video); + ++ if (!IS_ERR(video->crt2clk)) ++ clk_disable_unprepare(video->crt2clk); + clk_unprepare(video->vclk); + clk_unprepare(video->eclk); + +@@ -2215,7 +3024,7 @@ + + v4l2_device_unregister(v4l2_dev); + +- aspeed_video_free_buf(video, &video->jpeg); ++ aspeed_video_free_buf(video, &video->pool); + + of_reserved_mem_device_release(dev); + } +diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c +--- a/drivers/mmc/host/sdhci-of-aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/mmc/host/sdhci-of-aspeed.c 2026-09-09 15:51:44.134246896 +0000 +@@ -11,8 +11,10 @@ + #include + #include + #include ++#include + #include + #include ++#include + #include + + #include "sdhci-pltfm.h" +@@ -23,6 +25,8 @@ + #define ASPEED_SDC_PHASE 0xf4 + #define ASPEED_SDC_S1_PHASE_IN GENMASK(25, 21) + #define ASPEED_SDC_S0_PHASE_IN GENMASK(20, 16) ++#define ASPEED_SDC_S0_PHASE_IN_SHIFT 16 ++#define ASPEED_SDC_S0_PHASE_OUT_SHIFT 3 + #define ASPEED_SDC_S1_PHASE_OUT GENMASK(15, 11) + #define ASPEED_SDC_S1_PHASE_IN_EN BIT(10) + #define ASPEED_SDC_S1_PHASE_OUT_EN GENMASK(9, 8) +@@ -31,61 +35,53 @@ + #define ASPEED_SDC_S0_PHASE_OUT_EN GENMASK(1, 0) + #define ASPEED_SDC_PHASE_MAX 31 + ++#define ASPEED_SDHCI_TAP_PARAM_INVERT_CLK BIT(4) ++#define ASPEED_SDHCI_NR_TAPS 15 ++#define ASPEED_SDHCI_MAX_SWEEPS 10 ++ + /* SDIO{10,20} */ +-#define ASPEED_SDC_CAP1_1_8V (0 * 32 + 26) ++#define ASPEED_SDC_CAP1_1_8V (0 * 32 + 26) + /* SDIO{14,24} */ +-#define ASPEED_SDC_CAP2_SDR104 (1 * 32 + 1) ++#define ASPEED_SDC_CAP2_SDR104 (1 * 32 + 1) ++ ++#define PROBE_AFTER_ASSET_DEASSERT 0x1 ++ ++struct aspeed_sdc_info { ++ u32 flag; ++}; + + struct aspeed_sdc { + struct clk *clk; + struct resource *res; ++ struct reset_control *rst; + + spinlock_t lock; + void __iomem *regs; + }; + +-struct aspeed_sdhci_tap_param { +- bool valid; +- +-#define ASPEED_SDHCI_TAP_PARAM_INVERT_CLK BIT(4) +- u8 in; +- u8 out; +-}; +- +-struct aspeed_sdhci_tap_desc { +- u32 tap_mask; +- u32 enable_mask; +- u8 enable_value; +-}; +- +-struct aspeed_sdhci_phase_desc { +- struct aspeed_sdhci_tap_desc in; +- struct aspeed_sdhci_tap_desc out; +-}; +- + struct aspeed_sdhci_pdata { + unsigned int clk_div_start; +- const struct aspeed_sdhci_phase_desc *phase_desc; +- size_t nr_phase_descs; + }; + + struct aspeed_sdhci { + const struct aspeed_sdhci_pdata *pdata; + struct aspeed_sdc *parent; + u32 width_mask; +- struct mmc_clk_phase_map phase_map; +- const struct aspeed_sdhci_phase_desc *phase_desc; ++}; ++ ++static struct aspeed_sdc_info ast2600_sdc_info = { ++ .flag = PROBE_AFTER_ASSET_DEASSERT + }; + + /* + * The function sets the mirror register for updating + * capbilities of the current slot. + * +- * slot | capability | caps_reg | mirror_reg ++ * slot | capability | caps_reg | mirror_reg + * -----|-------------|----------|------------ +- * 0 | CAP1_1_8V | SDIO140 | SDIO10 ++ * 0 | CAP1_1_8V | SDIO140 | SDIO10 + * 0 | CAP2_SDR104 | SDIO144 | SDIO14 +- * 1 | CAP1_1_8V | SDIO240 | SDIO20 ++ * 1 | CAP1_1_8V | SDIO240 | SDIO20 + * 1 | CAP2_SDR104 | SDIO244 | SDIO24 + */ + static void aspeed_sdc_set_slot_capability(struct sdhci_host *host, struct aspeed_sdc *sdc, +@@ -125,222 +121,307 @@ + spin_unlock(&sdc->lock); + } + +-static u32 +-aspeed_sdc_set_phase_tap(const struct aspeed_sdhci_tap_desc *desc, +- u8 tap, bool enable, u32 reg) +-{ +- reg &= ~(desc->enable_mask | desc->tap_mask); +- if (enable) { +- reg |= tap << __ffs(desc->tap_mask); +- reg |= desc->enable_value << __ffs(desc->enable_mask); +- } +- +- return reg; +-} +- +-static void +-aspeed_sdc_set_phase_taps(struct aspeed_sdc *sdc, +- const struct aspeed_sdhci_phase_desc *desc, +- const struct aspeed_sdhci_tap_param *taps) ++static void aspeed_sdhci_set_bus_width(struct sdhci_host *host, int width) + { +- u32 reg; ++ struct sdhci_pltfm_host *pltfm_priv; ++ struct aspeed_sdhci *aspeed_sdhci; ++ struct aspeed_sdc *aspeed_sdc; ++ u8 ctrl; + +- spin_lock(&sdc->lock); +- reg = readl(sdc->regs + ASPEED_SDC_PHASE); ++ pltfm_priv = sdhci_priv(host); ++ aspeed_sdhci = sdhci_pltfm_priv(pltfm_priv); ++ aspeed_sdc = aspeed_sdhci->parent; + +- reg = aspeed_sdc_set_phase_tap(&desc->in, taps->in, taps->valid, reg); +- reg = aspeed_sdc_set_phase_tap(&desc->out, taps->out, taps->valid, reg); ++ /* Set/clear 8-bit mode */ ++ aspeed_sdc_configure_8bit_mode(aspeed_sdc, aspeed_sdhci, ++ width == MMC_BUS_WIDTH_8); + +- writel(reg, sdc->regs + ASPEED_SDC_PHASE); +- spin_unlock(&sdc->lock); ++ /* Set/clear 1 or 4 bit mode */ ++ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); ++ if (width == MMC_BUS_WIDTH_4) ++ ctrl |= SDHCI_CTRL_4BITBUS; ++ else ++ ctrl &= ~SDHCI_CTRL_4BITBUS; ++ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + } + +-#define PICOSECONDS_PER_SECOND 1000000000000ULL +-#define ASPEED_SDHCI_NR_TAPS 15 +-/* Measured value with *handwave* environmentals and static loading */ +-#define ASPEED_SDHCI_MAX_TAP_DELAY_PS 1253 +-static int aspeed_sdhci_phase_to_tap(struct device *dev, unsigned long rate_hz, +- int phase_deg) +-{ +- u64 phase_period_ps; +- u64 prop_delay_ps; +- u64 clk_period_ps; +- unsigned int tap; +- u8 inverted; +- +- phase_deg %= 360; +- +- if (phase_deg >= 180) { +- inverted = ASPEED_SDHCI_TAP_PARAM_INVERT_CLK; +- phase_deg -= 180; +- dev_dbg(dev, +- "Inverting clock to reduce phase correction from %d to %d degrees\n", +- phase_deg + 180, phase_deg); +- } else { +- inverted = 0; +- } ++static u32 aspeed_sdhci_readl(struct sdhci_host *host, int reg) ++{ ++ u32 val = readl(host->ioaddr + reg); + +- prop_delay_ps = ASPEED_SDHCI_MAX_TAP_DELAY_PS / ASPEED_SDHCI_NR_TAPS; +- clk_period_ps = div_u64(PICOSECONDS_PER_SECOND, (u64)rate_hz); +- phase_period_ps = div_u64((u64)phase_deg * clk_period_ps, 360ULL); +- +- tap = div_u64(phase_period_ps, prop_delay_ps); +- if (tap > ASPEED_SDHCI_NR_TAPS) { +- dev_dbg(dev, +- "Requested out of range phase tap %d for %d degrees of phase compensation at %luHz, clamping to tap %d\n", +- tap, phase_deg, rate_hz, ASPEED_SDHCI_NR_TAPS); +- tap = ASPEED_SDHCI_NR_TAPS; +- } ++ if (unlikely(reg == SDHCI_PRESENT_STATE) && ++ (host->mmc->caps2 & MMC_CAP2_CD_ACTIVE_HIGH)) ++ val ^= SDHCI_CARD_PRESENT; + +- return inverted | tap; ++ return val; + } + +-static void +-aspeed_sdhci_phases_to_taps(struct device *dev, unsigned long rate, +- const struct mmc_clk_phase *phases, +- struct aspeed_sdhci_tap_param *taps) ++static void aspeed_sdhci_reset(struct sdhci_host *host, u8 mask) + { +- taps->valid = phases->valid; ++ struct sdhci_pltfm_host *pltfm_priv; ++ struct aspeed_sdhci *aspeed_sdhci; ++ struct aspeed_sdc *aspeed_sdc; ++ u32 save_array[8]; ++ u32 reg_array[] = {SDHCI_DMA_ADDRESS, ++ SDHCI_BLOCK_SIZE, ++ SDHCI_ARGUMENT, ++ SDHCI_HOST_CONTROL, ++ SDHCI_CLOCK_CONTROL, ++ SDHCI_INT_ENABLE, ++ SDHCI_SIGNAL_ENABLE, ++ SDHCI_AUTO_CMD_STATUS}; ++ int i; ++ u16 tran_mode; ++ u32 mmc8_mode; + +- if (!phases->valid) +- return; ++ pltfm_priv = sdhci_priv(host); ++ aspeed_sdhci = sdhci_pltfm_priv(pltfm_priv); ++ aspeed_sdc = aspeed_sdhci->parent; ++ ++ if (!IS_ERR(aspeed_sdc->rst)) { ++ for (i = 0; i < ARRAY_SIZE(reg_array); i++) ++ save_array[i] = sdhci_readl(host, reg_array[i]); ++ ++ tran_mode = sdhci_readw(host, SDHCI_TRANSFER_MODE); ++ mmc8_mode = readl(aspeed_sdc->regs); ++ ++ reset_control_assert(aspeed_sdc->rst); ++ mdelay(1); ++ reset_control_deassert(aspeed_sdc->rst); ++ mdelay(1); ++ ++ for (i = 0; i < ARRAY_SIZE(reg_array); i++) ++ sdhci_writel(host, save_array[i], reg_array[i]); ++ ++ sdhci_writew(host, tran_mode, SDHCI_TRANSFER_MODE); ++ writel(mmc8_mode, aspeed_sdc->regs); + +- taps->in = aspeed_sdhci_phase_to_tap(dev, rate, phases->in_deg); +- taps->out = aspeed_sdhci_phase_to_tap(dev, rate, phases->out_deg); ++ sdhci_set_clock(host, host->clock); ++ } ++ ++ sdhci_reset(host, mask); + } + +-static void +-aspeed_sdhci_configure_phase(struct sdhci_host *host, unsigned long rate) ++/* ++ * aspeed_sdhci_find_center_tap - find the midpoint of the widest passing window ++ * @pass: boolean array [0..total-1], true = tuning passed at this position ++ * @total: number of phase positions (ASPEED_SDHCI_NR_TAPS * 2 = 30) ++ * ++ * Phase space layout (15 taps = half cycle at 200 MHz, ~167 ps/tap): ++ * pos [0..14] - rising edge, tap delay 0..14 ( 0° .. ~168°) ++ * pos [15..29] - falling edge, tap delay 0..14 (180° .. ~348°) ++ * ++ * The array is treated as circular so windows that wrap across the ++ * rising/falling boundary (around 0°/360°) are detected correctly. ++ * ++ * Returns a 5-bit phase_bits value: ++ * bits [3:0] = tap index ++ * bit [4] = ASPEED_SDHCI_TAP_PARAM_INVERT_CLK (set for falling edge) ++ */ ++static u32 aspeed_sdhci_find_center_tap(const bool *pass, int total) + { +- struct aspeed_sdhci_tap_param _taps = {0}, *taps = &_taps; +- struct mmc_clk_phase *params; +- struct aspeed_sdhci *sdhci; +- struct device *dev; ++ int pos, cur_start = -1, cur_len = 0, best_start = 0, best_len = 0; ++ int center_pos; + +- dev = mmc_dev(host->mmc); +- sdhci = sdhci_pltfm_priv(sdhci_priv(host)); ++ /* ++ * Double-scan the circular buffer: the second pass detects windows ++ * that straddle the index-0 wrap-around boundary. The window length ++ * is capped at 'total' so a full-circle window is counted only once. ++ */ ++ for (pos = 0; pos < total * 2; pos++) { ++ if (pass[pos % total]) { ++ if (cur_start < 0) ++ cur_start = pos; ++ if (++cur_len > total) ++ cur_len = total; ++ if (cur_len > best_len) { ++ best_len = cur_len; ++ best_start = cur_start; ++ } ++ } else { ++ cur_start = -1; ++ cur_len = 0; ++ } ++ } + +- if (!sdhci->phase_desc) +- return; ++ if (!best_len) ++ return 0; ++ ++ center_pos = (best_start + best_len / 2) % total; + +- params = &sdhci->phase_map.phase[host->timing]; +- aspeed_sdhci_phases_to_taps(dev, rate, params, taps); +- aspeed_sdc_set_phase_taps(sdhci->parent, sdhci->phase_desc, taps); +- dev_dbg(dev, +- "Using taps [%d, %d] for [%d, %d] degrees of phase correction at %luHz (%d)\n", +- taps->in & ASPEED_SDHCI_NR_TAPS, +- taps->out & ASPEED_SDHCI_NR_TAPS, +- params->in_deg, params->out_deg, rate, host->timing); ++ if (center_pos < ASPEED_SDHCI_NR_TAPS) ++ return (u32)center_pos; ++ ++ return (u32)(center_pos - ASPEED_SDHCI_NR_TAPS) | ASPEED_SDHCI_TAP_PARAM_INVERT_CLK; + } + +-static void aspeed_sdhci_set_clock(struct sdhci_host *host, unsigned int clock) ++static int aspeed_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode) + { +- struct sdhci_pltfm_host *pltfm_host; +- unsigned long parent, bus; ++ struct sdhci_pltfm_host *pltfm_priv; + struct aspeed_sdhci *sdhci; +- int div; +- u16 clk; ++ struct aspeed_sdc *sdc; ++ struct device *dev; ++ /* ++ * 15 taps cover half a 200 MHz clock cycle (2.5 ns ≈ 167 ps/tap). ++ * Rising and falling edges together span a full cycle: 30 positions. ++ */ ++ const int total = ASPEED_SDHCI_NR_TAPS * 2; ++ bool pass[ASPEED_SDHCI_NR_TAPS * 2]; ++ u32 enable_mask, base, val, phase_bits; ++ int pos, pass_count, sweep; + +- pltfm_host = sdhci_priv(host); +- sdhci = sdhci_pltfm_priv(pltfm_host); ++ dev = mmc_dev(host->mmc); ++ pltfm_priv = sdhci_priv(host); ++ sdhci = sdhci_pltfm_priv(pltfm_priv); ++ sdc = sdhci->parent; + +- parent = clk_get_rate(pltfm_host->clk); ++ enable_mask = ASPEED_SDC_S0_PHASE_OUT_EN | ASPEED_SDC_S0_PHASE_IN_EN; ++ base = readl(sdc->regs + ASPEED_SDC_PHASE) & ASPEED_SDC_S0_PHASE_OUT; + +- sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); ++ /* ++ * For each phase direction (input then output), sweep all 30 ++ * positions. If every position passes, the result is ++ * untrustworthy — all phases are not really reliable but we ++ * cannot detect where the unreliable ones are. Re-run the ++ * sweep up to ASPEED_SDHCI_MAX_SWEEPS times, skipping ++ * already-failed taps, until some fail and reveal the true ++ * window. ++ */ + +- if (clock == 0) +- return; ++ /* -------- Step 1: scan all input-phase positions -------- */ ++ for (pos = 0; pos < total; pos++) ++ pass[pos] = true; ++ ++ for (sweep = 0; sweep < ASPEED_SDHCI_MAX_SWEEPS; sweep++) { ++ for (pos = 0; pos < total; pos++) { ++ if (!pass[pos]) ++ continue; ++ ++ phase_bits = (pos < ASPEED_SDHCI_NR_TAPS) ++ ? (u32)pos ++ : (u32)(pos - ASPEED_SDHCI_NR_TAPS) | ++ ASPEED_SDHCI_TAP_PARAM_INVERT_CLK; ++ ++ val = base | enable_mask | (phase_bits << ASPEED_SDC_S0_PHASE_IN_SHIFT); ++ writel(val, sdc->regs + ASPEED_SDC_PHASE); ++ ++ if (mmc_send_tuning(host->mmc, opcode, NULL)) ++ pass[pos] = false; ++ ++ dev_dbg(dev, "in pos=%2d (%s tap%2u) %s\n", pos, ++ pos < ASPEED_SDHCI_NR_TAPS ? "rise" : "fall", ++ pos % ASPEED_SDHCI_NR_TAPS, ++ pass[pos] ? "pass" : "fail"); ++ } + +- if (WARN_ON(clock > host->max_clk)) +- clock = host->max_clk; ++ for (pos = 0, pass_count = 0; pos < total; pos++) ++ if (pass[pos]) ++ pass_count++; + +- /* +- * Regarding the AST2600: +- * +- * If (EMMC12C[7:6], EMMC12C[15:8] == 0) then +- * period of SDCLK = period of SDMCLK. +- * +- * If (EMMC12C[7:6], EMMC12C[15:8] != 0) then +- * period of SDCLK = period of SDMCLK * 2 * (EMMC12C[7:6], EMMC[15:8]) +- * +- * If you keep EMMC12C[7:6] = 0 and EMMC12C[15:8] as one-hot, +- * 0x1/0x2/0x4/etc, you will find it is compatible to AST2400 or AST2500 +- * +- * Keep the one-hot behaviour for backwards compatibility except for +- * supporting the value 0 in (EMMC12C[7:6], EMMC12C[15:8]), and capture +- * the 0-value capability in clk_div_start. +- */ +- for (div = sdhci->pdata->clk_div_start; div < 256; div *= 2) { +- bus = parent / div; +- if (bus <= clock) ++ dev_dbg(dev, "input phase: sweep %d, %d/%d passed\n", ++ sweep, pass_count, total); ++ ++ if (pass_count < total || !pass_count) + break; + } + +- div >>= 1; ++ if (!pass_count) ++ dev_warn(dev, "input phase: no positions passed tuning\n"); + +- clk = div << SDHCI_DIVIDER_SHIFT; ++ phase_bits = aspeed_sdhci_find_center_tap(pass, total); ++ val = base | enable_mask | (phase_bits << ASPEED_SDC_S0_PHASE_IN_SHIFT); ++ writel(val, sdc->regs + ASPEED_SDC_PHASE); ++ dev_dbg(dev, "input center: phase_bits=0x%02x reg=0x%08x\n", phase_bits, val); ++ ++ /* -------- Step 2: scan all output-phase positions -------- */ ++ base = val & ~ASPEED_SDC_S0_PHASE_OUT; ++ ++ for (pos = 0; pos < total; pos++) ++ pass[pos] = true; ++ ++ for (sweep = 0; sweep < ASPEED_SDHCI_MAX_SWEEPS; sweep++) { ++ for (pos = 0; pos < total; pos++) { ++ if (!pass[pos]) ++ continue; ++ ++ phase_bits = (pos < ASPEED_SDHCI_NR_TAPS) ++ ? (u32)pos ++ : (u32)(pos - ASPEED_SDHCI_NR_TAPS) | ++ ASPEED_SDHCI_TAP_PARAM_INVERT_CLK; ++ ++ val = base | enable_mask | (phase_bits << ASPEED_SDC_S0_PHASE_OUT_SHIFT); ++ writel(val, sdc->regs + ASPEED_SDC_PHASE); ++ ++ if (mmc_send_tuning(host->mmc, opcode, NULL)) ++ pass[pos] = false; ++ ++ dev_dbg(dev, "out pos=%2d (%s tap%2u) %s\n", pos, ++ pos < ASPEED_SDHCI_NR_TAPS ? "rise" : "fall", ++ pos % ASPEED_SDHCI_NR_TAPS, ++ pass[pos] ? "pass" : "fail"); ++ } + +- aspeed_sdhci_configure_phase(host, bus); ++ for (pos = 0, pass_count = 0; pos < total; pos++) ++ if (pass[pos]) ++ pass_count++; + +- sdhci_enable_clk(host, clk); +-} ++ dev_dbg(dev, "output phase: sweep %d, %d/%d passed\n", ++ sweep, pass_count, total); + +-static unsigned int aspeed_sdhci_get_max_clock(struct sdhci_host *host) +-{ +- if (host->mmc->f_max) +- return host->mmc->f_max; ++ if (pass_count < total || !pass_count) ++ break; ++ } ++ ++ if (!pass_count) ++ dev_warn(dev, "output phase: no positions passed tuning\n"); ++ ++ phase_bits = aspeed_sdhci_find_center_tap(pass, total); ++ val = base | enable_mask | (phase_bits << ASPEED_SDC_S0_PHASE_OUT_SHIFT); ++ writel(val, sdc->regs + ASPEED_SDC_PHASE); ++ dev_dbg(dev, "output center: phase_bits=0x%02x reg=0x%08x\n", phase_bits, val); + +- return sdhci_pltfm_clk_get_max_clock(host); ++ return mmc_send_tuning(host->mmc, opcode, NULL); + } + +-static void aspeed_sdhci_set_bus_width(struct sdhci_host *host, int width) ++static void aspeed_sdhci_voltage_switch(struct sdhci_host *host) + { + struct sdhci_pltfm_host *pltfm_priv; +- struct aspeed_sdhci *aspeed_sdhci; +- struct aspeed_sdc *aspeed_sdc; +- u8 ctrl; ++ struct aspeed_sdhci *sdhci; ++ struct aspeed_sdc *sdc; ++ u32 out_phase, enable_mask, inverted = 1; + + pltfm_priv = sdhci_priv(host); +- aspeed_sdhci = sdhci_pltfm_priv(pltfm_priv); +- aspeed_sdc = aspeed_sdhci->parent; ++ sdhci = sdhci_pltfm_priv(pltfm_priv); ++ sdc = sdhci->parent; + +- /* Set/clear 8-bit mode */ +- aspeed_sdc_configure_8bit_mode(aspeed_sdc, aspeed_sdhci, +- width == MMC_BUS_WIDTH_8); ++ enable_mask = ASPEED_SDC_S0_PHASE_OUT_EN | ASPEED_SDC_S0_PHASE_IN_EN; + +- /* Set/clear 1 or 4 bit mode */ +- ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); +- if (width == MMC_BUS_WIDTH_4) +- ctrl |= SDHCI_CTRL_4BITBUS; +- else +- ctrl &= ~SDHCI_CTRL_4BITBUS; +- sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); +-} ++ inverted = ASPEED_SDHCI_TAP_PARAM_INVERT_CLK; ++ out_phase = readl(sdc->regs + ASPEED_SDC_PHASE) & ASPEED_SDC_S0_PHASE_OUT; ++ out_phase |= (inverted << ASPEED_SDC_S0_PHASE_OUT_SHIFT); + +-static u32 aspeed_sdhci_readl(struct sdhci_host *host, int reg) +-{ +- u32 val = readl(host->ioaddr + reg); ++ /* output inverse */ ++ writel(out_phase | enable_mask, sdc->regs + ASPEED_SDC_PHASE); + +- if (unlikely(reg == SDHCI_PRESENT_STATE) && +- (host->mmc->caps2 & MMC_CAP2_CD_ACTIVE_HIGH)) +- val ^= SDHCI_CARD_PRESENT; +- +- return val; ++ mdelay(30); + } + + static const struct sdhci_ops aspeed_sdhci_ops = { + .read_l = aspeed_sdhci_readl, +- .set_clock = aspeed_sdhci_set_clock, +- .get_max_clock = aspeed_sdhci_get_max_clock, ++ .set_clock = sdhci_set_clock, ++ .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .set_bus_width = aspeed_sdhci_set_bus_width, + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, +- .reset = sdhci_reset, ++ .voltage_switch = aspeed_sdhci_voltage_switch, ++ .reset = aspeed_sdhci_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, ++ .platform_execute_tuning = aspeed_sdhci_execute_tuning, + }; + + static const struct sdhci_pltfm_data aspeed_sdhci_pdata = { + .ops = &aspeed_sdhci_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, ++ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }; + + static inline int aspeed_sdhci_calculate_slot(struct aspeed_sdhci *dev, +@@ -372,12 +453,6 @@ + int slot; + int ret; + +- aspeed_pdata = of_device_get_match_data(&pdev->dev); +- if (!aspeed_pdata) { +- dev_err(&pdev->dev, "Missing platform configuration data\n"); +- return -EINVAL; +- } +- + host = sdhci_pltfm_init(pdev, &aspeed_sdhci_pdata, sizeof(*dev)); + if (IS_ERR(host)) + return PTR_ERR(host); +@@ -395,14 +470,6 @@ + else if (slot >= 2) + return -EINVAL; + +- if (slot < dev->pdata->nr_phase_descs) { +- dev->phase_desc = &dev->pdata->phase_desc[slot]; +- } else { +- dev_info(&pdev->dev, +- "Phase control not supported for slot %d\n", slot); +- dev->phase_desc = NULL; +- } +- + dev->width_mask = !slot ? ASPEED_SDC_S0_MMC8 : ASPEED_SDC_S1_MMC8; + + dev_info(&pdev->dev, "Configured for slot %d\n", slot); +@@ -413,11 +480,13 @@ + of_property_read_bool(np, "sd-uhs-sdr104")) { + aspeed_sdc_set_slot_capability(host, dev->parent, ASPEED_SDC_CAP1_1_8V, + true, slot); +- } +- +- if (of_property_read_bool(np, "sd-uhs-sdr104")) { + aspeed_sdc_set_slot_capability(host, dev->parent, ASPEED_SDC_CAP2_SDR104, + true, slot); ++ } else { ++ aspeed_sdc_set_slot_capability(host, dev->parent, ASPEED_SDC_CAP1_1_8V, ++ 0, slot); ++ aspeed_sdc_set_slot_capability(host, dev->parent, ASPEED_SDC_CAP2_SDR104, ++ 0, slot); + } + + pltfm_host->clk = devm_clk_get(&pdev->dev, NULL); +@@ -434,9 +503,6 @@ + if (ret) + goto err_sdhci_add; + +- if (dev->phase_desc) +- mmc_of_parse_clk_phase(&pdev->dev, &dev->phase_map); +- + ret = sdhci_add_host(host); + if (ret) + goto err_sdhci_add; +@@ -469,45 +535,11 @@ + .clk_div_start = 2, + }; + +-static const struct aspeed_sdhci_phase_desc ast2600_sdhci_phase[] = { +- /* SDHCI/Slot 0 */ +- [0] = { +- .in = { +- .tap_mask = ASPEED_SDC_S0_PHASE_IN, +- .enable_mask = ASPEED_SDC_S0_PHASE_IN_EN, +- .enable_value = 1, +- }, +- .out = { +- .tap_mask = ASPEED_SDC_S0_PHASE_OUT, +- .enable_mask = ASPEED_SDC_S0_PHASE_OUT_EN, +- .enable_value = 3, +- }, +- }, +- /* SDHCI/Slot 1 */ +- [1] = { +- .in = { +- .tap_mask = ASPEED_SDC_S1_PHASE_IN, +- .enable_mask = ASPEED_SDC_S1_PHASE_IN_EN, +- .enable_value = 1, +- }, +- .out = { +- .tap_mask = ASPEED_SDC_S1_PHASE_OUT, +- .enable_mask = ASPEED_SDC_S1_PHASE_OUT_EN, +- .enable_value = 3, +- }, +- }, +-}; +- +-static const struct aspeed_sdhci_pdata ast2600_sdhci_pdata = { +- .clk_div_start = 1, +- .phase_desc = ast2600_sdhci_phase, +- .nr_phase_descs = ARRAY_SIZE(ast2600_sdhci_phase), +-}; +- + static const struct of_device_id aspeed_sdhci_of_match[] = { + { .compatible = "aspeed,ast2400-sdhci", .data = &ast2400_sdhci_pdata, }, +- { .compatible = "aspeed,ast2500-sdhci", .data = &ast2400_sdhci_pdata, }, +- { .compatible = "aspeed,ast2600-sdhci", .data = &ast2600_sdhci_pdata, }, ++ { .compatible = "aspeed,ast2500-sdhci", }, ++ { .compatible = "aspeed,ast2600-sdhci", }, ++ { .compatible = "aspeed,ast2600-emmc", }, + { } + }; + MODULE_DEVICE_TABLE(of, aspeed_sdhci_of_match); +@@ -522,11 +554,22 @@ + .remove_new = aspeed_sdhci_remove, + }; + ++static const struct of_device_id aspeed_sdc_of_match[] = { ++ { .compatible = "aspeed,ast2400-sd-controller", }, ++ { .compatible = "aspeed,ast2500-sd-controller", }, ++ { .compatible = "aspeed,ast2600-sd-controller", .data = &ast2600_sdc_info}, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(of, aspeed_sdc_of_match); ++ + static int aspeed_sdc_probe(struct platform_device *pdev) + + { + struct device_node *parent, *child; + struct aspeed_sdc *sdc; ++ const struct of_device_id *match = NULL; ++ const struct aspeed_sdc_info *info = NULL; + int ret; + + sdc = devm_kzalloc(&pdev->dev, sizeof(*sdc), GFP_KERNEL); +@@ -535,6 +578,23 @@ + + spin_lock_init(&sdc->lock); + ++ match = of_match_device(aspeed_sdc_of_match, &pdev->dev); ++ if (!match) ++ return -ENODEV; ++ ++ if (match->data) ++ info = match->data; ++ ++ if (info) { ++ if (info->flag & PROBE_AFTER_ASSET_DEASSERT) { ++ sdc->rst = devm_reset_control_get(&pdev->dev, NULL); ++ if (!IS_ERR(sdc->rst)) { ++ reset_control_assert(sdc->rst); ++ reset_control_deassert(sdc->rst); ++ } ++ } ++ } ++ + sdc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(sdc->clk)) + return PTR_ERR(sdc->clk); +@@ -579,15 +639,6 @@ + clk_disable_unprepare(sdc->clk); + } + +-static const struct of_device_id aspeed_sdc_of_match[] = { +- { .compatible = "aspeed,ast2400-sd-controller", }, +- { .compatible = "aspeed,ast2500-sd-controller", }, +- { .compatible = "aspeed,ast2600-sd-controller", }, +- { } +-}; +- +-MODULE_DEVICE_TABLE(of, aspeed_sdc_of_match); +- + static struct platform_driver aspeed_sdc_driver = { + .driver = { + .name = "sd-controller-aspeed", +diff --git a/drivers/net/can/aspeed_can.c b/drivers/net/can/aspeed_can.c +--- a/drivers/net/can/aspeed_can.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/net/can/aspeed_can.c 2026-09-09 15:51:44.506241275 +0000 +@@ -0,0 +1,1728 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ASPEED CAN device driver ++ * ++ * Copyright (C) 2023 ASPEED Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DRIVER_NAME "aspeed_can" ++ ++#define CAN_AC_SEG (0x0004) /* classic can seg */ ++#define CAN_FD_SEG (0x0008) /* can fd seg */ ++#define CAN_BITITME (0x0010) /* prescaler */ ++#define CAN_INTF (0x0014) /* can interrupt flag */ ++#define CAN_INTE (0x0018) /* can interrupt enabled */ ++#define CAN_TSTAT (0x001c) /* can transmit status */ ++ ++#define CAN_CTRL (0x0028) ++#define CAN_ERR_STAT (0x002c) /* err ctrl and rx/tx err status */ ++ ++#define CAN_RBUF (0x0070) /* receive buffer registers 0x070-0x88b(*/ ++#define CAN_TBUF (0x0890) /* transmit buffer registers 0x890-0x10ab */ ++ ++#define CAN_RBUF_ID (CAN_RBUF + 0x0000) ++#define CAN_RBUF_CTL (CAN_RBUF + 0x0004) ++#define CAN_RBUF_TYPE (CAN_RBUF + 0x0008) ++#define CAN_RBUF_ACF (CAN_RBUF + 0x000C) ++#define CAN_RBUF_DATA (CAN_RBUF + 0x0010) ++ ++#define CAN_TBUF_ID (CAN_TBUF + 0x0000) ++#define CAN_TBUF_CTL (CAN_TBUF + 0x0004) ++#define CAN_TBUF_TYPE (CAN_TBUF + 0x0008) ++#define CAN_TBUF_ACF (CAN_TBUF + 0x000C) ++#define CAN_TBUF_DATA (CAN_TBUF + 0x0010) ++ ++#define CAN_MODE_CONFIG (0x1100) ++ ++#define CAN_TBUF_MIRROR (0x1190) ++#define CAN_TBUF_READ_ID (CAN_TBUF_MIRROR + 0x0000) ++#define CAN_TBUF_READ_CTL (CAN_TBUF_MIRROR + 0x0004) ++#define CAN_TBUF_READ_TYPE (CAN_TBUF_MIRROR + 0x0008) ++#define CAN_TBUF_READ_ACF (CAN_TBUF_MIRROR + 0x000C) ++#define CAN_TBUF_READ_DATA (CAN_TBUF_MIRROR + 0x0010) ++ ++/* CAN_AC_SEG(0x0004) bit description */ ++#define CAN_TIMING_AC_SEG1_MASK GENMASK(8, 0) ++#define CAN_TIMING_AC_SEG2_MASK GENMASK(22, 16) ++#define CAN_TIMING_AC_SJW_MASK GENMASK(30, 24) ++ ++#define CAN_TIMING_AC_SEG1_BITOFF 0 ++#define CAN_TIMING_AC_SEG2_BITOFF 16 ++#define CAN_TIMING_AC_SJW_BITOFF 24 ++ ++/* CAN_FD_SEG(0x0008) bit description */ ++#define CAN_TIMING_FD_SEG1_MASK GENMASK(7, 0) ++#define CAN_TIMING_FD_SEG2_MASK GENMASK(22, 16) ++#define CAN_TIMING_FD_SJW_MASK GENMASK(30, 24) ++ ++#define CAN_TIMING_FD_SEG1_BITOFF 0 ++#define CAN_TIMING_FD_SEG2_BITOFF 16 ++#define CAN_TIMING_FD_SJW_BITOFF 24 ++ ++/* CAN_BITTIME(0x0010) bit description */ ++#define CAN_TIMING_PRESC_MASK GENMASK(4, 0) ++#define CAN_TIMING_PRESC_BITOFF 0 ++#define CAN_TIMING_FD_SSPOFF_MASK GENMASK(15, 8) ++#define CAN_TIMING_FD_SSPOFF_BITOFF 8 ++ ++/* CAN_INTF(0x0014) bit description */ ++#define CAN_INT_EIF_BIT BIT(1) /* error interrupt flag */ ++#define CAN_INT_TSIF_BIT BIT(2) /* transmission secondary interrupt flag */ ++#define CAN_INT_TPIF_BIT BIT(3) /* transmission primary interrupt flag */ ++#define CAN_INT_RAFIF_BIT BIT(4) /* RB almost full interrupt flag */ ++#define CAN_INT_RFIF_BIT BIT(5) /* RB full interrupt flag */ ++#define CAN_INT_ROIF_BIT BIT(6) /* RB overflow interrupt flag */ ++#define CAN_INT_RIF_BIT BIT(7) /* receive interrupt flag */ ++#define CAN_INT_BEIF_BIT BIT(8) /* bus error interrupt flag */ ++#define CAN_INT_ALIF_BIT BIT(9) /* arbitration loss interrupt flag */ ++#define CAN_INT_EPIF_BIT BIT(10) /* error passive interrupt flag */ ++#define CAN_EPASS_BIT BIT(30) /* check if device is error passive */ ++#define CAN_INT_EWARN_BIT BIT(31) /* error Warning limit reached */ ++ ++/* CAN_INTE(0x0018) bit description */ ++#define CAN_INT_EIE_BIT BIT(1) /* error interrupt enable */ ++#define CAN_INT_TSIE_BIT BIT(2) /* transmission secondary interrupt enable */ ++#define CAN_INT_TPIE_BIT BIT(3) /* transmission secondary interrupt enable */ ++#define CAN_INT_RAFIE_BIT BIT(4) /* RB almost full interrupt enable */ ++#define CAN_INT_RFIE_BIT BIT(5) /* RB full interrupt enable */ ++#define CAN_INT_ROIE_BIT BIT(6) /* RB overflow interrupt enable */ ++#define CAN_INT_RIE_BIT BIT(7) /* receive interrupt enable */ ++#define CAN_INT_BEIE_BIT BIT(8) /* bus error interrupt enable */ ++#define CAN_INT_ALIE_BIT BIT(9) /* arbitration loss interrupt enable */ ++#define CAN_INT_EPIE_BIT BIT(10) /* error passive interrupt enable */ ++ ++/* CAN_TSTAT(0x001C) bit description */ ++#define CAN_TSTAT1_MASK GENMASK(10, 8) ++#define CAN_TSTAT1_BITOFF 8 ++#define CAN_TSTAT2_HANDLE_MASK GENMASK(23, 16) ++#define CAN_TSTAT2_HANDLE_BITOFF 16 ++#define CAN_TSTAT2_MASK GENMASK(26, 24) ++#define CAN_TSTAT2_BITOFF 24 ++ ++/* CAN_CTRL(0x0028) bit description */ ++#define CAN_CTRL_BUSOFF_BIT BIT(0) ++#define CAN_CTRL_LBMIMOD_BIT BIT(5) /* set loop back mode, internal */ ++#define CAN_CTRL_LBMEMOD_BIT BIT(6) /* set loop back mode, external */ ++#define CAN_CTRL_RST_BIT BIT(7) /* set reset bit */ ++#define CAN_CTRL_TSALL_BIT BIT(9) /* transmit secondary all frame */ ++#define CAN_CTRL_TSONE_BIT BIT(10) /* transmit secondary one frame */ ++#define CAN_CTRL_TPE_BIT BIT(12) /* transmit primary enable */ ++#define CAN_CTRL_STBY_BIT BIT(13) /* transceiver standby */ ++#define CAN_CTRL_TBSEL_BIT BIT(15) /* transmit buffer select */ ++#define CAN_CTRL_TSSTAT_MASK GENMASK(17, 16) /* Transmission secondary status bits */ ++#define CAN_CTRL_TSFF_BIT BIT(18) /* transmit secondary buffer full flag */ ++#define CAN_CTRL_TTBM_BIT BIT(20) /* set TTTBM as 1->full TTCAN mode */ ++#define CAN_CTRL_TSMODE_BIT BIT(21) /* set TSMODE as 1->FIFO mode */ ++#define CAN_CTRL_TSNEXT_BIT BIT(22) /* transmit buffer secondary NEXT */ ++#define CAN_CTRL_RSTAT_NOT_EMPTY_MASKT GENMASK(25, 24) ++#define CAN_CTRL_RREL_BIT BIT(28) /* receive buffer release */ ++#define CAN_CTRL_SACK_BIT BIT(31) /* self-ack mode */ ++ ++#define STB_IS_EMPTY 0x0 ++ ++/* CAN_ERR(0x002c) bit description */ ++#define CAN_ERR_EWL_MASK GENMASK(3, 0) /* programmable error warning limit */ ++#define CAN_ERR_EWL_BITOFF 0 ++#define CAN_ERR_AFWL_MASK GENMASK(7, 4) /* receive buffer almost full warning limit */ ++#define CAN_ERR_AFWL_BITOFF 4 ++#define CAN_ERR_ALC_MASK GENMASK(12, 8) /* arbitration lost capture */ ++#define CAN_ERR_ALC_BITOFF 8 ++#define CAN_ERR_KOER_MASK GENMASK(15, 13) /* kind of error */ ++#define CAN_ERR_KOER_BITOFF 13 ++#define CAN_ERR_RECNT_MASK GENMASK(23, 16) /* receive error count */ ++#define CAN_ERR_RECNT_BITOFF 16 ++#define CAN_ERR_TECNT_MASK GENMASK(31, 24) /* transmit error count */ ++#define CAN_ERR_TECNT_BITOFF 24 ++ ++/* CAN BUF bit description*/ ++#define CAN_BUF_ID_BFF_MASK GENMASK(28, 18) /* frame identifier */ ++#define CAN_BUF_ID_BFF_BITOFF 18 ++#define CAN_BUF_ID_EFF_MASK GENMASK(28, 0) /* identifier extension */ ++#define CAN_BUF_ID_EFF_BITOFF 0 ++#define CAN_BUF_DLC_MASK GENMASK(10, 0) /* data length code */ ++#define CAN_BUF_IDE_BIT BIT(16) /* identifier extension */ ++#define CAN_BUF_FDF_BIT BIT(17) /* CAN FD frame format */ ++#define CAN_BUF_BRS_BIT BIT(18) /* CAN FD bit rate switch enable */ ++#define CAN_BUF_RMF_BIT BIT(20) /* remot frame */ ++#define CAN_BUF_HANDLE_BITOFF 24 ++ ++#define KOER_BIT_ERROR_MASK (BIT(0)) ++#define KOER_FORM_ERROR_MASK (BIT(1)) ++#define KOER_STUFF_ERROR_MASK (BIT(1) | BIT(0)) ++#define KOER_ACK_ERROR_MASK (BIT(2)) ++#define KOER_CRC_ERROR_MASK (BIT(2) | BIT(0)) ++#define KOER_OTH_ERROR_MASK (BIT(2) | BIT(1)) ++ ++#define STAT_AFWL 0x04 ++#define STAT_EWL 0x0b ++ ++#define STB_IDX_RING_SZ 3 ++ ++#define PTB_MODE 0x01 ++#define STB_MODE 0x02 ++ ++#define STB_TX_MODE_ALL 0x01 /* secondary tx buffer mode */ ++#define STB_TX_MODE_ONE 0x02 /* secondary tx buffer mode */ ++#define STB_POLICY_FIFO 0x10 /* secondary tx buffer fifo mode */ ++#define STB_POLICY_PRIO 0x20 /* secondary tx buffer prioity mode */ ++ ++#define STB_INVALID_HANDLE_VAL 0xffffffff ++#define STB_INVALID_SKB_IDX 0xffffffff ++ ++/* SW flag */ ++#define ASPEED_CAN_INTERNEL_LOOPBACK 0x00000001 ++ ++struct can_stb_ring_obj { ++ u32 idx; ++ u32 skb_idx; ++ u32 handle; ++ struct can_stb_ring_obj *next; ++}; ++ ++struct aspeed_can_priv { ++ /* Fix the location of "struct can_priv can" ++ * in this struct. ++ */ ++ struct can_priv can; ++ void __iomem *reg_base; ++ struct device *dev; ++ /* Lock for synchronizing TX interrupt handling */ ++ spinlock_t tx_lock; ++ struct can_stb_ring_obj *stb_ring; ++ struct can_stb_ring_obj *head_ptr; ++ struct can_stb_ring_obj *tail_ptr; ++ u32 tx_max; ++ struct napi_struct napi; ++ struct clk *clk; ++ struct reset_control *reset; ++ u32 tb_mode; ++ u32 stb_mode_policy; ++ u32 frame_handle; ++ u32 flag; ++}; ++ ++static const struct can_bittiming_const aspeed_can_bittiming_const = { ++ .name = DRIVER_NAME, ++ .tseg1_min = 2, ++ .tseg1_max = 513, ++ .tseg2_min = 1, ++ .tseg2_max = 128, ++ .sjw_max = 128, ++ .brp_min = 1, ++ .brp_max = 128, ++ .brp_inc = 1, ++}; ++ ++static const struct can_bittiming_const aspeed_canfd_bittiming_const = { ++ .name = DRIVER_NAME, ++ .tseg1_min = 2, ++ .tseg1_max = 257, ++ .tseg2_min = 1, ++ .tseg2_max = 128, ++ .sjw_max = 128, ++ .brp_min = 1, ++ .brp_max = 128, ++ .brp_inc = 1, ++}; ++ ++inline void aspeed_can_set_bit(struct aspeed_can_priv *priv, ++ u32 reg_off, u32 bit) ++{ ++ u32 reg_val; ++ ++ reg_val = readl(priv->reg_base + reg_off); ++ reg_val |= bit; ++ writel(reg_val, priv->reg_base + reg_off); ++} ++ ++inline void aspeed_can_clr_bit(struct aspeed_can_priv *priv, ++ u32 reg_off, u32 bit) ++{ ++ u32 reg_val; ++ ++ reg_val = readl(priv->reg_base + reg_off); ++ reg_val &= ~(bit); ++ writel(reg_val, priv->reg_base + reg_off); ++} ++ ++inline void aspeed_can_clr_irq_bits(struct aspeed_can_priv *priv, ++ u32 reg_off, u32 bits) ++{ ++ writel(bits, priv->reg_base + reg_off); ++} ++ ++static bool aspeed_can_check_reset_mode(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ if (!(readl(priv->reg_base + CAN_CTRL) & CAN_CTRL_RST_BIT)) ++ return false; ++ ++ return true; ++} ++ ++static void aspeed_can_stb_ring_obj_init(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ int i; ++ ++ for (i = 0; i < STB_IDX_RING_SZ; i++) { ++ priv->stb_ring[i].idx = i + 1; ++ priv->stb_ring[i].skb_idx = i + 1; ++ priv->stb_ring[i].handle = STB_INVALID_HANDLE_VAL; ++ ++ if (i == STB_IDX_RING_SZ - 1) { ++ priv->stb_ring[i].next = &priv->stb_ring[0]; ++ } else { ++ priv->stb_ring[i].next = ++ &priv->stb_ring[i + 1]; ++ } ++ } ++ ++ priv->head_ptr = &priv->stb_ring[0]; ++ priv->tail_ptr = &priv->stb_ring[0]; ++} ++ ++static u32 aspeed_can_get_skb_idx(struct net_device *ndev, ++ u32 handle) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ struct can_stb_ring_obj *tmp_ptr = priv->tail_ptr; ++ ++ if (tmp_ptr->handle == handle) ++ return tmp_ptr->skb_idx; ++ ++ tmp_ptr = priv->tail_ptr->next; ++ ++ while (tmp_ptr != priv->head_ptr) { ++ if (tmp_ptr->handle == handle) ++ return tmp_ptr->skb_idx; ++ tmp_ptr = tmp_ptr->next; ++ } ++ ++ return STB_INVALID_SKB_IDX; ++} ++ ++static int aspeed_can_drop_ring_obj(struct net_device *ndev, ++ u32 handle) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ struct can_stb_ring_obj *tmp_ptr = priv->tail_ptr; ++ u32 tmp_skb_idx; ++ ++ if (tmp_ptr->handle == handle) { ++ /* keep original skb idx */ ++ tmp_ptr->handle = STB_INVALID_HANDLE_VAL; ++ priv->tail_ptr = priv->tail_ptr->next; ++ return 0; ++ } ++ ++ tmp_ptr = priv->tail_ptr->next; ++ ++ while (tmp_ptr != priv->head_ptr) { ++ if (tmp_ptr->handle == handle) { ++ tmp_skb_idx = tmp_ptr->skb_idx; ++ tmp_ptr->handle = priv->tail_ptr->handle; ++ tmp_ptr->skb_idx = priv->tail_ptr->skb_idx; ++ ++ priv->tail_ptr->skb_idx = tmp_skb_idx; ++ priv->tail_ptr->handle = STB_INVALID_HANDLE_VAL; ++ priv->tail_ptr = priv->tail_ptr->next; ++ return 0; ++ } ++ ++ tmp_ptr = tmp_ptr->next; ++ } ++ ++ return -1; ++} ++ ++static u32 aspeed_can_get_frame_num(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ struct can_stb_ring_obj *tmp_ptr = priv->tail_ptr; ++ u32 len = 0; ++ ++ if (tmp_ptr == priv->head_ptr) ++ return STB_IDX_RING_SZ; ++ ++ while (tmp_ptr != priv->head_ptr) { ++ len++; ++ tmp_ptr = tmp_ptr->next; ++ } ++ ++ return len; ++} ++ ++static int aspeed_can_set_reset_mode(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ unsigned long timeout; ++ ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_RST_BIT); ++ ++ timeout = jiffies + (1 * HZ); ++ while (!aspeed_can_check_reset_mode(ndev)) { ++ if (time_after(jiffies, timeout)) { ++ netdev_warn(ndev, "timed out for config mode\n"); ++ return -ETIMEDOUT; ++ } ++ ++ usleep_range(500, 10000); ++ } ++ ++ aspeed_can_stb_ring_obj_init(ndev); ++ ++ return 0; ++} ++ ++static int aspeed_can_exit_reset_mode(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ unsigned long timeout; ++ ++ aspeed_can_clr_bit(priv, CAN_CTRL, CAN_CTRL_RST_BIT); ++ ++ timeout = jiffies + (1 * HZ); ++ while (aspeed_can_check_reset_mode(ndev)) { ++ if (time_after(jiffies, timeout)) { ++ netdev_warn(ndev, "timed out for config mode\n"); ++ return -ETIMEDOUT; ++ } ++ ++ usleep_range(500, 10000); ++ } ++ ++ return 0; ++} ++ ++static void aspeed_can_err_init(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 reg_val; ++ ++ reg_val = (STAT_AFWL << CAN_ERR_AFWL_BITOFF) & CAN_ERR_AFWL_MASK; ++ reg_val |= (STAT_EWL << CAN_ERR_EWL_BITOFF) & CAN_ERR_EWL_MASK; ++ ++ writel(reg_val, priv->reg_base + CAN_ERR_STAT); ++} ++ ++static void aspeed_can_interrupt_conf(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 inte; ++ ++ inte = CAN_INT_EIE_BIT | CAN_INT_TSIE_BIT | CAN_INT_TPIE_BIT | ++ CAN_INT_RAFIE_BIT | CAN_INT_RFIE_BIT | CAN_INT_ROIE_BIT | ++ CAN_INT_RIE_BIT | CAN_INT_BEIE_BIT | CAN_INT_ALIE_BIT | ++ CAN_INT_EPIE_BIT; ++ ++ writel(inte, priv->reg_base + CAN_INTE); ++} ++ ++static void aspeed_can_tb_mode_conf(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ if (priv->tb_mode == STB_MODE) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TBSEL_BIT); ++ else ++ aspeed_can_clr_bit(priv, CAN_CTRL, CAN_CTRL_TBSEL_BIT); ++} ++ ++static void aspeed_can_sack_conf(struct net_device *ndev, bool enable) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ if (enable) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_SACK_BIT); ++ else ++ aspeed_can_clr_bit(priv, CAN_CTRL, CAN_CTRL_SACK_BIT); ++} ++ ++static void aspeed_can_loopback_ext_conf(struct net_device *ndev, bool enable) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ if (enable) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_LBMEMOD_BIT); ++ else ++ aspeed_can_clr_bit(priv, CAN_CTRL, CAN_CTRL_LBMEMOD_BIT); ++} ++ ++static void aspeed_can_loopback_int_conf(struct net_device *ndev, bool enable) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ if (enable) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_LBMIMOD_BIT); ++ else ++ aspeed_can_clr_bit(priv, CAN_CTRL, CAN_CTRL_LBMIMOD_BIT); ++} ++ ++static void aspeed_can_fd_init(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ aspeed_can_set_bit(priv, CAN_MODE_CONFIG, BIT(0)); ++} ++ ++static void aspeed_can_reg_dump(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 reg_val; ++ u32 i; ++ ++ reg_val = readl(priv->reg_base + CAN_AC_SEG); ++ netdev_info(ndev, "(REG004) CAN_AC_SEG = 0x%08x\n", reg_val); ++ netdev_info(ndev, " ac_seg1 (8:0): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_AC_SEG1_MASK) >> ++ CAN_TIMING_AC_SEG1_BITOFF)); ++ netdev_info(ndev, " ac_seg2(22:16): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_AC_SEG2_MASK) >> ++ CAN_TIMING_AC_SEG2_BITOFF)); ++ netdev_info(ndev, " ac_sjw (30:24): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_AC_SJW_MASK) >> ++ CAN_TIMING_AC_SJW_BITOFF)); ++ ++ reg_val = readl(priv->reg_base + CAN_FD_SEG); ++ netdev_info(ndev, "(REG008) CAN_FD_SEG = 0x%08x\n", reg_val); ++ netdev_info(ndev, " fd_seg1 (7:0): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_FD_SEG1_MASK) >> ++ CAN_TIMING_FD_SEG1_BITOFF)); ++ netdev_info(ndev, " fd_seg2(22:16): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_FD_SEG2_MASK) >> ++ CAN_TIMING_FD_SEG2_BITOFF)); ++ netdev_info(ndev, " fd_sjw (30:24): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_FD_SJW_MASK) >> ++ CAN_TIMING_FD_SJW_BITOFF)); ++ ++ reg_val = readl(priv->reg_base + CAN_BITITME); ++ netdev_info(ndev, "(REG010) CAN_BITITME = 0x%08x\n", reg_val); ++ netdev_info(ndev, " prescaler (4:0): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_PRESC_MASK) >> ++ CAN_TIMING_PRESC_BITOFF)); ++ netdev_info(ndev, " fd_sspoff(15:8): 0x%02x\n", ++ (u32)((reg_val & CAN_TIMING_FD_SSPOFF_MASK) >> ++ CAN_TIMING_FD_SSPOFF_BITOFF)); ++ ++ reg_val = readl(priv->reg_base + CAN_INTF); ++ netdev_info(ndev, "(REG014) CAN_INTF = 0x%08x\n", reg_val); ++ netdev_info(ndev, " EIF (1): %d\n", (reg_val & CAN_INT_EIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " TSIF (2): %d\n", (reg_val & CAN_INT_TSIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " TPIF (3): %d\n", (reg_val & CAN_INT_TPIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " RAFIF (4): %d\n", (reg_val & CAN_INT_RAFIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " RFIF (5): %d\n", (reg_val & CAN_INT_RFIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " ROIF (6): %d\n", (reg_val & CAN_INT_ROIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " RIF (7): %d\n", (reg_val & CAN_INT_RIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " BEIF (8): %d\n", (reg_val & CAN_INT_BEIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " EPIF (10): %d\n", (reg_val & CAN_INT_EPIF_BIT) ? 1 : 0); ++ netdev_info(ndev, " EPASS(30): %d\n", (reg_val & CAN_EPASS_BIT) ? 1 : 0); ++ netdev_info(ndev, " EWARN(31): %d\n", (reg_val & CAN_INT_EWARN_BIT) ? 1 : 0); ++ ++ reg_val = readl(priv->reg_base + CAN_INTE); ++ netdev_info(ndev, "(REG018) CAN_INTE = 0x%08x\n", reg_val); ++ netdev_info(ndev, " EIE (1): %d\n", (reg_val & CAN_INT_EIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " TSIE (2): %d\n", (reg_val & CAN_INT_TSIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " TPIE (3): %d\n", (reg_val & CAN_INT_TPIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " RAFIE (4): %d\n", (reg_val & CAN_INT_RAFIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " RFIE (5): %d\n", (reg_val & CAN_INT_RFIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " ROIE (6): %d\n", (reg_val & CAN_INT_ROIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " RIE (7): %d\n", (reg_val & CAN_INT_RIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " BEIE (8): %d\n", (reg_val & CAN_INT_BEIE_BIT) ? 1 : 0); ++ netdev_info(ndev, " EPIE (10): %d\n", (reg_val & CAN_INT_EPIE_BIT) ? 1 : 0); ++ ++ reg_val = readl(priv->reg_base + CAN_TSTAT); ++ netdev_info(ndev, "(REG01C) CAN_TSTAT = 0x%08x\n", reg_val); ++ netdev_info(ndev, " TSTAT1 (10:8): 0x%02x\n", ++ (u32)((reg_val & CAN_TSTAT1_MASK) >> CAN_TSTAT1_BITOFF)); ++ netdev_info(ndev, " TSTAT2(26:24): 0x%02x\n", ++ (u32)((reg_val & CAN_TSTAT2_MASK) >> CAN_TSTAT2_BITOFF)); ++ netdev_info(ndev, " 000 : no tx\n"); ++ netdev_info(ndev, " 001 : on-going\n"); ++ netdev_info(ndev, " 010 : lost arbitration\n"); ++ netdev_info(ndev, " 011 : transmitted\n"); ++ netdev_info(ndev, " 100 : aborted\n"); ++ netdev_info(ndev, " 101 : disturbed\n"); ++ netdev_info(ndev, " 110 : reject\n"); ++ ++ reg_val = readl(priv->reg_base + CAN_CTRL); ++ netdev_info(ndev, "(REG028) CAN_CTRL = 0x%08x\n", reg_val); ++ netdev_info(ndev, " BUSOFF (0): %d\n", (reg_val & CAN_CTRL_BUSOFF_BIT) ? 1 : 0); ++ netdev_info(ndev, " LBMIMOD (5): %d\n", (reg_val & CAN_CTRL_LBMIMOD_BIT) ? 1 : 0); ++ netdev_info(ndev, " LBMEMOD (6): %d\n", (reg_val & CAN_CTRL_LBMEMOD_BIT) ? 1 : 0); ++ netdev_info(ndev, " RST (7): %d\n", (reg_val & CAN_CTRL_RST_BIT) ? 1 : 0); ++ netdev_info(ndev, " TPE (12): %d\n", (reg_val & CAN_CTRL_TPE_BIT) ? 1 : 0); ++ netdev_info(ndev, " STBY (13): %d\n", (reg_val & CAN_CTRL_STBY_BIT) ? 1 : 0); ++ netdev_info(ndev, " TBSEL (15): %d\n", (reg_val & CAN_CTRL_TBSEL_BIT) ? 1 : 0); ++ netdev_info(ndev, " TTBM (20): %d\n", (reg_val & CAN_CTRL_TTBM_BIT) ? 1 : 0); ++ netdev_info(ndev, " TSMODE (21): %d\n", (reg_val & CAN_CTRL_TSMODE_BIT) ? 1 : 0); ++ netdev_info(ndev, " TSNEXT (22): %d\n", (reg_val & CAN_CTRL_TSNEXT_BIT) ? 1 : 0); ++ netdev_info(ndev, " RSTAT (25:24): 0x%02x\n", ++ (u32)((reg_val & CAN_CTRL_RSTAT_NOT_EMPTY_MASKT) >> 24)); ++ netdev_info(ndev, " RREL (28): %d\n", (reg_val & CAN_CTRL_RREL_BIT) ? 1 : 0); ++ ++ reg_val = readl(priv->reg_base + CAN_ERR_STAT); ++ netdev_info(ndev, "(REG02C) ERR_STAT = 0x%08x\n", reg_val); ++ netdev_info(ndev, " EWL (3:0): 0x%02x\n", ++ (u32)((reg_val & CAN_ERR_EWL_MASK) >> CAN_ERR_EWL_BITOFF)); ++ netdev_info(ndev, " AFWL (7:4): 0x%02x\n", ++ (u32)((reg_val & CAN_ERR_AFWL_MASK) >> CAN_ERR_AFWL_BITOFF)); ++ netdev_info(ndev, " ALC (12:8): 0x%02x\n", ++ (u32)((reg_val & CAN_ERR_ALC_MASK) >> CAN_ERR_ALC_BITOFF)); ++ netdev_info(ndev, " KOER (15:13): 0x%02x\n", ++ (u32)((reg_val & CAN_ERR_KOER_MASK) >> CAN_ERR_KOER_BITOFF)); ++ netdev_info(ndev, " 000 : no error\n"); ++ netdev_info(ndev, " 001 : bit error\n"); ++ netdev_info(ndev, " 010 : form error\n"); ++ netdev_info(ndev, " 011 : stuff error\n"); ++ netdev_info(ndev, " 100 : ack error\n"); ++ netdev_info(ndev, " 101 : crc error\n"); ++ netdev_info(ndev, " 110 : other error\n"); ++ netdev_info(ndev, " RECNT (23:16): 0x%02x\n", ++ (u32)((reg_val & CAN_ERR_RECNT_MASK) >> CAN_ERR_RECNT_BITOFF)); ++ netdev_info(ndev, " TECNT (31:24): 0x%02x\n", ++ (u32)((reg_val & CAN_ERR_TECNT_MASK) >> CAN_ERR_TECNT_BITOFF)); ++ ++ for (i = 0; i < 0x50; i += 4) ++ netdev_info(ndev, "REG(%03x): 0x%08x\n", i, readl(priv->reg_base + i)); ++ ++ netdev_info(ndev, "TX_MIRROR\n"); ++ for (i = 0; i < 0x30; i += 4) ++ netdev_info(ndev, "(0x%02x): 0x%08x\n", i, readl(priv->reg_base + CAN_TBUF_MIRROR + i)); ++ ++ netdev_info(ndev, "RX_BUF\n"); ++ for (i = 0; i < 0x30; i += 4) ++ netdev_info(ndev, "(0x%02x): 0x%08x\n", i, readl(priv->reg_base + CAN_RBUF + i)); ++} ++ ++static int aspeed_can_set_bittiming(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ struct can_bittiming *bt = &priv->can.bittiming; ++ struct can_bittiming *dbt = &priv->can.data_bittiming; ++ u32 btr0, btr1; ++ u32 can_fd_ssp; ++ ++ /* check whether the CAN controller is in reset mode */ ++ if (!aspeed_can_check_reset_mode(ndev)) { ++ netdev_alert(ndev, ++ "BUG! Cannot set bittiming - not in reset mode\n"); ++ return -EPERM; ++ } ++ ++ /* parameter sanity */ ++ if (bt->brp < 1 || bt->prop_seg + bt->phase_seg1 < 1 || ++ bt->phase_seg2 < 1 || bt->sjw < 1) { ++ netdev_alert(ndev, ++ "invalid bittiming parameters\n"); ++ netdev_alert(ndev, ++ "brp: %x, prop: %x, seg1: %x, seg2: %x, sjw: %x\n", ++ bt->brp, bt->prop_seg, bt->phase_seg1, ++ bt->phase_seg2, bt->sjw); ++ return -EPERM; ++ } ++ ++ /* setting prescaler value in PRESC Register */ ++ btr0 = (bt->brp - 1); ++ ++ /* setting time segment 1 in SEG_1 Register */ ++ btr1 = (1 + bt->prop_seg + bt->phase_seg1 - 2); ++ ++ /* Setting Time Segment 2 in SEG_2 Register */ ++ btr1 |= (bt->phase_seg2 - 1) << 16; ++ ++ /* Setting Synchronous jump width in BTR Register */ ++ btr1 |= (bt->sjw - 1) << 24; ++ ++ writel(btr1, priv->reg_base + CAN_AC_SEG); ++ ++ if (dbt->prop_seg != 0 && dbt->phase_seg1 != 0 && ++ dbt->phase_seg2 != 0) { ++ if (bt->brp != dbt->brp) { ++ netdev_alert(ndev, ++ "nominal (%d) and data (%d) prescaler isn't the same\n", ++ bt->brp, dbt->brp); ++ return -EPERM; ++ } ++ ++ if (dbt->sjw < 1) { ++ netdev_alert(ndev, ++ "invalid data sjw %x\n", dbt->sjw); ++ return -EPERM; ++ } ++ ++ /* Setting Time Segment 1 in BTR Register */ ++ btr1 = 1 + dbt->prop_seg + dbt->phase_seg1 - 2; ++ ++ /* Setting Time Segment 2 in BTR Register */ ++ btr1 |= (dbt->phase_seg2 - 1) << 16; ++ ++ /* Setting Synchronous jump width in BTR Register */ ++ btr1 |= (dbt->sjw - 1) << 24; ++ ++ writel(btr1, priv->reg_base + CAN_FD_SEG); ++ ++ /* seg_1 + 1 */ ++ can_fd_ssp = 1 + dbt->prop_seg + dbt->phase_seg1 + 1; ++ ++ btr0 |= can_fd_ssp << 8; ++ } ++ ++ writel(btr0 | 0x10000000, priv->reg_base + CAN_BITITME); ++ ++ return 0; ++} ++ ++static int aspeed_can_chip_start(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ int err; ++ ++ /* Check if it is in reset mode */ ++ err = aspeed_can_set_reset_mode(ndev); ++ if (err < 0) ++ return err; ++ ++ err = aspeed_can_set_bittiming(ndev); ++ if (err < 0) ++ return err; ++ ++ /* Always config to FD mode since it is ++ * backward compatibility with CAN2.0B. ++ */ ++ aspeed_can_fd_init(ndev); ++ ++ err = aspeed_can_exit_reset_mode(ndev); ++ if (err < 0) ++ return err; ++ ++ aspeed_can_err_init(ndev); ++ aspeed_can_interrupt_conf(ndev); ++ ++ aspeed_can_tb_mode_conf(ndev); ++ ++ aspeed_can_loopback_ext_conf(ndev, false); ++ if (priv->flag & ASPEED_CAN_INTERNEL_LOOPBACK) { ++ aspeed_can_loopback_int_conf(ndev, true); ++ aspeed_can_sack_conf(ndev, true); ++ } else { ++ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) { ++ aspeed_can_loopback_ext_conf(ndev, true); ++ aspeed_can_sack_conf(ndev, true); ++ } ++ aspeed_can_loopback_int_conf(ndev, false); ++ } ++ ++ priv->can.state = CAN_STATE_ERROR_ACTIVE; ++ priv->frame_handle = 0; ++ ++ return 0; ++} ++ ++static int aspeed_can_do_set_mode(struct net_device *ndev, enum can_mode mode) ++{ ++ int ret; ++ ++ switch (mode) { ++ case CAN_MODE_START: ++ ret = aspeed_can_chip_start(ndev); ++ if (ret < 0) { ++ netdev_err(ndev, "aspeed_can_chip_start failed!\n"); ++ return ret; ++ } ++ netif_wake_queue(ndev); ++ break; ++ default: ++ netdev_err(ndev, "unexpect can mode: %d\n", (u32)mode); ++ ret = -EOPNOTSUPP; ++ break; ++ } ++ ++ return ret; ++} ++ ++static void aspeed_can_write_frame(struct net_device *ndev, ++ struct sk_buff *skb) ++{ ++ u32 id; ++ struct canfd_frame *cf = (struct canfd_frame *)skb->data; ++ u32 i; ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 buf_ctrl = 0; ++ u32 can_ctrl; ++ u32 can_type; ++ ++ can_ctrl = readl(priv->reg_base + CAN_CTRL); ++ ++ /* Watch carefully on the bit sequence */ ++ if (cf->can_id & CAN_EFF_FLAG) { ++ id = (cf->can_id & CAN_EFF_MASK) << CAN_BUF_ID_EFF_BITOFF; ++ buf_ctrl |= CAN_BUF_IDE_BIT; ++ } else { ++ /* Standard CAN ID format */ ++ id = (cf->can_id & CAN_SFF_MASK) << CAN_BUF_ID_BFF_BITOFF; ++ } ++ ++ if (cf->can_id & CAN_RTR_FLAG) ++ buf_ctrl |= CAN_BUF_RMF_BIT; ++ ++ buf_ctrl |= can_fd_len2dlc(cf->len); ++ ++ if (can_is_canfd_skb(skb)) { ++ buf_ctrl |= CAN_BUF_FDF_BIT; ++ if (cf->flags & CANFD_BRS) ++ buf_ctrl |= CAN_BUF_BRS_BIT; ++ } ++ ++ can_type = priv->frame_handle << CAN_BUF_HANDLE_BITOFF; ++ ++ writel(id, priv->reg_base + CAN_TBUF_ID); ++ writel(buf_ctrl, priv->reg_base + CAN_TBUF_CTL); ++ writel(can_type, priv->reg_base + CAN_TBUF_TYPE); ++ ++ writel(0x0, priv->reg_base + CAN_TBUF_TYPE); ++ writel(0x0, priv->reg_base + CAN_TBUF_ACF); ++ ++ if (cf->can_id & CAN_RTR_FLAG) ++ return; ++ ++ for (i = 0; i < (cf->len / 4 + 3) * 4; i += 4) { ++ writel(*(u32 *)(cf->data + i), ++ priv->reg_base + CAN_TBUF_DATA + i); ++ } ++} ++ ++static int aspeed_can_start_frame_xmit(struct sk_buff *skb, ++ struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 i = 0; ++ u32 can_ctrl; ++ u32 int_flag; ++ u32 skb_idx; ++ int ret; ++ ++ /* avoid transmitting data finish suddenly */ ++ while (i < 3) { ++ can_ctrl = readl(priv->reg_base + CAN_CTRL); ++ int_flag = readl(priv->reg_base + CAN_INTF); ++ i++; ++ } ++ ++ /* check whether STB or PTB is full */ ++ if ((can_ctrl & CAN_CTRL_TBSEL_BIT) && ++ (can_ctrl & CAN_CTRL_TSFF_BIT)) ++ return -ENOSPC; ++ ++ if (!(can_ctrl & CAN_CTRL_TBSEL_BIT) && ++ ((can_ctrl & CAN_CTRL_TPE_BIT) || ++ (int_flag & CAN_INT_TPIF_BIT))) ++ return -ENOSPC; ++ ++ if (priv->frame_handle == 0x100) ++ priv->frame_handle = 0; ++ ++ /* Use skb idex to check whether ++ * the same handle already in STB. ++ */ ++ skb_idx = aspeed_can_get_skb_idx(ndev, priv->frame_handle); ++ if (skb_idx != STB_INVALID_SKB_IDX) { ++ netdev_err(ndev, "repeat handle %d\n", priv->frame_handle); ++ return -ENOSPC; ++ } ++ ++ aspeed_can_write_frame(ndev, skb); ++ ++ if (can_ctrl & CAN_CTRL_TBSEL_BIT) { ++ /* STB */ ++ ret = can_put_echo_skb(skb, ndev, priv->head_ptr->skb_idx, 0); ++ if (ret) { ++ netdev_err(ndev, "fail to put skb for stb %d\n", ret); ++ return ret; ++ } ++ ++ priv->head_ptr->handle = priv->frame_handle; ++ priv->head_ptr = priv->head_ptr->next; ++ ++ /* STB is full */ ++ if (priv->head_ptr == priv->tail_ptr) ++ netif_stop_queue(ndev); ++ ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TSNEXT_BIT); ++ ++ /* no frame is transmitting or there is no unhandled finish flag */ ++ if ((priv->stb_mode_policy & STB_TX_MODE_ONE) && ++ !(can_ctrl & CAN_CTRL_TSONE_BIT) && ++ !(int_flag & CAN_INT_TSIF_BIT)) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TSONE_BIT); ++ else if ((priv->stb_mode_policy & STB_TX_MODE_ALL) && ++ !(can_ctrl & CAN_CTRL_TSALL_BIT) && ++ !(int_flag & CAN_INT_TSIF_BIT)) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TSALL_BIT); ++ } else { ++ /* PTB */ ++ ret = can_put_echo_skb(skb, ndev, 0, 0); ++ if (ret) { ++ netdev_err(ndev, "fail to put skb for ptb %d\n", ret); ++ return ret; ++ } ++ ++ netif_stop_queue(ndev); ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TPE_BIT); ++ } ++ ++ priv->frame_handle++; ++ ++ return 0; ++} ++ ++static netdev_tx_t aspeed_can_start_xmit(struct sk_buff *skb, ++ struct net_device *ndev) ++{ ++ int ret; ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ unsigned long flags; ++ ++ if (can_dev_dropped_skb(ndev, skb)) ++ return NET_XMIT_DROP; ++ ++ spin_lock_irqsave(&priv->tx_lock, flags); ++ ++ ret = aspeed_can_start_frame_xmit(skb, ndev); ++ ++ spin_unlock_irqrestore(&priv->tx_lock, flags); ++ ++ if (ret) { ++ netdev_err(ndev, "Fail to transmit data!\n"); ++ netif_stop_queue(ndev); ++ return NET_XMIT_DROP; ++ } ++ ++ return NETDEV_TX_OK; ++} ++ ++static int aspeed_can_rx(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ struct net_device_stats *stats = &ndev->stats; ++ struct canfd_frame *cf; ++ struct sk_buff *skb; ++ u32 data; ++ u32 rx_stat; ++ u32 buf_ctrl_reg; ++ u32 reg_val; ++ u32 i; ++ ++ rx_stat = readl(priv->reg_base + CAN_CTRL); ++ if (!(rx_stat & CAN_CTRL_RSTAT_NOT_EMPTY_MASKT)) ++ return 0; ++ ++ buf_ctrl_reg = readl(priv->reg_base + CAN_RBUF_CTL); ++ if (buf_ctrl_reg & CAN_BUF_FDF_BIT) ++ skb = alloc_canfd_skb(ndev, &cf); ++ else ++ skb = alloc_can_skb(ndev, (struct can_frame **)&cf); ++ ++ if (!skb) { ++ stats->rx_dropped++; ++ return 0; ++ } ++ ++ reg_val = readl(priv->reg_base + CAN_RBUF_ID); ++ if (buf_ctrl_reg & CAN_BUF_IDE_BIT) { ++ cf->can_id = reg_val & CAN_BUF_ID_EFF_MASK; ++ cf->can_id |= CAN_EFF_FLAG; ++ } else { ++ cf->can_id = (reg_val & CAN_BUF_ID_BFF_MASK) >> ++ CAN_BUF_ID_BFF_BITOFF; ++ } ++ ++ if (buf_ctrl_reg & CAN_BUF_RMF_BIT) ++ cf->can_id |= CAN_RTR_FLAG; ++ ++ if (buf_ctrl_reg & CAN_BUF_FDF_BIT) ++ cf->len = can_fd_dlc2len(buf_ctrl_reg & CAN_BUF_DLC_MASK); ++ else ++ cf->len = can_cc_dlc2len(buf_ctrl_reg & CAN_BUF_DLC_MASK); ++ ++ /* Check the frame received is FD or not*/ ++ for (i = 0; i < cf->len; i += 4) { ++ data = readl(priv->reg_base + CAN_RBUF_DATA + i); ++ *(u32 *)(cf->data + i) = data; ++ } ++ ++ if (!(cf->can_id & CAN_RTR_FLAG)) ++ stats->rx_bytes += cf->len; ++ ++ stats->rx_packets++; ++ ++ /* release frame */ ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_RREL_BIT); ++ ++ netif_receive_skb(skb); ++ ++ return 1; ++} ++ ++static enum can_state aspeed_can_current_error_state(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 status; ++ u32 rx_cnt; ++ u32 tx_cnt; ++ u32 ctrl; ++ ++ ctrl = readl(priv->reg_base + CAN_CTRL); ++ status = readl(priv->reg_base + CAN_INTF); ++ rx_cnt = (readl(priv->reg_base + CAN_ERR_STAT) & CAN_ERR_RECNT_MASK) >> ++ CAN_ERR_RECNT_BITOFF; ++ tx_cnt = (readl(priv->reg_base + CAN_ERR_STAT) & CAN_ERR_TECNT_MASK) >> ++ CAN_ERR_TECNT_BITOFF; ++ ++ if (ctrl & CAN_CTRL_BUSOFF_BIT) ++ return CAN_STATE_BUS_OFF; ++ else if ((status & CAN_EPASS_BIT) == CAN_EPASS_BIT) ++ return CAN_STATE_ERROR_PASSIVE; ++ else if (rx_cnt > 96 || tx_cnt > 96) ++ return CAN_STATE_ERROR_WARNING; ++ else ++ return CAN_STATE_ERROR_ACTIVE; ++} ++ ++static void aspeed_can_set_error_state(struct net_device *ndev, ++ enum can_state new_state, ++ struct can_frame *cf) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 ecr = readl(priv->reg_base + CAN_ERR_STAT); ++ u32 txerr = (ecr & CAN_ERR_RECNT_MASK) >> CAN_ERR_RECNT_BITOFF; ++ u32 rxerr = (ecr & CAN_ERR_TECNT_MASK) >> CAN_ERR_TECNT_BITOFF; ++ enum can_state tx_state = txerr >= rxerr ? new_state : 0; ++ enum can_state rx_state = txerr <= rxerr ? new_state : 0; ++ ++ /* non-ERROR states are handled elsewhere */ ++ if (WARN_ON(new_state > CAN_STATE_ERROR_PASSIVE)) ++ return; ++ ++ can_change_state(ndev, cf, tx_state, rx_state); ++ ++ if (cf) { ++ cf->can_id |= CAN_ERR_CNT; ++ cf->data[6] = txerr; ++ cf->data[7] = rxerr; ++ } ++} ++ ++static void aspeed_can_update_error_state_after_rxtx(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ enum can_state old_state = priv->can.state; ++ enum can_state new_state; ++ ++ /* changing error state due to successful frame RX/TX can only ++ * occur from these states ++ */ ++ if (old_state != CAN_STATE_ERROR_WARNING && ++ old_state != CAN_STATE_ERROR_PASSIVE) ++ return; ++ ++ new_state = aspeed_can_current_error_state(ndev); ++ ++ if (new_state != old_state) { ++ struct sk_buff *skb; ++ struct can_frame *cf; ++ ++ skb = alloc_can_err_skb(ndev, &cf); ++ ++ aspeed_can_set_error_state(ndev, new_state, skb ? cf : NULL); ++ ++ if (skb) ++ netif_rx(skb); ++ } ++} ++ ++static void aspeed_can_err_interrupt(struct net_device *ndev, u32 isr) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ struct net_device_stats *stats = &ndev->stats; ++ struct can_frame cf = { }; ++ u32 err; ++ u32 ctrl; ++ u32 koer; ++ u32 recnt; ++ u32 tecnt; ++ ++ netdev_err(ndev, "in error interrupt.\n"); ++ ++ aspeed_can_reg_dump(ndev); ++ ++ err = readl(priv->reg_base + CAN_ERR_STAT); ++ ctrl = readl(priv->reg_base + CAN_CTRL); ++ ++ koer = (err & CAN_ERR_KOER_MASK) >> CAN_ERR_KOER_BITOFF; ++ recnt = (err & CAN_ERR_RECNT_MASK) >> CAN_ERR_RECNT_BITOFF; ++ tecnt = (err & CAN_ERR_TECNT_MASK) >> CAN_ERR_TECNT_BITOFF; ++ ++ if (ctrl & CAN_CTRL_BUSOFF_BIT) { ++ priv->can.state = CAN_STATE_BUS_OFF; ++ priv->can.can_stats.bus_off++; ++ /* Leave device in Config Mode in bus-off state */ ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_RST_BIT); ++ can_bus_off(ndev); ++ cf.can_id |= CAN_ERR_BUSOFF; ++ } else { ++ enum can_state new_state = aspeed_can_current_error_state(ndev); ++ ++ if (new_state != priv->can.state) ++ aspeed_can_set_error_state(ndev, new_state, &cf); ++ } ++ ++ if (isr & CAN_INT_ALIF_BIT) { ++ priv->can.can_stats.arbitration_lost++; ++ cf.can_id |= CAN_ERR_LOSTARB; ++ cf.data[0] = CAN_ERR_LOSTARB_UNSPEC; ++ } ++ ++ if (isr & CAN_INT_ROIF_BIT) { ++ stats->rx_over_errors++; ++ stats->rx_errors++; ++ cf.can_id |= CAN_ERR_CRTL; ++ cf.data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; ++ } ++ ++ /* Check for error interrupt */ ++ if (isr & CAN_INT_BEIF_BIT) { ++ bool berr_reporting = false; ++ ++ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) { ++ berr_reporting = true; ++ cf.can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; ++ } ++ ++ if (koer == KOER_ACK_ERROR_MASK) { ++ netdev_err(ndev, "ACK error exists\n"); ++ stats->tx_errors++; ++ if (berr_reporting) { ++ cf.can_id |= CAN_ERR_ACK; ++ cf.data[3] = CAN_ERR_PROT_LOC_ACK; ++ } ++ } ++ ++ if (koer == KOER_BIT_ERROR_MASK) { ++ netdev_err(ndev, "BIT error exists\n"); ++ stats->tx_errors++; ++ if (berr_reporting) { ++ cf.can_id |= CAN_ERR_PROT; ++ cf.data[2] = CAN_ERR_PROT_BIT; ++ } ++ } ++ ++ if (koer == KOER_STUFF_ERROR_MASK) { ++ netdev_err(ndev, "STUFF error exists\n"); ++ stats->rx_errors++; ++ if (berr_reporting) { ++ cf.can_id |= CAN_ERR_PROT; ++ cf.data[2] = CAN_ERR_PROT_STUFF; ++ } ++ } ++ ++ if (koer == KOER_FORM_ERROR_MASK) { ++ netdev_err(ndev, "FORM error exists\n"); ++ stats->rx_errors++; ++ if (berr_reporting) { ++ cf.can_id |= CAN_ERR_PROT; ++ cf.data[2] = CAN_ERR_PROT_FORM; ++ } ++ } ++ ++ if (koer == KOER_CRC_ERROR_MASK) { ++ netdev_err(ndev, "CRC error exists\n"); ++ stats->rx_errors++; ++ if (berr_reporting) { ++ cf.can_id |= CAN_ERR_PROT; ++ cf.data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; ++ } ++ } ++ ++ priv->can.can_stats.bus_error++; ++ } ++ ++ if (cf.can_id) { ++ struct can_frame *skb_cf; ++ struct sk_buff *skb = alloc_can_err_skb(ndev, &skb_cf); ++ ++ if (skb) { ++ skb_cf->can_id |= cf.can_id; ++ memcpy(skb_cf->data, cf.data, CAN_ERR_DLC); ++ netif_rx(skb); ++ } ++ } ++} ++ ++static int aspeed_can_rx_poll(struct napi_struct *napi, int quota) ++{ ++ struct net_device *ndev = napi->dev; ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ int work_done = 0; ++ u32 rx_stat; ++ u32 ier; ++ ++ rx_stat = readl(priv->reg_base + CAN_CTRL); ++ ++ while ((rx_stat & CAN_CTRL_RSTAT_NOT_EMPTY_MASKT) != 0 && ++ (work_done < quota)) { ++ work_done += aspeed_can_rx(ndev); ++ rx_stat = readl(priv->reg_base + CAN_CTRL); ++ } ++ ++ if (work_done) ++ aspeed_can_update_error_state_after_rxtx(ndev); ++ ++ if (work_done < quota) { ++ if (napi_complete_done(napi, work_done)) { ++ ier = readl(priv->reg_base + CAN_INTE); ++ ier |= CAN_INT_RIE_BIT; ++ writel(ier, priv->reg_base + CAN_INTE); ++ } ++ } ++ ++ return work_done; ++} ++ ++static void aspeed_can_tx_interrupt(struct net_device *ndev, u32 intr_flag) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ struct net_device_stats *stats = &ndev->stats; ++ u32 frames_in_fifo; ++ unsigned long flags; ++ u32 can_ctrl; ++ u32 stauts_2; ++ u32 handle; ++ u32 skb_idx; ++ int ret; ++ ++ spin_lock_irqsave(&priv->tx_lock, flags); ++ ++ /* PTB */ ++ if (intr_flag & CAN_INT_TPIF_BIT) { ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, CAN_INT_TPIF_BIT); ++ stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL); ++ goto exit; ++ } ++ ++ /* STB */ ++ can_ctrl = readl(priv->reg_base + CAN_CTRL); ++ stauts_2 = readl(priv->reg_base + CAN_TSTAT); ++ ++ switch (priv->stb_mode_policy) { ++ case (STB_TX_MODE_ONE | STB_POLICY_PRIO): ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, CAN_INT_TSIF_BIT); ++ ++ handle = (stauts_2 & CAN_TSTAT2_HANDLE_MASK) >> ++ CAN_TSTAT2_HANDLE_BITOFF; ++ skb_idx = aspeed_can_get_skb_idx(ndev, handle); ++ if (skb_idx == STB_INVALID_SKB_IDX) { ++ netdev_err(ndev, "fail to get skb idx (%x)\n", ++ STB_TX_MODE_ONE | STB_POLICY_PRIO); ++ } else { ++ stats->tx_bytes += can_get_echo_skb(ndev, skb_idx, NULL); ++ stats->tx_packets++; ++ ret = aspeed_can_drop_ring_obj(ndev, handle); ++ /* priv->tail_ptr = priv->tail_ptr.next; */ ++ if (ret < 0) { ++ netdev_err(ndev, "fail to drop a ring obj (%x)\n", ++ STB_TX_MODE_ONE | STB_POLICY_PRIO); ++ } ++ } ++ ++ /* something still in STB */ ++ if ((can_ctrl & CAN_CTRL_TSSTAT_MASK) != 0x0) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TSONE_BIT); ++ break; ++ ++ case (STB_TX_MODE_ALL | STB_POLICY_FIFO): ++ case (STB_TX_MODE_ALL | STB_POLICY_PRIO): ++ ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, CAN_INT_TSIF_BIT); ++ ++ frames_in_fifo = aspeed_can_get_frame_num(ndev); ++ ++ /* Potential situation: A frame is submitted before this ISR. ++ * That new frame waits for transmission. ++ * This frame should be handled in the next ISR. ++ */ ++ if (can_ctrl & CAN_CTRL_TSSTAT_MASK) ++ frames_in_fifo--; ++ ++ while (frames_in_fifo != 0) { ++ stats->tx_bytes += can_get_echo_skb(ndev, ++ priv->tail_ptr->skb_idx, ++ NULL); ++ priv->tail_ptr->handle = STB_INVALID_HANDLE_VAL; ++ priv->tail_ptr = priv->tail_ptr->next; ++ stats->tx_packets++; ++ frames_in_fifo--; ++ } ++ ++ /* something still in STB */ ++ if (can_ctrl & CAN_CTRL_TSSTAT_MASK) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TSALL_BIT); ++ ++ break; ++ default: ++ /* including STB_TX_MODE_ONE and STB_POLICY_FIFO mode */ ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, CAN_INT_TSIF_BIT); ++ ++ stats->tx_bytes += can_get_echo_skb(ndev, ++ priv->tail_ptr->skb_idx, ++ NULL); ++ priv->tail_ptr = priv->tail_ptr->next; ++ stats->tx_packets++; ++ ++ /* something in STB */ ++ if ((can_ctrl & CAN_CTRL_TSSTAT_MASK) != 0x0) ++ aspeed_can_set_bit(priv, CAN_CTRL, CAN_CTRL_TSONE_BIT); ++ } ++ ++exit: ++ netif_wake_queue(ndev); ++ ++ spin_unlock_irqrestore(&priv->tx_lock, flags); ++ ++ aspeed_can_update_error_state_after_rxtx(ndev); ++} ++ ++static irqreturn_t aspeed_can_interrupt(int irq, void *dev_id) ++{ ++ struct net_device *ndev = (struct net_device *)dev_id; ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 isr; ++ u32 ier; ++ u32 isr_errors; ++ u32 rx_int_mask = CAN_INT_RIF_BIT; ++ ++ isr = readl(priv->reg_base + CAN_INTF); ++ if (!isr) ++ return IRQ_NONE; ++ ++ /* Check for Tx interrupt and Processing it */ ++ if (isr & (CAN_INT_TPIF_BIT | CAN_INT_TSIF_BIT)) ++ aspeed_can_tx_interrupt(ndev, isr); ++ ++ if (isr & CAN_INT_RAFIF_BIT) { ++ netdev_warn(ndev, "Receive buffer is almost full\n"); ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, CAN_INT_RAFIF_BIT); ++ } ++ ++ if (isr & CAN_INT_RFIF_BIT) { ++ netdev_warn(ndev, "Receive buffer is full\n"); ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, CAN_INT_RFIF_BIT); ++ } ++ ++ /* Check for the type of error interrupt and Processing it */ ++ isr_errors = isr & (CAN_INT_EIF_BIT | CAN_INT_ROIF_BIT | ++ CAN_INT_BEIF_BIT | CAN_INT_ALIF_BIT | ++ CAN_INT_EPIF_BIT | CAN_INT_EWARN_BIT); ++ if (isr_errors) { ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, isr_errors); ++ aspeed_can_err_interrupt(ndev, isr); ++ } ++ ++ /* Check for the type of receive interrupt and Processing it */ ++ if (isr & rx_int_mask) { ++ aspeed_can_clr_irq_bits(priv, CAN_INTF, rx_int_mask); ++ ier = readl(priv->reg_base + CAN_INTE); ++ ier &= ~rx_int_mask; /* CAN_INT_RIE_BIT */ ++ writel(ier, priv->reg_base + CAN_INTE); ++ napi_schedule(&priv->napi); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static void aspeed_can_chip_stop(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ int ret; ++ ++ /* Disable interrupts and leave the can in configuration mode */ ++ ret = aspeed_can_set_reset_mode(ndev); ++ if (ret < 0) ++ netdev_dbg(ndev, "aspeed_can_set_reset_mode() Failed\n"); ++ ++ priv->can.state = CAN_STATE_STOPPED; ++} ++ ++static int aspeed_can_open(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ int ret; ++ ++ ret = pm_runtime_get_sync(priv->dev); ++ if (ret < 0) { ++ netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", ++ __func__, ret); ++ goto err; ++ } ++ ++ ret = request_irq(ndev->irq, aspeed_can_interrupt, 0, ++ ndev->name, ndev); ++ if (ret < 0) { ++ netdev_err(ndev, "irq allocation for CAN failed\n"); ++ goto err; ++ } ++ ++ /* Set chip into reset mode */ ++ ret = aspeed_can_set_reset_mode(ndev); ++ if (ret < 0) { ++ netdev_err(ndev, "mode resetting failed!\n"); ++ goto err_irq; ++ } ++ ++ /* Common open */ ++ ret = open_candev(ndev); ++ if (ret) ++ goto err_irq; ++ ++ ret = aspeed_can_chip_start(ndev); ++ if (ret < 0) { ++ netdev_err(ndev, "aspeed_can_chip_start failed!\n"); ++ goto err_candev; ++ } ++ ++ napi_enable(&priv->napi); ++ netif_start_queue(ndev); ++ ++ return 0; ++ ++err_candev: ++ close_candev(ndev); ++err_irq: ++ free_irq(ndev->irq, ndev); ++err: ++ pm_runtime_put(priv->dev); ++ ++ return ret; ++} ++ ++static int aspeed_can_close(struct net_device *ndev) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ netif_stop_queue(ndev); ++ napi_disable(&priv->napi); ++ aspeed_can_chip_stop(ndev); ++ free_irq(ndev->irq, ndev); ++ close_candev(ndev); ++ pm_runtime_put(priv->dev); ++ ++ return 0; ++} ++ ++static int aspeed_can_get_berr_counter(const struct net_device *ndev, ++ struct can_berr_counter *bec) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ int ret; ++ ++ ret = pm_runtime_get_sync(priv->dev); ++ if (ret < 0) { ++ netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", ++ __func__, ret); ++ pm_runtime_put(priv->dev); ++ return ret; ++ } ++ ++ bec->rxerr = (readl(priv->reg_base + CAN_ERR_STAT) & ++ CAN_ERR_RECNT_MASK) >> CAN_ERR_RECNT_BITOFF; ++ bec->txerr = (readl(priv->reg_base + CAN_ERR_STAT) & ++ CAN_ERR_TECNT_MASK) >> CAN_ERR_TECNT_BITOFF; ++ ++ pm_runtime_put(priv->dev); ++ ++ return 0; ++} ++ ++static int aspeed_can_get_auto_tdcv(const struct net_device *ndev, u32 *tdcv) ++{ ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ u32 reg; ++ ++ reg = readl(priv->reg_base + CAN_BITITME); ++ *tdcv = (reg & CAN_TIMING_FD_SSPOFF_MASK) >> CAN_TIMING_FD_SSPOFF_BITOFF; ++ ++ return 0; ++} ++ ++static const struct net_device_ops aspeed_can_netdev_ops = { ++ .ndo_open = aspeed_can_open, ++ .ndo_stop = aspeed_can_close, ++ .ndo_start_xmit = aspeed_can_start_xmit, ++ .ndo_change_mtu = can_change_mtu, ++}; ++ ++static const struct ethtool_ops aspeed_can_ethtool_ops = { ++ .get_ts_info = ethtool_op_get_ts_info, ++}; ++ ++static int __maybe_unused aspeed_can_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ ++ if (netif_running(ndev)) { ++ netif_stop_queue(ndev); ++ netif_device_detach(ndev); ++ aspeed_can_chip_stop(ndev); ++ } ++ ++ return pm_runtime_force_suspend(dev); ++} ++ ++static int __maybe_unused aspeed_can_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) { ++ dev_err(dev, "pm_runtime_force_resume failed on resume\n"); ++ return ret; ++ } ++ ++ if (netif_running(ndev)) { ++ ret = aspeed_can_chip_start(ndev); ++ if (ret) { ++ dev_err(dev, "aspeed_can_chip_start failed on resume\n"); ++ return ret; ++ } ++ ++ netif_device_attach(ndev); ++ netif_start_queue(ndev); ++ } ++ ++ return 0; ++} ++ ++static int __maybe_unused aspeed_can_runtime_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ clk_disable_unprepare(priv->clk); ++ ++ return 0; ++} ++ ++static int __maybe_unused aspeed_can_runtime_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ int ret; ++ ++ ret = clk_prepare_enable(priv->clk); ++ if (ret) { ++ dev_err(dev, "Cannot enable clock.\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops aspeed_can_dev_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(aspeed_can_suspend, aspeed_can_resume) ++ SET_RUNTIME_PM_OPS(aspeed_can_runtime_suspend, aspeed_can_runtime_resume, NULL) ++}; ++ ++/* Match table for OF platform binding */ ++static const struct of_device_id aspeed_can_of_match[] = { ++ { .compatible = "aspeed,canfd", .data = NULL }, ++ { /* end of list */ }, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_can_of_match); ++ ++static int aspeed_can_probe(struct platform_device *pdev) ++{ ++ struct net_device *ndev; ++ struct aspeed_can_priv *priv; ++ int ret; ++ /* Fixed to temporarily. */ ++ u32 rx_max = 3; ++ u32 can_clk; ++ ++ ndev = alloc_candev(sizeof(struct aspeed_can_priv), 4); ++ if (!ndev) ++ return -ENOMEM; ++ ++ priv = netdev_priv(ndev); ++ ++ priv->dev = &pdev->dev; ++ priv->reg_base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(priv->reg_base)) { ++ ret = PTR_ERR(priv->reg_base); ++ goto err; ++ }; ++ ++ priv->can.bittiming_const = &aspeed_can_bittiming_const; ++ priv->can.data_bittiming_const = &aspeed_canfd_bittiming_const; ++ priv->can.do_set_mode = aspeed_can_do_set_mode; ++ priv->can.do_get_berr_counter = aspeed_can_get_berr_counter; ++ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | ++ CAN_CTRLMODE_BERR_REPORTING | ++ CAN_CTRLMODE_FD | ++ CAN_CTRLMODE_CC_LEN8_DLC | ++ CAN_CTRLMODE_TDC_AUTO; ++ priv->can.do_get_auto_tdcv = aspeed_can_get_auto_tdcv; ++ ++ priv->tx_max = 3; ++ spin_lock_init(&priv->tx_lock); ++ ++ /* Get IRQ for the device */ ++ ret = platform_get_irq(pdev, 0); ++ if (ret < 0) ++ goto err_free; ++ ++ ndev->irq = ret; ++ ++ /* We support local echo */ ++ ndev->flags |= IFF_ECHO; ++ ++ platform_set_drvdata(pdev, ndev); ++ SET_NETDEV_DEV(ndev, &pdev->dev); ++ ndev->netdev_ops = &aspeed_can_netdev_ops; ++ ndev->ethtool_ops = &aspeed_can_ethtool_ops; ++ ++ /* Getting the CAN can_clk info */ ++ priv->clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(priv->clk)) { ++ dev_err(&pdev->dev, "missing clock\n"); ++ return PTR_ERR(priv->clk); ++ } ++ ++ can_clk = clk_get_rate(priv->clk); ++ if (!can_clk) { ++ dev_err(&pdev->dev, "invalid clock\n"); ++ return -EINVAL; ++ } ++ ++ ret = clk_prepare_enable(priv->clk); ++ if (ret) { ++ dev_err(&pdev->dev, "can not enable the clock\n"); ++ return ret; ++ } ++ ++ priv->can.clock.freq = can_clk; ++ ++ priv->tb_mode = PTB_MODE; ++ if (of_property_read_bool(priv->dev->of_node, "can-stb-mode")) ++ priv->tb_mode = STB_MODE; ++ ++ priv->stb_mode_policy = STB_TX_MODE_ONE; ++ if (of_property_read_bool(priv->dev->of_node, "can-stb-tx-all")) ++ priv->stb_mode_policy = STB_TX_MODE_ALL; ++ ++ if (of_property_read_bool(priv->dev->of_node, "can-stb-priority")) ++ priv->stb_mode_policy |= STB_POLICY_PRIO; ++ else ++ priv->stb_mode_policy |= STB_POLICY_FIFO; ++ ++ priv->stb_ring = kzalloc(sizeof(*priv->stb_ring) * ++ STB_IDX_RING_SZ, GFP_KERNEL); ++ aspeed_can_stb_ring_obj_init(ndev); ++ ++ if (of_property_read_bool(priv->dev->of_node, "can-internal-loopback")) ++ priv->flag |= ASPEED_CAN_INTERNEL_LOOPBACK; ++ ++ priv->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); ++ if (IS_ERR(priv->reset)) ++ return PTR_ERR(priv->reset); ++ ++ reset_control_deassert(priv->reset); ++ ++ ret = aspeed_can_set_reset_mode(ndev); ++ if (ret < 0) ++ goto err; ++ ++ pm_runtime_enable(&pdev->dev); ++ ret = pm_runtime_get_sync(&pdev->dev); ++ if (ret < 0) { ++ netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", ++ __func__, ret); ++ goto err_disableclks; ++ } ++ ++ netif_napi_add_weight(ndev, &priv->napi, aspeed_can_rx_poll, rx_max); ++ ++ ret = register_candev(ndev); ++ if (ret) { ++ dev_err(&pdev->dev, "fail to register failed (err=%d)\n", ret); ++ goto err_disableclks; ++ } ++ ++ pm_runtime_put(&pdev->dev); ++ ++ netdev_dbg(ndev, "reg_base = 0x%p, irq = %d, clock = %d\n", ++ priv->reg_base, ndev->irq, priv->can.clock.freq); ++ ++ return 0; ++ ++err_disableclks: ++ pm_runtime_put(priv->dev); ++ pm_runtime_disable(&pdev->dev); ++err_free: ++ free_candev(ndev); ++err: ++ kfree(priv->stb_ring); ++ ++ return ret; ++} ++ ++static void aspeed_can_remove(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct aspeed_can_priv *priv = netdev_priv(ndev); ++ ++ reset_control_assert(priv->reset); ++ unregister_candev(ndev); ++ pm_runtime_disable(&pdev->dev); ++ kfree(priv->stb_ring); ++ ++ free_candev(ndev); ++} ++ ++static struct platform_driver aspeed_can_driver = { ++ .probe = aspeed_can_probe, ++ .remove = aspeed_can_remove, ++ .driver = { ++ .name = DRIVER_NAME, ++ .pm = &aspeed_can_dev_pm_ops, ++ .of_match_table = aspeed_can_of_match, ++ }, ++}; ++ ++module_platform_driver(aspeed_can_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Chin-Ting Kuo "); ++MODULE_DESCRIPTION("ASPEED CAN interface"); +diff --git a/drivers/net/mdio/mdio-aspeed.c b/drivers/net/mdio/mdio-aspeed.c +--- a/drivers/net/mdio/mdio-aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/net/mdio/mdio-aspeed.c 2026-09-09 15:51:44.501241351 +0000 +@@ -62,13 +62,8 @@ + | FIELD_PREP(ASPEED_MDIO_DATA_MIIRDATA, data); + + iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); +- +- /* Workaround for read-after-write issue. +- * The controller may return stale data if a read follows immediately +- * after a write. A dummy read forces the hardware to update its +- * internal state, ensuring that the next real read returns correct data. +- */ +- ioread32(ctx->base + ASPEED_MDIO_CTRL); ++ /* Add dummy read to ensure triggering mdio controller */ ++ (void)ioread32(ctx->base + ASPEED_MDIO_CTRL); + + return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl, + !(ctrl & ASPEED_MDIO_CTRL_FIRE), +diff --git a/drivers/pci/controller/pcie-aspeed.c b/drivers/pci/controller/pcie-aspeed.c +--- a/drivers/pci/controller/pcie-aspeed.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/pci/controller/pcie-aspeed.c 2026-09-09 15:51:44.696238404 +0000 +@@ -0,0 +1,1208 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * PCIe host controller driver for ASPEED PCIe Bridge ++ * ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "../pci.h" ++ ++#define MAX_MSI_HOST_IRQS 64 ++ ++/* AST2600 AHBC Registers */ ++#define AHBC_KEY 0x00 ++#define AHBC_UNLOCK 0xAEED1A03 ++#define AHBC_ADDR_MAPPING 0x8C ++#define PCIE_RC_MEMORY_EN BIT(5) ++ ++/* AST2600 PCIe Host Controller Registers */ ++#define PEHR_MISC_10 0x10 ++#define DATALINK_REPORT_CAPABLE BIT(4) ++#define PEHR_MISC_14 0x14 ++#define HOTPLUG_CAPABLE_ENABLE BIT(6) ++#define HOTPLUG_SURPRISE_ENABLE BIT(5) ++#define ATTENTION_BUTTON_ENABLE BIT(0) ++#define PEHR_GLOBAL 0x30 ++#define RC_SYNC_RESET_DISABLE BIT(20) ++#define PCIE_RC_SLOT_ENABLE BIT(1) ++#define ROOT_COMPLEX_ID(x) ((x) << 4) ++#define PEHR_LOCK 0x7C ++#define PCIE_UNLOCK 0xa8 ++#define PEHR_LINK 0xC0 ++#define PCIE_LINK_STS BIT(5) ++ ++/* AST2600 H2X Controller Registers */ ++/* Common Registers*/ ++#define H2X_INT_STS 0x08 ++#define PCIE_TX_IDLE_CLEAR BIT(0) ++#define H2X_TX_DESC0 0x10 ++#define H2X_TX_DESC1 0x14 ++#define H2X_TX_DESC2 0x18 ++#define H2X_TX_DESC3 0x1C ++#define H2X_TX_DESC_DATA 0x20 ++#define H2X_STS 0x24 ++#define PCIE_TX_IDLE BIT(31) ++#define PCIE_STATUS_OF_TX GENMASK(25, 24) ++#define PCIE_RC_L_TX_COMPLETE BIT(24) ++#define PCIE_RC_H_TX_COMPLETE BIT(25) ++#define PCIE_TRIGGER_TX BIT(0) ++#define H2X_AHB_ADDR_CONFIG0 0x60 ++#define H2X_AHB_ADDR_CONFIG1 0x64 ++#define H2X_AHB_ADDR_CONFIG2 0x68 ++/* Device Registers */ ++#define H2X_DEV_CTRL 0x00 ++#define PCIE_RX_DMA_EN BIT(9) ++#define PCIE_RX_LINEAR BIT(8) ++#define PCIE_RX_MSI_SEL BIT(7) ++#define PCIE_RX_MSI_EN BIT(6) ++#define PCIE_UNLOCK_RX_BUFF BIT(4) ++#define PCIE_Wait_RX_TLP_CLR BIT(2) ++#define PCIE_RC_RX_ENABLE BIT(1) ++#define PCIE_RC_ENABLE BIT(0) ++#define H2X_DEV_STS 0x08 ++#define PCIE_RC_RX_DONE_ISR BIT(4) ++#define H2X_DEV_RX_DESC_DATA 0x0C ++#define H2X_DEV_RX_DESC1 0x14 ++#define H2X_DEV_TX_TAG 0x3C ++ ++/* AST2700 H2X */ ++#define H2X_CTRL 0x00 ++#define H2X_BRIDGE_EN BIT(0) ++#define H2X_BRIDGE_DIRECT_EN BIT(1) ++#define H2X_CFGE_INT_STS 0x08 ++#define CFGE_TX_IDLE BIT(0) ++#define CFGE_RX_BUSY BIT(1) ++#define H2X_CTRL2 0x1c ++#define CPL_TIMEOUT_MASK GENMASK(9, 8) ++#define CPL_TIMEOUT_10US 0 ++#define CPL_TIMEOUT_20US 1 ++#define CPL_TIMEOUT_40US 2 ++#define CPL_TIMEOUT_160US 3 ++#define H2X_CFGI_TLP 0x20 ++#define H2X_CFGI_WR_DATA 0x24 ++#define H2X_CFGI_CTRL 0x28 ++#define CFGI_TLP_FIRE BIT(0) ++#define H2X_CFGI_RET_DATA 0x2C ++#define H2X_CFGE_TLP_1ST 0x30 ++#define H2X_CFGE_TLP_NEXT 0x34 ++#define H2X_CFGE_CTRL 0x38 ++#define CFGE_TLP_FIRE BIT(0) ++#define CFGE_TLP_RET_BYPASS BIT(1) ++#define H2X_CFGE_RET_DATA 0x3C ++#define H2X_REMAP_PREF_ADDR 0x70 ++#define H2X_REMAP_DIRECT_ADDR 0x78 ++ ++/* AST2700 PEHR */ ++#define PEHR_VID_DID 0x00 ++#define PEHR_MISC_44 0x44 ++#define ENABLE_SLOT_CAP BIT(12) ++#define PEHR_MISC_38 0x38 ++#define DATALINK_REPORT_CAP BIT(20) ++#define PEHR_MISC_3C 0x3C ++#define PEHR_MISC_58 0x58 ++#define LOCAL_SCALE_SUP BIT(0) ++#define PEHR_MISC_5C 0x5C ++#define PEHR_MISC_60 0x60 ++#define PORT_TPYE GENMASK(7, 4) ++#define PORT_TYPE_ROOT BIT(2) ++#define PEHR_MISC_70 0x70 ++#define PEHR_MISC_78 0x78 ++#define PEHR_MISC_1B8 0x1B8 ++#define SW_ATT_BTN BIT(0) ++#define PEHR_MISC_278 0x278 ++#define SET_TO_DOWNSTREAM BIT(22) ++#define ASSERT_INTERNAL_RESET BIT(31) ++#define PEHR_MISC_280 0x280 ++#define SEL_INTERNAL_RESET BIT(0) ++#define PEHR_MISC_344 0x344 ++#define LINK_STATUS_GEN2 BIT(18) ++#define PEHR_MISC_358 0x358 ++#define LINK_STATUS_GEN4 BIT(8) ++ ++/* AST2700 SCU */ ++#define SCU_60 0x60 ++#define RC_E2M_PATH_EN BIT(0) ++#define RC_H2XS_PATH_EN BIT(16) ++#define RC_H2XD_PATH_EN BIT(17) ++#define RC_H2XX_PATH_EN BIT(18) ++#define RC_UPSTREAM_MEM_EN BIT(19) ++#define SCU_64 0x64 ++#define SCU_70 0x70 ++#define SCU_78 0x78 ++ ++/* TLP configuration type 0 and type 1 */ ++#define CRG_READ_FMTTYPE(type) (0x04000000 | (type << 24)) ++#define CRG_WRITE_FMTTYPE(type) (0x44000000 | (type << 24)) ++#define CRG_PAYLOAD_SIZE 0x01 /* 1 DWORD */ ++#define TLP_COMP_STATUS(s) (((s) >> 13) & 7) ++ ++struct aspeed_pcie_rc_platform { ++ int (*setup)(struct platform_device *pdev); ++ /* Interrupt Register Offset */ ++ int reg_intx_en; ++ int reg_intx_sts; ++ int reg_msi_en; ++ int reg_msi_sts; ++ int msi_support; ++ int msi_address; ++}; ++ ++struct aspeed_pcie { ++ struct pci_host_bridge *host; ++ struct device *dev; ++ void __iomem *reg; ++ struct regmap *ahbc; ++ struct regmap *cfg; ++ struct regmap *pciephy; ++ struct clk *clock; ++ const struct aspeed_pcie_rc_platform *platform; ++ ++ int domain; ++ u8 tx_tag; ++ int host_bus_num; ++ ++ struct reset_control *h2xrst; ++ struct reset_control *perst; ++ ++ struct irq_domain *irq_domain; ++ struct irq_domain *dev_domain; ++ struct irq_domain *msi_domain; ++ /* Protects MSI IRQ allocation and release */ ++ struct mutex lock; ++ ++ int hotplug_event; ++ struct gpio_desc *perst_ep_in; ++ struct gpio_desc *perst_rc_out; ++ struct gpio_desc *perst_owner; ++ struct delayed_work rst_dwork; ++ DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_HOST_IRQS); ++}; ++ ++static void aspeed_pcie_intx_mask_irq(struct irq_data *d) ++{ ++ struct aspeed_pcie *pcie = irq_data_get_irq_chip_data(d); ++ int intx_en = pcie->platform->reg_intx_en; ++ ++ writel(readl(pcie->reg + intx_en) & ~BIT(d->hwirq), pcie->reg + intx_en); ++} ++ ++static void aspeed_pcie_intx_unmask_irq(struct irq_data *d) ++{ ++ struct aspeed_pcie *pcie = irq_data_get_irq_chip_data(d); ++ int intx_en = pcie->platform->reg_intx_en; ++ ++ writel(readl(pcie->reg + intx_en) | BIT(d->hwirq), pcie->reg + intx_en); ++} ++ ++static struct irq_chip aspeed_intx_irq_chip = { ++ .name = "ASPEED:IntX", ++ .irq_mask = aspeed_pcie_intx_mask_irq, ++ .irq_unmask = aspeed_pcie_intx_unmask_irq, ++}; ++ ++static int aspeed_pcie_intx_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &aspeed_intx_irq_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ irq_set_status_flags(irq, IRQ_LEVEL); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops aspeed_intx_domain_ops = { ++ .map = aspeed_pcie_intx_map, ++}; ++ ++static irqreturn_t aspeed_pcie_intr_handler(int irq, void *dev_id) ++{ ++ struct aspeed_pcie *pcie = dev_id; ++ const struct aspeed_pcie_rc_platform *platform = pcie->platform; ++ unsigned long status; ++ unsigned long intx; ++ u32 bit; ++ int i; ++ ++ intx = readl(pcie->reg + platform->reg_intx_sts) & 0xf; ++ if (intx) { ++ for_each_set_bit(bit, &intx, PCI_NUM_INTX) ++ generic_handle_domain_irq(pcie->irq_domain, bit); ++ } ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) { ++ for (i = 0; i < 2; i++) { ++ status = readl(pcie->reg + platform->reg_msi_sts + (i * 4)); ++ writel(status, pcie->reg + platform->reg_msi_sts + (i * 4)); ++ /* Workaround: AST2700 MSI needs to cleat status twice */ ++ if (of_device_is_compatible(pcie->dev->of_node, "aspeed,ast2700-pcie")) ++ writel(status, pcie->reg + platform->reg_msi_sts + (i * 4)); ++ if (!status) ++ continue; ++ ++ for_each_set_bit(bit, &status, 32) { ++ if (i) ++ bit += 32; ++ generic_handle_domain_irq(pcie->dev_domain, bit); ++ } ++ } ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_ast2600_rd_conf(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *val) ++{ ++ struct aspeed_pcie *pcie = bus->sysdata; ++ u32 bdf_offset; ++ int rx_done_fail = 0, slot = PCI_SLOT(devfn); ++ u32 cfg_val, isr, type = 0; ++ u32 link_sts = 0; ++ int ret; ++ ++ /* Driver may set unlock RX buffere before triggering next TX config */ ++ writel(PCIE_UNLOCK_RX_BUFF | readl(pcie->reg + H2X_DEV_CTRL), ++ pcie->reg + H2X_DEV_CTRL); ++ ++ if (bus->number == pcie->host_bus_num && slot != 0 && slot != 8) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ type = (bus->number > pcie->host_bus_num); ++ ++ if (type) { ++ regmap_read(pcie->pciephy, PEHR_LINK, &link_sts); ++ if (!(link_sts & PCIE_LINK_STS)) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ } ++ ++ bdf_offset = ((bus->number) << 24) | (PCI_SLOT(devfn) << 19) | ++ (PCI_FUNC(devfn) << 16) | (where & ~3); ++ ++ pcie->tx_tag %= 0x7; ++ ++ regmap_write(pcie->cfg, H2X_TX_DESC0, 0x04000001 | (type << 24)); ++ regmap_write(pcie->cfg, H2X_TX_DESC1, 0x0000200f | (pcie->tx_tag << 8)); ++ regmap_write(pcie->cfg, H2X_TX_DESC2, bdf_offset); ++ regmap_write(pcie->cfg, H2X_TX_DESC3, 0x00000000); ++ ++ regmap_write_bits(pcie->cfg, H2X_STS, PCIE_TRIGGER_TX, PCIE_TRIGGER_TX); ++ ++ ret = regmap_read_poll_timeout(pcie->cfg, H2X_STS, cfg_val, ++ (cfg_val & PCIE_TX_IDLE), 0, 50); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CR tx timeout sts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), cfg_val); ++ goto out; ++ } ++ ++ regmap_write_bits(pcie->cfg, H2X_INT_STS, PCIE_TX_IDLE_CLEAR, ++ PCIE_TX_IDLE_CLEAR); ++ ++ regmap_read(pcie->cfg, H2X_STS, &cfg_val); ++ switch (cfg_val & PCIE_STATUS_OF_TX) { ++ case PCIE_RC_L_TX_COMPLETE: ++ case PCIE_RC_H_TX_COMPLETE: ++ ret = readl_poll_timeout(pcie->reg + H2X_DEV_STS, isr, ++ (isr & PCIE_RC_RX_DONE_ISR), 0, 50); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CR rx timeoutsts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), isr); ++ rx_done_fail = 1; ++ *val = ~0; ++ } ++ if (!rx_done_fail) { ++ if (readl(pcie->reg + H2X_DEV_RX_DESC1) & BIT(13)) ++ *val = ~0; ++ else ++ *val = readl(pcie->reg + H2X_DEV_RX_DESC_DATA); ++ } ++ ++ writel(PCIE_UNLOCK_RX_BUFF | readl(pcie->reg + H2X_DEV_CTRL), ++ pcie->reg + H2X_DEV_CTRL); ++ break; ++ case PCIE_STATUS_OF_TX: ++ *val = ~0; ++ break; ++ default: ++ regmap_read(pcie->cfg, H2X_DEV_RX_DESC_DATA, &cfg_val); ++ *val = cfg_val; ++ break; ++ } ++ ++ switch (size) { ++ case 1: ++ *val = (*val >> ((where & 3) * 8)) & 0xff; ++ break; ++ case 2: ++ *val = (*val >> ((where & 2) * 8)) & 0xffff; ++ break; ++ } ++ ++ if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) { ++ if (where == (0x80 + PCI_EXP_SLTSTA) && ++ bus->number == pcie->host_bus_num && ++ PCI_SLOT(devfn) == 0x8 && ++ PCI_FUNC(devfn) == 0x0 && ++ pcie->hotplug_event) ++ *val |= PCI_EXP_SLTSTA_ABP; ++ } ++ ++ ret = PCIBIOS_SUCCESSFUL; ++out: ++ writel(readl(pcie->reg + H2X_DEV_STS), pcie->reg + H2X_DEV_STS); ++ pcie->tx_tag++; ++ return ret; ++} ++ ++static int aspeed_ast2600_wr_conf(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 val) ++{ ++ u32 type = 0; ++ u32 shift = 8 * (where & 3); ++ u32 bdf_offset; ++ u8 byte_en = 0; ++ struct aspeed_pcie *pcie = bus->sysdata; ++ u32 isr, cfg_val; ++ int ret; ++ ++ if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) { ++ if (where == (0x80 + PCI_EXP_SLTSTA) && ++ bus->number == pcie->host_bus_num && ++ PCI_SLOT(devfn) == 0x8 && ++ PCI_FUNC(devfn) == 0x0 && ++ pcie->hotplug_event && ++ (val & PCI_EXP_SLTSTA_ABP)) { ++ pcie->hotplug_event = 0; ++ return PCIBIOS_SUCCESSFUL; ++ } ++ } ++ ++ /* Driver may set unlock RX buffere before triggering next TX config */ ++ writel(PCIE_UNLOCK_RX_BUFF | readl(pcie->reg + H2X_DEV_CTRL), ++ pcie->reg + H2X_DEV_CTRL); ++ ++ switch (size) { ++ case 1: ++ byte_en = 1 << (where % 4); ++ val = (val & 0xff) << shift; ++ break; ++ case 2: ++ byte_en = 0x3 << (2 * ((where >> 1) % 2)); ++ val = (val & 0xffff) << shift; ++ break; ++ default: ++ byte_en = 0xf; ++ break; ++ } ++ ++ type = (bus->number > pcie->host_bus_num); ++ ++ bdf_offset = (bus->number << 24) | (PCI_SLOT(devfn) << 19) | ++ (PCI_FUNC(devfn) << 16) | (where & ~3); ++ pcie->tx_tag %= 0x7; ++ ++ regmap_write(pcie->cfg, H2X_TX_DESC0, 0x44000001 | (type << 24)); ++ regmap_write(pcie->cfg, H2X_TX_DESC1, ++ 0x00002000 | (pcie->tx_tag << 8) | byte_en); ++ regmap_write(pcie->cfg, H2X_TX_DESC2, bdf_offset); ++ regmap_write(pcie->cfg, H2X_TX_DESC3, 0x00000000); ++ regmap_write(pcie->cfg, H2X_TX_DESC_DATA, val); ++ ++ regmap_write_bits(pcie->cfg, H2X_STS, PCIE_TRIGGER_TX, PCIE_TRIGGER_TX); ++ ++ ret = regmap_read_poll_timeout(pcie->cfg, H2X_STS, cfg_val, ++ (cfg_val & PCIE_TX_IDLE), 0, 50); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CT tx timeout sts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), cfg_val); ++ ret = PCIBIOS_SET_FAILED; ++ goto out; ++ } ++ ++ regmap_write_bits(pcie->cfg, H2X_INT_STS, PCIE_TX_IDLE_CLEAR, ++ PCIE_TX_IDLE_CLEAR); ++ ++ regmap_read(pcie->cfg, H2X_STS, &cfg_val); ++ switch (cfg_val & PCIE_STATUS_OF_TX) { ++ case PCIE_RC_L_TX_COMPLETE: ++ case PCIE_RC_H_TX_COMPLETE: ++ ret = readl_poll_timeout(pcie->reg + H2X_DEV_STS, isr, ++ (isr & PCIE_RC_RX_DONE_ISR), 0, 50); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CT rx timeout sts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), isr); ++ ret = PCIBIOS_SET_FAILED; ++ goto out; ++ } ++ break; ++ } ++ ret = PCIBIOS_SUCCESSFUL; ++out: ++ writel(readl(pcie->reg + H2X_DEV_STS), pcie->reg + H2X_DEV_STS); ++ pcie->tx_tag++; ++ return ret; ++} ++ ++static bool aspeed_ast2700_get_link(struct aspeed_pcie *pcie) ++{ ++ u32 reg; ++ bool link; ++ ++ if (pcie->domain == 2) { ++ regmap_read(pcie->pciephy, PEHR_MISC_344, ®); ++ link = !!(reg & LINK_STATUS_GEN2); ++ } else { ++ regmap_read(pcie->pciephy, PEHR_MISC_358, ®); ++ link = !!(reg & LINK_STATUS_GEN4); ++ } ++ ++ return link; ++} ++ ++static int aspeed_ast2700_rd_conf(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *val) ++{ ++ struct aspeed_pcie *pcie = bus->sysdata; ++ u32 bdf_offset, status; ++ u8 type; ++ int ret; ++ ++ if ((bus->number == pcie->host_bus_num && devfn != 0)) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ if (bus->number == pcie->host_bus_num) { ++ /* Internal access to bridge */ ++ writel(0xF << 16 | (where & ~3), pcie->reg + H2X_CFGI_TLP); ++ writel(CFGI_TLP_FIRE, pcie->reg + H2X_CFGI_CTRL); ++ *val = readl(pcie->reg + H2X_CFGI_RET_DATA); ++ } else { ++ if (!aspeed_ast2700_get_link(pcie)) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ bdf_offset = ((bus->number) << 24) | (PCI_SLOT(devfn) << 19) | ++ (PCI_FUNC(devfn) << 16) | (where & ~3); ++ ++ pcie->tx_tag %= 0xF; ++ ++ type = (bus->number == (pcie->host_bus_num + 1)) ? ++ PCI_HEADER_TYPE_NORMAL : ++ PCI_HEADER_TYPE_BRIDGE; ++ ++ writel(CRG_READ_FMTTYPE(type) | CRG_PAYLOAD_SIZE, pcie->reg + H2X_CFGE_TLP_1ST); ++ writel(0x40100F | (pcie->tx_tag << 8), pcie->reg + H2X_CFGE_TLP_NEXT); ++ writel(bdf_offset, pcie->reg + H2X_CFGE_TLP_NEXT); ++ writel(CFGE_TX_IDLE | CFGE_RX_BUSY, pcie->reg + H2X_CFGE_INT_STS); ++ writel(CFGE_TLP_FIRE, pcie->reg + H2X_CFGE_CTRL); ++ ++ ret = readl_poll_timeout(pcie->reg + H2X_CFGE_INT_STS, status, ++ (status & CFGE_TX_IDLE), 0, 50); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CR tx timeout sts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), status); ++ goto out; ++ } ++ ++ ret = readl_poll_timeout(pcie->reg + H2X_CFGE_INT_STS, status, ++ (status & CFGE_RX_BUSY), 0, 50000); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CR rx timeoutsts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), status); ++ writel(CFGE_TLP_RET_BYPASS, pcie->reg + H2X_CFGE_CTRL); ++ goto out; ++ } ++ *val = readl(pcie->reg + H2X_CFGE_RET_DATA); ++ } ++ ++ switch (size) { ++ case 1: ++ *val = (*val >> ((where & 3) * 8)) & 0xff; ++ break; ++ case 2: ++ *val = (*val >> ((where & 2) * 8)) & 0xffff; ++ break; ++ } ++ ++ writel(status, pcie->reg + H2X_CFGE_INT_STS); ++ pcie->tx_tag++; ++ return PCIBIOS_SUCCESSFUL; ++out: ++ *val = ~0; ++ writel(status, pcie->reg + H2X_CFGE_INT_STS); ++ pcie->tx_tag++; ++ return PCIBIOS_SET_FAILED; ++} ++ ++static int aspeed_ast2700_wr_conf(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 val) ++{ ++ struct aspeed_pcie *pcie = bus->sysdata; ++ u32 shift = 8 * (where & 3); ++ u8 byte_en; ++ u32 bdf_offset, status, type; ++ int ret; ++ ++ if ((bus->number == pcie->host_bus_num && devfn != 0)) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ switch (size) { ++ case 1: ++ byte_en = 1 << (where % 4); ++ val = (val & 0xff) << shift; ++ break; ++ case 2: ++ byte_en = 0x3 << (2 * ((where >> 1) % 2)); ++ val = (val & 0xffff) << shift; ++ break; ++ default: ++ byte_en = 0xf; ++ break; ++ } ++ ++ if (bus->number == pcie->host_bus_num) { ++ /* Internal access to bridge */ ++ writel(0x100000 | byte_en << 16 | (where & ~3), pcie->reg + H2X_CFGI_TLP); ++ writel(val, pcie->reg + H2X_CFGI_WR_DATA); ++ writel(CFGI_TLP_FIRE, pcie->reg + H2X_CFGI_CTRL); ++ } else { ++ if (!aspeed_ast2700_get_link(pcie)) ++ return PCIBIOS_SET_FAILED; ++ ++ bdf_offset = (bus->number << 24) | (PCI_SLOT(devfn) << 19) | ++ (PCI_FUNC(devfn) << 16) | (where & ~3); ++ pcie->tx_tag %= 0xF; ++ ++ type = (bus->number == (pcie->host_bus_num + 1)) ? ++ PCI_HEADER_TYPE_NORMAL : ++ PCI_HEADER_TYPE_BRIDGE; ++ ++ writel(CRG_WRITE_FMTTYPE(type) | CRG_PAYLOAD_SIZE, pcie->reg + H2X_CFGE_TLP_1ST); ++ writel(0x401000 | (pcie->tx_tag << 8) | byte_en, pcie->reg + H2X_CFGE_TLP_NEXT); ++ writel(bdf_offset, pcie->reg + H2X_CFGE_TLP_NEXT); ++ writel(val, pcie->reg + H2X_CFGE_TLP_NEXT); ++ writel(CFGE_TX_IDLE | CFGE_RX_BUSY, pcie->reg + H2X_CFGE_INT_STS); ++ writel(CFGE_TLP_FIRE, pcie->reg + H2X_CFGE_CTRL); ++ ++ ret = readl_poll_timeout(pcie->reg + H2X_CFGE_INT_STS, status, ++ (status & CFGE_TX_IDLE), 0, 50); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CT tx timeout sts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), status); ++ ret = PCIBIOS_SET_FAILED; ++ goto out; ++ } ++ ++ ret = readl_poll_timeout(pcie->reg + H2X_CFGE_INT_STS, status, ++ (status & CFGE_RX_BUSY), 0, 50000); ++ if (ret) { ++ dev_err(pcie->dev, ++ "[%X:%02X:%02X.%02X]CT rx timeout sts: 0x%08x\n", ++ pcie->domain, bus->number, PCI_SLOT(devfn), ++ PCI_FUNC(devfn), status); ++ ret = PCIBIOS_SET_FAILED; ++ goto out; ++ } ++ ++ (void)readl(pcie->reg + H2X_CFGE_RET_DATA); ++ } ++ ret = PCIBIOS_SUCCESSFUL; ++out: ++ writel(status, pcie->reg + H2X_CFGE_INT_STS); ++ pcie->tx_tag++; ++ return ret; ++} ++ ++static struct pci_ops aspeed_ast2600_pcie_ops = { ++ .read = aspeed_ast2600_rd_conf, ++ .write = aspeed_ast2600_wr_conf, ++}; ++ ++static struct pci_ops aspeed_ast2700_pcie_ops = { ++ .read = aspeed_ast2700_rd_conf, ++ .write = aspeed_ast2700_wr_conf, ++}; ++ ++#ifdef CONFIG_PCI_MSI ++static void aspeed_msi_compose_msi_msg(struct irq_data *data, ++ struct msi_msg *msg) ++{ ++ struct aspeed_pcie *pcie = irq_data_get_irq_chip_data(data); ++ ++ msg->address_hi = 0; ++ msg->address_lo = pcie->platform->msi_address; ++ msg->data = data->hwirq; ++} ++ ++static int aspeed_msi_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) ++{ ++ return -EINVAL; ++} ++ ++static struct irq_chip aspeed_msi_bottom_irq_chip = { ++ .name = "ASPEED MSI", ++ .irq_compose_msi_msg = aspeed_msi_compose_msi_msg, ++ .irq_set_affinity = aspeed_msi_set_affinity, ++}; ++ ++static int aspeed_irq_msi_domain_alloc(struct irq_domain *domain, ++ unsigned int virq, unsigned int nr_irqs, ++ void *args) ++{ ++ struct aspeed_pcie *pcie = domain->host_data; ++ int bit; ++ int i; ++ ++ mutex_lock(&pcie->lock); ++ ++ bit = bitmap_find_free_region(pcie->msi_irq_in_use, MAX_MSI_HOST_IRQS, ++ get_count_order(nr_irqs)); ++ ++ mutex_unlock(&pcie->lock); ++ ++ if (bit < 0) ++ return -ENOSPC; ++ ++ for (i = 0; i < nr_irqs; i++) { ++ irq_domain_set_info(domain, virq + i, bit + i, ++ &aspeed_msi_bottom_irq_chip, ++ domain->host_data, handle_simple_irq, NULL, ++ NULL); ++ } ++ ++ return 0; ++} ++ ++static void aspeed_irq_msi_domain_free(struct irq_domain *domain, ++ unsigned int virq, unsigned int nr_irqs) ++{ ++ struct irq_data *data = irq_domain_get_irq_data(domain, virq); ++ struct aspeed_pcie *pcie = irq_data_get_irq_chip_data(data); ++ ++ mutex_lock(&pcie->lock); ++ ++ bitmap_release_region(pcie->msi_irq_in_use, data->hwirq, ++ get_count_order(nr_irqs)); ++ ++ mutex_unlock(&pcie->lock); ++} ++ ++static const struct irq_domain_ops aspeed_msi_domain_ops = { ++ .alloc = aspeed_irq_msi_domain_alloc, ++ .free = aspeed_irq_msi_domain_free, ++}; ++ ++static struct irq_chip aspeed_msi_irq_chip = { ++ .name = "PCIe MSI", ++ .irq_enable = pci_msi_unmask_irq, ++ .irq_disable = pci_msi_mask_irq, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++}; ++ ++static struct msi_domain_info aspeed_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX), ++ .chip = &aspeed_msi_irq_chip, ++}; ++#endif ++ ++static void aspeed_pcie_irq_domain_free(struct aspeed_pcie *pcie) ++{ ++ if (pcie->irq_domain) { ++ irq_domain_remove(pcie->irq_domain); ++ pcie->irq_domain = NULL; ++ } ++#ifdef CONFIG_PCI_MSI ++ if (pcie->msi_domain) { ++ irq_domain_remove(pcie->msi_domain); ++ pcie->msi_domain = NULL; ++ } ++ ++ if (pcie->dev_domain) { ++ irq_domain_remove(pcie->dev_domain); ++ pcie->dev_domain = NULL; ++ } ++#endif ++} ++ ++static int aspeed_pcie_init_irq_domain(struct aspeed_pcie *pcie) ++{ ++ struct device *dev = pcie->dev; ++ struct device_node *node = dev->of_node; ++ struct device_node *pcie_intc_node; ++ int ret; ++ ++ pcie_intc_node = of_get_next_child(node, NULL); ++ if (!pcie_intc_node) ++ return dev_err_probe(dev, -ENODEV, "No PCIe Intc node found\n"); ++ ++ pcie->irq_domain = ++ irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX, &aspeed_intx_domain_ops, pcie); ++ of_node_put(pcie_intc_node); ++ if (!pcie->irq_domain) { ++ ret = dev_err_probe(dev, -ENOMEM, "failed to get an INTx IRQ domain\n"); ++ goto err; ++ } ++ ++ writel(0, pcie->reg + pcie->platform->reg_intx_en); ++ writel(~0, pcie->reg + pcie->platform->reg_intx_sts); ++ ++ if (!pcie->platform->msi_support) ++ return 0; ++ ++#ifdef CONFIG_PCI_MSI ++ pcie->dev_domain = ++ irq_domain_add_linear(NULL, MAX_MSI_HOST_IRQS, &aspeed_msi_domain_ops, pcie); ++ if (!pcie->dev_domain) { ++ ret = dev_err_probe(pcie->dev, -ENOMEM, "failed to create IRQ domain\n"); ++ goto err; ++ } ++ ++ pcie->msi_domain = pci_msi_create_irq_domain(dev_fwnode(pcie->dev), &aspeed_msi_domain_info, ++ pcie->dev_domain); ++ if (!pcie->msi_domain) { ++ ret = dev_err_probe(pcie->dev, -ENOMEM, "failed to create MSI domain\n"); ++ goto err; ++ } ++ ++ writel(~0, pcie->reg + pcie->platform->reg_msi_en); ++ writel(~0, pcie->reg + pcie->platform->reg_msi_en + 0x04); ++ writel(~0, pcie->reg + pcie->platform->reg_msi_sts); ++ writel(~0, pcie->reg + pcie->platform->reg_msi_sts + 0x04); ++#endif ++ return 0; ++err: ++ aspeed_pcie_irq_domain_free(pcie); ++ return ret; ++} ++ ++static void aspeed_pcie_port_init(struct aspeed_pcie *pcie) ++{ ++ u32 link_sts = 0; ++ ++ regmap_write(pcie->pciephy, PEHR_LOCK, PCIE_UNLOCK); ++ ++ if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) { ++ regmap_write(pcie->pciephy, PEHR_GLOBAL, ++ RC_SYNC_RESET_DISABLE | ROOT_COMPLEX_ID(0x3) | PCIE_RC_SLOT_ENABLE); ++ regmap_write(pcie->pciephy, PEHR_MISC_10, 0xd7040022 | DATALINK_REPORT_CAPABLE); ++ regmap_write(pcie->pciephy, PEHR_MISC_14, ++ HOTPLUG_CAPABLE_ENABLE | HOTPLUG_SURPRISE_ENABLE | ++ ATTENTION_BUTTON_ENABLE); ++ } else { ++ regmap_write(pcie->pciephy, PEHR_GLOBAL, ROOT_COMPLEX_ID(0x3)); ++ } ++ ++ if (pcie->perst_rc_out) { ++ mdelay(100); ++ gpiod_set_value(pcie->perst_rc_out, 1); ++ } ++ ++ reset_control_deassert(pcie->perst); ++ mdelay(500); ++ ++ writel(PCIE_RX_DMA_EN | PCIE_RX_LINEAR | PCIE_RX_MSI_SEL | PCIE_RX_MSI_EN | ++ PCIE_Wait_RX_TLP_CLR | PCIE_RC_RX_ENABLE | PCIE_RC_ENABLE, ++ pcie->reg + H2X_DEV_CTRL); ++ ++ writel(0x28, pcie->reg + H2X_DEV_TX_TAG); ++ ++ regmap_read(pcie->pciephy, PEHR_LINK, &link_sts); ++ if (link_sts & PCIE_LINK_STS) ++ dev_info(pcie->dev, "PCIE- Link up\n"); ++ else ++ dev_info(pcie->dev, "PCIE- Link down\n"); ++} ++ ++static ssize_t hotplug_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ struct aspeed_pcie *pcie = dev_get_drvdata(dev); ++ ++ pcie->hotplug_event = 1; ++ ++ if (of_device_is_compatible(pcie->dev->of_node, "aspeed,ast2700-pcie")) { ++ regmap_write_bits(pcie->pciephy, PEHR_MISC_1B8, SW_ATT_BTN, SW_ATT_BTN); ++ regmap_clear_bits(pcie->pciephy, PEHR_MISC_1B8, SW_ATT_BTN); ++ } ++ ++ return len; ++} ++ ++static DEVICE_ATTR_WO(hotplug); ++ ++static void aspeed_pcie_reset_work(struct work_struct *work) ++{ ++ struct aspeed_pcie *pcie = ++ container_of(work, typeof(*pcie), rst_dwork.work); ++ struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie); ++ struct pci_bus *parent = host->bus; ++ struct pci_dev *dev, *temp; ++ u32 link_sts = 0; ++ u16 command; ++ ++ pci_lock_rescan_remove(); ++ ++ list_for_each_entry_safe_reverse(dev, temp, &parent->devices, ++ bus_list) { ++ pci_dev_get(dev); ++ pci_stop_and_remove_bus_device(dev); ++ /* ++ * Ensure that no new Requests will be generated from ++ * the device. ++ */ ++ pci_read_config_word(dev, PCI_COMMAND, &command); ++ command &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_SERR); ++ command |= PCI_COMMAND_INTX_DISABLE; ++ pci_write_config_word(dev, PCI_COMMAND, command); ++ pci_dev_put(dev); ++ } ++ ++ /* ++ * With perst_rc_out GPIO, the perst will only affect our PCIe controller, so it only ++ * needs to stay low for 1ms. ++ * Without perst_rc_out GPIO, the perst will affect external devices, so it needs to ++ * follow the spec and stay low for at least 100ms. ++ */ ++ reset_control_assert(pcie->perst); ++ if (pcie->perst_rc_out) { ++ gpiod_set_value(pcie->perst_rc_out, 0); ++ mdelay(1); ++ } else { ++ mdelay(100); ++ } ++ reset_control_deassert(pcie->perst); ++ if (pcie->perst_rc_out) { ++ mdelay(100); ++ gpiod_set_value(pcie->perst_rc_out, 1); ++ } ++ mdelay(10); ++ ++ regmap_read(pcie->pciephy, PEHR_LINK, &link_sts); ++ if (link_sts & PCIE_LINK_STS) ++ dev_info(pcie->dev, "PCIE- Link up\n"); ++ else ++ dev_info(pcie->dev, "PCIE- Link down\n"); ++ ++ pci_rescan_bus(host->bus); ++ pci_unlock_rescan_remove(); ++} ++ ++static irqreturn_t pcie_rst_irq_handler(int irq, void *dev_id) ++{ ++ struct aspeed_pcie *pcie = dev_id; ++ ++ schedule_delayed_work(&pcie->rst_dwork, 0); ++ ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_ast2600_setup(struct platform_device *pdev) ++{ ++ struct aspeed_pcie *pcie = platform_get_drvdata(pdev); ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ if (pcie->host_bus_num != 0x80) { ++ dev_err(dev, "AST2600 only supports to start bus number 0x80\n"); ++ return -EINVAL; ++ } ++ ++ pcie->ahbc = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,ahbc"); ++ if (IS_ERR(pcie->ahbc)) ++ return dev_err_probe(dev, PTR_ERR(pcie->ahbc), "failed to map ahbc base\n"); ++ ++ reset_control_assert(pcie->h2xrst); ++ mdelay(5); ++ reset_control_deassert(pcie->h2xrst); ++ ++ regmap_write(pcie->ahbc, AHBC_KEY, AHBC_UNLOCK); ++ regmap_update_bits(pcie->ahbc, AHBC_ADDR_MAPPING, PCIE_RC_MEMORY_EN, PCIE_RC_MEMORY_EN); ++ regmap_write(pcie->ahbc, AHBC_KEY, 0x1); ++ ++ regmap_write(pcie->cfg, H2X_AHB_ADDR_CONFIG0, 0xe0006000); ++ regmap_write(pcie->cfg, H2X_AHB_ADDR_CONFIG1, 0); ++ regmap_write(pcie->cfg, H2X_AHB_ADDR_CONFIG2, ~0); ++ ++ regmap_write(pcie->cfg, H2X_CTRL, H2X_BRIDGE_EN); ++ ++ aspeed_pcie_port_init(pcie); ++ ++ pcie->host->ops = &aspeed_ast2600_pcie_ops; ++ ++ pcie->perst_ep_in = devm_gpiod_get_optional(pcie->dev, "perst-ep-in", GPIOD_IN); ++ if (pcie->perst_ep_in) { ++ gpiod_set_debounce(pcie->perst_ep_in, 100); ++ irq_set_irq_type(gpiod_to_irq(pcie->perst_ep_in), IRQ_TYPE_EDGE_BOTH); ++ ret = devm_request_irq(pcie->dev, gpiod_to_irq(pcie->perst_ep_in), ++ pcie_rst_irq_handler, IRQF_SHARED, "PERST monitor", pcie); ++ if (ret) ++ return dev_err_probe(pcie->dev, ret, "Failed to request gpio irq\n"); ++ INIT_DELAYED_WORK(&pcie->rst_dwork, aspeed_pcie_reset_work); ++ } ++ pcie->perst_owner = ++ devm_gpiod_get_optional(pcie->dev, "perst-owner", GPIOD_OUT_HIGH); ++ ++ return 0; ++} ++ ++static int aspeed_ast2700_setup(struct platform_device *pdev) ++{ ++ struct aspeed_pcie *pcie = platform_get_drvdata(pdev); ++ struct device *dev = pcie->dev; ++ u32 cfg_val; ++ ++ reset_control_assert(pcie->perst); ++ ++ regmap_write(pcie->pciephy, PEHR_MISC_70, 0xa00c0); ++ regmap_write(pcie->pciephy, PEHR_MISC_78, 0x80030); ++ regmap_write(pcie->pciephy, PEHR_MISC_58, LOCAL_SCALE_SUP); ++ ++ regmap_update_bits(pcie->cfg, SCU_60, ++ RC_E2M_PATH_EN | RC_H2XS_PATH_EN | RC_H2XD_PATH_EN | RC_H2XX_PATH_EN | ++ RC_UPSTREAM_MEM_EN, ++ RC_E2M_PATH_EN | RC_H2XS_PATH_EN | RC_H2XD_PATH_EN | RC_H2XX_PATH_EN | ++ RC_UPSTREAM_MEM_EN); ++ regmap_write(pcie->cfg, SCU_64, 0xff00ff00); ++ regmap_write(pcie->cfg, SCU_70, 0); ++ regmap_write(pcie->cfg, SCU_78, (pcie->domain == 1) ? BIT(31) : 0); ++ ++ reset_control_assert(pcie->h2xrst); ++ mdelay(10); ++ reset_control_deassert(pcie->h2xrst); ++ ++ regmap_write(pcie->pciephy, PEHR_MISC_278, 0); ++ if (!IS_ENABLED(CONFIG_PHY_ASPEED_COMBINE_PERST)) ++ regmap_write(pcie->pciephy, PEHR_MISC_280, SEL_INTERNAL_RESET); ++ regmap_write(pcie->pciephy, PEHR_MISC_5C, 0x40000000); ++ regmap_read(pcie->pciephy, PEHR_MISC_60, &cfg_val); ++ regmap_write(pcie->pciephy, PEHR_MISC_60, ++ (cfg_val & ~PORT_TPYE) | FIELD_PREP(PORT_TPYE, PORT_TYPE_ROOT)); ++ ++ writel(0, pcie->reg + H2X_CTRL); ++ writel(H2X_BRIDGE_EN | H2X_BRIDGE_DIRECT_EN, pcie->reg + H2X_CTRL); ++ ++ writel(FIELD_PREP(CPL_TIMEOUT_MASK, CPL_TIMEOUT_160US), ++ pcie->reg + H2X_CTRL2); ++ ++ /* The BAR mapping: ++ * CPU Node0(domain 0): 0x60000000 ++ * CPU Node1(domain 1): 0x80000000 ++ * IO (domain 2): 0xa0000000 ++ */ ++ writel(0x60000000 + (0x20000000 * pcie->domain), pcie->reg + H2X_REMAP_DIRECT_ADDR); ++ ++ /* Prepare for 64-bit BAR pref */ ++ writel(0x3, pcie->reg + H2X_REMAP_PREF_ADDR); ++ ++ if (IS_ENABLED(CONFIG_PHY_ASPEED_COMBINE_PERST)) ++ regmap_write(pcie->pciephy, PEHR_MISC_278, SET_TO_DOWNSTREAM); ++ else ++ regmap_write(pcie->pciephy, PEHR_MISC_278, ++ ASSERT_INTERNAL_RESET | SET_TO_DOWNSTREAM); ++ ++ reset_control_deassert(pcie->perst); ++ if (pcie->perst_rc_out) ++ gpiod_set_value(pcie->perst_rc_out, 1); ++ mdelay(1000); ++ ++ pcie->host->ops = &aspeed_ast2700_pcie_ops; ++ ++ if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) { ++ regmap_write_bits(pcie->pciephy, PEHR_MISC_44, ENABLE_SLOT_CAP, ++ ENABLE_SLOT_CAP); ++ regmap_write(pcie->pciephy, PEHR_MISC_3C, ++ HOTPLUG_CAPABLE_ENABLE | HOTPLUG_SURPRISE_ENABLE | ++ ATTENTION_BUTTON_ENABLE); ++ regmap_write_bits(pcie->pciephy, PEHR_MISC_38, ++ DATALINK_REPORT_CAP, DATALINK_REPORT_CAP); ++ regmap_write(pcie->pciephy, PEHR_MISC_278, SET_TO_DOWNSTREAM); ++ } ++ ++ if (!aspeed_ast2700_get_link(pcie)) ++ dev_info(dev, "PCIe Link DOWN"); ++ else ++ dev_info(dev, "PCIe Link UP"); ++ ++ return 0; ++} ++ ++static int aspeed_pcie_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct pci_host_bridge *host; ++ struct aspeed_pcie *pcie; ++ struct device_node *node = dev->of_node; ++ struct resource bus_range; ++ const void *md = of_device_get_match_data(dev); ++ int irq, ret; ++ ++ if (!md) ++ return -ENODEV; ++ ++ host = devm_pci_alloc_host_bridge(dev, sizeof(*pcie)); ++ if (!host) ++ return -ENOMEM; ++ ++ pcie = pci_host_bridge_priv(host); ++ pcie->dev = dev; ++ pcie->tx_tag = 0; ++ platform_set_drvdata(pdev, pcie); ++ ++ pcie->platform = md; ++ pcie->host = host; ++ ++ if (of_pci_parse_bus_range(node, &bus_range)) { ++ dev_warn(dev, "Failed to parse bus range\n"); ++ pcie->host_bus_num = 0; ++ } ++ pcie->host_bus_num = bus_range.start; ++ ++ pcie->reg = devm_platform_ioremap_resource(pdev, 0); ++ ++ pcie->domain = of_get_pci_domain_nr(node); ++ ++ pcie->cfg = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,pciecfg"); ++ if (IS_ERR(pcie->cfg)) ++ return dev_err_probe(dev, PTR_ERR(pcie->cfg), "Failed to map pciecfg base\n"); ++ ++ pcie->pciephy = syscon_regmap_lookup_by_phandle(node, "aspeed,pciephy"); ++ if (IS_ERR(pcie->pciephy)) ++ return dev_err_probe(dev, PTR_ERR(pcie->pciephy), "Failed to map pciephy base\n"); ++ ++ pcie->h2xrst = devm_reset_control_get_exclusive(dev, "h2x"); ++ if (IS_ERR(pcie->h2xrst)) ++ return dev_err_probe(dev, PTR_ERR(pcie->h2xrst), "Failed to get h2x reset\n"); ++ ++ pcie->perst = devm_reset_control_get_exclusive(dev, "perst"); ++ if (IS_ERR(pcie->perst)) ++ return dev_err_probe(dev, PTR_ERR(pcie->perst), "Failed to get perst reset\n"); ++ ++ pcie->perst_rc_out = devm_gpiod_get_optional(dev, "perst-rc-out", ++ GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_NONEXCLUSIVE); ++ ++ ret = devm_mutex_init(dev, &pcie->lock); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to init mutex\n"); ++ ++ ret = pcie->platform->setup(pdev); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to setup PCIe RC\n"); ++ ++ if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) { ++ ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_hotplug.attr); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, "unable to create sysfs interface\n"); ++ } ++ ++ host->sysdata = pcie; ++ ++ ret = aspeed_pcie_init_irq_domain(pcie); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to initialize IntX/MSI domain\n"); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return dev_err_probe(dev, irq, "Failed to get IRQ\n"); ++ ++ ret = devm_request_irq(dev, irq, aspeed_pcie_intr_handler, IRQF_SHARED, ++ dev_name(dev), pcie); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to request IRQ\n"); ++ ++ pcie->clock = clk_get(dev, NULL); ++ if (IS_ERR(pcie->clock)) ++ return dev_err_probe(dev, PTR_ERR(pcie->clock), "Failed to request clock\n"); ++ ++ ret = clk_prepare_enable(pcie->clock); ++ if (ret) { ++ clk_put(pcie->clock); ++ return dev_err_probe(dev, ret, "Failed to enable the clock\n"); ++ } ++ ++ return pci_host_probe(host); ++} ++ ++static void aspeed_pcie_remove(struct platform_device *pdev) ++{ ++ struct aspeed_pcie *pcie = platform_get_drvdata(pdev); ++ ++ if (pcie->clock) { ++ clk_disable_unprepare(pcie->clock); ++ clk_put(pcie->clock); ++ } ++ ++ pci_stop_root_bus(pcie->host->bus); ++ pci_remove_root_bus(pcie->host->bus); ++ aspeed_pcie_irq_domain_free(pcie); ++} ++ ++static struct aspeed_pcie_rc_platform pcie_rc_ast2600 = { ++ .setup = aspeed_ast2600_setup, ++ .reg_intx_en = 0x04, ++ .reg_intx_sts = 0x08, ++ .reg_msi_en = 0x20, ++ .reg_msi_sts = 0x28, ++ .msi_support = false, ++ .msi_address = 0x1e77005c, ++}; ++ ++static struct aspeed_pcie_rc_platform pcie_rc_ast2700 = { ++ .setup = aspeed_ast2700_setup, ++ .reg_intx_en = 0x40, ++ .reg_intx_sts = 0x48, ++ .reg_msi_en = 0x50, ++ .reg_msi_sts = 0x58, ++ .msi_support = true, ++ .msi_address = 0x000000f0, ++}; ++ ++static const struct of_device_id aspeed_pcie_of_match[] = { ++ { .compatible = "aspeed,ast2600-pcie", .data = &pcie_rc_ast2600 }, ++ { .compatible = "aspeed,ast2700-pcie", .data = &pcie_rc_ast2700 }, ++ {} ++}; ++ ++static struct platform_driver aspeed_pcie_driver = { ++ .driver = { ++ .name = "aspeed-pcie", ++ .suppress_bind_attrs = true, ++ .of_match_table = aspeed_pcie_of_match, ++ }, ++ .probe = aspeed_pcie_probe, ++ .remove_new = aspeed_pcie_remove, ++}; ++ ++module_platform_driver(aspeed_pcie_driver); +diff --git a/drivers/phy/aspeed/Kconfig b/drivers/phy/aspeed/Kconfig +--- a/drivers/phy/aspeed/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/phy/aspeed/Kconfig 2026-09-09 15:51:42.428272675 +0000 +@@ -0,0 +1,22 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++# ++# PHY drivers for ASPEED ++# ++ ++config PHY_ASPEED_SGMII ++ tristate "ASPEED SGMII PHY driver" ++ select REGMAP ++ select MFD_SYSCON ++ select GENERIC_PHY ++ depends on ARCH_ASPEED ++ default n ++ help ++ Enable driver support for Aspeed AST2700 PHY SGMII. ++ ++config PHY_ASPEED_USB3 ++ tristate "ASPEED USB3 PHY driver" ++ select GENERIC_PHY ++ depends on (ARCH_ASPEED || COMPILE_TEST) ++ help ++ Enable driver support for Aspeed AST2700 USB3 PHY. +diff --git a/drivers/phy/aspeed/Makefile b/drivers/phy/aspeed/Makefile +--- a/drivers/phy/aspeed/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/phy/aspeed/Makefile 2026-09-09 15:51:42.428272675 +0000 +@@ -0,0 +1,4 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++obj-$(CONFIG_PHY_ASPEED_SGMII) += phy-aspeed-sgmii.o ++obj-$(CONFIG_PHY_ASPEED_USB3) += phy-aspeed-usb3.o +diff --git a/drivers/phy/aspeed/phy-aspeed-sgmii.c b/drivers/phy/aspeed/phy-aspeed-sgmii.c +--- a/drivers/phy/aspeed/phy-aspeed-sgmii.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/phy/aspeed/phy-aspeed-sgmii.c 2026-09-09 15:51:44.614239643 +0000 +@@ -0,0 +1,247 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SCU_HW_REVISION_ID GENMASK(23, 16) ++ ++#define SGMII_CFG 0x00 ++#define SGMII_CFG_FIFO_MODE BIT(0) ++#define SGMII_CFG_SPEED_SEL_MASK GENMASK(5, 4) ++#define SGMII_CFG_SPEED_SEL(x) FIELD_PREP(SGMII_CFG_SPEED_SEL_MASK, (x)) ++#define SGMII_CFG_PWR_DOWN BIT(11) ++#define SGMII_CFG_AN_ENABLE BIT(12) ++#define SGMII_CFG_SW_RESET BIT(15) ++#define SGMII_LINK_TIMER 0x08 ++#define SGMII_NWAY_ACK 0x0c ++#define SGMII_PHY_CFG1 0x18 ++#define SGMII_PHY_SPEED_MASK GENMASK(3, 2) ++#define SGMII_PHY_SPEED(x) FIELD_PREP(SGMII_PHY_SPEED_MASK, (x)) ++#define SGMII_PHY_PIPE_CTL 0x20 ++#define SGMII_PCTL_TX_NO_DEEMPH BIT(7) ++#define SGMII_FIFO_DELAY_THREHOLD 0x28 ++#define SGMII_MODE 0x30 ++#define SGMII_MODE_ENABLE BIT(0) ++#define SGMII_MODE_USE_LOCAL_CONFIG BIT(2) ++ ++#define PEHR280 0x280 ++#define SGMII_INTERNAL_CLK_EN BIT(26) ++#define PCIEPHY_CLK 0x268 ++#define PCIEPHY_CLK_FREQ_MULTI_MASK GENMASK(7, 0) ++#define PCIEPHY_CLK_FREQ_MULTI(x) FIELD_PREP(PCIEPHY_CLK_FREQ_MULTI_MASK, (x)) ++#define PCIEPHY_CLK_SEL_INTERNAL_25M BIT(8) ++ ++#define SGMII_SPEED_10M 0x00 ++#define SGMII_SPEED_100M 0x01 ++#define SGMII_SPEED_1G 0x02 ++ ++struct aspeed_sgmii { ++ struct device *dev; ++ void __iomem *regs; ++ struct regmap *pcie_phy_regmap; ++ u8 revision; ++}; ++ ++static int aspeed_sgmii_conf(struct phy *phy, int speed) ++{ ++ struct aspeed_sgmii *sgmii = phy_get_drvdata(phy); ++ u32 cfg; ++ ++ writel(0, sgmii->regs + SGMII_MODE); ++ ++ writel(0, sgmii->regs + SGMII_CFG); ++ if (speed == 0) { ++ /* Configure for auto-negotiation */ ++ if (sgmii->revision == 1) ++ writel(SGMII_CFG_AN_ENABLE, sgmii->regs + SGMII_CFG); ++ else ++ writel(SGMII_CFG_AN_ENABLE | SGMII_CFG_FIFO_MODE, ++ sgmii->regs + SGMII_CFG); ++ } else { ++ switch (speed) { ++ case SPEED_10: ++ cfg = SGMII_SPEED_10M; ++ break; ++ case SPEED_100: ++ cfg = SGMII_SPEED_100M; ++ break; ++ case SPEED_1000: ++ cfg = SGMII_SPEED_1G; ++ break; ++ default: ++ return -EINVAL; ++ } ++ writel(SGMII_PHY_SPEED(cfg), sgmii->regs + SGMII_PHY_CFG1); ++ if (sgmii->revision == 1) ++ writel(SGMII_CFG_SPEED_SEL(cfg), ++ sgmii->regs + SGMII_CFG); ++ else ++ writel(SGMII_CFG_SPEED_SEL(cfg) | SGMII_CFG_FIFO_MODE, ++ sgmii->regs + SGMII_CFG); ++ } ++ ++ if (sgmii->revision == 1) ++ writel(0x0c, sgmii->regs + SGMII_FIFO_DELAY_THREHOLD); ++ else ++ writel(0x0e, sgmii->regs + SGMII_FIFO_DELAY_THREHOLD); ++ writel(SGMII_PCTL_TX_NO_DEEMPH, sgmii->regs + SGMII_PHY_PIPE_CTL); ++ ++ /* Set link timer for state change */ ++ writel(0x100, sgmii->regs + SGMII_LINK_TIMER); ++ ++ /* Bit 0 always sets to 1 in ACK message */ ++ writel(0x1, sgmii->regs + SGMII_NWAY_ACK); ++ ++ cfg = SGMII_MODE_ENABLE; ++ if (speed) ++ cfg |= SGMII_MODE_USE_LOCAL_CONFIG; ++ writel(cfg, sgmii->regs + SGMII_MODE); ++ ++ return 0; ++} ++ ++static int aspeed_sgmii_phy_init(struct phy *phy) ++{ ++ struct aspeed_sgmii *sgmii = phy_get_drvdata(phy); ++ u32 reg; ++ ++ /* ++ * The PLDA frequency multiplication is X xor 0x19. ++ * (X xor 0x19) * clock source = data rate. ++ * SGMII data rate is 1.25G, so (0x2b xor 0x19) * 25MHz is equal 1.25G. ++ */ ++ reg = PCIEPHY_CLK_SEL_INTERNAL_25M | PCIEPHY_CLK_FREQ_MULTI(0x2b); ++ regmap_write(sgmii->pcie_phy_regmap, PCIEPHY_CLK, reg); ++ if (sgmii->revision > 1) { ++ regmap_read(sgmii->pcie_phy_regmap, PEHR280, ®); ++ reg |= SGMII_INTERNAL_CLK_EN; ++ regmap_write(sgmii->pcie_phy_regmap, PEHR280, reg); ++ } ++ ++ return 0; ++} ++ ++static int aspeed_sgmii_phy_reset(struct phy *phy) ++{ ++ struct aspeed_sgmii *sgmii = phy_get_drvdata(phy); ++ ++ writel(SGMII_CFG_PWR_DOWN, sgmii->regs + SGMII_CFG); ++ writel(0, sgmii->regs + SGMII_CFG); ++ ++ return 0; ++} ++ ++static int aspeed_sgmii_phy_set_speed(struct phy *phy, int speed) ++{ ++ return aspeed_sgmii_conf(phy, speed); ++} ++ ++static int aspeed_sgmii_phy_exit(struct phy *phy) ++{ ++ struct aspeed_sgmii *sgmii = phy_get_drvdata(phy); ++ ++ /* Disable SGMII controller */ ++ writel(0, sgmii->regs + SGMII_MODE); ++ ++ return 0; ++} ++ ++static const struct phy_ops aspeed_sgmii_phyops = { ++ .init = aspeed_sgmii_phy_init, ++ .reset = aspeed_sgmii_phy_reset, ++ .set_speed = aspeed_sgmii_phy_set_speed, ++ .exit = aspeed_sgmii_phy_exit, ++ .owner = THIS_MODULE, ++}; ++ ++static int aspeed_sgmii_probe(struct platform_device *pdev) ++{ ++ struct phy_provider *provider; ++ struct aspeed_sgmii *sgmii; ++ struct regmap *scu_regmap; ++ struct device_node *np; ++ struct resource *res; ++ struct device *dev; ++ struct phy *phy; ++ u32 reg; ++ ++ dev = &pdev->dev; ++ ++ sgmii = devm_kzalloc(dev, sizeof(*sgmii), GFP_KERNEL); ++ if (!sgmii) ++ return -ENOMEM; ++ ++ sgmii->dev = dev; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(dev, "cannot get resource\n"); ++ return -ENODEV; ++ } ++ ++ sgmii->regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR(sgmii->regs)) { ++ dev_err(dev, "cannot map registers\n"); ++ return PTR_ERR(sgmii->regs); ++ } ++ ++ np = pdev->dev.of_node; ++ sgmii->pcie_phy_regmap = syscon_regmap_lookup_by_phandle(np, "phys"); ++ if (IS_ERR(sgmii->pcie_phy_regmap)) { ++ dev_err(sgmii->dev, "Unable to find phys regmap (%ld)\n", ++ PTR_ERR(sgmii->pcie_phy_regmap)); ++ return PTR_ERR(sgmii->pcie_phy_regmap); ++ } ++ ++ scu_regmap = syscon_regmap_lookup_by_phandle(np, "aspeed,scu"); ++ if (IS_ERR(scu_regmap)) { ++ dev_err(sgmii->dev, "Unable to find SCU regmap (%ld)\n", ++ PTR_ERR(scu_regmap)); ++ return PTR_ERR(scu_regmap); ++ } ++ ++ regmap_read(scu_regmap, 0x00, ®); ++ sgmii->revision = FIELD_GET(SCU_HW_REVISION_ID, reg); ++ ++ phy = devm_phy_create(dev, NULL, &aspeed_sgmii_phyops); ++ if (IS_ERR(phy)) { ++ dev_err(&pdev->dev, "failed to create PHY\n"); ++ return PTR_ERR(phy); ++ } ++ ++ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); ++ if (IS_ERR(provider)) ++ return PTR_ERR(provider); ++ ++ phy_set_drvdata(phy, sgmii); ++ ++ dev_info(dev, "module loaded\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_sgmii_of_matches[] = { ++ { .compatible = "aspeed,ast2700-sgmii" }, ++ { }, ++}; ++ ++static struct platform_driver aspeed_sgmii_driver = { ++ .probe = aspeed_sgmii_probe, ++ .driver = { ++ .name = "aspeed-sgmii", ++ .of_match_table = aspeed_sgmii_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_sgmii_driver); ++ ++MODULE_AUTHOR("Jacky Chou "); ++MODULE_DESCRIPTION("ASPEED SGMII Serdes/PHY controller"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/phy/aspeed/phy-aspeed-usb3.c b/drivers/phy/aspeed/phy-aspeed-usb3.c +--- a/drivers/phy/aspeed/phy-aspeed-usb3.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/phy/aspeed/phy-aspeed-usb3.c 2026-09-09 15:51:44.620239552 +0000 +@@ -0,0 +1,236 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define PHY3S00 0x00 ++#define PHY3S00_INIT_DONE BIT(15) ++#define PHY3S00_SRAM_BYPASS BIT(7) ++#define PHY3S00_SRAM_EXT_LOAD BIT(6) ++#define PHY3S04 0x04 ++#define PHY3C00 0x08 ++#define PHY3C04 0x0C ++#define PHY3P00 0x10 ++#define PHY3P00_RX_ADAPT_AFE_EN_G1 BIT(0) ++#define PHY3P00_RX_ADAPT_AFE_EN_G2 BIT(1) ++#define PHY3P00_RX_ADAPT_DFE_EN_G1 BIT(2) ++#define PHY3P00_RX_ADAPT_DFE_EN_G2 BIT(3) ++#define PHY3P00_RX_CDR_VCO_LOWFREQ_G1 BIT(4) ++#define PHY3P00_RX_CDR_VCO_LOWFREQ_G2 BIT(5) ++#define PHY3P00_RX_EQ_AFE_GAIN_G1 GENMASK(9, 6) ++#define PHY3P00_RX_EQ_AFE_GAIN_G2 GENMASK(13, 10) ++#define PHY3P00_RX_EQ_ATT_LVL_G1 GENMASK(16, 14) ++#define PHY3P00_RX_EQ_ATT_LVL_G2 GENMASK(19, 17) ++#define PHY3P00_RX_EQ_CTLE_BOOST_G1 GENMASK(24, 20) ++#define PHY3P00_RX_EQ_CTLE_BOOST_G2 GENMASK(29, 25) ++#define PHY3P00_RX_EQ_DELTA_IQ_G1_LO GENMASK(31, 30) ++ ++#define PHY3P04 0x14 ++#define PHY3P04_RX_EQ_DELTA_IQ_G1_HI GENMASK(1, 0) ++#define PHY3P04_RX_EQ_DELTA_IQ_G2 GENMASK(5, 2) ++#define PHY3P04_RX_EQ_DFE_TAP1_G1 GENMASK(13, 6) ++#define PHY3P04_RX_EQ_DFE_TAP1_G2 GENMASK(21, 14) ++#define PHY3P04_RX_LOS_LFPS_EN BIT(22) ++#define PHY3P04_RX_LOS_THRESHOLD GENMASK(25, 23) ++#define PHY3P04_RX_TERM_CTRL GENMASK(28, 26) ++#define PHY3P04_TX_EQ_MAIN_G1_LO GENMASK(31, 29) ++ ++#define PHY3P08 0x18 ++#define PHY3P08_TX_EQ_MAIN_G1_HI GENMASK(1, 0) ++#define PHY3P08_TX_EQ_MAIN_G2 GENMASK(6, 2) ++#define PHY3P08_TX_EQ_OVRD BIT(7) ++#define PHY3P08_TX_EQ_POST_G1 GENMASK(12, 9) ++#define PHY3P08_TX_EQ_POST_G2 GENMASK(16, 13) ++#define PHY3P08_TX_EQ_PRE_G1 GENMASK(20, 17) ++#define PHY3P08_TX_EQ_PRE_G2 GENMASK(24, 21) ++#define PHY3P08_TX_IBOOST_LVL GENMASK(28, 25) ++#define PHY3P08_TX_TERM_CTRL GENMASK(31, 29) ++ ++#define PHY3P0C 0x1C ++#define PHY3P0C_TX_VBOOST_EN BIT(0) ++ ++#define PHY3CMD 0x40 ++ ++#define PHY3P_RX_EQ_CTLE_BOOST_G1_DEFAULT 0x7 ++#define PHY3P_RX_EQ_CTLE_BOOST_G2_DEFAULT 0x7 ++#define PHY3P_RX_EQ_DELTA_IQ_G1_DEFAULT 0x3 ++#define PHY3P_RX_EQ_DELTA_IQ_G2_DEFAULT 0x5 ++#define PHY3P_RX_LOS_THRESHOLD_DEFAULT 0x3 ++#define PHY3P_RX_TERM_CTRL_DEFAULT 0x2 ++#define PHY3P_TX_EQ_MAIN_G1_DEFAULT 0xa ++#define PHY3P_TX_EQ_MAIN_G2_DEFAULT 0x9 ++#define PHY3P_TX_EQ_POST_G1_DEFAULT 0x4 ++#define PHY3P_TX_EQ_POST_G2_DEFAULT 0x3 ++#define PHY3P_TX_EQ_PRE_G2_DEFAULT 0x2 ++#define PHY3P_TX_IBOOST_LVL_DEFAULT 0xf ++#define PHY3P_TX_TERM_CTRL_DEFAULT 0x2 ++ ++#define PHY3P00_DEFAULT ( \ ++ PHY3P00_RX_ADAPT_AFE_EN_G1 | \ ++ PHY3P00_RX_ADAPT_AFE_EN_G2 | \ ++ PHY3P00_RX_ADAPT_DFE_EN_G1 | \ ++ PHY3P00_RX_ADAPT_DFE_EN_G2 | \ ++ FIELD_PREP(PHY3P00_RX_EQ_CTLE_BOOST_G1, PHY3P_RX_EQ_CTLE_BOOST_G1_DEFAULT) | \ ++ FIELD_PREP(PHY3P00_RX_EQ_CTLE_BOOST_G2, PHY3P_RX_EQ_CTLE_BOOST_G2_DEFAULT) | \ ++ FIELD_PREP(PHY3P00_RX_EQ_DELTA_IQ_G1_LO, \ ++ PHY3P_RX_EQ_DELTA_IQ_G1_DEFAULT & 0x3) \ ++) ++ ++#define PHY3P04_DEFAULT ( \ ++ FIELD_PREP(PHY3P04_RX_EQ_DELTA_IQ_G1_HI, \ ++ PHY3P_RX_EQ_DELTA_IQ_G1_DEFAULT >> 2) | \ ++ FIELD_PREP(PHY3P04_RX_EQ_DELTA_IQ_G2, PHY3P_RX_EQ_DELTA_IQ_G2_DEFAULT) | \ ++ PHY3P04_RX_LOS_LFPS_EN | \ ++ FIELD_PREP(PHY3P04_RX_LOS_THRESHOLD, PHY3P_RX_LOS_THRESHOLD_DEFAULT) | \ ++ FIELD_PREP(PHY3P04_RX_TERM_CTRL, PHY3P_RX_TERM_CTRL_DEFAULT) | \ ++ FIELD_PREP(PHY3P04_TX_EQ_MAIN_G1_LO, \ ++ PHY3P_TX_EQ_MAIN_G1_DEFAULT & 0x7) \ ++) ++ ++#define PHY3P08_DEFAULT ( \ ++ FIELD_PREP(PHY3P08_TX_EQ_MAIN_G1_HI, PHY3P_TX_EQ_MAIN_G1_DEFAULT >> 3) | \ ++ FIELD_PREP(PHY3P08_TX_EQ_MAIN_G2, PHY3P_TX_EQ_MAIN_G2_DEFAULT) | \ ++ FIELD_PREP(PHY3P08_TX_EQ_POST_G1, PHY3P_TX_EQ_POST_G1_DEFAULT) | \ ++ FIELD_PREP(PHY3P08_TX_EQ_POST_G2, PHY3P_TX_EQ_POST_G2_DEFAULT) | \ ++ FIELD_PREP(PHY3P08_TX_EQ_PRE_G2, PHY3P_TX_EQ_PRE_G2_DEFAULT) | \ ++ FIELD_PREP(PHY3P08_TX_IBOOST_LVL, PHY3P_TX_IBOOST_LVL_DEFAULT) | \ ++ FIELD_PREP(PHY3P08_TX_TERM_CTRL, PHY3P_TX_TERM_CTRL_DEFAULT) \ ++) ++ ++#define PHY3P0C_DEFAULT \ ++ PHY3P0C_TX_VBOOST_EN ++ ++struct aspeed_usb3_phy { ++ void __iomem *regs; ++ struct reset_control *rst; ++ struct device *dev; ++ struct clk *clk; ++}; ++ ++static int aspeed_usb3_phy_init(struct phy *phy) ++{ ++ struct aspeed_usb3_phy *aspeed_phy = phy_get_drvdata(phy); ++ u32 val; ++ int ret; ++ ++ ret = clk_prepare_enable(aspeed_phy->clk); ++ if (ret) { ++ dev_err(aspeed_phy->dev, "Failed to enable clock %d\n", ret); ++ return ret; ++ } ++ ++ ret = reset_control_deassert(aspeed_phy->rst); ++ if (ret) { ++ clk_disable_unprepare(aspeed_phy->clk); ++ return ret; ++ } ++ ++ /* Wait for USB3 PHY internal SRAM initialization done */ ++ ret = readl_poll_timeout(aspeed_phy->regs + PHY3S00, val, ++ val & PHY3S00_INIT_DONE, ++ USEC_PER_MSEC, 10 * USEC_PER_MSEC); ++ if (ret) { ++ dev_err(aspeed_phy->dev, "SRAM init timeout\n"); ++ goto err_assert_reset; ++ } ++ ++ val = readl(aspeed_phy->regs + PHY3S00); ++ val |= PHY3S00_SRAM_BYPASS; ++ writel(val, aspeed_phy->regs + PHY3S00); ++ ++ /* Set protocol1_ext signals as default PHY3 settings based on SNPS documents. ++ * Including PCFGI[54]: protocol1_ext_rx_los_lfps_en for better compatibility ++ */ ++ writel(PHY3P00_DEFAULT, aspeed_phy->regs + PHY3P00); ++ writel(PHY3P04_DEFAULT, aspeed_phy->regs + PHY3P04); ++ writel(PHY3P08_DEFAULT, aspeed_phy->regs + PHY3P08); ++ writel(PHY3P0C_DEFAULT, aspeed_phy->regs + PHY3P0C); ++ ++ return 0; ++ ++err_assert_reset: ++ reset_control_assert(aspeed_phy->rst); ++ clk_disable_unprepare(aspeed_phy->clk); ++ return ret; ++} ++ ++static int aspeed_usb3_phy_exit(struct phy *phy) ++{ ++ struct aspeed_usb3_phy *aspeed_phy = phy_get_drvdata(phy); ++ ++ reset_control_assert(aspeed_phy->rst); ++ clk_disable_unprepare(aspeed_phy->clk); ++ ++ return 0; ++} ++ ++static const struct phy_ops aspeed_usb3_phy_ops = { ++ .init = aspeed_usb3_phy_init, ++ .exit = aspeed_usb3_phy_exit, ++ .owner = THIS_MODULE, ++}; ++ ++static int aspeed_usb3_phy_probe(struct platform_device *pdev) ++{ ++ struct aspeed_usb3_phy *aspeed_phy; ++ struct phy_provider *phy_provider; ++ struct device *dev = &pdev->dev; ++ struct phy *phy; ++ ++ aspeed_phy = devm_kzalloc(dev, sizeof(*aspeed_phy), GFP_KERNEL); ++ if (!aspeed_phy) ++ return -ENOMEM; ++ ++ aspeed_phy->dev = dev; ++ ++ aspeed_phy->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(aspeed_phy->clk)) ++ return PTR_ERR(aspeed_phy->clk); ++ ++ aspeed_phy->rst = devm_reset_control_get_exclusive(dev, NULL); ++ if (IS_ERR(aspeed_phy->rst)) ++ return PTR_ERR(aspeed_phy->rst); ++ ++ aspeed_phy->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(aspeed_phy->regs)) ++ return PTR_ERR(aspeed_phy->regs); ++ ++ phy = devm_phy_create(dev, NULL, &aspeed_usb3_phy_ops); ++ if (IS_ERR(phy)) ++ return PTR_ERR(phy); ++ ++ phy_set_drvdata(phy, aspeed_phy); ++ ++ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); ++ return PTR_ERR_OR_ZERO(phy_provider); ++} ++ ++static const struct of_device_id aspeed_usb3_phy_match_table[] = { ++ { ++ .compatible = "aspeed,ast2700-usb3-phy", ++ }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_usb3_phy_match_table); ++ ++static struct platform_driver aspeed_usb3_phy_driver = { ++ .probe = aspeed_usb3_phy_probe, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_usb3_phy_match_table, ++ }, ++}; ++module_platform_driver(aspeed_usb3_phy_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("ASPEED USB3.0 PHY Driver"); +diff --git a/drivers/pinctrl/aspeed/Kconfig b/drivers/pinctrl/aspeed/Kconfig +--- a/drivers/pinctrl/aspeed/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/Kconfig 2026-09-09 15:51:44.731237875 +0000 +@@ -31,3 +31,11 @@ + help + Say Y here to enable pin controller support for Aspeed's 6th + generation SoCs. GPIO is provided by a separate GPIO driver. ++ ++config PINCTRL_ASPEED_G7 ++ bool "Aspeed G7 SoC pin control" ++ depends on (ARCH_ASPEED || COMPILE_TEST) && OF ++ select PINCTRL_ASPEED ++ help ++ Say Y here to enable pin controller support for Aspeed's 7th ++ generation SoCs. GPIO is provided by a separate GPIO driver. +diff --git a/drivers/pinctrl/aspeed/Makefile b/drivers/pinctrl/aspeed/Makefile +--- a/drivers/pinctrl/aspeed/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/Makefile 2026-09-09 15:51:44.731237875 +0000 +@@ -6,3 +6,4 @@ + obj-$(CONFIG_PINCTRL_ASPEED_G4) += pinctrl-aspeed-g4.o + obj-$(CONFIG_PINCTRL_ASPEED_G5) += pinctrl-aspeed-g5.o + obj-$(CONFIG_PINCTRL_ASPEED_G6) += pinctrl-aspeed-g6.o ++obj-$(CONFIG_PINCTRL_ASPEED_G7) += pinctrl-aspeed-g7-soc0.o pinctrl-aspeed-g7-soc1.o pinctrl-aspeed-g7-ltpi.o +\ No newline at end of file +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g6.c 2026-09-09 15:51:44.732237860 +0000 +@@ -17,6 +17,8 @@ + #include "../pinctrl-utils.h" + #include "pinctrl-aspeed.h" + ++#define SCU040 0x040 /* Reset Control Set 1 */ ++#define SCU0C8 0x0C8 /* Debug Control */ + #define SCU400 0x400 /* Multi-function Pin Control #1 */ + #define SCU404 0x404 /* Multi-function Pin Control #2 */ + #define SCU40C 0x40C /* Multi-function Pin Control #3 */ +@@ -31,6 +33,7 @@ + #define SCU450 0x450 /* Multi-function Pin Control #14 */ + #define SCU454 0x454 /* Multi-function Pin Control #15 */ + #define SCU458 0x458 /* Multi-function Pin Control #16 */ ++#define SCU470 0x470 + #define SCU4B0 0x4B0 /* Multi-function Pin Control #17 */ + #define SCU4B4 0x4B4 /* Multi-function Pin Control #18 */ + #define SCU4B8 0x4B8 /* Multi-function Pin Control #19 */ +@@ -46,13 +49,16 @@ + #define SCU630 0x630 /* Disable GPIO Internal Pull-Down #4 */ + #define SCU634 0x634 /* Disable GPIO Internal Pull-Down #5 */ + #define SCU638 0x638 /* Disable GPIO Internal Pull-Down #6 */ ++#define SCU650 0x650 /* Driving Strength */ + #define SCU690 0x690 /* Multi-function Pin Control #24 */ + #define SCU694 0x694 /* Multi-function Pin Control #25 */ ++#define SCU698 0x698 /* Multi-function Pin Control #26 */ + #define SCU69C 0x69C /* Multi-function Pin Control #27 */ + #define SCU6D0 0x6D0 /* Multi-function Pin Control #29 */ + #define SCUC20 0xC20 /* PCIE configuration Setting Control */ ++#define SCUC24 0xC24 /* BMC MMIO Decode Setting */ + +-#define ASPEED_G6_NR_PINS 256 ++#define ASPEED_G6_NR_PINS 258 + + #define M24 0 + SIG_EXPR_LIST_DECL_SESG(M24, MDC3, MDIO3, SIG_DESC_SET(SCU410, 0)); +@@ -171,81 +177,93 @@ + + #define H24 16 + SIG_EXPR_LIST_DECL_SESG(H24, RGMII3TXCK, RGMII3, SIG_DESC_SET(SCU410, 16), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(H24, RMII3RCLKO, RMII3, SIG_DESC_SET(SCU410, 16), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(H24, GPIOC0, RGMII3TXCK, RMII3RCLKO); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(H24, VPA_B0, VPA, SIG_DESC_CLEAR(SCU410, 16)); ++PIN_DECL_3(H24, GPIOC0, RGMII3TXCK, RMII3RCLKO, VPA_B0); + + #define J22 17 + SIG_EXPR_LIST_DECL_SESG(J22, RGMII3TXCTL, RGMII3, SIG_DESC_SET(SCU410, 17), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(J22, RMII3TXEN, RMII3, SIG_DESC_SET(SCU410, 17), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(J22, GPIOC1, RGMII3TXCTL, RMII3TXEN); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(J22, VPA_B1, VPA, SIG_DESC_CLEAR(SCU410, 17)); ++PIN_DECL_3(J22, GPIOC1, RGMII3TXCTL, RMII3TXEN, VPA_B1); + + #define H22 18 + SIG_EXPR_LIST_DECL_SESG(H22, RGMII3TXD0, RGMII3, SIG_DESC_SET(SCU410, 18), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(H22, RMII3TXD0, RMII3, SIG_DESC_SET(SCU410, 18), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(H22, GPIOC2, RGMII3TXD0, RMII3TXD0); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(H22, VPA_B2, VPA, SIG_DESC_CLEAR(SCU410, 18)); ++PIN_DECL_3(H22, GPIOC2, RGMII3TXD0, RMII3TXD0, VPA_B2); + + #define H23 19 + SIG_EXPR_LIST_DECL_SESG(H23, RGMII3TXD1, RGMII3, SIG_DESC_SET(SCU410, 19), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(H23, RMII3TXD1, RMII3, SIG_DESC_SET(SCU410, 19), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(H23, GPIOC3, RGMII3TXD1, RMII3TXD1); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(H23, VPA_B3, VPA, SIG_DESC_CLEAR(SCU410, 19)); ++PIN_DECL_3(H23, GPIOC3, RGMII3TXD1, RMII3TXD1, VPA_B3); + + #define G22 20 + SIG_EXPR_LIST_DECL_SESG(G22, RGMII3TXD2, RGMII3, SIG_DESC_SET(SCU410, 20), +- SIG_DESC_SET(SCU510, 0)); +-PIN_DECL_1(G22, GPIOC4, RGMII3TXD2); ++ SIG_DESC_SET(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(G22, VPA_B4, VPA, SIG_DESC_CLEAR(SCU410, 20)); ++PIN_DECL_2(G22, GPIOC4, RGMII3TXD2, VPA_B4); + + #define F22 21 + SIG_EXPR_LIST_DECL_SESG(F22, RGMII3TXD3, RGMII3, SIG_DESC_SET(SCU410, 21), +- SIG_DESC_SET(SCU510, 0)); +-PIN_DECL_1(F22, GPIOC5, RGMII3TXD3); ++ SIG_DESC_SET(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(F22, VPA_B5, VPA, SIG_DESC_CLEAR(SCU410, 21)); ++PIN_DECL_2(F22, GPIOC5, RGMII3TXD3, VPA_B5); + + #define G23 22 + SIG_EXPR_LIST_DECL_SESG(G23, RGMII3RXCK, RGMII3, SIG_DESC_SET(SCU410, 22), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(G23, RMII3RCLKI, RMII3, SIG_DESC_SET(SCU410, 22), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(G23, GPIOC6, RGMII3RXCK, RMII3RCLKI); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(G23, VPAPCLK, VPA, SIG_DESC_CLEAR(SCU410, 22)); ++PIN_DECL_3(G23, GPIOC6, RGMII3RXCK, RMII3RCLKI, VPAPCLK); + + #define G24 23 + SIG_EXPR_LIST_DECL_SESG(G24, RGMII3RXCTL, RGMII3, SIG_DESC_SET(SCU410, 23), +- SIG_DESC_SET(SCU510, 0)); +-PIN_DECL_1(G24, GPIOC7, RGMII3RXCTL); ++ SIG_DESC_SET(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(G24, VPA_B6, VPA, SIG_DESC_CLEAR(SCU410, 23)); ++PIN_DECL_2(G24, GPIOC7, RGMII3RXCTL, VPA_B6); + + #define F23 24 + SIG_EXPR_LIST_DECL_SESG(F23, RGMII3RXD0, RGMII3, SIG_DESC_SET(SCU410, 24), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(F23, RMII3RXD0, RMII3, SIG_DESC_SET(SCU410, 24), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(F23, GPIOD0, RGMII3RXD0, RMII3RXD0); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(F23, VPA_B7, VPA, SIG_DESC_CLEAR(SCU410, 24)); ++PIN_DECL_3(F23, GPIOD0, RGMII3RXD0, RMII3RXD0, VPA_B7); + + #define F26 25 + SIG_EXPR_LIST_DECL_SESG(F26, RGMII3RXD1, RGMII3, SIG_DESC_SET(SCU410, 25), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(F26, RMII3RXD1, RMII3, SIG_DESC_SET(SCU410, 25), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(F26, GPIOD1, RGMII3RXD1, RMII3RXD1); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(F26, VPA_G0, VPA, SIG_DESC_CLEAR(SCU410, 25)); ++PIN_DECL_3(F26, GPIOD1, RGMII3RXD1, RMII3RXD1, VPA_G0); + + #define F25 26 + SIG_EXPR_LIST_DECL_SESG(F25, RGMII3RXD2, RGMII3, SIG_DESC_SET(SCU410, 26), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(F25, RMII3CRSDV, RMII3, SIG_DESC_SET(SCU410, 26), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(F25, GPIOD2, RGMII3RXD2, RMII3CRSDV); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(F25, VPA_G1, VPA, SIG_DESC_CLEAR(SCU410, 26)); ++PIN_DECL_3(F25, GPIOD2, RGMII3RXD2, RMII3CRSDV, VPA_G1); + + #define E26 27 + SIG_EXPR_LIST_DECL_SESG(E26, RGMII3RXD3, RGMII3, SIG_DESC_SET(SCU410, 27), +- SIG_DESC_SET(SCU510, 0)); ++ SIG_DESC_SET(SCU510, 0)); + SIG_EXPR_LIST_DECL_SESG(E26, RMII3RXER, RMII3, SIG_DESC_SET(SCU410, 27), +- SIG_DESC_CLEAR(SCU510, 0)); +-PIN_DECL_2(E26, GPIOD3, RGMII3RXD3, RMII3RXER); ++ SIG_DESC_CLEAR(SCU510, 0)); ++SIG_EXPR_LIST_DECL_SESG(E26, VPA_G2, VPA, SIG_DESC_CLEAR(SCU410, 27)); ++PIN_DECL_3(E26, GPIOD3, RGMII3RXD3, RMII3RXER, VPA_G2); + + FUNC_GROUP_DECL(RGMII3, H24, J22, H22, H23, G22, F22, G23, G24, F23, F26, F25, + E26); +@@ -259,7 +277,9 @@ + SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(F24, RMII4RCLKO, RMII4, SIG_DESC_SET(SCU4B0, 28), + SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(F24, GPIOD4, NCTS3, RGMII4TXCK, RMII4RCLKO); ++SIG_EXPR_LIST_DECL_SESG(F24, VPA_G3, VPA, SIG_DESC_CLEAR(SCU410, 28), ++ SIG_DESC_CLEAR(SCU4B0, 28)); ++PIN_DECL_4(F24, GPIOD4, NCTS3, RGMII4TXCK, RMII4RCLKO, VPA_G3); + FUNC_GROUP_DECL(NCTS3, F24); + + #define E23 29 +@@ -268,7 +288,9 @@ + SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(E23, RMII4TXEN, RMII4, SIG_DESC_SET(SCU4B0, 29), + SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(E23, GPIOD5, NDCD3, RGMII4TXCTL, RMII4TXEN); ++SIG_EXPR_LIST_DECL_SESG(E23, VPA_G4, VPA, SIG_DESC_CLEAR(SCU410, 29), ++ SIG_DESC_CLEAR(SCU4B0, 29)); ++PIN_DECL_4(E23, GPIOD5, NDCD3, RGMII4TXCTL, RMII4TXEN, VPA_G4); + FUNC_GROUP_DECL(NDCD3, E23); + + #define E24 30 +@@ -277,7 +299,9 @@ + SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(E24, RMII4TXD0, RMII4, SIG_DESC_SET(SCU4B0, 30), + SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(E24, GPIOD6, NDSR3, RGMII4TXD0, RMII4TXD0); ++SIG_EXPR_LIST_DECL_SESG(E24, VPA_G5, VPA, SIG_DESC_CLEAR(SCU410, 30), ++ SIG_DESC_CLEAR(SCU4B0, 30)); ++PIN_DECL_4(E24, GPIOD6, NDSR3, RGMII4TXD0, RMII4TXD0, VPA_G5); + FUNC_GROUP_DECL(NDSR3, E24); + + #define E25 31 +@@ -286,73 +310,99 @@ + SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(E25, RMII4TXD1, RMII4, SIG_DESC_SET(SCU4B0, 31), + SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(E25, GPIOD7, NRI3, RGMII4TXD1, RMII4TXD1); ++SIG_EXPR_LIST_DECL_SESG(E25, VPA_G6, VPA, SIG_DESC_CLEAR(SCU410, 31), ++ SIG_DESC_CLEAR(SCU4B0, 31)); ++PIN_DECL_4(E25, GPIOD7, NRI3, RGMII4TXD1, RMII4TXD1, VPA_G6); + FUNC_GROUP_DECL(NRI3, E25); + + #define D26 32 +-SIG_EXPR_LIST_DECL_SESG(D26, NDTR3, NDTR3, SIG_DESC_SET(SCU414, 0)); ++SIG_EXPR_LIST_DECL_SESG(D26, NDTR3, NDTR3, SIG_DESC_SET(SCU414, 0), ++ SIG_DESC_CLEAR(SCU470, 16)); + SIG_EXPR_LIST_DECL_SESG(D26, RGMII4TXD2, RGMII4, SIG_DESC_SET(SCU4B4, 0), +- SIG_DESC_SET(SCU510, 1)); +-PIN_DECL_2(D26, GPIOE0, NDTR3, RGMII4TXD2); ++ SIG_DESC_CLEAR(SCU470, 16), SIG_DESC_SET(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(D26, VPA_G7, VPA, SIG_DESC_CLEAR(SCU414, 0), ++ SIG_DESC_CLEAR(SCU4B4, 0)); ++PIN_DECL_3(D26, GPIOE0, NDTR3, RGMII4TXD2, VPA_G7); + FUNC_GROUP_DECL(NDTR3, D26); + + #define D24 33 +-SIG_EXPR_LIST_DECL_SESG(D24, NRTS3, NRTS3, SIG_DESC_SET(SCU414, 1)); ++SIG_EXPR_LIST_DECL_SESG(D24, NRTS3, NRTS3, SIG_DESC_SET(SCU414, 1), ++ SIG_DESC_CLEAR(SCU470, 17)); + SIG_EXPR_LIST_DECL_SESG(D24, RGMII4TXD3, RGMII4, SIG_DESC_SET(SCU4B4, 1), +- SIG_DESC_SET(SCU510, 1)); +-PIN_DECL_2(D24, GPIOE1, NRTS3, RGMII4TXD3); ++ SIG_DESC_CLEAR(SCU470, 17), SIG_DESC_SET(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(D24, VPA_R0, VPA, SIG_DESC_CLEAR(SCU414, 1), ++ SIG_DESC_CLEAR(SCU4B4, 1)); ++PIN_DECL_3(D24, GPIOE1, NRTS3, RGMII4TXD3, VPA_R0); + FUNC_GROUP_DECL(NRTS3, D24); + + #define C25 34 +-SIG_EXPR_LIST_DECL_SESG(C25, NCTS4, NCTS4, SIG_DESC_SET(SCU414, 2)); ++SIG_EXPR_LIST_DECL_SESG(C25, NCTS4, NCTS4, SIG_DESC_SET(SCU414, 2), ++ SIG_DESC_CLEAR(SCU470, 18)); + SIG_EXPR_LIST_DECL_SESG(C25, RGMII4RXCK, RGMII4, SIG_DESC_SET(SCU4B4, 2), +- SIG_DESC_SET(SCU510, 1)); ++ SIG_DESC_CLEAR(SCU470, 18), SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(C25, RMII4RCLKI, RMII4, SIG_DESC_SET(SCU4B4, 2), +- SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(C25, GPIOE2, NCTS4, RGMII4RXCK, RMII4RCLKI); ++ SIG_DESC_CLEAR(SCU470, 18), SIG_DESC_CLEAR(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(C25, VPA_R1, VPA, SIG_DESC_CLEAR(SCU414, 2), ++ SIG_DESC_CLEAR(SCU4B4, 2)); ++PIN_DECL_4(C25, GPIOE2, NCTS4, RGMII4RXCK, RMII4RCLKI, VPA_R1); + FUNC_GROUP_DECL(NCTS4, C25); + + #define C26 35 +-SIG_EXPR_LIST_DECL_SESG(C26, NDCD4, NDCD4, SIG_DESC_SET(SCU414, 3)); ++SIG_EXPR_LIST_DECL_SESG(C26, NDCD4, NDCD4, SIG_DESC_SET(SCU414, 3), ++ SIG_DESC_CLEAR(SCU470, 19)); + SIG_EXPR_LIST_DECL_SESG(C26, RGMII4RXCTL, RGMII4, SIG_DESC_SET(SCU4B4, 3), +- SIG_DESC_SET(SCU510, 1)); +-PIN_DECL_2(C26, GPIOE3, NDCD4, RGMII4RXCTL); ++ SIG_DESC_CLEAR(SCU470, 19), SIG_DESC_SET(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(C26, VPA_R2, VPA, SIG_DESC_CLEAR(SCU414, 3), ++ SIG_DESC_CLEAR(SCU4B4, 3)); ++PIN_DECL_3(C26, GPIOE3, NDCD4, RGMII4RXCTL, VPA_R2); + FUNC_GROUP_DECL(NDCD4, C26); + + #define C24 36 +-SIG_EXPR_LIST_DECL_SESG(C24, NDSR4, NDSR4, SIG_DESC_SET(SCU414, 4)); ++SIG_EXPR_LIST_DECL_SESG(C24, NDSR4, NDSR4, SIG_DESC_SET(SCU414, 4), ++ SIG_DESC_CLEAR(SCU470, 20)); + SIG_EXPR_LIST_DECL_SESG(C24, RGMII4RXD0, RGMII4, SIG_DESC_SET(SCU4B4, 4), +- SIG_DESC_SET(SCU510, 1)); ++ SIG_DESC_CLEAR(SCU470, 20), SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(C24, RMII4RXD0, RMII4, SIG_DESC_SET(SCU4B4, 4), +- SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(C24, GPIOE4, NDSR4, RGMII4RXD0, RMII4RXD0); ++ SIG_DESC_CLEAR(SCU470, 20), SIG_DESC_CLEAR(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(C24, VPA_R3, VPA, SIG_DESC_CLEAR(SCU414, 4), ++ SIG_DESC_CLEAR(SCU4B4, 4)); ++PIN_DECL_4(C24, GPIOE4, NDSR4, RGMII4RXD0, RMII4RXD0, VPA_R3); + FUNC_GROUP_DECL(NDSR4, C24); + + #define B26 37 +-SIG_EXPR_LIST_DECL_SESG(B26, NRI4, NRI4, SIG_DESC_SET(SCU414, 5)); ++SIG_EXPR_LIST_DECL_SESG(B26, NRI4, NRI4, SIG_DESC_SET(SCU414, 5), ++ SIG_DESC_CLEAR(SCU470, 21)); + SIG_EXPR_LIST_DECL_SESG(B26, RGMII4RXD1, RGMII4, SIG_DESC_SET(SCU4B4, 5), +- SIG_DESC_SET(SCU510, 1)); ++ SIG_DESC_CLEAR(SCU470, 21), SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(B26, RMII4RXD1, RMII4, SIG_DESC_SET(SCU4B4, 5), +- SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(B26, GPIOE5, NRI4, RGMII4RXD1, RMII4RXD1); ++ SIG_DESC_CLEAR(SCU470, 21), SIG_DESC_CLEAR(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(B26, VPA_R4, VPA, SIG_DESC_CLEAR(SCU414, 5), ++ SIG_DESC_CLEAR(SCU4B4, 5)); ++PIN_DECL_4(B26, GPIOE5, NRI4, RGMII4RXD1, RMII4RXD1, VPA_R4); + FUNC_GROUP_DECL(NRI4, B26); + + #define B25 38 +-SIG_EXPR_LIST_DECL_SESG(B25, NDTR4, NDTR4, SIG_DESC_SET(SCU414, 6)); ++SIG_EXPR_LIST_DECL_SESG(B25, NDTR4, NDTR4, SIG_DESC_SET(SCU414, 6), ++ SIG_DESC_CLEAR(SCU470, 22)); + SIG_EXPR_LIST_DECL_SESG(B25, RGMII4RXD2, RGMII4, SIG_DESC_SET(SCU4B4, 6), +- SIG_DESC_SET(SCU510, 1)); ++ SIG_DESC_CLEAR(SCU470, 22), SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(B25, RMII4CRSDV, RMII4, SIG_DESC_SET(SCU4B4, 6), +- SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(B25, GPIOE6, NDTR4, RGMII4RXD2, RMII4CRSDV); ++ SIG_DESC_CLEAR(SCU470, 22), SIG_DESC_CLEAR(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(B25, VPA_R5, VPA, SIG_DESC_CLEAR(SCU414, 6), ++ SIG_DESC_CLEAR(SCU4B4, 6)); ++PIN_DECL_4(B25, GPIOE6, NDTR4, RGMII4RXD2, RMII4CRSDV, VPA_R5); + FUNC_GROUP_DECL(NDTR4, B25); + + #define B24 39 +-SIG_EXPR_LIST_DECL_SESG(B24, NRTS4, NRTS4, SIG_DESC_SET(SCU414, 7)); ++SIG_EXPR_LIST_DECL_SESG(B24, NRTS4, NRTS4, SIG_DESC_SET(SCU414, 7), ++ SIG_DESC_CLEAR(SCU470, 23)); + SIG_EXPR_LIST_DECL_SESG(B24, RGMII4RXD3, RGMII4, SIG_DESC_SET(SCU4B4, 7), +- SIG_DESC_SET(SCU510, 1)); ++ SIG_DESC_CLEAR(SCU470, 23), SIG_DESC_SET(SCU510, 1)); + SIG_EXPR_LIST_DECL_SESG(B24, RMII4RXER, RMII4, SIG_DESC_SET(SCU4B4, 7), +- SIG_DESC_CLEAR(SCU510, 1)); +-PIN_DECL_3(B24, GPIOE7, NRTS4, RGMII4RXD3, RMII4RXER); ++ SIG_DESC_CLEAR(SCU470, 23), SIG_DESC_CLEAR(SCU510, 1)); ++SIG_EXPR_LIST_DECL_SESG(B24, VPA_R6, VPA, SIG_DESC_CLEAR(SCU414, 7), ++ SIG_DESC_CLEAR(SCU4B4, 7)); ++PIN_DECL_4(B24, GPIOE7, NRTS4, RGMII4RXD3, RMII4RXER, VPA_R6); + FUNC_GROUP_DECL(NRTS4, B24); + + FUNC_GROUP_DECL(RGMII4, F24, E23, E24, E25, D26, D24, C25, C26, C24, B26, B25, +@@ -364,27 +414,39 @@ + #define D22 40 + SIG_EXPR_LIST_DECL_SESG(D22, SD1CLK, SD1, SIG_DESC_SET(SCU414, 8)); + SIG_EXPR_LIST_DECL_SEMG(D22, PWM8, PWM8G0, PWM8, SIG_DESC_SET(SCU4B4, 8)); +-PIN_DECL_2(D22, GPIOF0, SD1CLK, PWM8); ++SIG_EXPR_LIST_DECL_SESG(D22, VPA_R7, VPA, SIG_DESC_CLEAR(SCU414, 8), ++ SIG_DESC_CLEAR(SCU4B4, 8)); ++PIN_DECL_3(D22, GPIOF0, SD1CLK, PWM8, VPA_R7); + GROUP_DECL(PWM8G0, D22); + + #define E22 41 + SIG_EXPR_LIST_DECL_SESG(E22, SD1CMD, SD1, SIG_DESC_SET(SCU414, 9)); + SIG_EXPR_LIST_DECL_SEMG(E22, PWM9, PWM9G0, PWM9, SIG_DESC_SET(SCU4B4, 9)); +-PIN_DECL_2(E22, GPIOF1, SD1CMD, PWM9); ++SIG_EXPR_LIST_DECL_SESG(E22, VPAHS, VPA, SIG_DESC_CLEAR(SCU414, 9), ++ SIG_DESC_CLEAR(SCU4B4, 9)); ++PIN_DECL_3(E22, GPIOF1, SD1CMD, PWM9, VPAHS); + GROUP_DECL(PWM9G0, E22); + + #define D23 42 + SIG_EXPR_LIST_DECL_SESG(D23, SD1DAT0, SD1, SIG_DESC_SET(SCU414, 10)); + SIG_EXPR_LIST_DECL_SEMG(D23, PWM10, PWM10G0, PWM10, SIG_DESC_SET(SCU4B4, 10)); +-PIN_DECL_2(D23, GPIOF2, SD1DAT0, PWM10); ++SIG_EXPR_LIST_DECL_SESG(D23, VPAVS, VPA, SIG_DESC_CLEAR(SCU414, 10), ++ SIG_DESC_CLEAR(SCU4B4, 10)); ++PIN_DECL_3(D23, GPIOF2, SD1DAT0, PWM10, VPAVS); + GROUP_DECL(PWM10G0, D23); + + #define C23 43 + SIG_EXPR_LIST_DECL_SESG(C23, SD1DAT1, SD1, SIG_DESC_SET(SCU414, 11)); + SIG_EXPR_LIST_DECL_SEMG(C23, PWM11, PWM11G0, PWM11, SIG_DESC_SET(SCU4B4, 11)); +-PIN_DECL_2(C23, GPIOF3, SD1DAT1, PWM11); ++SIG_EXPR_LIST_DECL_SESG(C23, VPADE, VPA, SIG_DESC_CLEAR(SCU414, 11), ++ SIG_DESC_CLEAR(SCU4B4, 11)); ++PIN_DECL_3(C23, GPIOF3, SD1DAT1, PWM11, VPADE); + GROUP_DECL(PWM11G0, C23); + ++FUNC_GROUP_DECL(VPA, H24, J22, H22, H23, G22, F22, G23, G24, F23, F26, F25, ++ E26, F24, E23, E24, E25, D26, D24, C25, C26, C24, B26, B25, ++ B24, D22, E22, D23, C23); ++ + #define C22 44 + SIG_EXPR_LIST_DECL_SESG(C22, SD1DAT2, SD1, SIG_DESC_SET(SCU414, 12)); + SIG_EXPR_LIST_DECL_SEMG(C22, PWM12, PWM12G0, PWM12, SIG_DESC_SET(SCU4B4, 12)); +@@ -414,7 +476,7 @@ + #define E21 48 + SIG_EXPR_LIST_DECL_SESG(E21, TXD6, UART6, SIG_DESC_SET(SCU414, 16)); + SIG_EXPR_LIST_DECL_SESG(E21, SD2CLK, SD2, SIG_DESC_SET(SCU4B4, 16), +- SIG_DESC_SET(SCU450, 1)); ++ SIG_DESC_SET(SCU450, 1)); + SIG_EXPR_LIST_DECL_SEMG(E21, SALT9, SALT9G0, SALT9, SIG_DESC_SET(SCU694, 16)); + PIN_DECL_3(E21, GPIOG0, TXD6, SD2CLK, SALT9); + GROUP_DECL(SALT9G0, E21); +@@ -422,7 +484,7 @@ + #define B22 49 + SIG_EXPR_LIST_DECL_SESG(B22, RXD6, UART6, SIG_DESC_SET(SCU414, 17)); + SIG_EXPR_LIST_DECL_SESG(B22, SD2CMD, SD2, SIG_DESC_SET(SCU4B4, 17), +- SIG_DESC_SET(SCU450, 1)); ++ SIG_DESC_SET(SCU450, 1)); + SIG_EXPR_LIST_DECL_SEMG(B22, SALT10, SALT10G0, SALT10, + SIG_DESC_SET(SCU694, 17)); + PIN_DECL_3(B22, GPIOG1, RXD6, SD2CMD, SALT10); +@@ -433,7 +495,7 @@ + #define C21 50 + SIG_EXPR_LIST_DECL_SESG(C21, TXD7, UART7, SIG_DESC_SET(SCU414, 18)); + SIG_EXPR_LIST_DECL_SESG(C21, SD2DAT0, SD2, SIG_DESC_SET(SCU4B4, 18), +- SIG_DESC_SET(SCU450, 1)); ++ SIG_DESC_SET(SCU450, 1)); + SIG_EXPR_LIST_DECL_SEMG(C21, SALT11, SALT11G0, SALT11, + SIG_DESC_SET(SCU694, 18)); + PIN_DECL_3(C21, GPIOG2, TXD7, SD2DAT0, SALT11); +@@ -442,7 +504,7 @@ + #define A22 51 + SIG_EXPR_LIST_DECL_SESG(A22, RXD7, UART7, SIG_DESC_SET(SCU414, 19)); + SIG_EXPR_LIST_DECL_SESG(A22, SD2DAT1, SD2, SIG_DESC_SET(SCU4B4, 19), +- SIG_DESC_SET(SCU450, 1)); ++ SIG_DESC_SET(SCU450, 1)); + SIG_EXPR_LIST_DECL_SEMG(A22, SALT12, SALT12G0, SALT12, + SIG_DESC_SET(SCU694, 19)); + PIN_DECL_3(A22, GPIOG3, RXD7, SD2DAT1, SALT12); +@@ -453,7 +515,7 @@ + #define A21 52 + SIG_EXPR_LIST_DECL_SESG(A21, TXD8, UART8, SIG_DESC_SET(SCU414, 20)); + SIG_EXPR_LIST_DECL_SESG(A21, SD2DAT2, SD2, SIG_DESC_SET(SCU4B4, 20), +- SIG_DESC_SET(SCU450, 1)); ++ SIG_DESC_SET(SCU450, 1)); + SIG_EXPR_LIST_DECL_SEMG(A21, SALT13, SALT13G0, SALT13, + SIG_DESC_SET(SCU694, 20)); + PIN_DECL_3(A21, GPIOG4, TXD8, SD2DAT2, SALT13); +@@ -462,7 +524,7 @@ + #define E20 53 + SIG_EXPR_LIST_DECL_SESG(E20, RXD8, UART8, SIG_DESC_SET(SCU414, 21)); + SIG_EXPR_LIST_DECL_SESG(E20, SD2DAT3, SD2, SIG_DESC_SET(SCU4B4, 21), +- SIG_DESC_SET(SCU450, 1)); ++ SIG_DESC_SET(SCU450, 1)); + SIG_EXPR_LIST_DECL_SEMG(E20, SALT14, SALT14G0, SALT14, + SIG_DESC_SET(SCU694, 21)); + PIN_DECL_3(E20, GPIOG5, RXD8, SD2DAT3, SALT14); +@@ -473,7 +535,7 @@ + #define D21 54 + SIG_EXPR_LIST_DECL_SESG(D21, TXD9, UART9, SIG_DESC_SET(SCU414, 22)); + SIG_EXPR_LIST_DECL_SESG(D21, SD2CD, SD2, SIG_DESC_SET(SCU4B4, 22), +- SIG_DESC_SET(SCU450, 1)); ++ SIG_DESC_SET(SCU450, 1)); + SIG_EXPR_LIST_DECL_SEMG(D21, SALT15, SALT15G0, SALT15, + SIG_DESC_SET(SCU694, 22)); + PIN_DECL_3(D21, GPIOG6, TXD9, SD2CD, SALT15); +@@ -583,116 +645,150 @@ + FUNC_GROUP_DECL(SIOSCI, A15); + + #define B20 72 +-SIG_EXPR_LIST_DECL_SEMG(B20, I3C3SCL, HVI3C3, I3C3, SIG_DESC_SET(SCU418, 8)); ++SIG_EXPR_LIST_DECL_SEMG(B20, I3C3SCL, HVI3C3, I3C3, SIG_DESC_SET(SCU418, 8), ++ SIG_DESC_CLEAR(SCU438, 20)); + SIG_EXPR_LIST_DECL_SESG(B20, SCL1, I2C1, SIG_DESC_SET(SCU4B8, 8)); +-PIN_DECL_2(B20, GPIOJ0, I3C3SCL, SCL1); ++SIG_EXPR_LIST_DECL_SESG(B20, SSCL1, SI2C1, SIG_DESC_SET(SCU698, 8)); ++PIN_DECL_3(B20, GPIOJ0, I3C3SCL, SCL1, SSCL1); + + #define A20 73 +-SIG_EXPR_LIST_DECL_SEMG(A20, I3C3SDA, HVI3C3, I3C3, SIG_DESC_SET(SCU418, 9)); ++SIG_EXPR_LIST_DECL_SEMG(A20, I3C3SDA, HVI3C3, I3C3, SIG_DESC_SET(SCU418, 9), ++ SIG_DESC_CLEAR(SCU438, 21)); + SIG_EXPR_LIST_DECL_SESG(A20, SDA1, I2C1, SIG_DESC_SET(SCU4B8, 9)); +-PIN_DECL_2(A20, GPIOJ1, I3C3SDA, SDA1); ++SIG_EXPR_LIST_DECL_SESG(A20, SSDA1, SI2C1, SIG_DESC_SET(SCU698, 9)); ++PIN_DECL_3(A20, GPIOJ1, I3C3SDA, SDA1, SSDA1); + + GROUP_DECL(HVI3C3, B20, A20); + FUNC_GROUP_DECL(I2C1, B20, A20); ++FUNC_GROUP_DECL(SI2C1, B20, A20); + + #define E19 74 +-SIG_EXPR_LIST_DECL_SEMG(E19, I3C4SCL, HVI3C4, I3C4, SIG_DESC_SET(SCU418, 10)); ++SIG_EXPR_LIST_DECL_SEMG(E19, I3C4SCL, HVI3C4, I3C4, SIG_DESC_SET(SCU418, 10), ++ SIG_DESC_CLEAR(SCU438, 22)); + SIG_EXPR_LIST_DECL_SESG(E19, SCL2, I2C2, SIG_DESC_SET(SCU4B8, 10)); +-PIN_DECL_2(E19, GPIOJ2, I3C4SCL, SCL2); ++SIG_EXPR_LIST_DECL_SESG(E19, SSCL2, SI2C2, SIG_DESC_SET(SCU698, 10)); ++PIN_DECL_3(E19, GPIOJ2, I3C4SCL, SCL2, SSCL2); + + #define D20 75 +-SIG_EXPR_LIST_DECL_SEMG(D20, I3C4SDA, HVI3C4, I3C4, SIG_DESC_SET(SCU418, 11)); ++SIG_EXPR_LIST_DECL_SEMG(D20, I3C4SDA, HVI3C4, I3C4, SIG_DESC_SET(SCU418, 11), ++ SIG_DESC_CLEAR(SCU438, 23)); + SIG_EXPR_LIST_DECL_SESG(D20, SDA2, I2C2, SIG_DESC_SET(SCU4B8, 11)); +-PIN_DECL_2(D20, GPIOJ3, I3C4SDA, SDA2); ++SIG_EXPR_LIST_DECL_SESG(D20, SSDA2, SI2C2, SIG_DESC_SET(SCU698, 11)); ++PIN_DECL_3(D20, GPIOJ3, I3C4SDA, SDA2, SSDA2); + + GROUP_DECL(HVI3C4, E19, D20); + FUNC_GROUP_DECL(I2C2, E19, D20); ++FUNC_GROUP_DECL(SI2C2, E19, D20); + + #define C19 76 + SIG_EXPR_LIST_DECL_SESG(C19, I3C5SCL, I3C5, SIG_DESC_SET(SCU418, 12)); + SIG_EXPR_LIST_DECL_SESG(C19, SCL3, I2C3, SIG_DESC_SET(SCU4B8, 12)); +-PIN_DECL_2(C19, GPIOJ4, I3C5SCL, SCL3); ++SIG_EXPR_LIST_DECL_SESG(C19, SSCL3, SI2C3, SIG_DESC_SET(SCU698, 12)); ++PIN_DECL_3(C19, GPIOJ4, I3C5SCL, SCL3, SSCL3); + + #define A19 77 + SIG_EXPR_LIST_DECL_SESG(A19, I3C5SDA, I3C5, SIG_DESC_SET(SCU418, 13)); + SIG_EXPR_LIST_DECL_SESG(A19, SDA3, I2C3, SIG_DESC_SET(SCU4B8, 13)); +-PIN_DECL_2(A19, GPIOJ5, I3C5SDA, SDA3); ++SIG_EXPR_LIST_DECL_SESG(A19, SSDA3, SI2C3, SIG_DESC_SET(SCU698, 13)); ++PIN_DECL_3(A19, GPIOJ5, I3C5SDA, SDA3, SSDA3); + + FUNC_GROUP_DECL(I3C5, C19, A19); + FUNC_GROUP_DECL(I2C3, C19, A19); ++FUNC_GROUP_DECL(SI2C3, C19, A19); + + #define C20 78 + SIG_EXPR_LIST_DECL_SESG(C20, I3C6SCL, I3C6, SIG_DESC_SET(SCU418, 14)); + SIG_EXPR_LIST_DECL_SESG(C20, SCL4, I2C4, SIG_DESC_SET(SCU4B8, 14)); +-PIN_DECL_2(C20, GPIOJ6, I3C6SCL, SCL4); ++SIG_EXPR_LIST_DECL_SESG(C20, SSCL4, SI2C4, SIG_DESC_SET(SCU698, 14)); ++PIN_DECL_3(C20, GPIOJ6, I3C6SCL, SCL4, SSCL4); + + #define D19 79 + SIG_EXPR_LIST_DECL_SESG(D19, I3C6SDA, I3C6, SIG_DESC_SET(SCU418, 15)); + SIG_EXPR_LIST_DECL_SESG(D19, SDA4, I2C4, SIG_DESC_SET(SCU4B8, 15)); +-PIN_DECL_2(D19, GPIOJ7, I3C6SDA, SDA4); ++SIG_EXPR_LIST_DECL_SESG(D19, SSDA4, SI2C4, SIG_DESC_SET(SCU698, 15)); ++PIN_DECL_3(D19, GPIOJ7, I3C6SDA, SDA4, SSDA4); + + FUNC_GROUP_DECL(I3C6, C20, D19); + FUNC_GROUP_DECL(I2C4, C20, D19); ++FUNC_GROUP_DECL(SI2C4, C20, D19); + + #define A11 80 + SIG_EXPR_LIST_DECL_SESG(A11, SCL5, I2C5, SIG_DESC_SET(SCU418, 16)); +-PIN_DECL_1(A11, GPIOK0, SCL5); ++SIG_EXPR_LIST_DECL_SESG(A11, SSCL5, SI2C5, SIG_DESC_SET(SCU4B8, 16)); ++PIN_DECL_2(A11, GPIOK0, SCL5, SSCL5); + + #define C11 81 + SIG_EXPR_LIST_DECL_SESG(C11, SDA5, I2C5, SIG_DESC_SET(SCU418, 17)); +-PIN_DECL_1(C11, GPIOK1, SDA5); ++SIG_EXPR_LIST_DECL_SESG(C11, SSDA5, SI2C5, SIG_DESC_SET(SCU4B8, 17)); ++PIN_DECL_2(C11, GPIOK1, SDA5, SSDA5); + + FUNC_GROUP_DECL(I2C5, A11, C11); ++FUNC_GROUP_DECL(SI2C5, A11, C11); + + #define D12 82 + SIG_EXPR_LIST_DECL_SESG(D12, SCL6, I2C6, SIG_DESC_SET(SCU418, 18)); +-PIN_DECL_1(D12, GPIOK2, SCL6); ++SIG_EXPR_LIST_DECL_SESG(D12, SSCL6, SI2C6, SIG_DESC_SET(SCU4B8, 18)); ++PIN_DECL_2(D12, GPIOK2, SCL6, SSCL6); + + #define E13 83 + SIG_EXPR_LIST_DECL_SESG(E13, SDA6, I2C6, SIG_DESC_SET(SCU418, 19)); +-PIN_DECL_1(E13, GPIOK3, SDA6); ++SIG_EXPR_LIST_DECL_SESG(E13, SSDA6, SI2C6, SIG_DESC_SET(SCU4B8, 19)); ++PIN_DECL_2(E13, GPIOK3, SDA6, SSDA6); + + FUNC_GROUP_DECL(I2C6, D12, E13); ++FUNC_GROUP_DECL(SI2C6, D12, E13); + + #define D11 84 + SIG_EXPR_LIST_DECL_SESG(D11, SCL7, I2C7, SIG_DESC_SET(SCU418, 20)); +-PIN_DECL_1(D11, GPIOK4, SCL7); ++SIG_EXPR_LIST_DECL_SESG(D11, SSCL7, SI2C7, SIG_DESC_SET(SCU4B8, 20)); ++PIN_DECL_2(D11, GPIOK4, SCL7, SSCL7); + + #define E11 85 + SIG_EXPR_LIST_DECL_SESG(E11, SDA7, I2C7, SIG_DESC_SET(SCU418, 21)); +-PIN_DECL_1(E11, GPIOK5, SDA7); ++SIG_EXPR_LIST_DECL_SESG(E11, SSDA7, SI2C7, SIG_DESC_SET(SCU4B8, 21)); ++PIN_DECL_2(E11, GPIOK5, SDA7, SSDA7); + + FUNC_GROUP_DECL(I2C7, D11, E11); ++FUNC_GROUP_DECL(SI2C7, D11, E11); + + #define F13 86 + SIG_EXPR_LIST_DECL_SESG(F13, SCL8, I2C8, SIG_DESC_SET(SCU418, 22)); +-PIN_DECL_1(F13, GPIOK6, SCL8); ++SIG_EXPR_LIST_DECL_SESG(F13, SSCL8, SI2C8, SIG_DESC_SET(SCU4B8, 22)); ++PIN_DECL_2(F13, GPIOK6, SCL8, SSCL8); + + #define E12 87 + SIG_EXPR_LIST_DECL_SESG(E12, SDA8, I2C8, SIG_DESC_SET(SCU418, 23)); +-PIN_DECL_1(E12, GPIOK7, SDA8); ++SIG_EXPR_LIST_DECL_SESG(E12, SSDA8, SI2C8, SIG_DESC_SET(SCU4B8, 23)); ++PIN_DECL_2(E12, GPIOK7, SDA8, SSDA8); + + FUNC_GROUP_DECL(I2C8, F13, E12); ++FUNC_GROUP_DECL(SI2C8, F13, E12); + + #define D15 88 + SIG_EXPR_LIST_DECL_SESG(D15, SCL9, I2C9, SIG_DESC_SET(SCU418, 24)); +-PIN_DECL_1(D15, GPIOL0, SCL9); ++SIG_EXPR_LIST_DECL_SESG(D15, SSCL9, SI2C9, SIG_DESC_SET(SCU4B8, 24)); ++PIN_DECL_2(D15, GPIOL0, SCL9, SSCL9); + + #define A14 89 + SIG_EXPR_LIST_DECL_SESG(A14, SDA9, I2C9, SIG_DESC_SET(SCU418, 25)); +-PIN_DECL_1(A14, GPIOL1, SDA9); ++SIG_EXPR_LIST_DECL_SESG(A14, SSDA9, SI2C9, SIG_DESC_SET(SCU4B8, 25)); ++PIN_DECL_2(A14, GPIOL1, SDA9, SSDA9); + + FUNC_GROUP_DECL(I2C9, D15, A14); ++FUNC_GROUP_DECL(SI2C9, D15, A14); + + #define E15 90 + SIG_EXPR_LIST_DECL_SESG(E15, SCL10, I2C10, SIG_DESC_SET(SCU418, 26)); +-PIN_DECL_1(E15, GPIOL2, SCL10); ++SIG_EXPR_LIST_DECL_SESG(E15, SSCL10, SI2C10, SIG_DESC_SET(SCU4B8, 26)); ++PIN_DECL_2(E15, GPIOL2, SCL10, SSCL10); + + #define A13 91 + SIG_EXPR_LIST_DECL_SESG(A13, SDA10, I2C10, SIG_DESC_SET(SCU418, 27)); +-PIN_DECL_1(A13, GPIOL3, SDA10); ++SIG_EXPR_LIST_DECL_SESG(A13, SSDA10, SI2C10, SIG_DESC_SET(SCU4B8, 27)); ++PIN_DECL_2(A13, GPIOL3, SDA10, SSDA10); + + FUNC_GROUP_DECL(I2C10, E15, A13); ++FUNC_GROUP_DECL(SI2C10, E15, A13); + + #define C15 92 + SSSF_PIN_DECL(C15, GPIOL4, TXD3, SIG_DESC_SET(SCU418, 28)); +@@ -987,9 +1083,8 @@ + + #define AB16 160 + SIG_EXPR_LIST_DECL_SEMG(AB16, SALT9, SALT9G1, SALT9, SIG_DESC_SET(SCU434, 0), +- SIG_DESC_CLEAR(SCU694, 16)); +-SIG_EXPR_LIST_DECL_SESG(AB16, GPIU0, GPIU0, SIG_DESC_SET(SCU434, 0), +- SIG_DESC_SET(SCU694, 16)); ++ SIG_DESC_CLEAR(SCU694, 16), SIG_DESC_SET(SCU4D4, 0)); ++SIG_EXPR_LIST_DECL_SESG(AB16, GPIU0, GPIU0, SIG_DESC_SET(SCU434, 0)); + SIG_EXPR_LIST_DECL_SESG(AB16, ADC8, ADC8); + PIN_DECL_(AB16, SIG_EXPR_LIST_PTR(AB16, SALT9), SIG_EXPR_LIST_PTR(AB16, GPIU0), + SIG_EXPR_LIST_PTR(AB16, ADC8)); +@@ -1000,9 +1095,8 @@ + + #define AA17 161 + SIG_EXPR_LIST_DECL_SEMG(AA17, SALT10, SALT10G1, SALT10, SIG_DESC_SET(SCU434, 1), +- SIG_DESC_CLEAR(SCU694, 17)); +-SIG_EXPR_LIST_DECL_SESG(AA17, GPIU1, GPIU1, SIG_DESC_SET(SCU434, 1), +- SIG_DESC_SET(SCU694, 17)); ++ SIG_DESC_CLEAR(SCU694, 17), SIG_DESC_SET(SCU4D4, 1)); ++SIG_EXPR_LIST_DECL_SESG(AA17, GPIU1, GPIU1, SIG_DESC_SET(SCU434, 1)); + SIG_EXPR_LIST_DECL_SESG(AA17, ADC9, ADC9); + PIN_DECL_(AA17, SIG_EXPR_LIST_PTR(AA17, SALT10), SIG_EXPR_LIST_PTR(AA17, GPIU1), + SIG_EXPR_LIST_PTR(AA17, ADC9)); +@@ -1013,9 +1107,8 @@ + + #define AB17 162 + SIG_EXPR_LIST_DECL_SEMG(AB17, SALT11, SALT11G1, SALT11, SIG_DESC_SET(SCU434, 2), +- SIG_DESC_CLEAR(SCU694, 18)); +-SIG_EXPR_LIST_DECL_SESG(AB17, GPIU2, GPIU2, SIG_DESC_SET(SCU434, 2), +- SIG_DESC_SET(SCU694, 18)); ++ SIG_DESC_CLEAR(SCU694, 18), SIG_DESC_SET(SCU4D4, 2)); ++SIG_EXPR_LIST_DECL_SESG(AB17, GPIU2, GPIU2, SIG_DESC_SET(SCU434, 2)); + SIG_EXPR_LIST_DECL_SESG(AB17, ADC10, ADC10); + PIN_DECL_(AB17, SIG_EXPR_LIST_PTR(AB17, SALT11), SIG_EXPR_LIST_PTR(AB17, GPIU2), + SIG_EXPR_LIST_PTR(AB17, ADC10)); +@@ -1026,9 +1119,8 @@ + + #define AE16 163 + SIG_EXPR_LIST_DECL_SEMG(AE16, SALT12, SALT12G1, SALT12, SIG_DESC_SET(SCU434, 3), +- SIG_DESC_CLEAR(SCU694, 19)); +-SIG_EXPR_LIST_DECL_SESG(AE16, GPIU3, GPIU3, SIG_DESC_SET(SCU434, 3), +- SIG_DESC_SET(SCU694, 19)); ++ SIG_DESC_CLEAR(SCU694, 19), SIG_DESC_SET(SCU4D4, 3)); ++SIG_EXPR_LIST_DECL_SESG(AE16, GPIU3, GPIU3, SIG_DESC_SET(SCU434, 3)); + SIG_EXPR_LIST_DECL_SESG(AE16, ADC11, ADC11); + PIN_DECL_(AE16, SIG_EXPR_LIST_PTR(AE16, SALT12), SIG_EXPR_LIST_PTR(AE16, GPIU3), + SIG_EXPR_LIST_PTR(AE16, ADC11)); +@@ -1039,9 +1131,8 @@ + + #define AC16 164 + SIG_EXPR_LIST_DECL_SEMG(AC16, SALT13, SALT13G1, SALT13, SIG_DESC_SET(SCU434, 4), +- SIG_DESC_CLEAR(SCU694, 20)); +-SIG_EXPR_LIST_DECL_SESG(AC16, GPIU4, GPIU4, SIG_DESC_SET(SCU434, 4), +- SIG_DESC_SET(SCU694, 20)); ++ SIG_DESC_CLEAR(SCU694, 20), SIG_DESC_SET(SCU4D4, 4)); ++SIG_EXPR_LIST_DECL_SESG(AC16, GPIU4, GPIU4, SIG_DESC_SET(SCU434, 4)); + SIG_EXPR_LIST_DECL_SESG(AC16, ADC12, ADC12); + PIN_DECL_(AC16, SIG_EXPR_LIST_PTR(AC16, SALT13), SIG_EXPR_LIST_PTR(AC16, GPIU4), + SIG_EXPR_LIST_PTR(AC16, ADC12)); +@@ -1052,9 +1143,8 @@ + + #define AA16 165 + SIG_EXPR_LIST_DECL_SEMG(AA16, SALT14, SALT14G1, SALT14, SIG_DESC_SET(SCU434, 5), +- SIG_DESC_CLEAR(SCU694, 21)); +-SIG_EXPR_LIST_DECL_SESG(AA16, GPIU5, GPIU5, SIG_DESC_SET(SCU434, 5), +- SIG_DESC_SET(SCU694, 21)); ++ SIG_DESC_CLEAR(SCU694, 21), SIG_DESC_SET(SCU4D4, 5)); ++SIG_EXPR_LIST_DECL_SESG(AA16, GPIU5, GPIU5, SIG_DESC_SET(SCU434, 5)); + SIG_EXPR_LIST_DECL_SESG(AA16, ADC13, ADC13); + PIN_DECL_(AA16, SIG_EXPR_LIST_PTR(AA16, SALT14), SIG_EXPR_LIST_PTR(AA16, GPIU5), + SIG_EXPR_LIST_PTR(AA16, ADC13)); +@@ -1065,9 +1155,8 @@ + + #define AD16 166 + SIG_EXPR_LIST_DECL_SEMG(AD16, SALT15, SALT15G1, SALT15, SIG_DESC_SET(SCU434, 6), +- SIG_DESC_CLEAR(SCU694, 22)); +-SIG_EXPR_LIST_DECL_SESG(AD16, GPIU6, GPIU6, SIG_DESC_SET(SCU434, 6), +- SIG_DESC_SET(SCU694, 22)); ++ SIG_DESC_CLEAR(SCU694, 22), SIG_DESC_SET(SCU4D4, 6)); ++SIG_EXPR_LIST_DECL_SESG(AD16, GPIU6, GPIU6, SIG_DESC_SET(SCU434, 6)); + SIG_EXPR_LIST_DECL_SESG(AD16, ADC14, ADC14); + PIN_DECL_(AD16, SIG_EXPR_LIST_PTR(AD16, SALT15), SIG_EXPR_LIST_PTR(AD16, GPIU6), + SIG_EXPR_LIST_PTR(AD16, ADC14)); +@@ -1078,9 +1167,8 @@ + + #define AC17 167 + SIG_EXPR_LIST_DECL_SEMG(AC17, SALT16, SALT16G1, SALT16, SIG_DESC_SET(SCU434, 7), +- SIG_DESC_CLEAR(SCU694, 23)); +-SIG_EXPR_LIST_DECL_SESG(AC17, GPIU7, GPIU7, SIG_DESC_SET(SCU434, 7), +- SIG_DESC_SET(SCU694, 23)); ++ SIG_DESC_CLEAR(SCU694, 23), SIG_DESC_SET(SCU4D4, 7)); ++SIG_EXPR_LIST_DECL_SESG(AC17, GPIU7, GPIU7, SIG_DESC_SET(SCU434, 7)); + SIG_EXPR_LIST_DECL_SESG(AC17, ADC15, ADC15); + PIN_DECL_(AC17, SIG_EXPR_LIST_PTR(AC17, SALT16), SIG_EXPR_LIST_PTR(AC17, GPIU7), + SIG_EXPR_LIST_PTR(AC17, ADC15)); +@@ -1205,7 +1293,7 @@ + SIG_DESC_SET(SCU4D4, 31)); + PIN_DECL_2(AB10, GPIOX7, SPI2DQ3, RXD12); + +-GROUP_DECL(QSPI2, AE8, AF8, AB9, AD9, AF9, AB10); ++GROUP_DECL(QSPI2, AF9, AB10); + FUNC_DECL_2(SPI2, SPI2, QSPI2); + + GROUP_DECL(UART12G1, AF9, AB10); +@@ -1240,15 +1328,21 @@ + FUNC_GROUP_DECL(WDTRST4, AA12); + + #define AE12 196 ++SIG_EXPR_LIST_DECL_SEMG(AE12, FWSPIDQ2, FWQSPID, FWSPID, ++ SIG_DESC_SET(SCU438, 4)); + SIG_EXPR_LIST_DECL_SESG(AE12, FWSPIQ2, FWQSPI, SIG_DESC_SET(SCU438, 4)); + SIG_EXPR_LIST_DECL_SESG(AE12, GPIOY4, GPIOY4); +-PIN_DECL_(AE12, SIG_EXPR_LIST_PTR(AE12, FWSPIQ2), ++PIN_DECL_(AE12, SIG_EXPR_LIST_PTR(AE12, FWSPIDQ2), ++ SIG_EXPR_LIST_PTR(AE12, FWSPIQ2), + SIG_EXPR_LIST_PTR(AE12, GPIOY4)); + + #define AF12 197 ++SIG_EXPR_LIST_DECL_SEMG(AF12, FWSPIDQ3, FWQSPID, FWSPID, ++ SIG_DESC_SET(SCU438, 5)); + SIG_EXPR_LIST_DECL_SESG(AF12, FWSPIQ3, FWQSPI, SIG_DESC_SET(SCU438, 5)); + SIG_EXPR_LIST_DECL_SESG(AF12, GPIOY5, GPIOY5); +-PIN_DECL_(AF12, SIG_EXPR_LIST_PTR(AF12, FWSPIQ3), ++PIN_DECL_(AF12, SIG_EXPR_LIST_PTR(AF12, FWSPIDQ3), ++ SIG_EXPR_LIST_PTR(AF12, FWSPIQ3), + SIG_EXPR_LIST_PTR(AF12, GPIOY5)); + FUNC_GROUP_DECL(FWQSPI, AE12, AF12); + +@@ -1293,7 +1387,7 @@ + SIG_DESC_CLEAR(SCU4B8, 3), SIG_DESC_SET(SCU4D8, 15)); + PIN_DECL_2(AF10, GPIOZ7, SPI1DQ3, RXD13); + +-GROUP_DECL(QSPI1, AB11, AC11, AA11, AD11, AF10); ++GROUP_DECL(QSPI1, AD11, AF10); + FUNC_DECL_2(SPI1, SPI1, QSPI1); + + GROUP_DECL(UART13G1, AD11, AF10); +@@ -1301,80 +1395,80 @@ + + #define C6 208 + SIG_EXPR_LIST_DECL_SESG(C6, RGMII1TXCK, RGMII1, SIG_DESC_SET(SCU400, 0), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(C6, RMII1RCLKO, RMII1, SIG_DESC_SET(SCU400, 0), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(C6, GPIO18A0, RGMII1TXCK, RMII1RCLKO); + + #define D6 209 + SIG_EXPR_LIST_DECL_SESG(D6, RGMII1TXCTL, RGMII1, SIG_DESC_SET(SCU400, 1), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(D6, RMII1TXEN, RMII1, SIG_DESC_SET(SCU400, 1), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(D6, GPIO18A1, RGMII1TXCTL, RMII1TXEN); + + #define D5 210 + SIG_EXPR_LIST_DECL_SESG(D5, RGMII1TXD0, RGMII1, SIG_DESC_SET(SCU400, 2), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(D5, RMII1TXD0, RMII1, SIG_DESC_SET(SCU400, 2), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(D5, GPIO18A2, RGMII1TXD0, RMII1TXD0); + + #define A3 211 + SIG_EXPR_LIST_DECL_SESG(A3, RGMII1TXD1, RGMII1, SIG_DESC_SET(SCU400, 3), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(A3, RMII1TXD1, RMII1, SIG_DESC_SET(SCU400, 3), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(A3, GPIO18A3, RGMII1TXD1, RMII1TXD1); + + #define C5 212 + SIG_EXPR_LIST_DECL_SESG(C5, RGMII1TXD2, RGMII1, SIG_DESC_SET(SCU400, 4), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + PIN_DECL_1(C5, GPIO18A4, RGMII1TXD2); + + #define E6 213 + SIG_EXPR_LIST_DECL_SESG(E6, RGMII1TXD3, RGMII1, SIG_DESC_SET(SCU400, 5), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + PIN_DECL_1(E6, GPIO18A5, RGMII1TXD3); + + #define B3 214 + SIG_EXPR_LIST_DECL_SESG(B3, RGMII1RXCK, RGMII1, SIG_DESC_SET(SCU400, 6), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(B3, RMII1RCLKI, RMII1, SIG_DESC_SET(SCU400, 6), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(B3, GPIO18A6, RGMII1RXCK, RMII1RCLKI); + + #define A2 215 + SIG_EXPR_LIST_DECL_SESG(A2, RGMII1RXCTL, RGMII1, SIG_DESC_SET(SCU400, 7), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + PIN_DECL_1(A2, GPIO18A7, RGMII1RXCTL); + + #define B2 216 + SIG_EXPR_LIST_DECL_SESG(B2, RGMII1RXD0, RGMII1, SIG_DESC_SET(SCU400, 8), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(B2, RMII1RXD0, RMII1, SIG_DESC_SET(SCU400, 8), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(B2, GPIO18B0, RGMII1RXD0, RMII1RXD0); + + #define B1 217 + SIG_EXPR_LIST_DECL_SESG(B1, RGMII1RXD1, RGMII1, SIG_DESC_SET(SCU400, 9), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(B1, RMII1RXD1, RMII1, SIG_DESC_SET(SCU400, 9), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(B1, GPIO18B1, RGMII1RXD1, RMII1RXD1); + + #define C4 218 + SIG_EXPR_LIST_DECL_SESG(C4, RGMII1RXD2, RGMII1, SIG_DESC_SET(SCU400, 10), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(C4, RMII1CRSDV, RMII1, SIG_DESC_SET(SCU400, 10), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(C4, GPIO18B2, RGMII1RXD2, RMII1CRSDV); + + #define E5 219 + SIG_EXPR_LIST_DECL_SESG(E5, RGMII1RXD3, RGMII1, SIG_DESC_SET(SCU400, 11), +- SIG_DESC_SET(SCU500, 6)); ++ SIG_DESC_SET(SCU500, 6)); + SIG_EXPR_LIST_DECL_SESG(E5, RMII1RXER, RMII1, SIG_DESC_SET(SCU400, 11), +- SIG_DESC_CLEAR(SCU500, 6)); ++ SIG_DESC_CLEAR(SCU500, 6)); + PIN_DECL_2(E5, GPIO18B3, RGMII1RXD3, RMII1RXER); + + FUNC_GROUP_DECL(RGMII1, C6, D6, D5, A3, C5, E6, B3, A2, B2, B1, C4, E5); +@@ -1382,80 +1476,80 @@ + + #define D4 220 + SIG_EXPR_LIST_DECL_SESG(D4, RGMII2TXCK, RGMII2, SIG_DESC_SET(SCU400, 12), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(D4, RMII2RCLKO, RMII2, SIG_DESC_SET(SCU400, 12), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(D4, GPIO18B4, RGMII2TXCK, RMII2RCLKO); + + #define C2 221 + SIG_EXPR_LIST_DECL_SESG(C2, RGMII2TXCTL, RGMII2, SIG_DESC_SET(SCU400, 13), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(C2, RMII2TXEN, RMII2, SIG_DESC_SET(SCU400, 13), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(C2, GPIO18B5, RGMII2TXCTL, RMII2TXEN); + + #define C1 222 + SIG_EXPR_LIST_DECL_SESG(C1, RGMII2TXD0, RGMII2, SIG_DESC_SET(SCU400, 14), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(C1, RMII2TXD0, RMII2, SIG_DESC_SET(SCU400, 14), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(C1, GPIO18B6, RGMII2TXD0, RMII2TXD0); + + #define D3 223 + SIG_EXPR_LIST_DECL_SESG(D3, RGMII2TXD1, RGMII2, SIG_DESC_SET(SCU400, 15), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(D3, RMII2TXD1, RMII2, SIG_DESC_SET(SCU400, 15), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(D3, GPIO18B7, RGMII2TXD1, RMII2TXD1); + + #define E4 224 + SIG_EXPR_LIST_DECL_SESG(E4, RGMII2TXD2, RGMII2, SIG_DESC_SET(SCU400, 16), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + PIN_DECL_1(E4, GPIO18C0, RGMII2TXD2); + + #define F5 225 + SIG_EXPR_LIST_DECL_SESG(F5, RGMII2TXD3, RGMII2, SIG_DESC_SET(SCU400, 17), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + PIN_DECL_1(F5, GPIO18C1, RGMII2TXD3); + + #define D2 226 + SIG_EXPR_LIST_DECL_SESG(D2, RGMII2RXCK, RGMII2, SIG_DESC_SET(SCU400, 18), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(D2, RMII2RCLKI, RMII2, SIG_DESC_SET(SCU400, 18), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(D2, GPIO18C2, RGMII2RXCK, RMII2RCLKI); + + #define E3 227 + SIG_EXPR_LIST_DECL_SESG(E3, RGMII2RXCTL, RGMII2, SIG_DESC_SET(SCU400, 19), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + PIN_DECL_1(E3, GPIO18C3, RGMII2RXCTL); + + #define D1 228 + SIG_EXPR_LIST_DECL_SESG(D1, RGMII2RXD0, RGMII2, SIG_DESC_SET(SCU400, 20), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(D1, RMII2RXD0, RMII2, SIG_DESC_SET(SCU400, 20), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(D1, GPIO18C4, RGMII2RXD0, RMII2RXD0); + + #define F4 229 + SIG_EXPR_LIST_DECL_SESG(F4, RGMII2RXD1, RGMII2, SIG_DESC_SET(SCU400, 21), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(F4, RMII2RXD1, RMII2, SIG_DESC_SET(SCU400, 21), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(F4, GPIO18C5, RGMII2RXD1, RMII2RXD1); + + #define E2 230 + SIG_EXPR_LIST_DECL_SESG(E2, RGMII2RXD2, RGMII2, SIG_DESC_SET(SCU400, 22), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(E2, RMII2CRSDV, RMII2, SIG_DESC_SET(SCU400, 22), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(E2, GPIO18C6, RGMII2RXD2, RMII2CRSDV); + + #define E1 231 + SIG_EXPR_LIST_DECL_SESG(E1, RGMII2RXD3, RGMII2, SIG_DESC_SET(SCU400, 23), +- SIG_DESC_SET(SCU500, 7)); ++ SIG_DESC_SET(SCU500, 7)); + SIG_EXPR_LIST_DECL_SESG(E1, RMII2RXER, RMII2, SIG_DESC_SET(SCU400, 23), +- SIG_DESC_CLEAR(SCU500, 7)); ++ SIG_DESC_CLEAR(SCU500, 7)); + PIN_DECL_2(E1, GPIO18C7, RGMII2RXD3, RMII2RXER); + + FUNC_GROUP_DECL(RGMII2, D4, C2, C1, D3, E4, F5, D2, E3, D1, F4, E2, E1); +@@ -1523,8 +1617,9 @@ + PIN_DECL_3(Y4, GPIO18E3, FWSPIDMISO, VBMISO, EMMCDAT7); + + GROUP_DECL(FWSPID, Y1, Y2, Y3, Y4); ++GROUP_DECL(FWQSPID, Y1, Y2, Y3, Y4, AE12, AF12); + GROUP_DECL(EMMCG8, AB4, AA4, AC4, AA5, Y5, AB5, AB6, AC5, Y1, Y2, Y3, Y4); +-FUNC_DECL_1(FWSPID, FWSPID); ++FUNC_DECL_2(FWSPID, FWSPID, FWQSPID); + FUNC_GROUP_DECL(VB, Y1, Y2, Y3, Y4); + FUNC_DECL_3(EMMC, EMMCG1, EMMCG4, EMMCG8); + /* +@@ -1532,13 +1627,15 @@ + * following 4 pins + */ + #define AF25 244 +-SIG_EXPR_LIST_DECL_SEMG(AF25, I3C3SCL, I3C3, I3C3, SIG_DESC_SET(SCU438, 20)); ++SIG_EXPR_LIST_DECL_SEMG(AF25, I3C3SCL, I3C3, I3C3, SIG_DESC_SET(SCU438, 20), ++ SIG_DESC_CLEAR(SCU418, 8)); + SIG_EXPR_LIST_DECL_SESG(AF25, FSI1CLK, FSI1, SIG_DESC_SET(SCU4D8, 20)); + PIN_DECL_(AF25, SIG_EXPR_LIST_PTR(AF25, I3C3SCL), + SIG_EXPR_LIST_PTR(AF25, FSI1CLK)); + + #define AE26 245 +-SIG_EXPR_LIST_DECL_SEMG(AE26, I3C3SDA, I3C3, I3C3, SIG_DESC_SET(SCU438, 21)); ++SIG_EXPR_LIST_DECL_SEMG(AE26, I3C3SDA, I3C3, I3C3, SIG_DESC_SET(SCU438, 21), ++ SIG_DESC_CLEAR(SCU418, 9)); + SIG_EXPR_LIST_DECL_SESG(AE26, FSI1DATA, FSI1, SIG_DESC_SET(SCU4D8, 21)); + PIN_DECL_(AE26, SIG_EXPR_LIST_PTR(AE26, I3C3SDA), + SIG_EXPR_LIST_PTR(AE26, FSI1DATA)); +@@ -1548,13 +1645,15 @@ + FUNC_GROUP_DECL(FSI1, AF25, AE26); + + #define AE25 246 +-SIG_EXPR_LIST_DECL_SEMG(AE25, I3C4SCL, I3C4, I3C4, SIG_DESC_SET(SCU438, 22)); ++SIG_EXPR_LIST_DECL_SEMG(AE25, I3C4SCL, I3C4, I3C4, SIG_DESC_SET(SCU438, 22), ++ SIG_DESC_CLEAR(SCU418, 10)); + SIG_EXPR_LIST_DECL_SESG(AE25, FSI2CLK, FSI2, SIG_DESC_SET(SCU4D8, 22)); + PIN_DECL_(AE25, SIG_EXPR_LIST_PTR(AE25, I3C4SCL), + SIG_EXPR_LIST_PTR(AE25, FSI2CLK)); + + #define AF24 247 +-SIG_EXPR_LIST_DECL_SEMG(AF24, I3C4SDA, I3C4, I3C4, SIG_DESC_SET(SCU438, 23)); ++SIG_EXPR_LIST_DECL_SEMG(AF24, I3C4SDA, I3C4, I3C4, SIG_DESC_SET(SCU438, 23), ++ SIG_DESC_CLEAR(SCU418, 11)); + SIG_EXPR_LIST_DECL_SESG(AF24, FSI2DATA, FSI2, SIG_DESC_SET(SCU4D8, 23)); + PIN_DECL_(AF24, SIG_EXPR_LIST_PTR(AF24, I3C4SDA), + SIG_EXPR_LIST_PTR(AF24, FSI2DATA)); +@@ -1636,6 +1735,23 @@ + FUNC_DECL_1(USB2BD, USBB); + FUNC_DECL_1(USB2BH, USBB); + ++/* bit19: Enable RC-L DMA mode ++ * bit23: Enable RC-L DMA decode ++ */ ++#define PCIERC0_DESC { ASPEED_IP_SCU, SCUC24, GENMASK(23, 19), 0x1f, 0 } ++ ++#define A7 256 ++SIG_EXPR_LIST_DECL_SESG(A7, PERST, PCIERC0, SIG_DESC_SET(SCU040, 21), ++ SIG_DESC_CLEAR(SCU0C8, 6), PCIERC0_DESC); ++PIN_DECL_(A7, SIG_EXPR_LIST_PTR(A7, PERST)); ++FUNC_GROUP_DECL(PCIERC0, A7); ++ ++#define D7 257 ++SIG_EXPR_LIST_DECL_SESG(D7, RCRST, PCIERC1, SIG_DESC_SET(SCU040, 19), ++ SIG_DESC_SET(SCU500, 24)); ++PIN_DECL_(D7, SIG_EXPR_LIST_PTR(D7, RCRST)); ++FUNC_GROUP_DECL(PCIERC1, D7); ++ + /* Pins, groups and functions are sort(1):ed alphabetically for sanity */ + + static struct pinctrl_pin_desc aspeed_g6_pins[ASPEED_G6_NR_PINS] = { +@@ -1658,6 +1774,7 @@ + ASPEED_PINCTRL_PIN(A3), + ASPEED_PINCTRL_PIN(A4), + ASPEED_PINCTRL_PIN(A6), ++ ASPEED_PINCTRL_PIN(A7), + ASPEED_PINCTRL_PIN(AA11), + ASPEED_PINCTRL_PIN(AA12), + ASPEED_PINCTRL_PIN(AA16), +@@ -1806,6 +1923,7 @@ + ASPEED_PINCTRL_PIN(D4), + ASPEED_PINCTRL_PIN(D5), + ASPEED_PINCTRL_PIN(D6), ++ ASPEED_PINCTRL_PIN(D7), + ASPEED_PINCTRL_PIN(E1), + ASPEED_PINCTRL_PIN(E11), + ASPEED_PINCTRL_PIN(E12), +@@ -1921,6 +2039,7 @@ + ASPEED_PINCTRL_GROUP(FSI2), + ASPEED_PINCTRL_GROUP(FWSPIABR), + ASPEED_PINCTRL_GROUP(FWSPID), ++ ASPEED_PINCTRL_GROUP(FWQSPID), + ASPEED_PINCTRL_GROUP(FWQSPI), + ASPEED_PINCTRL_GROUP(FWSPIWP), + ASPEED_PINCTRL_GROUP(GPIT0), +@@ -1958,6 +2077,16 @@ + ASPEED_PINCTRL_GROUP(I2C7), + ASPEED_PINCTRL_GROUP(I2C8), + ASPEED_PINCTRL_GROUP(I2C9), ++ ASPEED_PINCTRL_GROUP(SI2C1), ++ ASPEED_PINCTRL_GROUP(SI2C2), ++ ASPEED_PINCTRL_GROUP(SI2C3), ++ ASPEED_PINCTRL_GROUP(SI2C4), ++ ASPEED_PINCTRL_GROUP(SI2C5), ++ ASPEED_PINCTRL_GROUP(SI2C6), ++ ASPEED_PINCTRL_GROUP(SI2C7), ++ ASPEED_PINCTRL_GROUP(SI2C8), ++ ASPEED_PINCTRL_GROUP(SI2C9), ++ ASPEED_PINCTRL_GROUP(SI2C10), + ASPEED_PINCTRL_GROUP(I3C1), + ASPEED_PINCTRL_GROUP(I3C2), + ASPEED_PINCTRL_GROUP(I3C3), +@@ -2073,6 +2202,8 @@ + ASPEED_PINCTRL_GROUP(SALT9G1), + ASPEED_PINCTRL_GROUP(SD1), + ASPEED_PINCTRL_GROUP(SD2), ++ ASPEED_PINCTRL_GROUP(PCIERC0), ++ ASPEED_PINCTRL_GROUP(PCIERC1), + ASPEED_PINCTRL_GROUP(EMMCG1), + ASPEED_PINCTRL_GROUP(EMMCG4), + ASPEED_PINCTRL_GROUP(EMMCG8), +@@ -2132,6 +2263,7 @@ + ASPEED_PINCTRL_GROUP(USBA), + ASPEED_PINCTRL_GROUP(USBB), + ASPEED_PINCTRL_GROUP(VB), ++ ASPEED_PINCTRL_GROUP(VPA), + ASPEED_PINCTRL_GROUP(VGAHS), + ASPEED_PINCTRL_GROUP(VGAVS), + ASPEED_PINCTRL_GROUP(WDTRST1), +@@ -2200,6 +2332,16 @@ + ASPEED_PINCTRL_FUNC(I2C7), + ASPEED_PINCTRL_FUNC(I2C8), + ASPEED_PINCTRL_FUNC(I2C9), ++ ASPEED_PINCTRL_FUNC(SI2C1), ++ ASPEED_PINCTRL_FUNC(SI2C2), ++ ASPEED_PINCTRL_FUNC(SI2C3), ++ ASPEED_PINCTRL_FUNC(SI2C4), ++ ASPEED_PINCTRL_FUNC(SI2C5), ++ ASPEED_PINCTRL_FUNC(SI2C6), ++ ASPEED_PINCTRL_FUNC(SI2C7), ++ ASPEED_PINCTRL_FUNC(SI2C8), ++ ASPEED_PINCTRL_FUNC(SI2C9), ++ ASPEED_PINCTRL_FUNC(SI2C10), + ASPEED_PINCTRL_FUNC(I3C1), + ASPEED_PINCTRL_FUNC(I3C2), + ASPEED_PINCTRL_FUNC(I3C3), +@@ -2314,6 +2456,8 @@ + ASPEED_PINCTRL_FUNC(SPI2), + ASPEED_PINCTRL_FUNC(SPI2CS1), + ASPEED_PINCTRL_FUNC(SPI2CS2), ++ ASPEED_PINCTRL_FUNC(PCIERC0), ++ ASPEED_PINCTRL_FUNC(PCIERC1), + ASPEED_PINCTRL_FUNC(TACH0), + ASPEED_PINCTRL_FUNC(TACH1), + ASPEED_PINCTRL_FUNC(TACH10), +@@ -2354,6 +2498,7 @@ + ASPEED_PINCTRL_FUNC(USB2BD), + ASPEED_PINCTRL_FUNC(USB2BH), + ASPEED_PINCTRL_FUNC(VB), ++ ASPEED_PINCTRL_FUNC(VPA), + ASPEED_PINCTRL_FUNC(VGAHS), + ASPEED_PINCTRL_FUNC(VGAVS), + ASPEED_PINCTRL_FUNC(WDTRST1), +@@ -2607,6 +2752,10 @@ + { PIN_CONFIG_DRIVE_STRENGTH, { AB8, AB8 }, SCU454, GENMASK(27, 26)}, + /* LAD0 */ + { PIN_CONFIG_DRIVE_STRENGTH, { AB7, AB7 }, SCU454, GENMASK(25, 24)}, ++ /* GPIOF */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { D22, A23 }, SCU458, GENMASK(9, 8)}, ++ /* GPIOG */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { E21, B21 }, SCU458, GENMASK(11, 10)}, + + /* MAC3 */ + { PIN_CONFIG_POWER_SOURCE, { H24, E26 }, SCU458, BIT_MASK(4)}, +@@ -2615,6 +2764,11 @@ + { PIN_CONFIG_POWER_SOURCE, { F24, B24 }, SCU458, BIT_MASK(5)}, + { PIN_CONFIG_DRIVE_STRENGTH, { F24, B24 }, SCU458, GENMASK(3, 2)}, + ++ /* GPIOJ */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { B20, A20 }, SCU650, BIT_MASK(12)}, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E19, D20 }, SCU650, BIT_MASK(13)}, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C19, A19 }, SCU650, BIT_MASK(14)}, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C20, D19 }, SCU650, BIT_MASK(15)}, + /* GPIO18E */ + ASPEED_SB_PINCONF(PIN_CONFIG_BIAS_PULL_DOWN, Y1, Y4, SCU40C, 4), + ASPEED_SB_PINCONF(PIN_CONFIG_BIAS_DISABLE, Y1, Y4, SCU40C, 4), +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-ltpi.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-ltpi.c +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-ltpi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-ltpi.c 2026-09-09 15:51:44.732237860 +0000 +@@ -0,0 +1,1528 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pinctrl-aspeed.h" ++ ++#define SCU3B0 0x3B0 /* USB Controller Register */ ++#define SCU3B4 0x3B4 /* USB Controller Lock Register */ ++#define SCU3B8 0x3B8 /* USB Controller Secure Register #1 */ ++#define SCU3BC 0x3BC /* USB Controller Secure Register #2 */ ++#define SCU3C0 0x3C0 /* USB Controller Secure Register #3 */ ++#define SCU400 0x400 /* Multi-function Pin Control #1 */ ++#define SCU404 0x404 /* Multi-function Pin Control #2 */ ++#define SCU408 0x408 /* Multi-function Pin Control #3 */ ++#define SCU40C 0x40C /* Multi-function Pin Control #4 */ ++#define SCU410 0x410 /* Multi-function Pin Control #5 */ ++#define SCU414 0x414 /* Multi-function Pin Control #6 */ ++#define SCU418 0x418 /* Multi-function Pin Control #7 */ ++#define SCU41C 0x41C /* Multi-function Pin Control #8 */ ++#define SCU420 0x420 /* Multi-function Pin Control #9 */ ++#define SCU424 0x424 /* Multi-function Pin Control #10 */ ++#define SCU428 0x428 /* Multi-function Pin Control #11 */ ++#define SCU42C 0x42C /* Multi-function Pin Control #12 */ ++#define SCU430 0x430 /* Multi-function Pin Control #13 */ ++#define SCU434 0x434 /* Multi-function Pin Control #14 */ ++#define SCU438 0x438 /* Multi-function Pin Control #15 */ ++#define SCU43C 0x43C /* Multi-function Pin Control #16 */ ++#define SCU440 0x440 /* Multi-function Pin Control #17 */ ++#define SCU444 0x444 /* Multi-function Pin Control #18 */ ++#define SCU448 0x448 /* Multi-function Pin Control #19 */ ++#define SCU44C 0x44C /* Multi-function Pin Control #20 */ ++#define SCU450 0x450 /* Multi-function Pin Control #21 */ ++#define SCU454 0x454 /* Multi-function Pin Control #22 */ ++#define SCU458 0x458 /* Multi-function Pin Control #23 */ ++#define SCU45C 0x45C /* Multi-function Pin Control #24 */ ++#define SCU460 0x460 /* Multi-function Pin Control #25 */ ++#define SCU464 0x464 /* Multi-function Pin Control #26 */ ++#define SCU468 0x468 /* Multi-function Pin Control #27 */ ++#define SCU46C 0x46C /* Multi-function Pin Control #28 */ ++#define SCU470 0x470 /* Multi-function Pin Control #29 */ ++#define SCU474 0x474 /* Multi-function Pin Control #30 */ ++#define SCU478 0x478 /* Multi-function Pin Control #31 */ ++#define SCU47C 0x47C ++#define SCU480 0x480 /* Disable Pull-Down Control #1 */ ++#define SCU484 0x484 /* Disable Pull-Down Control #2 */ ++#define SCU488 0x488 /* Disable Pull-Down Control #3 */ ++#define SCU48C 0x48C /* Disable Pull-Down Control #4 */ ++#define SCU490 0x490 /* Disable Pull-Down Control #5 */ ++#define SCU494 0x494 /* Disable Pull-Down Control #6 */ ++#define SCU498 0x498 /* Disable Pull-Down Control #7 */ ++#define SCU4A0 0x4A0 /* Voltage Selection */ ++#define SCU4C0 0x4C0 /* Driving Strength #0 A-I */ ++#define SCU4C4 0x4C4 /* Driving Strength #1 J-K */ ++#define SCU4C8 0x4C8 /* Driving Strength #2 L-M */ ++#define SCU4CC 0x4CC /* Driving Strength #3 N-O */ ++#define SCU4D0 0x4D0 /* Driving Strength #4 P-Q */ ++#define SCU4D4 0x4D4 /* Driving Strength #5 R-S */ ++#define SCU4D8 0x4D8 /* Driving Strength #6 T-U */ ++#define SCU4DC 0x4DC /* Driving Strength #7 W */ ++ ++#define SCU908 0x908 /* PCIe RC PERST Pin Control */ ++ ++enum { ++ D6, ++ B7, ++ A7, ++ C6, ++ B6, ++ A6, ++ C5, ++ D5, ++ L15, ++ H17, ++ L16, ++ K15, ++ K16, ++ K14, ++ J14, ++ J16, ++ J15, ++ G17, ++ F17, ++ D17, ++ C17, ++ H16, ++ B17, ++ E17, ++ A16, ++ B16, ++ H14, ++ A15, ++ G16, ++ F15, ++ H15, ++ D16, ++ M2, ++ M1, ++ M3, ++ M4, ++ N1, ++ N2, ++ N3, ++ L5, ++ P2, ++ P1, ++ N4, ++ N5, ++ HOLE0, ++ P3, ++ R1, ++ P4, ++ J2, ++ J1, ++ K4, ++ K2, ++ K3, ++ K1, ++ L4, ++ L2, ++ L1, ++ L3, ++ A14, ++ B14, ++ HOLE1, ++ HOLE2, ++ HOLE3, ++ HOLE4, ++ U15, ++ U16, ++ T17, ++ P12, ++ R13, ++ T15, ++ T16, ++ R12, ++ R14, ++ R17, ++ R15, ++ P13, ++ R16, ++ P14, ++ P17, ++ P16, ++ P15, ++ N17, ++ N14, ++ M17, ++ N15, ++ N16, ++ L17, ++ K17, ++ J17, ++ M14, ++ M15, ++ M16, ++ A4, ++ B5, ++ A5, ++ B4, ++ A10, ++ B9, ++ C7, ++ E8, ++ E7, ++ B8, ++ A9, ++ A8, ++ HOLE5, ++ HOLE6, ++ HOLE7, ++ HOLE8, ++ HOLE9, ++ HOLE10, ++ HOLE11, ++ HOLE12, ++ E13, ++ C12, ++ D12, ++ E12, ++ D13, ++ HOLE13, ++ B12, ++ HOLE14, ++ D15, ++ B13, ++ C14, ++ C13, ++ D14, ++ F13, ++ E14, ++ HOLE15, ++ HOLE16, ++ HOLE17, ++ B1, ++ C2, ++ D3, ++ C1, ++ E5, ++ HOLE18, ++ HOLE19, ++ HOLE20, ++ HOLE21, ++ HOLE22, ++ HOLE23, ++ HOLE24, ++ HOLE25, ++ HOLE26, ++ C11, ++ D11, ++ HOLE27, ++ HOLE28, ++ C10, ++ D10, ++ HOLE29, ++ HOLE30, ++ HOLE31, ++ HOLE32, ++ D9, ++ E9, ++ C9, ++ D8, ++ C8, ++ B11, ++ B10, ++ A12, ++ A11, ++ D7, ++ HOLE33, ++ HOLE34, ++ HOLE35, ++ HOLE36, ++ G2, ++ G1, ++ G4, ++ H3, ++ H2, ++ H1, ++ J4, ++ J3, ++ C4, ++ A2, ++ C3, ++ B2, ++ B3, ++ A3, ++ F2, ++ F1, ++ D2, ++ D1, ++ F4, ++ E4, ++ E2, ++ E1, ++ F3, ++ G5, ++ T4, ++ T3, ++ R5, ++ R6, ++ T2, ++ T5, ++ U2, ++ R7, ++ P9, ++ U4, ++ U5, ++ U6, ++ T7, ++ U7, ++ R10, ++ R11, ++ C16, ++ F16, ++ B15, ++ G15, ++ G14, ++ F14, ++ E15, ++ C15, ++}; ++ ++GROUP_DECL(TACH0, L15); ++GROUP_DECL(TACH1, H17); ++GROUP_DECL(TACH2, L16); ++GROUP_DECL(TACH3, K15); ++GROUP_DECL(TACH4, K16); ++GROUP_DECL(TACH5, K14); ++GROUP_DECL(TACH6, J14); ++GROUP_DECL(TACH7, J16); ++GROUP_DECL(TACH8, J15); ++GROUP_DECL(TACH9, G17); ++GROUP_DECL(TACH10, F17); ++GROUP_DECL(TACH11, D17); ++GROUP_DECL(TACH12, C17); ++GROUP_DECL(TACH13, H16); ++GROUP_DECL(TACH14, B17); ++GROUP_DECL(TACH15, E17); ++GROUP_DECL(PWM0, A16); ++GROUP_DECL(PWM1, B16); ++GROUP_DECL(PWM2, H14); ++GROUP_DECL(PWM3, A15); ++GROUP_DECL(PWM4, G16); ++GROUP_DECL(PWM5, F15); ++GROUP_DECL(PWM6, H15); ++GROUP_DECL(PWM7, D16); ++GROUP_DECL(PWM8, K4); ++GROUP_DECL(PWM9, K2); ++GROUP_DECL(PWM10, K3); ++GROUP_DECL(PWM11, K1); ++GROUP_DECL(PWM12, L4); ++GROUP_DECL(PWM13, L2); ++GROUP_DECL(PWM14, L1); ++GROUP_DECL(PWM15, L3); ++GROUP_DECL(ADC0, T4); ++GROUP_DECL(ADC1, T3); ++GROUP_DECL(ADC2, R5); ++GROUP_DECL(ADC3, R6); ++GROUP_DECL(ADC4, T2); ++GROUP_DECL(ADC5, T5); ++GROUP_DECL(ADC6, U2); ++GROUP_DECL(ADC7, R7); ++GROUP_DECL(ADC8, P9); ++GROUP_DECL(ADC9, U4); ++GROUP_DECL(ADC10, U5); ++GROUP_DECL(ADC11, U6); ++GROUP_DECL(ADC12, T7); ++GROUP_DECL(ADC13, U7); ++GROUP_DECL(ADC14, R10); ++GROUP_DECL(ADC15, R11); ++GROUP_DECL(SGPM1, F14, E15, A14, B14); ++GROUP_DECL(I2C0, G2, G1); ++GROUP_DECL(I2C1, G4, H3); ++GROUP_DECL(I2C2, H2, H1); ++GROUP_DECL(I2C3, J4, J3); ++GROUP_DECL(I2C4, C4, A2); ++GROUP_DECL(I2C5, C3, B2); ++GROUP_DECL(I2C6, B3, A3); ++GROUP_DECL(I2C7, F2, F1); ++GROUP_DECL(I2C8, D2, D1); ++GROUP_DECL(I2C9, F4, E4); ++GROUP_DECL(I2C10, E2, E1); ++GROUP_DECL(I2C11, F3, G5); ++GROUP_DECL(I2C12, J1, K2); ++GROUP_DECL(I2C13, K3, K1); ++GROUP_DECL(I2C14, L4, L2); ++GROUP_DECL(I2C15, L1, L3); ++GROUP_DECL(HVI3C12, U15, U16); ++GROUP_DECL(HVI3C13, T17, P12); ++GROUP_DECL(HVI3C14, R13, T15); ++GROUP_DECL(HVI3C15, T16, R12); ++GROUP_DECL(I3C4, R14, R17); ++GROUP_DECL(I3C5, R15, P13); ++GROUP_DECL(I3C6, R16, P14); ++GROUP_DECL(I3C7, P17, P16); ++GROUP_DECL(I3C8, P15, N17); ++GROUP_DECL(I3C9, N14, M17); ++GROUP_DECL(I3C10, N15, N16); ++GROUP_DECL(I3C11, L17, K17); ++GROUP_DECL(HVI3C0, J17, M14); ++GROUP_DECL(HVI3C1, M15, M16); ++GROUP_DECL(HVI3C2, A4, B5); ++GROUP_DECL(HVI3C3, A5, B4); ++GROUP_DECL(JTAGM, C2, D3, C1, E5); ++ ++static struct aspeed_pin_group aspeed_g7_ltpi_pingroups[] = { ++ ASPEED_PINCTRL_GROUP(TACH0), ++ ASPEED_PINCTRL_GROUP(TACH1), ++ ASPEED_PINCTRL_GROUP(TACH2), ++ ASPEED_PINCTRL_GROUP(TACH3), ++ ASPEED_PINCTRL_GROUP(TACH4), ++ ASPEED_PINCTRL_GROUP(TACH5), ++ ASPEED_PINCTRL_GROUP(TACH6), ++ ASPEED_PINCTRL_GROUP(TACH7), ++ ASPEED_PINCTRL_GROUP(TACH8), ++ ASPEED_PINCTRL_GROUP(TACH9), ++ ASPEED_PINCTRL_GROUP(TACH10), ++ ASPEED_PINCTRL_GROUP(TACH11), ++ ASPEED_PINCTRL_GROUP(TACH12), ++ ASPEED_PINCTRL_GROUP(TACH13), ++ ASPEED_PINCTRL_GROUP(TACH14), ++ ASPEED_PINCTRL_GROUP(TACH15), ++ ASPEED_PINCTRL_GROUP(PWM0), ++ ASPEED_PINCTRL_GROUP(PWM1), ++ ASPEED_PINCTRL_GROUP(PWM2), ++ ASPEED_PINCTRL_GROUP(PWM3), ++ ASPEED_PINCTRL_GROUP(PWM4), ++ ASPEED_PINCTRL_GROUP(PWM5), ++ ASPEED_PINCTRL_GROUP(PWM6), ++ ASPEED_PINCTRL_GROUP(PWM7), ++ ASPEED_PINCTRL_GROUP(PWM8), ++ ASPEED_PINCTRL_GROUP(PWM9), ++ ASPEED_PINCTRL_GROUP(PWM10), ++ ASPEED_PINCTRL_GROUP(PWM11), ++ ASPEED_PINCTRL_GROUP(PWM12), ++ ASPEED_PINCTRL_GROUP(PWM13), ++ ASPEED_PINCTRL_GROUP(PWM14), ++ ASPEED_PINCTRL_GROUP(PWM15), ++ ASPEED_PINCTRL_GROUP(ADC0), ++ ASPEED_PINCTRL_GROUP(ADC1), ++ ASPEED_PINCTRL_GROUP(ADC2), ++ ASPEED_PINCTRL_GROUP(ADC3), ++ ASPEED_PINCTRL_GROUP(ADC4), ++ ASPEED_PINCTRL_GROUP(ADC5), ++ ASPEED_PINCTRL_GROUP(ADC6), ++ ASPEED_PINCTRL_GROUP(ADC7), ++ ASPEED_PINCTRL_GROUP(ADC8), ++ ASPEED_PINCTRL_GROUP(ADC9), ++ ASPEED_PINCTRL_GROUP(ADC10), ++ ASPEED_PINCTRL_GROUP(ADC11), ++ ASPEED_PINCTRL_GROUP(ADC12), ++ ASPEED_PINCTRL_GROUP(ADC13), ++ ASPEED_PINCTRL_GROUP(ADC14), ++ ASPEED_PINCTRL_GROUP(ADC15), ++ ASPEED_PINCTRL_GROUP(I2C0), ++ ASPEED_PINCTRL_GROUP(I2C1), ++ ASPEED_PINCTRL_GROUP(I2C2), ++ ASPEED_PINCTRL_GROUP(I2C3), ++ ASPEED_PINCTRL_GROUP(I2C4), ++ ASPEED_PINCTRL_GROUP(I2C5), ++ ASPEED_PINCTRL_GROUP(I2C6), ++ ASPEED_PINCTRL_GROUP(I2C7), ++ ASPEED_PINCTRL_GROUP(I2C8), ++ ASPEED_PINCTRL_GROUP(I2C9), ++ ASPEED_PINCTRL_GROUP(I2C10), ++ ASPEED_PINCTRL_GROUP(I2C11), ++ ASPEED_PINCTRL_GROUP(I2C12), ++ ASPEED_PINCTRL_GROUP(I2C13), ++ ASPEED_PINCTRL_GROUP(I2C14), ++ ASPEED_PINCTRL_GROUP(I2C15), ++ ASPEED_PINCTRL_GROUP(HVI3C12), ++ ASPEED_PINCTRL_GROUP(HVI3C13), ++ ASPEED_PINCTRL_GROUP(HVI3C14), ++ ASPEED_PINCTRL_GROUP(HVI3C15), ++ ASPEED_PINCTRL_GROUP(I3C4), ++ ASPEED_PINCTRL_GROUP(I3C5), ++ ASPEED_PINCTRL_GROUP(I3C6), ++ ASPEED_PINCTRL_GROUP(I3C7), ++ ASPEED_PINCTRL_GROUP(I3C8), ++ ASPEED_PINCTRL_GROUP(I3C9), ++ ASPEED_PINCTRL_GROUP(I3C10), ++ ASPEED_PINCTRL_GROUP(I3C11), ++ ASPEED_PINCTRL_GROUP(HVI3C0), ++ ASPEED_PINCTRL_GROUP(HVI3C1), ++ ASPEED_PINCTRL_GROUP(HVI3C2), ++ ASPEED_PINCTRL_GROUP(HVI3C3), ++ ASPEED_PINCTRL_GROUP(JTAGM), ++ ASPEED_PINCTRL_GROUP(SGPM1), ++}; ++ ++FUNC_DECL_(TACH0, "TACH0"); ++FUNC_DECL_(TACH1, "TACH1"); ++FUNC_DECL_(TACH2, "TACH2"); ++FUNC_DECL_(TACH3, "TACH3"); ++FUNC_DECL_(TACH4, "TACH4"); ++FUNC_DECL_(TACH5, "TACH5"); ++FUNC_DECL_(TACH6, "TACH6"); ++FUNC_DECL_(TACH7, "TACH7"); ++FUNC_DECL_(TACH8, "TACH8"); ++FUNC_DECL_(TACH9, "TACH9"); ++FUNC_DECL_(TACH10, "TACH10"); ++FUNC_DECL_(TACH11, "TACH11"); ++FUNC_DECL_(TACH12, "TACH12"); ++FUNC_DECL_(TACH13, "TACH13"); ++FUNC_DECL_(TACH14, "TACH14"); ++FUNC_DECL_(TACH15, "TACH15"); ++FUNC_DECL_(PWM0, "PWM0"); ++FUNC_DECL_(PWM1, "PWM1"); ++FUNC_DECL_(PWM2, "PWM2"); ++FUNC_DECL_(PWM3, "PWM3"); ++FUNC_DECL_(PWM4, "PWM4"); ++FUNC_DECL_(PWM5, "PWM5"); ++FUNC_DECL_(PWM6, "PWM6"); ++FUNC_DECL_(PWM7, "PWM7"); ++FUNC_DECL_(PWM8, "PWM8"); ++FUNC_DECL_(PWM9, "PWM9"); ++FUNC_DECL_(PWM10, "PWM10"); ++FUNC_DECL_(PWM11, "PWM11"); ++FUNC_DECL_(PWM12, "PWM12"); ++FUNC_DECL_(PWM13, "PWM13"); ++FUNC_DECL_(PWM14, "PWM14"); ++FUNC_DECL_(PWM15, "PWM15"); ++FUNC_DECL_(ADC0, "ADC0"); ++FUNC_DECL_(ADC1, "ADC1"); ++FUNC_DECL_(ADC2, "ADC2"); ++FUNC_DECL_(ADC3, "ADC3"); ++FUNC_DECL_(ADC4, "ADC4"); ++FUNC_DECL_(ADC5, "ADC5"); ++FUNC_DECL_(ADC6, "ADC6"); ++FUNC_DECL_(ADC7, "ADC7"); ++FUNC_DECL_(ADC8, "ADC8"); ++FUNC_DECL_(ADC9, "ADC9"); ++FUNC_DECL_(ADC10, "ADC10"); ++FUNC_DECL_(ADC11, "ADC11"); ++FUNC_DECL_(ADC12, "ADC12"); ++FUNC_DECL_(ADC13, "ADC13"); ++FUNC_DECL_(ADC14, "ADC14"); ++FUNC_DECL_(ADC15, "ADC15"); ++FUNC_DECL_(I2C0, "I2C0"); ++FUNC_DECL_(I2C1, "I2C1"); ++FUNC_DECL_(I2C2, "I2C2"); ++FUNC_DECL_(I2C3, "I2C3"); ++FUNC_DECL_(I2C4, "I2C4"); ++FUNC_DECL_(I2C5, "I2C5"); ++FUNC_DECL_(I2C6, "I2C6"); ++FUNC_DECL_(I2C7, "I2C7"); ++FUNC_DECL_(I2C8, "I2C8"); ++FUNC_DECL_(I2C9, "I2C9"); ++FUNC_DECL_(I2C10, "I2C10"); ++FUNC_DECL_(I2C11, "I2C11"); ++FUNC_DECL_(I2C12, "I2C12"); ++FUNC_DECL_(I2C13, "I2C13"); ++FUNC_DECL_(I2C14, "I2C14"); ++FUNC_DECL_(I2C15, "I2C15"); ++FUNC_DECL_(I3C12, "HVI3C12"); ++FUNC_DECL_(I3C13, "HVI3C13"); ++FUNC_DECL_(I3C14, "HVI3C14"); ++FUNC_DECL_(I3C15, "HVI3C15"); ++FUNC_DECL_(I3C4, "I3C4"); ++FUNC_DECL_(I3C5, "I3C5"); ++FUNC_DECL_(I3C6, "I3C6"); ++FUNC_DECL_(I3C7, "I3C7"); ++FUNC_DECL_(I3C8, "I3C8"); ++FUNC_DECL_(I3C9, "I3C9"); ++FUNC_DECL_(I3C10, "I3C10"); ++FUNC_DECL_(I3C11, "I3C11"); ++FUNC_DECL_(I3C0, "HVI3C0"); ++FUNC_DECL_(I3C1, "HVI3C1"); ++FUNC_DECL_(I3C2, "HVI3C2"); ++FUNC_DECL_(I3C3, "HVI3C3"); ++FUNC_DECL_(JTAGM, "JTAGM"); ++FUNC_DECL_(SGPM1, "SGPM1"); ++ ++static struct aspeed_pin_function aspeed_g7_ltpi_funcs[] = { ++ ASPEED_PINCTRL_FUNC(TACH0), ++ ASPEED_PINCTRL_FUNC(TACH1), ++ ASPEED_PINCTRL_FUNC(TACH2), ++ ASPEED_PINCTRL_FUNC(TACH3), ++ ASPEED_PINCTRL_FUNC(TACH4), ++ ASPEED_PINCTRL_FUNC(TACH5), ++ ASPEED_PINCTRL_FUNC(TACH6), ++ ASPEED_PINCTRL_FUNC(TACH7), ++ ASPEED_PINCTRL_FUNC(TACH8), ++ ASPEED_PINCTRL_FUNC(TACH9), ++ ASPEED_PINCTRL_FUNC(TACH10), ++ ASPEED_PINCTRL_FUNC(TACH11), ++ ASPEED_PINCTRL_FUNC(TACH12), ++ ASPEED_PINCTRL_FUNC(TACH13), ++ ASPEED_PINCTRL_FUNC(TACH14), ++ ASPEED_PINCTRL_FUNC(TACH15), ++ ASPEED_PINCTRL_FUNC(PWM0), ++ ASPEED_PINCTRL_FUNC(PWM1), ++ ASPEED_PINCTRL_FUNC(PWM2), ++ ASPEED_PINCTRL_FUNC(PWM3), ++ ASPEED_PINCTRL_FUNC(PWM4), ++ ASPEED_PINCTRL_FUNC(PWM5), ++ ASPEED_PINCTRL_FUNC(PWM6), ++ ASPEED_PINCTRL_FUNC(PWM7), ++ ASPEED_PINCTRL_FUNC(PWM8), ++ ASPEED_PINCTRL_FUNC(PWM9), ++ ASPEED_PINCTRL_FUNC(PWM10), ++ ASPEED_PINCTRL_FUNC(PWM11), ++ ASPEED_PINCTRL_FUNC(PWM12), ++ ASPEED_PINCTRL_FUNC(PWM13), ++ ASPEED_PINCTRL_FUNC(PWM14), ++ ASPEED_PINCTRL_FUNC(PWM15), ++ ASPEED_PINCTRL_FUNC(ADC0), ++ ASPEED_PINCTRL_FUNC(ADC1), ++ ASPEED_PINCTRL_FUNC(ADC2), ++ ASPEED_PINCTRL_FUNC(ADC3), ++ ASPEED_PINCTRL_FUNC(ADC4), ++ ASPEED_PINCTRL_FUNC(ADC5), ++ ASPEED_PINCTRL_FUNC(ADC6), ++ ASPEED_PINCTRL_FUNC(ADC7), ++ ASPEED_PINCTRL_FUNC(ADC8), ++ ASPEED_PINCTRL_FUNC(ADC9), ++ ASPEED_PINCTRL_FUNC(ADC10), ++ ASPEED_PINCTRL_FUNC(ADC11), ++ ASPEED_PINCTRL_FUNC(ADC12), ++ ASPEED_PINCTRL_FUNC(ADC13), ++ ASPEED_PINCTRL_FUNC(ADC14), ++ ASPEED_PINCTRL_FUNC(ADC15), ++ ASPEED_PINCTRL_FUNC(I2C0), ++ ASPEED_PINCTRL_FUNC(I2C1), ++ ASPEED_PINCTRL_FUNC(I2C2), ++ ASPEED_PINCTRL_FUNC(I2C3), ++ ASPEED_PINCTRL_FUNC(I2C4), ++ ASPEED_PINCTRL_FUNC(I2C5), ++ ASPEED_PINCTRL_FUNC(I2C6), ++ ASPEED_PINCTRL_FUNC(I2C7), ++ ASPEED_PINCTRL_FUNC(I2C8), ++ ASPEED_PINCTRL_FUNC(I2C9), ++ ASPEED_PINCTRL_FUNC(I2C10), ++ ASPEED_PINCTRL_FUNC(I2C11), ++ ASPEED_PINCTRL_FUNC(I2C12), ++ ASPEED_PINCTRL_FUNC(I2C13), ++ ASPEED_PINCTRL_FUNC(I2C14), ++ ASPEED_PINCTRL_FUNC(I2C15), ++ ASPEED_PINCTRL_FUNC(I3C12), ++ ASPEED_PINCTRL_FUNC(I3C13), ++ ASPEED_PINCTRL_FUNC(I3C14), ++ ASPEED_PINCTRL_FUNC(I3C15), ++ ASPEED_PINCTRL_FUNC(I3C4), ++ ASPEED_PINCTRL_FUNC(I3C5), ++ ASPEED_PINCTRL_FUNC(I3C6), ++ ASPEED_PINCTRL_FUNC(I3C7), ++ ASPEED_PINCTRL_FUNC(I3C8), ++ ASPEED_PINCTRL_FUNC(I3C9), ++ ASPEED_PINCTRL_FUNC(I3C10), ++ ASPEED_PINCTRL_FUNC(I3C11), ++ ASPEED_PINCTRL_FUNC(I3C0), ++ ASPEED_PINCTRL_FUNC(I3C1), ++ ASPEED_PINCTRL_FUNC(I3C2), ++ ASPEED_PINCTRL_FUNC(I3C3), ++ ASPEED_PINCTRL_FUNC(JTAGM), ++ ASPEED_PINCTRL_FUNC(SGPM1), ++}; ++ ++/* number, name, drv_data */ ++static const struct pinctrl_pin_desc aspeed_g7_ltpi_pins[] = { ++ PINCTRL_PIN(D6, "D6"), ++ PINCTRL_PIN(B7, "B7"), ++ PINCTRL_PIN(A7, "A7"), ++ PINCTRL_PIN(C6, "C6"), ++ PINCTRL_PIN(B6, "B6"), ++ PINCTRL_PIN(A6, "A6"), ++ PINCTRL_PIN(C5, "C5"), ++ PINCTRL_PIN(D5, "D5"), ++ PINCTRL_PIN(L15, "L15"), ++ PINCTRL_PIN(H17, "H17"), ++ PINCTRL_PIN(L16, "L16"), ++ PINCTRL_PIN(K15, "K15"), ++ PINCTRL_PIN(K16, "K16"), ++ PINCTRL_PIN(K14, "K14"), ++ PINCTRL_PIN(J14, "J14"), ++ PINCTRL_PIN(J16, "J16"), ++ PINCTRL_PIN(J15, "J15"), ++ PINCTRL_PIN(G17, "G17"), ++ PINCTRL_PIN(F17, "F17"), ++ PINCTRL_PIN(D17, "D17"), ++ PINCTRL_PIN(C17, "C17"), ++ PINCTRL_PIN(H16, "H16"), ++ PINCTRL_PIN(B17, "B17"), ++ PINCTRL_PIN(E17, "E17"), ++ PINCTRL_PIN(A16, "A16"), ++ PINCTRL_PIN(B16, "B16"), ++ PINCTRL_PIN(H14, "H14"), ++ PINCTRL_PIN(A15, "A15"), ++ PINCTRL_PIN(G16, "G16"), ++ PINCTRL_PIN(F15, "F15"), ++ PINCTRL_PIN(H15, "H15"), ++ PINCTRL_PIN(D16, "D16"), ++ PINCTRL_PIN(M2, "M2"), ++ PINCTRL_PIN(M1, "M1"), ++ PINCTRL_PIN(M3, "M3"), ++ PINCTRL_PIN(M4, "M4"), ++ PINCTRL_PIN(N1, "N1"), ++ PINCTRL_PIN(N2, "N2"), ++ PINCTRL_PIN(N3, "N3"), ++ PINCTRL_PIN(L5, "L5"), ++ PINCTRL_PIN(P2, "P2"), ++ PINCTRL_PIN(P1, "P1"), ++ PINCTRL_PIN(N4, "N4"), ++ PINCTRL_PIN(N5, "N5"), ++ PINCTRL_PIN(HOLE0, "HOLE0"), ++ PINCTRL_PIN(P3, "P3"), ++ PINCTRL_PIN(R1, "R1"), ++ PINCTRL_PIN(P4, "P4"), ++ PINCTRL_PIN(J2, "J2"), ++ PINCTRL_PIN(J1, "J1"), ++ PINCTRL_PIN(K4, "K4"), ++ PINCTRL_PIN(K2, "K2"), ++ PINCTRL_PIN(K3, "K3"), ++ PINCTRL_PIN(K1, "K1"), ++ PINCTRL_PIN(L4, "L4"), ++ PINCTRL_PIN(L2, "L2"), ++ PINCTRL_PIN(L1, "L1"), ++ PINCTRL_PIN(L3, "L3"), ++ PINCTRL_PIN(A14, "A14"), ++ PINCTRL_PIN(B14, "B14"), ++ PINCTRL_PIN(HOLE1, "HOLE1"), ++ PINCTRL_PIN(HOLE2, "HOLE2"), ++ PINCTRL_PIN(HOLE3, "HOLE3"), ++ PINCTRL_PIN(HOLE4, "HOLE4"), ++ PINCTRL_PIN(U15, "U15"), ++ PINCTRL_PIN(U16, "U16"), ++ PINCTRL_PIN(T17, "T17"), ++ PINCTRL_PIN(P12, "P12"), ++ PINCTRL_PIN(R13, "R13"), ++ PINCTRL_PIN(T15, "T15"), ++ PINCTRL_PIN(T16, "T16"), ++ PINCTRL_PIN(R12, "R12"), ++ PINCTRL_PIN(R14, "R14"), ++ PINCTRL_PIN(R17, "R17"), ++ PINCTRL_PIN(R15, "R15"), ++ PINCTRL_PIN(P13, "P13"), ++ PINCTRL_PIN(R16, "R16"), ++ PINCTRL_PIN(P14, "P14"), ++ PINCTRL_PIN(P17, "P17"), ++ PINCTRL_PIN(P16, "P16"), ++ PINCTRL_PIN(P15, "P15"), ++ PINCTRL_PIN(N17, "N17"), ++ PINCTRL_PIN(N14, "N14"), ++ PINCTRL_PIN(M17, "M17"), ++ PINCTRL_PIN(N15, "N15"), ++ PINCTRL_PIN(N16, "N16"), ++ PINCTRL_PIN(L17, "L17"), ++ PINCTRL_PIN(K17, "K17"), ++ PINCTRL_PIN(J17, "J17"), ++ PINCTRL_PIN(M14, "M14"), ++ PINCTRL_PIN(M15, "M15"), ++ PINCTRL_PIN(M16, "M16"), ++ PINCTRL_PIN(A4, "A4"), ++ PINCTRL_PIN(B5, "B5"), ++ PINCTRL_PIN(A5, "A5"), ++ PINCTRL_PIN(B4, "B4"), ++ PINCTRL_PIN(A10, "A10"), ++ PINCTRL_PIN(B9, "B9"), ++ PINCTRL_PIN(C7, "C7"), ++ PINCTRL_PIN(E8, "E8"), ++ PINCTRL_PIN(E7, "E7"), ++ PINCTRL_PIN(B8, "B8"), ++ PINCTRL_PIN(A9, "A9"), ++ PINCTRL_PIN(A8, "A8"), ++ PINCTRL_PIN(HOLE5, "HOLE5"), ++ PINCTRL_PIN(HOLE6, "HOLE6"), ++ PINCTRL_PIN(HOLE7, "HOLE7"), ++ PINCTRL_PIN(HOLE8, "HOLE8"), ++ PINCTRL_PIN(HOLE9, "HOLE9"), ++ PINCTRL_PIN(HOLE10, "HOLE10"), ++ PINCTRL_PIN(HOLE11, "HOLE11"), ++ PINCTRL_PIN(HOLE12, "HOLE12"), ++ PINCTRL_PIN(E13, "E13"), ++ PINCTRL_PIN(C12, "C12"), ++ PINCTRL_PIN(D12, "D12"), ++ PINCTRL_PIN(E12, "E12"), ++ PINCTRL_PIN(D13, "D13"), ++ PINCTRL_PIN(HOLE13, "HOLE13"), ++ PINCTRL_PIN(B12, "B12"), ++ PINCTRL_PIN(HOLE14, "HOLE14"), ++ PINCTRL_PIN(D15, "D15"), ++ PINCTRL_PIN(B13, "B13"), ++ PINCTRL_PIN(C14, "C14"), ++ PINCTRL_PIN(C13, "C13"), ++ PINCTRL_PIN(D14, "D14"), ++ PINCTRL_PIN(F13, "F13"), ++ PINCTRL_PIN(E14, "E14"), ++ PINCTRL_PIN(HOLE15, "HOLE15"), ++ PINCTRL_PIN(HOLE16, "HOLE16"), ++ PINCTRL_PIN(HOLE17, "HOLE17"), ++ PINCTRL_PIN(B1, "B1"), ++ PINCTRL_PIN(C2, "C2"), ++ PINCTRL_PIN(D3, "D3"), ++ PINCTRL_PIN(C1, "C1"), ++ PINCTRL_PIN(E5, "E5"), ++ PINCTRL_PIN(HOLE18, "HOLE18"), ++ PINCTRL_PIN(HOLE19, "HOLE19"), ++ PINCTRL_PIN(HOLE20, "HOLE20"), ++ PINCTRL_PIN(HOLE21, "HOLE21"), ++ PINCTRL_PIN(HOLE22, "HOLE22"), ++ PINCTRL_PIN(HOLE23, "HOLE23"), ++ PINCTRL_PIN(HOLE24, "HOLE24"), ++ PINCTRL_PIN(HOLE25, "HOLE25"), ++ PINCTRL_PIN(HOLE26, "HOLE26"), ++ PINCTRL_PIN(C11, "C11"), ++ PINCTRL_PIN(D11, "D11"), ++ PINCTRL_PIN(HOLE27, "HOLE27"), ++ PINCTRL_PIN(HOLE28, "HOLE28"), ++ PINCTRL_PIN(C10, "C10"), ++ PINCTRL_PIN(D10, "D10"), ++ PINCTRL_PIN(HOLE29, "HOLE29"), ++ PINCTRL_PIN(HOLE30, "HOLE30"), ++ PINCTRL_PIN(HOLE31, "HOLE31"), ++ PINCTRL_PIN(HOLE32, "HOLE32"), ++ PINCTRL_PIN(D9, "D9"), ++ PINCTRL_PIN(E9, "E9"), ++ PINCTRL_PIN(C9, "C9"), ++ PINCTRL_PIN(D8, "D8"), ++ PINCTRL_PIN(C8, "C8"), ++ PINCTRL_PIN(B11, "B11"), ++ PINCTRL_PIN(B10, "B10"), ++ PINCTRL_PIN(A12, "A12"), ++ PINCTRL_PIN(A11, "A11"), ++ PINCTRL_PIN(D7, "D7"), ++ PINCTRL_PIN(HOLE33, "HOLE33"), ++ PINCTRL_PIN(HOLE34, "HOLE34"), ++ PINCTRL_PIN(HOLE35, "HOLE35"), ++ PINCTRL_PIN(HOLE36, "HOLE36"), ++ PINCTRL_PIN(G2, "G2"), ++ PINCTRL_PIN(G1, "G1"), ++ PINCTRL_PIN(G4, "G4"), ++ PINCTRL_PIN(H3, "H3"), ++ PINCTRL_PIN(H2, "H2"), ++ PINCTRL_PIN(H1, "H1"), ++ PINCTRL_PIN(J4, "J4"), ++ PINCTRL_PIN(J3, "J3"), ++ PINCTRL_PIN(C4, "C4"), ++ PINCTRL_PIN(A2, "A2"), ++ PINCTRL_PIN(C3, "C3"), ++ PINCTRL_PIN(B2, "B2"), ++ PINCTRL_PIN(B3, "B3"), ++ PINCTRL_PIN(A3, "A3"), ++ PINCTRL_PIN(F2, "F2"), ++ PINCTRL_PIN(F1, "F1"), ++ PINCTRL_PIN(D2, "D2"), ++ PINCTRL_PIN(D1, "D1"), ++ PINCTRL_PIN(F4, "F4"), ++ PINCTRL_PIN(E4, "E4"), ++ PINCTRL_PIN(E2, "E2"), ++ PINCTRL_PIN(E1, "E1"), ++ PINCTRL_PIN(F3, "F3"), ++ PINCTRL_PIN(G5, "G5"), ++ PINCTRL_PIN(T4, "T4"), ++ PINCTRL_PIN(T3, "T3"), ++ PINCTRL_PIN(R5, "R5"), ++ PINCTRL_PIN(R6, "R6"), ++ PINCTRL_PIN(T2, "T2"), ++ PINCTRL_PIN(T5, "T5"), ++ PINCTRL_PIN(U2, "U2"), ++ PINCTRL_PIN(R7, "R7"), ++ PINCTRL_PIN(P9, "P9"), ++ PINCTRL_PIN(U4, "U4"), ++ PINCTRL_PIN(U5, "U5"), ++ PINCTRL_PIN(U6, "U6"), ++ PINCTRL_PIN(T7, "T7"), ++ PINCTRL_PIN(U7, "U7"), ++ PINCTRL_PIN(R10, "R10"), ++ PINCTRL_PIN(R11, "R11"), ++ PINCTRL_PIN(C16, "C16"), ++ PINCTRL_PIN(F16, "F16"), ++ PINCTRL_PIN(B15, "B15"), ++ PINCTRL_PIN(G15, "G15"), ++ PINCTRL_PIN(G14, "G14"), ++ PINCTRL_PIN(F14, "F14"), ++ PINCTRL_PIN(E15, "E15"), ++ PINCTRL_PIN(C15, "C15"), ++}; ++ ++FUNCFG_DESCL(L15, PIN_CFG(TACH0, SCU404, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(H17, PIN_CFG(TACH1, SCU404, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(L16, PIN_CFG(TACH2, SCU404, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(K15, PIN_CFG(TACH3, SCU404, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(K16, PIN_CFG(TACH4, SCU404, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(K14, PIN_CFG(TACH5, SCU404, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(J14, PIN_CFG(TACH6, SCU404, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(J16, PIN_CFG(TACH7, SCU404, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(J15, PIN_CFG(TACH8, SCU408, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(G17, PIN_CFG(TACH9, SCU408, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(F17, PIN_CFG(TACH10, SCU408, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(D17, PIN_CFG(TACH11, SCU408, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(C17, PIN_CFG(TACH12, SCU408, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(H16, PIN_CFG(TACH13, SCU408, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(B17, PIN_CFG(TACH14, SCU408, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(E17, PIN_CFG(TACH15, SCU408, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(A16, PIN_CFG(PWM0, SCU40C, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(B16, PIN_CFG(PWM1, SCU40C, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(H14, PIN_CFG(PWM2, SCU40C, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(A15, PIN_CFG(PWM3, SCU40C, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(G16, PIN_CFG(PWM4, SCU40C, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(F15, PIN_CFG(PWM5, SCU40C, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(H15, PIN_CFG(PWM6, SCU40C, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(D16, PIN_CFG(PWM7, SCU40C, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(J1, PIN_CFG(I2C12, SCU418, GENMASK(6, 4), (4 << 4))); ++FUNCFG_DESCL(K4, PIN_CFG(PWM8, SCU418, GENMASK(10, 8), (3 << 8))); ++FUNCFG_DESCL(K2, PIN_CFG(PWM9, SCU418, GENMASK(14, 12), (3 << 12)), ++ PIN_CFG(I2C12, SCU418, GENMASK(14, 12), (4 << 12))); ++FUNCFG_DESCL(K3, PIN_CFG(PWM10, SCU418, GENMASK(18, 16), (3 << 16)), ++ PIN_CFG(I2C13, SCU418, GENMASK(18, 16), (4 << 16))); ++FUNCFG_DESCL(K1, PIN_CFG(PWM11, SCU418, GENMASK(22, 20), (3 << 20)), ++ PIN_CFG(I2C13, SCU418, GENMASK(22, 20), (4 << 20))); ++FUNCFG_DESCL(L4, PIN_CFG(PWM12, SCU418, GENMASK(26, 24), (3 << 24)), ++ PIN_CFG(I2C14, SCU418, GENMASK(26, 24), (4 << 24))); ++FUNCFG_DESCL(L2, PIN_CFG(PWM13, SCU418, GENMASK(30, 28), (3 << 28)), ++ PIN_CFG(I2C14, SCU418, GENMASK(30, 28), (4 << 28))); ++FUNCFG_DESCL(L1, PIN_CFG(I2C15, SCU41C, GENMASK(2, 0), 2), ++ PIN_CFG(PWM14, SCU41C, GENMASK(2, 0), 3)); ++FUNCFG_DESCL(L3, PIN_CFG(I2C15, SCU41C, GENMASK(6, 4), (2 << 4)), ++ PIN_CFG(PWM15, SCU41C, GENMASK(6, 4), (3 << 4))); ++FUNCFG_DESCL(A14, PIN_CFG(SGPM1, SCU41C, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(B14, PIN_CFG(SGPM1, SCU41C, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(U15, PIN_CFG(HVI3C12, SCU420, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(U16, PIN_CFG(HVI3C12, SCU420, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(T17, PIN_CFG(HVI3C13, SCU420, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(P12, PIN_CFG(HVI3C13, SCU420, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(R13, PIN_CFG(HVI3C14, SCU420, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(T15, PIN_CFG(HVI3C14, SCU420, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(T16, PIN_CFG(HVI3C15, SCU420, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(R12, PIN_CFG(HVI3C15, SCU420, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(R14, PIN_CFG(I3C4, SCU424, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(R17, PIN_CFG(I3C4, SCU424, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(R15, PIN_CFG(I3C5, SCU424, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(P13, PIN_CFG(I3C5, SCU424, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(R16, PIN_CFG(I3C6, SCU424, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(P14, PIN_CFG(I3C6, SCU424, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(P17, PIN_CFG(I3C7, SCU424, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(P16, PIN_CFG(I3C7, SCU424, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(P15, PIN_CFG(I3C8, SCU428, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(N17, PIN_CFG(I3C8, SCU428, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(N14, PIN_CFG(I3C9, SCU428, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(M17, PIN_CFG(I3C9, SCU428, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(N15, PIN_CFG(I3C10, SCU428, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(N16, PIN_CFG(I3C10, SCU428, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(L17, PIN_CFG(I3C11, SCU428, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(K17, PIN_CFG(I3C11, SCU428, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(J17, PIN_CFG(HVI3C0, SCU42C, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(M14, PIN_CFG(HVI3C0, SCU42C, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(M15, PIN_CFG(HVI3C1, SCU42C, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(M16, PIN_CFG(HVI3C1, SCU42C, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(A4, PIN_CFG(HVI3C2, SCU42C, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(B5, PIN_CFG(HVI3C2, SCU42C, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(A5, PIN_CFG(HVI3C3, SCU42C, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(B4, PIN_CFG(HVI3C3, SCU42C, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(C2, PIN_CFG(JTAGM, SCU440, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(D3, PIN_CFG(JTAGM, SCU440, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(C1, PIN_CFG(JTAGM, SCU440, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(E5, PIN_CFG(JTAGM, SCU440, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(G2, PIN_CFG(I2C0, SCU454, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(G1, PIN_CFG(I2C0, SCU454, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(G4, PIN_CFG(I2C1, SCU454, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(H3, PIN_CFG(I2C1, SCU454, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(H2, PIN_CFG(I2C2, SCU454, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(H1, PIN_CFG(I2C2, SCU454, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(J4, PIN_CFG(I2C3, SCU454, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(J3, PIN_CFG(I2C3, SCU454, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(C4, PIN_CFG(I2C4, SCU458, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(A2, PIN_CFG(I2C4, SCU458, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(C3, PIN_CFG(I2C5, SCU458, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(B2, PIN_CFG(I2C5, SCU458, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(B3, PIN_CFG(I2C6, SCU458, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(A3, PIN_CFG(I2C6, SCU458, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(F2, PIN_CFG(I2C7, SCU458, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(F1, PIN_CFG(I2C7, SCU458, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(D2, PIN_CFG(I2C8, SCU45C, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(D1, PIN_CFG(I2C8, SCU45C, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(F4, PIN_CFG(I2C9, SCU45C, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(E4, PIN_CFG(I2C9, SCU45C, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(E2, PIN_CFG(I2C10, SCU45C, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(E1, PIN_CFG(I2C10, SCU45C, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(F3, PIN_CFG(I2C11, SCU45C, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(G5, PIN_CFG(I2C11, SCU45C, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(T4, PIN_CFG(ADC0, SCU460, GENMASK(2, 0), 0)); ++FUNCFG_DESCL(T3, PIN_CFG(ADC1, SCU460, GENMASK(6, 4), 0)); ++FUNCFG_DESCL(R5, PIN_CFG(ADC2, SCU460, GENMASK(10, 8), 0)); ++FUNCFG_DESCL(R6, PIN_CFG(ADC3, SCU460, GENMASK(14, 12), 0)); ++FUNCFG_DESCL(T2, PIN_CFG(ADC4, SCU460, GENMASK(18, 16), 0)); ++FUNCFG_DESCL(T5, PIN_CFG(ADC5, SCU460, GENMASK(22, 20), 0)); ++FUNCFG_DESCL(U2, PIN_CFG(ADC6, SCU460, GENMASK(26, 24), 0)); ++FUNCFG_DESCL(R7, PIN_CFG(ADC7, SCU460, GENMASK(30, 28), 0)); ++FUNCFG_DESCL(P9, PIN_CFG(ADC8, SCU464, GENMASK(2, 0), 0)); ++FUNCFG_DESCL(U4, PIN_CFG(ADC9, SCU464, GENMASK(6, 4), 0)); ++FUNCFG_DESCL(U5, PIN_CFG(ADC10, SCU464, GENMASK(10, 8), 0)); ++FUNCFG_DESCL(U6, PIN_CFG(ADC11, SCU464, GENMASK(14, 12), 0)); ++FUNCFG_DESCL(T7, PIN_CFG(ADC12, SCU464, GENMASK(18, 16), 0)); ++FUNCFG_DESCL(U7, PIN_CFG(ADC13, SCU464, GENMASK(22, 20), 0)); ++FUNCFG_DESCL(R10, PIN_CFG(ADC14, SCU464, GENMASK(26, 24), 0)); ++FUNCFG_DESCL(R11, PIN_CFG(ADC15, SCU464, GENMASK(30, 28), 0)); ++FUNCFG_DESCL(F14, PIN_CFG(SGPM1, SCU468, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(E15, PIN_CFG(SGPM1, SCU468, GENMASK(26, 24), (1 << 24))); ++ ++static const struct aspeed_g7_pincfg pin_cfg[] = { ++ PINCFG_PIN(L15), ++ PINCFG_PIN(H17), ++ PINCFG_PIN(L16), ++ PINCFG_PIN(K15), ++ PINCFG_PIN(K16), ++ PINCFG_PIN(K14), ++ PINCFG_PIN(J14), ++ PINCFG_PIN(J16), ++ PINCFG_PIN(J15), ++ PINCFG_PIN(G17), ++ PINCFG_PIN(F17), ++ PINCFG_PIN(D17), ++ PINCFG_PIN(C17), ++ PINCFG_PIN(H16), ++ PINCFG_PIN(B17), ++ PINCFG_PIN(E17), ++ PINCFG_PIN(A16), ++ PINCFG_PIN(B16), ++ PINCFG_PIN(H14), ++ PINCFG_PIN(A15), ++ PINCFG_PIN(G16), ++ PINCFG_PIN(F15), ++ PINCFG_PIN(H15), ++ PINCFG_PIN(D16), ++ PINCFG_PIN(J1), ++ PINCFG_PIN(K4), ++ PINCFG_PIN(K2), ++ PINCFG_PIN(K3), ++ PINCFG_PIN(K1), ++ PINCFG_PIN(L4), ++ PINCFG_PIN(L2), ++ PINCFG_PIN(L1), ++ PINCFG_PIN(L3), ++ PINCFG_PIN(A14), ++ PINCFG_PIN(B14), ++ PINCFG_PIN(U15), ++ PINCFG_PIN(U16), ++ PINCFG_PIN(T17), ++ PINCFG_PIN(P12), ++ PINCFG_PIN(R13), ++ PINCFG_PIN(T15), ++ PINCFG_PIN(T16), ++ PINCFG_PIN(R12), ++ PINCFG_PIN(R14), ++ PINCFG_PIN(R17), ++ PINCFG_PIN(R15), ++ PINCFG_PIN(P13), ++ PINCFG_PIN(R16), ++ PINCFG_PIN(P14), ++ PINCFG_PIN(P17), ++ PINCFG_PIN(P16), ++ PINCFG_PIN(P15), ++ PINCFG_PIN(N17), ++ PINCFG_PIN(N14), ++ PINCFG_PIN(M17), ++ PINCFG_PIN(N15), ++ PINCFG_PIN(N16), ++ PINCFG_PIN(L17), ++ PINCFG_PIN(K17), ++ PINCFG_PIN(J17), ++ PINCFG_PIN(M14), ++ PINCFG_PIN(M15), ++ PINCFG_PIN(M16), ++ PINCFG_PIN(A4), ++ PINCFG_PIN(B5), ++ PINCFG_PIN(A5), ++ PINCFG_PIN(B4), ++ PINCFG_PIN(C2), ++ PINCFG_PIN(D3), ++ PINCFG_PIN(C1), ++ PINCFG_PIN(E5), ++ PINCFG_PIN(G2), ++ PINCFG_PIN(G1), ++ PINCFG_PIN(G4), ++ PINCFG_PIN(H3), ++ PINCFG_PIN(H2), ++ PINCFG_PIN(H1), ++ PINCFG_PIN(J4), ++ PINCFG_PIN(J3), ++ PINCFG_PIN(C4), ++ PINCFG_PIN(A2), ++ PINCFG_PIN(C3), ++ PINCFG_PIN(B2), ++ PINCFG_PIN(B3), ++ PINCFG_PIN(A3), ++ PINCFG_PIN(F2), ++ PINCFG_PIN(F1), ++ PINCFG_PIN(D2), ++ PINCFG_PIN(D1), ++ PINCFG_PIN(F4), ++ PINCFG_PIN(E4), ++ PINCFG_PIN(E2), ++ PINCFG_PIN(E1), ++ PINCFG_PIN(F3), ++ PINCFG_PIN(G5), ++ PINCFG_PIN(T4), ++ PINCFG_PIN(T3), ++ PINCFG_PIN(R5), ++ PINCFG_PIN(R6), ++ PINCFG_PIN(T2), ++ PINCFG_PIN(T5), ++ PINCFG_PIN(U2), ++ PINCFG_PIN(R7), ++ PINCFG_PIN(P9), ++ PINCFG_PIN(U4), ++ PINCFG_PIN(U5), ++ PINCFG_PIN(U6), ++ PINCFG_PIN(T7), ++ PINCFG_PIN(U7), ++ PINCFG_PIN(R10), ++ PINCFG_PIN(R11), ++ PINCFG_PIN(F14), ++ PINCFG_PIN(E15), ++}; ++ ++static struct aspeed_pin_config aspeed_g7_ltpi_configs[] = { ++ /* GPIOA */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { D6, D6 }, SCU4C0, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B7, B7 }, SCU4C0, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A7, A7 }, SCU4C0, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C6, C6 }, SCU4C0, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B6, B6 }, SCU4C0, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A6, A6 }, SCU4C0, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C5, C5 }, SCU4C0, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D5, D5 }, SCU4C0, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { D6, D5 }, SCU4A0, BIT_MASK(4) }, ++ /* GPIOI */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { U15, U15 }, SCU4C0, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { U16, U16 }, SCU4C0, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { T17, T17 }, SCU4C0, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { P12, P12 }, SCU4C0, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R13, R13 }, SCU4C0, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { T15, T15 }, SCU4C0, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { T16, T16 }, SCU4C0, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R12, R12 }, SCU4C0, GENMASK(31, 30) }, ++ { PIN_CONFIG_POWER_SOURCE, { U15, R12 }, SCU4A0, BIT_MASK(12) }, ++ /* GPIOJ */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { R14, R14 }, SCU4C4, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R17, R17 }, SCU4C4, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R15, R15 }, SCU4C4, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { P13, P13 }, SCU4C4, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R16, R16 }, SCU4C4, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { P14, P14 }, SCU4C4, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { P17, P17 }, SCU4C4, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { P16, P16 }, SCU4C4, GENMASK(15, 14) }, ++ /* GPIOK */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { P15, P15 }, SCU4C4, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { N17, N17 }, SCU4C4, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { N14, N14 }, SCU4C4, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M17, M17 }, SCU4C4, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { N15, N15 }, SCU4C4, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { N16, N16 }, SCU4C4, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { L17, L17 }, SCU4C4, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { K17, K17 }, SCU4C4, GENMASK(31, 30) }, ++ /* GPIOL */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { J17, J17 }, SCU4C8, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M14, M14 }, SCU4C8, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M15, M15 }, SCU4C8, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M16, M16 }, SCU4C8, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A4, A4 }, SCU4C8, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B5, B5 }, SCU4C8, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A5, A5 }, SCU4C8, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B4, B4 }, SCU4C8, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { J17, B4 }, SCU4A0, BIT_MASK(15) }, ++ /* GPIOM */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { A10, A10 }, SCU4C8, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B9, B9 }, SCU4C8, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C7, C7 }, SCU4C8, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E8, E8 }, SCU4C8, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E7, E7 }, SCU4C8, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B8, B8 }, SCU4C8, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A9, A9 }, SCU4C8, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A8, A8 }, SCU4C8, GENMASK(31, 30) }, ++ { PIN_CONFIG_POWER_SOURCE, { A10, A8 }, SCU4A0, BIT_MASK(16) }, ++ /* GPIOO */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { E13, E13 }, SCU4CC, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C12, C12 }, SCU4CC, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D12, D12 }, SCU4CC, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E12, E12 }, SCU4CC, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D13, D13 }, SCU4CC, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B12, B12 }, SCU4CC, GENMASK(29, 28) }, ++ { PIN_CONFIG_POWER_SOURCE, { E13, B12 }, SCU4A0, BIT_MASK(18) }, ++ /* GPIOP */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { D15, D15 }, SCU4D0, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B13, B13 }, SCU4D0, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C14, C14 }, SCU4D0, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C13, C13 }, SCU4D0, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D14, D14 }, SCU4D0, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F13, F13 }, SCU4D0, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E14, E14 }, SCU4D0, GENMASK(13, 12) }, ++ { PIN_CONFIG_POWER_SOURCE, { D15, E14 }, SCU4A0, BIT_MASK(19) }, ++ /* GPIOQ */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { B1, B1 }, SCU4D0, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C2, C2 }, SCU4D0, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D3, D3 }, SCU4D0, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C1, C1 }, SCU4D0, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E5, E5 }, SCU4D0, GENMASK(29, 28) }, ++ /* GPIOS */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { C11, C11 }, SCU4D4, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D11, D11 }, SCU4D4, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C10, C10 }, SCU4D4, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D10, D10 }, SCU4D4, GENMASK(27, 26) }, ++ { PIN_CONFIG_POWER_SOURCE, { C11, D10 }, SCU4A0, BIT_MASK(22) }, ++ /* GPIOT */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { D9, D9 }, SCU4D8, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E9, E9 }, SCU4D8, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C9, C9 }, SCU4D8, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D8, D8 }, SCU4D8, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C8, C8 }, SCU4D8, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B11, B11 }, SCU4D8, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { D9, B11 }, SCU4A0, BIT_MASK(23) }, ++ /* GPIOU */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { B10, B10 }, SCU4D8, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A12, A12 }, SCU4D8, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A11, A11 }, SCU4D8, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D7, D7 }, SCU4D8, GENMASK(23, 22) }, ++ { PIN_CONFIG_POWER_SOURCE, { B10, D7 }, SCU4A0, BIT_MASK(24) }, ++ /* GPIOW */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { C4, C4 }, SCU4DC, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A2, A2 }, SCU4DC, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C3, C3 }, SCU4DC, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B2, B2 }, SCU4DC, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B3, B3 }, SCU4DC, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A3, A3 }, SCU4DC, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F2, F2 }, SCU4DC, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F1, F1 }, SCU4DC, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { C4, F1 }, SCU4A0, BIT_MASK(26) }, ++ ++ ASPEED_PULL_DOWN_PINCONF(D6, SCU480, 0), ++ ASPEED_PULL_DOWN_PINCONF(B7, SCU480, 1), ++ ASPEED_PULL_DOWN_PINCONF(A7, SCU480, 2), ++ ASPEED_PULL_DOWN_PINCONF(C6, SCU480, 3), ++ ASPEED_PULL_DOWN_PINCONF(B6, SCU480, 4), ++ ASPEED_PULL_DOWN_PINCONF(A6, SCU480, 5), ++ ASPEED_PULL_DOWN_PINCONF(C5, SCU480, 6), ++ ASPEED_PULL_DOWN_PINCONF(D5, SCU480, 7), ++ ASPEED_PULL_DOWN_PINCONF(L15, SCU480, 8), ++ ASPEED_PULL_DOWN_PINCONF(H17, SCU480, 9), ++ ASPEED_PULL_DOWN_PINCONF(L16, SCU480, 10), ++ ASPEED_PULL_DOWN_PINCONF(K15, SCU480, 11), ++ ASPEED_PULL_DOWN_PINCONF(K16, SCU480, 12), ++ ASPEED_PULL_DOWN_PINCONF(K14, SCU480, 13), ++ ASPEED_PULL_DOWN_PINCONF(J14, SCU480, 14), ++ ASPEED_PULL_DOWN_PINCONF(J16, SCU480, 15), ++ ASPEED_PULL_DOWN_PINCONF(J15, SCU480, 16), ++ ASPEED_PULL_DOWN_PINCONF(G17, SCU480, 17), ++ ASPEED_PULL_DOWN_PINCONF(F17, SCU480, 18), ++ ASPEED_PULL_DOWN_PINCONF(D17, SCU480, 19), ++ ASPEED_PULL_DOWN_PINCONF(C17, SCU480, 20), ++ ASPEED_PULL_DOWN_PINCONF(H16, SCU480, 21), ++ ASPEED_PULL_DOWN_PINCONF(B17, SCU480, 22), ++ ASPEED_PULL_DOWN_PINCONF(E17, SCU480, 23), ++ ASPEED_PULL_DOWN_PINCONF(A16, SCU480, 24), ++ ASPEED_PULL_DOWN_PINCONF(B16, SCU480, 25), ++ ASPEED_PULL_DOWN_PINCONF(H14, SCU480, 26), ++ ASPEED_PULL_DOWN_PINCONF(A15, SCU480, 27), ++ ASPEED_PULL_DOWN_PINCONF(G16, SCU480, 28), ++ ASPEED_PULL_DOWN_PINCONF(F15, SCU480, 29), ++ ASPEED_PULL_DOWN_PINCONF(H15, SCU480, 30), ++ ASPEED_PULL_DOWN_PINCONF(D16, SCU480, 31), ++ ASPEED_PULL_DOWN_PINCONF(M2, SCU484, 0), ++ ASPEED_PULL_DOWN_PINCONF(M1, SCU484, 1), ++ ASPEED_PULL_DOWN_PINCONF(M3, SCU484, 2), ++ ASPEED_PULL_DOWN_PINCONF(M4, SCU484, 3), ++ ASPEED_PULL_DOWN_PINCONF(N1, SCU484, 4), ++ ASPEED_PULL_DOWN_PINCONF(N2, SCU484, 5), ++ ASPEED_PULL_DOWN_PINCONF(N3, SCU484, 6), ++ ASPEED_PULL_DOWN_PINCONF(L5, SCU484, 7), ++ ASPEED_PULL_DOWN_PINCONF(P2, SCU484, 8), ++ ASPEED_PULL_DOWN_PINCONF(P1, SCU484, 9), ++ ASPEED_PULL_DOWN_PINCONF(N4, SCU484, 10), ++ ASPEED_PULL_DOWN_PINCONF(N5, SCU484, 11), ++ ASPEED_PULL_DOWN_PINCONF(HOLE0, SCU484, 12), ++ ASPEED_PULL_DOWN_PINCONF(P3, SCU484, 13), ++ ASPEED_PULL_DOWN_PINCONF(R1, SCU484, 14), ++ ASPEED_PULL_DOWN_PINCONF(P4, SCU484, 15), ++ ASPEED_PULL_DOWN_PINCONF(J2, SCU484, 16), ++ ASPEED_PULL_DOWN_PINCONF(J1, SCU484, 17), ++ ASPEED_PULL_DOWN_PINCONF(K4, SCU484, 18), ++ ASPEED_PULL_DOWN_PINCONF(K2, SCU484, 19), ++ ASPEED_PULL_DOWN_PINCONF(K3, SCU484, 20), ++ ASPEED_PULL_DOWN_PINCONF(K1, SCU484, 21), ++ ASPEED_PULL_DOWN_PINCONF(L4, SCU484, 22), ++ ASPEED_PULL_DOWN_PINCONF(L2, SCU484, 23), ++ ASPEED_PULL_DOWN_PINCONF(L1, SCU484, 24), ++ ASPEED_PULL_DOWN_PINCONF(L3, SCU484, 25), ++ ASPEED_PULL_DOWN_PINCONF(A14, SCU484, 26), ++ ASPEED_PULL_DOWN_PINCONF(B14, SCU484, 27), ++ ASPEED_PULL_DOWN_PINCONF(HOLE1, SCU484, 28), ++ ASPEED_PULL_DOWN_PINCONF(HOLE2, SCU484, 29), ++ ASPEED_PULL_DOWN_PINCONF(HOLE3, SCU484, 30), ++ ASPEED_PULL_DOWN_PINCONF(HOLE4, SCU484, 31), ++ ASPEED_PULL_DOWN_PINCONF(U15, SCU488, 0), ++ ASPEED_PULL_DOWN_PINCONF(U16, SCU488, 1), ++ ASPEED_PULL_DOWN_PINCONF(T17, SCU488, 2), ++ ASPEED_PULL_DOWN_PINCONF(P12, SCU488, 3), ++ ASPEED_PULL_DOWN_PINCONF(R13, SCU488, 4), ++ ASPEED_PULL_DOWN_PINCONF(T15, SCU488, 5), ++ ASPEED_PULL_DOWN_PINCONF(T16, SCU488, 6), ++ ASPEED_PULL_DOWN_PINCONF(R12, SCU488, 7), ++ ASPEED_PULL_DOWN_PINCONF(R14, SCU488, 8), ++ ASPEED_PULL_DOWN_PINCONF(R17, SCU488, 9), ++ ASPEED_PULL_DOWN_PINCONF(R15, SCU488, 10), ++ ASPEED_PULL_DOWN_PINCONF(P13, SCU488, 11), ++ ASPEED_PULL_DOWN_PINCONF(R16, SCU488, 12), ++ ASPEED_PULL_DOWN_PINCONF(P14, SCU488, 13), ++ ASPEED_PULL_DOWN_PINCONF(P17, SCU488, 14), ++ ASPEED_PULL_DOWN_PINCONF(P16, SCU488, 15), ++ ASPEED_PULL_DOWN_PINCONF(P15, SCU488, 16), ++ ASPEED_PULL_DOWN_PINCONF(N17, SCU488, 17), ++ ASPEED_PULL_DOWN_PINCONF(N14, SCU488, 18), ++ ASPEED_PULL_DOWN_PINCONF(M17, SCU488, 19), ++ ASPEED_PULL_DOWN_PINCONF(N15, SCU488, 20), ++ ASPEED_PULL_DOWN_PINCONF(N16, SCU488, 21), ++ ASPEED_PULL_DOWN_PINCONF(L17, SCU488, 22), ++ ASPEED_PULL_DOWN_PINCONF(K17, SCU488, 23), ++ ASPEED_PULL_DOWN_PINCONF(J17, SCU488, 24), ++ ASPEED_PULL_DOWN_PINCONF(M14, SCU488, 25), ++ ASPEED_PULL_DOWN_PINCONF(M15, SCU488, 26), ++ ASPEED_PULL_DOWN_PINCONF(M16, SCU488, 27), ++ ASPEED_PULL_DOWN_PINCONF(A4, SCU488, 28), ++ ASPEED_PULL_DOWN_PINCONF(B5, SCU488, 29), ++ ASPEED_PULL_DOWN_PINCONF(A5, SCU488, 30), ++ ASPEED_PULL_DOWN_PINCONF(B4, SCU488, 31), ++ ASPEED_PULL_DOWN_PINCONF(A10, SCU48C, 0), ++ ASPEED_PULL_DOWN_PINCONF(B9, SCU48C, 1), ++ ASPEED_PULL_DOWN_PINCONF(C7, SCU48C, 2), ++ ASPEED_PULL_DOWN_PINCONF(E8, SCU48C, 3), ++ ASPEED_PULL_DOWN_PINCONF(E7, SCU48C, 4), ++ ASPEED_PULL_DOWN_PINCONF(B8, SCU48C, 5), ++ ASPEED_PULL_DOWN_PINCONF(A9, SCU48C, 6), ++ ASPEED_PULL_DOWN_PINCONF(A8, SCU48C, 7), ++ ASPEED_PULL_DOWN_PINCONF(HOLE5, SCU48C, 8), ++ ASPEED_PULL_DOWN_PINCONF(HOLE6, SCU48C, 9), ++ ASPEED_PULL_DOWN_PINCONF(HOLE7, SCU48C, 10), ++ ASPEED_PULL_DOWN_PINCONF(HOLE8, SCU48C, 11), ++ ASPEED_PULL_DOWN_PINCONF(HOLE9, SCU48C, 12), ++ ASPEED_PULL_DOWN_PINCONF(HOLE10, SCU48C, 13), ++ ASPEED_PULL_DOWN_PINCONF(HOLE11, SCU48C, 14), ++ ASPEED_PULL_DOWN_PINCONF(HOLE12, SCU48C, 15), ++ ASPEED_PULL_DOWN_PINCONF(E13, SCU48C, 16), ++ ASPEED_PULL_DOWN_PINCONF(C12, SCU48C, 17), ++ ASPEED_PULL_DOWN_PINCONF(D12, SCU48C, 18), ++ ASPEED_PULL_DOWN_PINCONF(E12, SCU48C, 19), ++ ASPEED_PULL_DOWN_PINCONF(D13, SCU48C, 20), ++ ASPEED_PULL_DOWN_PINCONF(HOLE13, SCU48C, 21), ++ ASPEED_PULL_DOWN_PINCONF(B12, SCU48C, 22), ++ ASPEED_PULL_DOWN_PINCONF(HOLE14, SCU48C, 23), ++ ASPEED_PULL_DOWN_PINCONF(D15, SCU48C, 24), ++ ASPEED_PULL_DOWN_PINCONF(B13, SCU48C, 25), ++ ASPEED_PULL_DOWN_PINCONF(C14, SCU48C, 26), ++ ASPEED_PULL_DOWN_PINCONF(C13, SCU48C, 27), ++ ASPEED_PULL_DOWN_PINCONF(D14, SCU48C, 28), ++ ASPEED_PULL_DOWN_PINCONF(F13, SCU48C, 29), ++ ASPEED_PULL_DOWN_PINCONF(E14, SCU48C, 30), ++ ASPEED_PULL_DOWN_PINCONF(HOLE15, SCU48C, 31), ++ ASPEED_PULL_DOWN_PINCONF(HOLE16, SCU490, 0), ++ ASPEED_PULL_DOWN_PINCONF(HOLE17, SCU490, 1), ++ ASPEED_PULL_DOWN_PINCONF(B1, SCU490, 2), ++ ASPEED_PULL_DOWN_PINCONF(C2, SCU490, 3), ++ ASPEED_PULL_DOWN_PINCONF(D3, SCU490, 4), ++ ASPEED_PULL_DOWN_PINCONF(C1, SCU490, 5), ++ ASPEED_PULL_DOWN_PINCONF(E5, SCU490, 6), ++ ASPEED_PULL_DOWN_PINCONF(HOLE18, SCU490, 7), ++ ASPEED_PULL_DOWN_PINCONF(HOLE19, SCU490, 8), ++ ASPEED_PULL_DOWN_PINCONF(HOLE20, SCU490, 9), ++ ASPEED_PULL_DOWN_PINCONF(HOLE21, SCU490, 10), ++ ASPEED_PULL_DOWN_PINCONF(HOLE22, SCU490, 11), ++ ASPEED_PULL_DOWN_PINCONF(HOLE23, SCU490, 12), ++ ASPEED_PULL_DOWN_PINCONF(HOLE24, SCU490, 13), ++ ASPEED_PULL_DOWN_PINCONF(HOLE25, SCU490, 14), ++ ASPEED_PULL_DOWN_PINCONF(HOLE26, SCU490, 15), ++ ASPEED_PULL_DOWN_PINCONF(C11, SCU490, 16), ++ ASPEED_PULL_DOWN_PINCONF(D11, SCU490, 17), ++ ASPEED_PULL_DOWN_PINCONF(HOLE27, SCU490, 18), ++ ASPEED_PULL_DOWN_PINCONF(HOLE28, SCU490, 19), ++ ASPEED_PULL_DOWN_PINCONF(C10, SCU490, 20), ++ ASPEED_PULL_DOWN_PINCONF(D10, SCU490, 21), ++ ASPEED_PULL_DOWN_PINCONF(HOLE29, SCU490, 22), ++ ASPEED_PULL_DOWN_PINCONF(HOLE30, SCU490, 23), ++ ASPEED_PULL_DOWN_PINCONF(HOLE31, SCU490, 24), ++ ASPEED_PULL_DOWN_PINCONF(HOLE32, SCU490, 25), ++ ASPEED_PULL_DOWN_PINCONF(D9, SCU490, 26), ++ ASPEED_PULL_DOWN_PINCONF(E9, SCU490, 27), ++ ASPEED_PULL_DOWN_PINCONF(C9, SCU490, 28), ++ ASPEED_PULL_DOWN_PINCONF(D8, SCU490, 29), ++ ASPEED_PULL_DOWN_PINCONF(C8, SCU490, 30), ++ ASPEED_PULL_DOWN_PINCONF(B11, SCU490, 31), ++ ASPEED_PULL_DOWN_PINCONF(B10, SCU494, 0), ++ ASPEED_PULL_DOWN_PINCONF(A12, SCU494, 1), ++ ASPEED_PULL_DOWN_PINCONF(A11, SCU494, 2), ++ ASPEED_PULL_DOWN_PINCONF(D7, SCU494, 3), ++ ASPEED_PULL_DOWN_PINCONF(HOLE33, SCU494, 4), ++ ASPEED_PULL_DOWN_PINCONF(HOLE34, SCU494, 5), ++ ASPEED_PULL_DOWN_PINCONF(HOLE35, SCU494, 6), ++ ASPEED_PULL_DOWN_PINCONF(HOLE36, SCU494, 7), ++ ASPEED_PULL_DOWN_PINCONF(G2, SCU494, 8), ++ ASPEED_PULL_DOWN_PINCONF(G1, SCU494, 9), ++ ASPEED_PULL_DOWN_PINCONF(G4, SCU494, 10), ++ ASPEED_PULL_DOWN_PINCONF(H3, SCU494, 11), ++ ASPEED_PULL_DOWN_PINCONF(H2, SCU494, 12), ++ ASPEED_PULL_DOWN_PINCONF(H1, SCU494, 13), ++ ASPEED_PULL_DOWN_PINCONF(J4, SCU494, 14), ++ ASPEED_PULL_DOWN_PINCONF(J3, SCU494, 15), ++ ASPEED_PULL_DOWN_PINCONF(C4, SCU494, 16), ++ ASPEED_PULL_DOWN_PINCONF(A2, SCU494, 17), ++ ASPEED_PULL_DOWN_PINCONF(C3, SCU494, 18), ++ ASPEED_PULL_DOWN_PINCONF(B2, SCU494, 19), ++ ASPEED_PULL_DOWN_PINCONF(B3, SCU494, 20), ++ ASPEED_PULL_DOWN_PINCONF(A3, SCU494, 21), ++ ASPEED_PULL_DOWN_PINCONF(F2, SCU494, 22), ++ ASPEED_PULL_DOWN_PINCONF(F1, SCU494, 23), ++ ASPEED_PULL_DOWN_PINCONF(D2, SCU494, 24), ++ ASPEED_PULL_DOWN_PINCONF(D1, SCU494, 25), ++ ASPEED_PULL_DOWN_PINCONF(F4, SCU494, 26), ++ ASPEED_PULL_DOWN_PINCONF(E4, SCU494, 27), ++ ASPEED_PULL_DOWN_PINCONF(E2, SCU494, 28), ++ ASPEED_PULL_DOWN_PINCONF(E1, SCU494, 29), ++ ASPEED_PULL_DOWN_PINCONF(F3, SCU494, 30), ++ ASPEED_PULL_DOWN_PINCONF(G5, SCU494, 31), ++ ASPEED_PULL_DOWN_PINCONF(T4, SCU498, 0), ++ ASPEED_PULL_DOWN_PINCONF(T3, SCU498, 1), ++ ASPEED_PULL_DOWN_PINCONF(R5, SCU498, 2), ++ ASPEED_PULL_DOWN_PINCONF(R6, SCU498, 3), ++ ASPEED_PULL_DOWN_PINCONF(T2, SCU498, 4), ++ ASPEED_PULL_DOWN_PINCONF(T5, SCU498, 5), ++ ASPEED_PULL_DOWN_PINCONF(U2, SCU498, 6), ++ ASPEED_PULL_DOWN_PINCONF(R7, SCU498, 7), ++ ASPEED_PULL_DOWN_PINCONF(P9, SCU498, 8), ++ ASPEED_PULL_DOWN_PINCONF(U4, SCU498, 9), ++ ASPEED_PULL_DOWN_PINCONF(U5, SCU498, 10), ++ ASPEED_PULL_DOWN_PINCONF(U6, SCU498, 11), ++ ASPEED_PULL_DOWN_PINCONF(T7, SCU498, 12), ++ ASPEED_PULL_DOWN_PINCONF(U7, SCU498, 13), ++ ASPEED_PULL_DOWN_PINCONF(R10, SCU498, 14), ++ ASPEED_PULL_DOWN_PINCONF(R11, SCU498, 15), ++ ASPEED_PULL_DOWN_PINCONF(C16, SCU498, 16), ++ ASPEED_PULL_DOWN_PINCONF(F16, SCU498, 17), ++ ASPEED_PULL_DOWN_PINCONF(B15, SCU498, 18), ++ ASPEED_PULL_DOWN_PINCONF(G15, SCU498, 19), ++ ASPEED_PULL_DOWN_PINCONF(G14, SCU498, 20), ++ ASPEED_PULL_DOWN_PINCONF(F14, SCU498, 21), ++ ASPEED_PULL_DOWN_PINCONF(E15, SCU498, 22), ++ ASPEED_PULL_DOWN_PINCONF(C15, SCU498, 23), ++}; ++ ++static const struct aspeed_pin_config_map aspeed_g7_ltpi_pin_config_map[] = { ++ { PIN_CONFIG_BIAS_PULL_DOWN, 0, 1, BIT_MASK(0) }, ++ { PIN_CONFIG_BIAS_PULL_DOWN, -1, 0, BIT_MASK(0) }, ++ { PIN_CONFIG_BIAS_PULL_UP, 0, 1, BIT_MASK(0) }, ++ { PIN_CONFIG_BIAS_PULL_UP, -1, 0, BIT_MASK(0) }, ++ { PIN_CONFIG_BIAS_DISABLE, -1, 1, BIT_MASK(0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 0, 0, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 1, 1, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 2, 2, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 3, 3, GENMASK(1, 0) }, ++ { PIN_CONFIG_POWER_SOURCE, 3300, 0, BIT_MASK(0) }, ++ { PIN_CONFIG_POWER_SOURCE, 1800, 1, BIT_MASK(0) }, ++}; ++ ++static int aspeed_g7_ltpi_dt_node_to_map(struct pinctrl_dev *pctldev, ++ struct device_node *np_config, ++ struct pinctrl_map **map, u32 *num_maps) ++{ ++ return pinconf_generic_dt_node_to_map(pctldev, np_config, map, num_maps, ++ PIN_MAP_TYPE_INVALID); ++} ++ ++static void aspeed_g7_ltpi_dt_free_map(struct pinctrl_dev *pctldev, ++ struct pinctrl_map *map, u32 num_maps) ++{ ++ kfree(map); ++} ++ ++static const struct pinctrl_ops aspeed_g7_ltpi_pinctrl_ops = { ++ .get_groups_count = aspeed_pinctrl_get_groups_count, ++ .get_group_name = aspeed_pinctrl_get_group_name, ++ .get_group_pins = aspeed_pinctrl_get_group_pins, ++ .pin_dbg_show = aspeed_pinctrl_pin_dbg_show, ++ .dt_node_to_map = aspeed_g7_ltpi_dt_node_to_map, ++ .dt_free_map = aspeed_g7_ltpi_dt_free_map, ++}; ++ ++static const struct pinmux_ops aspeed_g7_ltpi_pinmux_ops = { ++ .get_functions_count = aspeed_pinmux_get_fn_count, ++ .get_function_name = aspeed_pinmux_get_fn_name, ++ .get_function_groups = aspeed_pinmux_get_fn_groups, ++ .set_mux = aspeed_g7_pinmux_set_mux, ++ .gpio_request_enable = aspeed_g7_gpio_request_enable, ++ .strict = true, ++}; ++ ++static const struct pinconf_ops aspeed_g7_ltpi_pinconf_ops = { ++ .is_generic = true, ++ .pin_config_get = aspeed_pin_config_get, ++ .pin_config_set = aspeed_pin_config_set, ++ .pin_config_group_get = aspeed_pin_config_group_get, ++ .pin_config_group_set = aspeed_pin_config_group_set, ++}; ++ ++/* pinctrl_desc */ ++static struct pinctrl_desc aspeed_g7_ltpi_pinctrl_desc = { ++ .name = "aspeed-g7-ltpi-pinctrl", ++ .pins = aspeed_g7_ltpi_pins, ++ .npins = ARRAY_SIZE(aspeed_g7_ltpi_pins), ++ .pctlops = &aspeed_g7_ltpi_pinctrl_ops, ++ .pmxops = &aspeed_g7_ltpi_pinmux_ops, ++ .confops = &aspeed_g7_ltpi_pinconf_ops, ++ .owner = THIS_MODULE, ++}; ++ ++static int aspeed_g7_ltpi_pinctrl_probe(struct platform_device *pdev) ++{ ++ struct aspeed_pinctrl_data *ltpi_pinctrl_data; ++ ++ ltpi_pinctrl_data = devm_kzalloc(&pdev->dev, sizeof(*ltpi_pinctrl_data), ++ GFP_KERNEL); ++ if (!ltpi_pinctrl_data) ++ return -ENOMEM; ++ ++ ltpi_pinctrl_data->pins = aspeed_g7_ltpi_pins; ++ ltpi_pinctrl_data->npins = ARRAY_SIZE(aspeed_g7_ltpi_pins); ++ ltpi_pinctrl_data->pinmux.groups = aspeed_g7_ltpi_pingroups; ++ ltpi_pinctrl_data->pinmux.ngroups = ARRAY_SIZE(aspeed_g7_ltpi_pingroups); ++ ltpi_pinctrl_data->pinmux.functions = aspeed_g7_ltpi_funcs; ++ ltpi_pinctrl_data->pinmux.nfunctions = ARRAY_SIZE(aspeed_g7_ltpi_funcs); ++ ltpi_pinctrl_data->pinmux.configs_g7 = pin_cfg; ++ ltpi_pinctrl_data->pinmux.nconfigs_g7 = ARRAY_SIZE(pin_cfg); ++ ltpi_pinctrl_data->configs = aspeed_g7_ltpi_configs; ++ ltpi_pinctrl_data->nconfigs = ARRAY_SIZE(aspeed_g7_ltpi_configs); ++ ltpi_pinctrl_data->confmaps = aspeed_g7_ltpi_pin_config_map; ++ ltpi_pinctrl_data->nconfmaps = ARRAY_SIZE(aspeed_g7_ltpi_pin_config_map); ++ ++ return aspeed_pinctrl_probe(pdev, &aspeed_g7_ltpi_pinctrl_desc, ltpi_pinctrl_data); ++} ++ ++static const struct of_device_id aspeed_g7_ltpi_pinctrl_match[] = { ++ { .compatible = "aspeed,ast1700-pinctrl" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aspeed_g7_ltpi_pinctrl_match); ++ ++static struct platform_driver aspeed_g7_ltpi_pinctrl_driver = { ++ .probe = aspeed_g7_ltpi_pinctrl_probe, ++ .driver = { ++ .name = "aspeed-g7-ltpi-pinctrl", ++ .of_match_table = aspeed_g7_ltpi_pinctrl_match, ++ .suppress_bind_attrs = true, ++ }, ++}; ++ ++static int __init aspeed_g7_ltpi_pinctrl_register(void) ++{ ++ return platform_driver_register(&aspeed_g7_ltpi_pinctrl_driver); ++} ++arch_initcall(aspeed_g7_ltpi_pinctrl_register); +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc0.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc0.c +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc0.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc0.c 2026-09-09 15:51:44.732237860 +0000 +@@ -0,0 +1,503 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pinctrl-aspeed.h" ++#include "../pinctrl-utils.h" ++ ++#define SCU200 0x200 /* System Reset Control #1 */ ++ ++#define SCU400 0x400 /* Multi-function Pin Control #1 */ ++#define SCU404 0x404 /* Multi-function Pin Control #2 */ ++#define SCU408 0x408 /* Multi-function Pin Control #3 */ ++#define SCU40C 0x40C /* Multi-function Pin Control #3 */ ++#define SCU410 0x410 /* USB Multi-function Control Register */ ++#define SCU414 0x414 /* VGA Function Control Register */ ++ ++#define SCU480 0x480 /* GPIO18A0 IO Control Register */ ++#define SCU484 0x484 /* GPIO18A1 IO Control Register */ ++#define SCU488 0x488 /* GPIO18A2 IO Control Register */ ++#define SCU48C 0x48c /* GPIO18A3 IO Control Register */ ++#define SCU490 0x490 /* GPIO18A4 IO Control Register */ ++#define SCU494 0x494 /* GPIO18A5 IO Control Register */ ++#define SCU498 0x498 /* GPIO18A6 IO Control Register */ ++#define SCU49C 0x49c /* GPIO18A7 IO Control Register */ ++#define SCU4A0 0x4A0 /* GPIO18B0 IO Control Register */ ++#define SCU4A4 0x4A4 /* GPIO18B1 IO Control Register */ ++#define SCU4A8 0x4A8 /* GPIO18B2 IO Control Register */ ++#define SCU4AC 0x4AC /* GPIO18B3 IO Control Register */ ++ ++enum { ++ AC14, ++ AE15, ++ AD14, ++ AE14, ++ AF14, ++ AB13, ++ AB14, ++ AF15, ++ AF13, ++ AC13, ++ AD13, ++ AE13, ++ PORTA_U3, // SCU410[1:0] ++ PORTA_U2, // SCU410[3:2] ++ PORTB_U3, // SCU410[5:4] ++ PORTB_U2, // SCU410[7:6] ++ PORTA_U3_XHCI, // SCU410[9] ++ PORTA_U2_XHCI, // SCU410[9] ++ PORTB_U3_XHCI, // SCU410[10] ++ PORTB_U2_XHCI, // SCU410[10] ++ PORTA_MODE, // SCU410[25:24] ++ PORTB_MODE, // SCU410[29:28] ++ PORTA_U2_PHY, ++ PORTA_U3_PHY, ++ PORTB_U2_PHY, ++ PORTB_U3_PHY, ++ JTAG_PORT, ++ PCIERC0_PERST, ++ PCIERC1_PERST, ++}; ++ ++GROUP_DECL(EMMCG1, AC14, AE15, AD14); ++GROUP_DECL(EMMCG4, AC14, AE15, AD14, AE14, AF14, AB13); ++GROUP_DECL(EMMCG8, AC14, AE15, AD14, AE14, AF14, AB13, AF13, AC13, AD13, AE13); ++GROUP_DECL(EMMCWPN, AF15); ++GROUP_DECL(EMMCCDN, AB14); ++GROUP_DECL(VGADDC, AD13, AE13); ++GROUP_DECL(VB1, AC14, AE15, AD14, AE14); ++GROUP_DECL(VB0, AF15, AB14, AF13, AC13); ++//USB3A ++//xhci: BMC/PCIE, vHub/PHY/EXT port ++GROUP_DECL(USB3AXHD, PORTA_U3, PORTA_U3_XHCI); ++GROUP_DECL(USB3AXHPD, PORTA_U3, PORTA_U3_XHCI); ++GROUP_DECL(USB3AXH, PORTA_U3, PORTA_U3_XHCI, PORTA_U3_PHY); ++GROUP_DECL(USB3AXHP, PORTA_U3, PORTA_U3_XHCI, PORTA_U3_PHY); ++GROUP_DECL(USB3AXH2B, PORTA_U3, PORTA_U3_XHCI, PORTB_U3_PHY); ++GROUP_DECL(USB3AXHP2B, PORTA_U3, PORTA_U3_XHCI, PORTB_U3_PHY); ++ ++//USB2A ++//xhci: BMC/PCIE, vHub/PHY/EXT port ++GROUP_DECL(USB2AXHD1, PORTA_U2, PORTA_U2_XHCI); ++GROUP_DECL(USB2AXHPD1, PORTA_U2, PORTA_U2_XHCI); ++GROUP_DECL(USB2AXH, PORTA_U2, PORTA_U2_XHCI, PORTA_U2_PHY); ++GROUP_DECL(USB2AXHP, PORTA_U2, PORTA_U2_XHCI, PORTA_U2_PHY); ++GROUP_DECL(USB2AXH2B, PORTA_U2, PORTA_U2_XHCI, PORTB_U2_PHY); ++GROUP_DECL(USB2AXHP2B, PORTA_U2, PORTA_U2_XHCI, PORTB_U2_PHY); ++// vhub to phy ++GROUP_DECL(USB2AD1, PORTA_U2, PORTA_U2_PHY); ++//ehci ++GROUP_DECL(USB2AHPD0, PORTA_MODE); ++GROUP_DECL(USB2AH, PORTA_MODE, PORTA_U2_PHY); ++GROUP_DECL(USB2AHP, PORTA_MODE, PORTA_U2_PHY); ++GROUP_DECL(USB2AD0, PORTA_MODE, PORTA_U2_PHY); ++ ++//USB3B ++//xhci: BMC/PCIE, vHub/PHY/EXT port ++GROUP_DECL(USB3BXHD, PORTB_U3, PORTB_U3_XHCI); ++GROUP_DECL(USB3BXHPD, PORTB_U3, PORTB_U3_XHCI); ++GROUP_DECL(USB3BXH, PORTB_U3, PORTB_U3_XHCI, PORTB_U3_PHY); ++GROUP_DECL(USB3BXHP, PORTB_U3, PORTB_U3_XHCI, PORTB_U3_PHY); ++GROUP_DECL(USB3BXH2A, PORTB_U3, PORTB_U3_XHCI, PORTA_U3_PHY); ++GROUP_DECL(USB3BXHP2A, PORTB_U3, PORTB_U3_XHCI, PORTA_U3_PHY); ++ ++//USB2B ++//xhci: BMC/PCIE, vHub/PHY/EXT port ++GROUP_DECL(USB2BXHD1, PORTB_U2, PORTB_U2_XHCI); ++GROUP_DECL(USB2BXHPD1, PORTB_U2, PORTB_U2_XHCI); ++GROUP_DECL(USB2BXH, PORTB_U2, PORTB_U2_XHCI, PORTB_U2_PHY); ++GROUP_DECL(USB2BXHP, PORTB_U2, PORTB_U2_XHCI, PORTB_U2_PHY); ++GROUP_DECL(USB2BXH2A, PORTB_U2, PORTB_U2_XHCI, PORTA_U2_PHY); ++GROUP_DECL(USB2BXHP2A, PORTB_U2, PORTB_U2_XHCI, PORTA_U2_PHY); ++// vhub to phy ++GROUP_DECL(USB2BD1, PORTB_U2, PORTB_U2_PHY); ++//ehci ++GROUP_DECL(USB2BHPD0, PORTB_MODE); ++GROUP_DECL(USB2BH, PORTB_MODE, PORTB_U2_PHY); ++GROUP_DECL(USB2BHP, PORTB_MODE, PORTB_U2_PHY); ++GROUP_DECL(USB2BD0, PORTB_MODE, PORTB_U2_PHY); ++//JTAG port ++GROUP_DECL(PSP, JTAG_PORT); ++GROUP_DECL(SSP, JTAG_PORT); ++GROUP_DECL(TSP, JTAG_PORT); ++GROUP_DECL(DDR, JTAG_PORT); ++GROUP_DECL(USB3A, JTAG_PORT); ++GROUP_DECL(USB3B, JTAG_PORT); ++GROUP_DECL(PCIEA, JTAG_PORT); ++GROUP_DECL(PCIEB, JTAG_PORT); ++GROUP_DECL(JTAGM0, JTAG_PORT); ++//PCIE RC PERST ++GROUP_DECL(PCIERC0PERST, PCIERC0_PERST); ++GROUP_DECL(PCIERC1PERST, PCIERC1_PERST); ++ ++static struct aspeed_pin_group aspeed_g7_soc0_pingroups[] = { ++ ASPEED_PINCTRL_GROUP(EMMCG1), ++ ASPEED_PINCTRL_GROUP(EMMCG4), ++ ASPEED_PINCTRL_GROUP(EMMCG8), ++ ASPEED_PINCTRL_GROUP(EMMCWPN), ++ ASPEED_PINCTRL_GROUP(EMMCCDN), ++ ASPEED_PINCTRL_GROUP(VGADDC), ++ ASPEED_PINCTRL_GROUP(VB1), ++ ASPEED_PINCTRL_GROUP(VB0), ++ ASPEED_PINCTRL_GROUP(USB3AXHD), ++ ASPEED_PINCTRL_GROUP(USB3AXHPD), ++ ASPEED_PINCTRL_GROUP(USB3AXH), ++ ASPEED_PINCTRL_GROUP(USB3AXHP), ++ ASPEED_PINCTRL_GROUP(USB3AXH2B), ++ ASPEED_PINCTRL_GROUP(USB3AXHP2B), ++ ASPEED_PINCTRL_GROUP(USB2AXHD1), ++ ASPEED_PINCTRL_GROUP(USB2AXHPD1), ++ ASPEED_PINCTRL_GROUP(USB2AXH), ++ ASPEED_PINCTRL_GROUP(USB2AXHP), ++ ASPEED_PINCTRL_GROUP(USB2AXH2B), ++ ASPEED_PINCTRL_GROUP(USB2AXHP2B), ++ ASPEED_PINCTRL_GROUP(USB2AD1), ++ ASPEED_PINCTRL_GROUP(USB2AHPD0), ++ ASPEED_PINCTRL_GROUP(USB2AH), ++ ASPEED_PINCTRL_GROUP(USB2AHP), ++ ASPEED_PINCTRL_GROUP(USB2AD0), ++ ASPEED_PINCTRL_GROUP(USB3BXHD), ++ ASPEED_PINCTRL_GROUP(USB3BXHPD), ++ ASPEED_PINCTRL_GROUP(USB3BXH), ++ ASPEED_PINCTRL_GROUP(USB3BXHP), ++ ASPEED_PINCTRL_GROUP(USB3BXH2A), ++ ASPEED_PINCTRL_GROUP(USB3BXHP2A), ++ ASPEED_PINCTRL_GROUP(USB2BXHD1), ++ ASPEED_PINCTRL_GROUP(USB2BXHPD1), ++ ASPEED_PINCTRL_GROUP(USB2BXH), ++ ASPEED_PINCTRL_GROUP(USB2BXHP), ++ ASPEED_PINCTRL_GROUP(USB2BXH2A), ++ ASPEED_PINCTRL_GROUP(USB2BXHP2A), ++ ASPEED_PINCTRL_GROUP(USB2BD1), ++ ASPEED_PINCTRL_GROUP(USB2BHPD0), ++ ASPEED_PINCTRL_GROUP(USB2BH), ++ ASPEED_PINCTRL_GROUP(USB2BHP), ++ ASPEED_PINCTRL_GROUP(USB2BD0), ++ ASPEED_PINCTRL_GROUP(PSP), ++ ASPEED_PINCTRL_GROUP(SSP), ++ ASPEED_PINCTRL_GROUP(TSP), ++ ASPEED_PINCTRL_GROUP(DDR), ++ ASPEED_PINCTRL_GROUP(USB3A), ++ ASPEED_PINCTRL_GROUP(USB3B), ++ ASPEED_PINCTRL_GROUP(PCIEA), ++ ASPEED_PINCTRL_GROUP(PCIEB), ++ ASPEED_PINCTRL_GROUP(JTAGM0), ++ ASPEED_PINCTRL_GROUP(PCIERC0PERST), ++ ASPEED_PINCTRL_GROUP(PCIERC1PERST), ++}; ++ ++FUNC_DECL_(EMMC, "EMMCG1", "EMMCG4", "EMMCG8", "EMMCWPN", "EMMCCDN"); ++FUNC_DECL_(VGADDC, "VGADDC"); ++FUNC_DECL_(VB, "VB0", "VB1"); ++FUNC_DECL_(USB3A, "USB3AXHD", "USB3AXHPD", "USB3AXH", "USB3AXHP", "USB3AXH2B", ++ "USB3AXHP2B"); ++FUNC_DECL_(USB2A, "USB2AXHD1", "USB2AXHPD1", "USB2AXH", "USB2AXHP", "USB2AXH2B", ++ "USB2AXHP2B", "USB2AD1", "USB2AHPD0", "USB2AH", "USB2AHP", ++ "USB2AD0"); ++FUNC_DECL_(USB3B, "USB3BXHD", "USB3BXHPD", "USB3BXH", "USB3BXHP", "USB3BXH2A", ++ "USB3BXHP2A"); ++FUNC_DECL_(USB2B, "USB2BXHD1", "USB2BXHPD1", "USB2BXH", "USB2BXHP", "USB2BXH2A", ++ "USB2BXHP2A", "USB2BD1", "USB2BHPD0", "USB2BH", "USB2BHP", ++ "USB2BD0"); ++FUNC_DECL_(JTAG0, "PSP", "SSP", "TSP", "DDR", "USB3A", "USB3B", ++ "PCIEA", "PCIEB", "JTAGM0"); ++FUNC_DECL_(PCIERC, "PCIERC0PERST", "PCIERC1PERST"); ++ ++static struct aspeed_pin_function aspeed_g7_soc0_funcs[] = { ++ ASPEED_PINCTRL_FUNC(EMMC), ++ ASPEED_PINCTRL_FUNC(VGADDC), ++ ASPEED_PINCTRL_FUNC(VB), ++ ASPEED_PINCTRL_FUNC(USB3A), ++ ASPEED_PINCTRL_FUNC(USB2A), ++ ASPEED_PINCTRL_FUNC(USB3B), ++ ASPEED_PINCTRL_FUNC(USB2B), ++ ASPEED_PINCTRL_FUNC(JTAG0), ++ ASPEED_PINCTRL_FUNC(PCIERC), ++}; ++ ++static const struct pinctrl_pin_desc aspeed_g7_soc0_pins[] = { ++ PINCTRL_PIN(AC14, "AC14"), ++ PINCTRL_PIN(AE15, "AE15"), ++ PINCTRL_PIN(AD14, "AD14"), ++ PINCTRL_PIN(AE14, "AE14"), ++ PINCTRL_PIN(AF14, "AF14"), ++ PINCTRL_PIN(AB13, "AB13"), ++ PINCTRL_PIN(AF15, "AF15"), ++ PINCTRL_PIN(AB14, "AB14"), ++ PINCTRL_PIN(AF13, "AF13"), ++ PINCTRL_PIN(AC13, "AC13"), ++ PINCTRL_PIN(AD13, "AD13"), ++ PINCTRL_PIN(AE13, "AE13"), ++ PINCTRL_PIN(PORTA_U3, "PORTA_U3"), ++ PINCTRL_PIN(PORTA_U2, "PORTA_U2"), ++ PINCTRL_PIN(PORTB_U3, "PORTB_U3"), ++ PINCTRL_PIN(PORTB_U2, "PORTB_U2"), ++ PINCTRL_PIN(PORTA_U3_XHCI, "PORTA_U3_XHCI"), ++ PINCTRL_PIN(PORTA_U2_XHCI, "PORTA_U2_XHCI"), ++ PINCTRL_PIN(PORTB_U3_XHCI, "PORTB_U3_XHCI"), ++ PINCTRL_PIN(PORTB_U2_XHCI, "PORTB_U2_XHCI"), ++ PINCTRL_PIN(PORTA_MODE, "PORTA_MODE"), ++ PINCTRL_PIN(PORTA_U3_PHY, "PORTA_U3_PHY"), ++ PINCTRL_PIN(PORTA_U2_PHY, "PORTA_U2_PHY"), ++ PINCTRL_PIN(PORTB_MODE, "PORTB_MODE"), ++ PINCTRL_PIN(PORTB_U3_PHY, "PORTB_U3_PHY"), ++ PINCTRL_PIN(PORTB_U2_PHY, "PORTB_U2_PHY"), ++ PINCTRL_PIN(JTAG_PORT, "JTAG_PORT"), ++ PINCTRL_PIN(PCIERC0_PERST, "PCIERC0_PERST"), ++ PINCTRL_PIN(PCIERC1_PERST, "PCIERC1_PERST"), ++}; ++ ++FUNCFG_DESCL(AC14, PIN_CFG(EMMCG1, SCU400, BIT_MASK(0), BIT(0)), ++ PIN_CFG(EMMCG4, SCU400, BIT_MASK(0), BIT(0)), ++ PIN_CFG(EMMCG8, SCU400, BIT_MASK(0), BIT(0)), ++ PIN_CFG(VB1, SCU404, BIT_MASK(0), BIT(0))); ++FUNCFG_DESCL(AE15, PIN_CFG(EMMCG1, SCU400, BIT_MASK(1), BIT(1)), ++ PIN_CFG(EMMCG4, SCU400, BIT_MASK(1), BIT(1)), ++ PIN_CFG(EMMCG8, SCU400, BIT_MASK(1), BIT(1)), ++ PIN_CFG(VB1, SCU404, BIT_MASK(1), BIT(1))); ++FUNCFG_DESCL(AD14, PIN_CFG(EMMCG1, SCU400, BIT_MASK(2), BIT(2)), ++ PIN_CFG(EMMCG4, SCU400, BIT_MASK(2), BIT(2)), ++ PIN_CFG(EMMCG8, SCU400, BIT_MASK(2), BIT(2)), ++ PIN_CFG(VB1, SCU404, BIT_MASK(2), BIT(2))); ++FUNCFG_DESCL(AE14, PIN_CFG(EMMCG4, SCU400, BIT_MASK(3), BIT(3)), ++ PIN_CFG(EMMCG8, SCU400, BIT_MASK(3), BIT(3)), ++ PIN_CFG(VB1, SCU404, BIT_MASK(3), BIT(3))); ++FUNCFG_DESCL(AF14, PIN_CFG(EMMCG4, SCU400, BIT_MASK(4), BIT(4)), ++ PIN_CFG(EMMCG8, SCU400, BIT_MASK(4), BIT(4))); ++FUNCFG_DESCL(AB13, PIN_CFG(EMMCG4, SCU400, BIT_MASK(5), BIT(5)), ++ PIN_CFG(EMMCG8, SCU400, BIT_MASK(5), BIT(5))); ++FUNCFG_DESCL(AB14, PIN_CFG(EMMCCDN, SCU400, BIT_MASK(6), BIT(6)), ++ PIN_CFG(VB0, SCU404, BIT_MASK(6), BIT(6))); ++FUNCFG_DESCL(AF15, PIN_CFG(EMMCWPN, SCU400, BIT_MASK(7), BIT(7)), ++ PIN_CFG(VB0, SCU404, BIT_MASK(7), BIT(7))); ++FUNCFG_DESCL(AF13, PIN_CFG(EMMCG8, SCU400, BIT_MASK(8), BIT(8)), ++ PIN_CFG(VB0, SCU404, BIT_MASK(8), BIT(8))); ++FUNCFG_DESCL(AC13, PIN_CFG(EMMCG8, SCU400, BIT_MASK(9), BIT(9)), ++ PIN_CFG(VB0, SCU404, BIT_MASK(9), BIT(9))); ++FUNCFG_DESCL(AD13, PIN_CFG(EMMCG8, SCU400, BIT_MASK(10), BIT(10)), ++ PIN_CFG(VGADDC, SCU404, BIT_MASK(10), BIT(10))); ++FUNCFG_DESCL(AE13, PIN_CFG(EMMCG8, SCU400, BIT_MASK(11), BIT(11)), ++ PIN_CFG(VGADDC, SCU404, BIT_MASK(11), BIT(11))); ++FUNCFG_DESCL(PORTA_U3, PIN_CFG(USB3AXHD, SCU410, GENMASK(1, 0), 0), ++ PIN_CFG(USB3AXHPD, SCU410, GENMASK(1, 0), 0), ++ PIN_CFG(USB3AXH, SCU410, GENMASK(1, 0), 2), ++ PIN_CFG(USB3AXHP, SCU410, GENMASK(1, 0), 2), ++ PIN_CFG(USB3AXH2B, SCU410, GENMASK(1, 0), 3), ++ PIN_CFG(USB3AXHP2B, SCU410, GENMASK(1, 0), 3)); ++FUNCFG_DESCL(PORTA_U2, PIN_CFG(USB2AXHD1, SCU410, GENMASK(3, 2), 0), ++ PIN_CFG(USB2AXHPD1, SCU410, GENMASK(3, 2), 0), ++ PIN_CFG(USB2AXH, SCU410, GENMASK(3, 2), 2 << 2), ++ PIN_CFG(USB2AXHP, SCU410, GENMASK(3, 2), 2 << 2), ++ PIN_CFG(USB2AXH2B, SCU410, GENMASK(3, 2), 3 << 2), ++ PIN_CFG(USB2AXHP2B, SCU410, GENMASK(3, 2), 3 << 2), ++ PIN_CFG(USB2AD1, SCU410, GENMASK(3, 2), 1 << 2)); ++FUNCFG_DESCL(PORTB_U3, PIN_CFG(USB3BXHD, SCU410, GENMASK(5, 4), 0), ++ PIN_CFG(USB3BXHPD, SCU410, GENMASK(5, 4), 0), ++ PIN_CFG(USB3BXH, SCU410, GENMASK(5, 4), 2 << 4), ++ PIN_CFG(USB3BXHP, SCU410, GENMASK(5, 4), 2 << 4), ++ PIN_CFG(USB3BXH2A, SCU410, GENMASK(5, 4), 3 << 4), ++ PIN_CFG(USB3BXHP2A, SCU410, GENMASK(5, 4), 3 << 4)); ++FUNCFG_DESCL(PORTB_U2, PIN_CFG(USB2BXHD1, SCU410, GENMASK(7, 6), 0), ++ PIN_CFG(USB2BXHPD1, SCU410, GENMASK(7, 6), 0), ++ PIN_CFG(USB2BXH, SCU410, GENMASK(7, 6), 2 << 6), ++ PIN_CFG(USB2BXHP, SCU410, GENMASK(7, 6), 2 << 6), ++ PIN_CFG(USB2BXH2A, SCU410, GENMASK(7, 6), 3 << 6), ++ PIN_CFG(USB2BXHP2A, SCU410, GENMASK(7, 6), 3 << 6), ++ PIN_CFG(USB2BD1, SCU410, GENMASK(7, 6), 1 << 6)); ++FUNCFG_DESCL(PORTA_U3_XHCI, PIN_CFG(USB3AXHD, SCU410, BIT_MASK(9), 1 << 9), ++ PIN_CFG(USB3AXHPD, SCU410, BIT_MASK(9), 0), ++ PIN_CFG(USB3AXH, SCU410, BIT_MASK(9), 1 << 9), ++ PIN_CFG(USB3AXHP, SCU410, BIT_MASK(9), 0), ++ PIN_CFG(USB3AXH2B, SCU410, BIT_MASK(9), 1 << 9), ++ PIN_CFG(USB3AXHP2B, SCU410, BIT_MASK(9), 0)); ++FUNCFG_DESCL(PORTA_U2_XHCI, PIN_CFG(USB2AXHD1, SCU410, BIT_MASK(9), 1 << 9), ++ PIN_CFG(USB2AXHPD1, SCU410, BIT_MASK(9), 0), ++ PIN_CFG(USB2AXH, SCU410, BIT_MASK(9), 1 << 9), ++ PIN_CFG(USB2AXHP, SCU410, BIT_MASK(9), 0), ++ PIN_CFG(USB2AXH2B, SCU410, BIT_MASK(9), 1 << 9), ++ PIN_CFG(USB2AXHP2B, SCU410, BIT_MASK(9), 0)); ++FUNCFG_DESCL(PORTB_U3_XHCI, PIN_CFG(USB3BXHD, SCU410, BIT_MASK(10), 1 << 10), ++ PIN_CFG(USB3BXHPD, SCU410, BIT_MASK(10), 0), ++ PIN_CFG(USB3BXH, SCU410, BIT_MASK(10), 1 << 10), ++ PIN_CFG(USB3BXHP, SCU410, BIT_MASK(10), 0), ++ PIN_CFG(USB3BXH2A, SCU410, BIT_MASK(10), 1 << 10), ++ PIN_CFG(USB3BXHP2A, SCU410, BIT_MASK(10), 0)); ++FUNCFG_DESCL(PORTB_U2_XHCI, PIN_CFG(USB2BXHD1, SCU410, BIT_MASK(10), 1 << 10), ++ PIN_CFG(USB2BXHPD1, SCU410, BIT_MASK(10), 0), ++ PIN_CFG(USB2BXH, SCU410, BIT_MASK(10), 1 << 10), ++ PIN_CFG(USB2BXHP, SCU410, BIT_MASK(10), 0), ++ PIN_CFG(USB2BXH2A, SCU410, BIT_MASK(10), 1 << 10), ++ PIN_CFG(USB2BXHP2A, SCU410, BIT_MASK(10), 0)); ++FUNCFG_DESCL(PORTA_MODE, PIN_CFG(USB2AHPD0, SCU410, GENMASK(25, 24), 0), ++ PIN_CFG(USB2AH, SCU410, GENMASK(25, 24), 2 << 24), ++ PIN_CFG(USB2AHP, SCU410, GENMASK(25, 24), 3 << 24), ++ PIN_CFG(USB2AD0, SCU410, GENMASK(25, 24), 1 << 24)); ++FUNCFG_DESCL(PORTB_MODE, PIN_CFG(USB2BHPD0, SCU410, GENMASK(29, 28), 0), ++ PIN_CFG(USB2BH, SCU410, GENMASK(29, 28), 2 << 28), ++ PIN_CFG(USB2BHP, SCU410, GENMASK(29, 28), 3 << 28), ++ PIN_CFG(USB2BD0, SCU410, GENMASK(29, 28), 1 << 28)); ++FUNCFG_DESCL(PORTA_U3_PHY); ++FUNCFG_DESCL(PORTA_U2_PHY); ++FUNCFG_DESCL(PORTB_U3_PHY); ++FUNCFG_DESCL(PORTB_U2_PHY); ++FUNCFG_DESCL(JTAG_PORT, PIN_CFG(PSP, SCU408, GENMASK(12, 5), 0x0 << 5), ++ PIN_CFG(SSP, SCU408, GENMASK(12, 5), 0x41 << 5), ++ PIN_CFG(TSP, SCU408, GENMASK(12, 5), 0x42 << 5), ++ PIN_CFG(DDR, SCU408, GENMASK(12, 5), 0x43 << 5), ++ PIN_CFG(USB3A, SCU408, GENMASK(12, 5), 0x44 << 5), ++ PIN_CFG(USB3B, SCU408, GENMASK(12, 5), 0x45 << 5), ++ PIN_CFG(PCIEA, SCU408, GENMASK(12, 5), 0x46 << 5), ++ PIN_CFG(PCIEB, SCU408, GENMASK(12, 5), 0x47 << 5), ++ PIN_CFG(JTAGM0, SCU408, GENMASK(12, 5), 0x8 << 5)); ++FUNCFG_DESCL(PCIERC0_PERST, PIN_CFG(PCIERC0PERST, SCU200, BIT_MASK(21), 1 << 21)); ++FUNCFG_DESCL(PCIERC1_PERST, PIN_CFG(PCIERC1PERST, SCU200, BIT_MASK(19), 1 << 19)); ++ ++static const struct aspeed_g7_pincfg pin_cfg[] = { ++ PINCFG_PIN(AC14), PINCFG_PIN(AE15), ++ PINCFG_PIN(AD14), PINCFG_PIN(AE14), ++ PINCFG_PIN(AF14), PINCFG_PIN(AB13), ++ PINCFG_PIN(AB14), PINCFG_PIN(AF15), ++ PINCFG_PIN(AF13), PINCFG_PIN(AC13), ++ PINCFG_PIN(AD13), PINCFG_PIN(AE13), ++ PINCFG_PIN(PORTA_U3), PINCFG_PIN(PORTA_U2), ++ PINCFG_PIN(PORTB_U3), PINCFG_PIN(PORTB_U2), ++ PINCFG_PIN(PORTA_U3_XHCI), PINCFG_PIN(PORTA_U2_XHCI), ++ PINCFG_PIN(PORTB_U3_XHCI), PINCFG_PIN(PORTB_U2_XHCI), ++ PINCFG_PIN(PORTA_MODE), PINCFG_PIN(PORTB_MODE), ++ PINCFG_PIN(PORTA_U3_PHY), PINCFG_PIN(PORTA_U2_PHY), ++ PINCFG_PIN(PORTB_U3_PHY), PINCFG_PIN(PORTB_U2_PHY), ++ PINCFG_PIN(JTAG_PORT), PINCFG_PIN(PCIERC0_PERST), ++ PINCFG_PIN(PCIERC1_PERST), ++}; ++ ++static const struct pinctrl_ops aspeed_g7_soc0_pinctrl_ops = { ++ .get_groups_count = aspeed_pinctrl_get_groups_count, ++ .get_group_name = aspeed_pinctrl_get_group_name, ++ .get_group_pins = aspeed_pinctrl_get_group_pins, ++ .pin_dbg_show = aspeed_pinctrl_pin_dbg_show, ++ .dt_node_to_map = pinconf_generic_dt_node_to_map_all, ++ .dt_free_map = pinctrl_utils_free_map, ++}; ++ ++static const struct pinmux_ops aspeed_g7_soc0_pinmux_ops = { ++ .get_functions_count = aspeed_pinmux_get_fn_count, ++ .get_function_name = aspeed_pinmux_get_fn_name, ++ .get_function_groups = aspeed_pinmux_get_fn_groups, ++ .set_mux = aspeed_g7_pinmux_set_mux, ++ .gpio_request_enable = aspeed_g7_gpio_request_enable, ++ .strict = true, ++}; ++ ++static const struct pinconf_ops aspeed_g7_soc0_pinconf_ops = { ++ .is_generic = true, ++ .pin_config_get = aspeed_pin_config_get, ++ .pin_config_set = aspeed_pin_config_set, ++ .pin_config_group_get = aspeed_pin_config_group_get, ++ .pin_config_group_set = aspeed_pin_config_group_set, ++}; ++ ++/* pinctrl_desc */ ++static struct pinctrl_desc aspeed_g7_soc0_pinctrl_desc = { ++ .name = "aspeed-g7-soc0-pinctrl", ++ .pins = aspeed_g7_soc0_pins, ++ .npins = ARRAY_SIZE(aspeed_g7_soc0_pins), ++ .pctlops = &aspeed_g7_soc0_pinctrl_ops, ++ .pmxops = &aspeed_g7_soc0_pinmux_ops, ++ .confops = &aspeed_g7_soc0_pinconf_ops, ++ .owner = THIS_MODULE, ++}; ++ ++static struct aspeed_pin_config aspeed_g7_configs[] = { ++ /* GPIO18A */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { AC14, AC14 }, SCU480, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AE15, AE15 }, SCU484, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AD14, AD14 }, SCU488, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AE14, AE14 }, SCU48C, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AF14, AF14 }, SCU490, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AB13, AB13 }, SCU494, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AB14, AB14 }, SCU498, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AF15, AF15 }, SCU49C, GENMASK(3, 0) }, ++ /* GPIO18B */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { AF13, AF13 }, SCU4A0, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AC13, AC13 }, SCU4A4, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AD13, AD13 }, SCU4A8, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AE13, AE13 }, SCU4AC, GENMASK(3, 0) }, ++}; ++ ++static const struct aspeed_pin_config_map aspeed_g7_pin_config_map[] = { ++ { PIN_CONFIG_DRIVE_STRENGTH, 0, 0, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 1, 1, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 2, 2, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 3, 3, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 4, 4, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 5, 5, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 6, 6, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 7, 7, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 8, 8, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 9, 9, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 10, 10, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 11, 11, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 12, 12, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 13, 13, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 14, 14, GENMASK(3, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, 15, 15, GENMASK(3, 0) }, ++ ++}; ++ ++static struct aspeed_pinctrl_data aspeed_g7_pinctrl_data = { ++ .pins = aspeed_g7_soc0_pins, ++ .npins = ARRAY_SIZE(aspeed_g7_soc0_pins), ++ .pinmux = { ++ .groups = aspeed_g7_soc0_pingroups, ++ .ngroups = ARRAY_SIZE(aspeed_g7_soc0_pingroups), ++ .functions = aspeed_g7_soc0_funcs, ++ .nfunctions = ARRAY_SIZE(aspeed_g7_soc0_funcs), ++ .configs_g7 = pin_cfg, ++ .nconfigs_g7 = ARRAY_SIZE(pin_cfg), ++ }, ++ .configs = aspeed_g7_configs, ++ .nconfigs = ARRAY_SIZE(aspeed_g7_configs), ++ .confmaps = aspeed_g7_pin_config_map, ++ .nconfmaps = ARRAY_SIZE(aspeed_g7_pin_config_map), ++}; ++ ++static int aspeed_g7_soc0_pinctrl_probe(struct platform_device *pdev) ++{ ++ return aspeed_pinctrl_probe(pdev, &aspeed_g7_soc0_pinctrl_desc, ++ &aspeed_g7_pinctrl_data); ++} ++ ++static const struct of_device_id aspeed_g7_soc0_pinctrl_match[] = { ++ { .compatible = "aspeed,ast2700-soc0-pinctrl" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aspeed_g7_soc0_pinctrl_match); ++ ++static struct platform_driver aspeed_g7_soc0_pinctrl_driver = { ++ .probe = aspeed_g7_soc0_pinctrl_probe, ++ .driver = { ++ .name = "aspeed-g7-soc0-pinctrl", ++ .of_match_table = aspeed_g7_soc0_pinctrl_match, ++ .suppress_bind_attrs = true, ++ }, ++}; ++ ++static int __init aspeed_g7_soc0_pinctrl_register(void) ++{ ++ return platform_driver_register(&aspeed_g7_soc0_pinctrl_driver); ++} ++arch_initcall(aspeed_g7_soc0_pinctrl_register); +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c 2026-09-09 15:51:44.732237860 +0000 +@@ -0,0 +1,2537 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pinctrl-aspeed.h" ++ ++#define SCU3B0 0x3B0 /* USB Controller Register */ ++#define SCU3B4 0x3B4 /* USB Controller Lock Register */ ++#define SCU3B8 0x3B8 /* USB Controller Secure Register #1 */ ++#define SCU3BC 0x3BC /* USB Controller Secure Register #2 */ ++#define SCU3C0 0x3C0 /* USB Controller Secure Register #3 */ ++#define SCU400 0x400 /* Multi-function Pin Control #1 */ ++#define SCU404 0x404 /* Multi-function Pin Control #2 */ ++#define SCU408 0x408 /* Multi-function Pin Control #3 */ ++#define SCU40C 0x40C /* Multi-function Pin Control #4 */ ++#define SCU410 0x410 /* Multi-function Pin Control #5 */ ++#define SCU414 0x414 /* Multi-function Pin Control #6 */ ++#define SCU418 0x418 /* Multi-function Pin Control #7 */ ++#define SCU41C 0x41C /* Multi-function Pin Control #8 */ ++#define SCU420 0x420 /* Multi-function Pin Control #9 */ ++#define SCU424 0x424 /* Multi-function Pin Control #10 */ ++#define SCU428 0x428 /* Multi-function Pin Control #11 */ ++#define SCU42C 0x42C /* Multi-function Pin Control #12 */ ++#define SCU430 0x430 /* Multi-function Pin Control #13 */ ++#define SCU434 0x434 /* Multi-function Pin Control #14 */ ++#define SCU438 0x438 /* Multi-function Pin Control #15 */ ++#define SCU43C 0x43C /* Multi-function Pin Control #16 */ ++#define SCU440 0x440 /* Multi-function Pin Control #17 */ ++#define SCU444 0x444 /* Multi-function Pin Control #18 */ ++#define SCU448 0x448 /* Multi-function Pin Control #19 */ ++#define SCU44C 0x44C /* Multi-function Pin Control #20 */ ++#define SCU450 0x450 /* Multi-function Pin Control #21 */ ++#define SCU454 0x454 /* Multi-function Pin Control #22 */ ++#define SCU458 0x458 /* Multi-function Pin Control #23 */ ++#define SCU45C 0x45C /* Multi-function Pin Control #24 */ ++#define SCU460 0x460 /* Multi-function Pin Control #25 */ ++#define SCU464 0x464 /* Multi-function Pin Control #26 */ ++#define SCU468 0x468 /* Multi-function Pin Control #27 */ ++#define SCU46C 0x46C /* Multi-function Pin Control #28 */ ++#define SCU470 0x470 /* Multi-function Pin Control #29 */ ++#define SCU474 0x474 /* Multi-function Pin Control #30 */ ++#define SCU478 0x478 /* Multi-function Pin Control #31 */ ++#define SCU47C 0x47C ++#define SCU480 0x480 /* Disable Pull-Down Control #1 */ ++#define SCU484 0x484 /* Disable Pull-Down Control #2 */ ++#define SCU488 0x488 /* Disable Pull-Down Control #3 */ ++#define SCU48C 0x48C /* Disable Pull-Down Control #4 */ ++#define SCU490 0x490 /* Disable Pull-Down Control #5 */ ++#define SCU494 0x494 /* Disable Pull-Down Control #6 */ ++#define SCU498 0x498 /* Disable Pull-Down Control #7 */ ++#define SCU4A0 0x4A0 /* Voltage Selection */ ++#define SCU4C0 0x4C0 /* Driving Strength #0 A-I */ ++#define SCU4C4 0x4C4 /* Driving Strength #1 J-K */ ++#define SCU4C8 0x4C8 /* Driving Strength #2 L-M */ ++#define SCU4CC 0x4CC /* Driving Strength #3 N-O */ ++#define SCU4D0 0x4D0 /* Driving Strength #4 P-Q */ ++#define SCU4D4 0x4D4 /* Driving Strength #5 R-S */ ++#define SCU4D8 0x4D8 /* Driving Strength #6 T-U */ ++#define SCU4DC 0x4DC /* Driving Strength #7 W */ ++ ++#define SCU908 0x908 /* PCIe RC PERST Pin Control */ ++ ++enum { ++ C16, ++ C14, ++ C11, ++ D9, ++ F14, ++ D10, ++ C12, ++ C13, ++ AC26, ++ AA25, ++ AB23, ++ U22, ++ V21, ++ N26, ++ P25, ++ N25, ++ V23, ++ W22, ++ AB26, ++ AD26, ++ P26, ++ AE26, ++ AF26, ++ AF25, ++ AE25, ++ AD25, ++ AF23, ++ AF20, ++ AF21, ++ AE21, ++ AE23, ++ AD22, ++ AF17, ++ AA16, ++ Y16, ++ V17, ++ J13, ++ AB16, ++ AC16, ++ AF16, ++ AA15, ++ AB15, ++ AC15, ++ AD15, ++ Y15, ++ AA14, ++ W16, ++ V16, ++ AB18, ++ AC18, ++ K13, ++ AA17, ++ AB17, ++ AD16, ++ AC17, ++ AD17, ++ AE16, ++ AE17, ++ AB24, ++ W26, ++ HOLE0, ++ HOLE1, ++ HOLE2, ++ HOLE3, ++ W25, ++ Y23, ++ Y24, ++ W21, ++ AA23, ++ AC22, ++ AB22, ++ Y21, ++ AE20, ++ AF19, ++ Y22, ++ AA20, ++ AA22, ++ AB20, ++ AF18, ++ AE19, ++ AD20, ++ AC20, ++ AA21, ++ AB21, ++ AC19, ++ AE18, ++ AD19, ++ AD18, ++ U25, ++ U26, ++ Y26, ++ AA24, ++ R25, ++ AA26, ++ R26, ++ Y25, ++ B16, ++ D14, ++ B15, ++ B14, ++ C17, ++ B13, ++ E14, ++ C15, ++ D24, ++ B23, ++ B22, ++ C23, ++ B18, ++ B21, ++ M15, ++ B19, ++ B26, ++ A25, ++ A24, ++ B24, ++ E26, ++ A21, ++ A19, ++ A18, ++ D26, ++ C26, ++ A23, ++ A22, ++ B25, ++ F26, ++ A26, ++ A14, ++ E10, ++ E13, ++ D12, ++ F10, ++ E11, ++ F11, ++ F13, ++ N15, ++ C20, ++ C19, ++ A8, ++ R14, ++ A7, ++ P14, ++ D20, ++ A6, ++ B6, ++ N14, ++ B7, ++ B8, ++ B9, ++ M14, ++ J11, ++ E7, ++ D19, ++ B11, ++ D15, ++ B12, ++ B10, ++ P13, ++ C18, ++ C6, ++ C7, ++ D7, ++ N13, ++ C8, ++ C9, ++ C10, ++ M16, ++ A15, ++ G11, ++ H7, ++ H8, ++ H9, ++ H10, ++ H11, ++ J9, ++ J10, ++ E9, ++ F9, ++ F8, ++ M13, ++ F7, ++ D8, ++ E8, ++ L12, ++ F12, ++ E12, ++ J12, ++ G7, ++ G8, ++ G9, ++ G10, ++ K12, ++ W17, ++ V18, ++ W18, ++ Y17, ++ AA18, ++ AA13, ++ Y18, ++ AA12, ++ W20, ++ V20, ++ Y11, ++ V14, ++ V19, ++ W14, ++ Y20, ++ AB19, ++ U21, ++ T24, ++ V24, ++ V22, ++ T23, ++ AC25, ++ AB25, ++ AC24, ++ SGMII0, ++ PCIERC2_PERST, ++ PORTC_MODE, // SCU3B0[1:0] ++ PORTD_MODE, // SCU3B0[3:2] ++}; ++ ++GROUP_DECL(ESPI0, B16, D14, B15, B14, C17, B13, E14, C15); ++GROUP_DECL(ESPI1, C16, C14, C11, D9, F14, D10, C12, C13); ++GROUP_DECL(LPC0, AF26, AF25, B16, D14, B15, B14, C17, B13, E14, C15); ++GROUP_DECL(LPC1, C16, C14, C11, D9, F14, D10, C12, C13, AE16, AE17); ++GROUP_DECL(SD, C16, C14, C11, D9, F14, D10, C12, C13); ++GROUP_DECL(VPI, C16, C14, C11, D9, F14, D10, C12, C13, AC26, AA25, AB23, U22, ++ V21, N26, P25, N25, V23, W22, AB26, AD26, P26, AE26, AF26, AF25, ++ AE25, AD25, AF23, AF20, AF21, AE21); ++GROUP_DECL(OSCCLK, C17); ++GROUP_DECL(TACH0, AC26); ++GROUP_DECL(TACH1, AA25); ++GROUP_DECL(TACH2, AB23); ++GROUP_DECL(TACH3, U22); ++GROUP_DECL(THRU0, AC26, AA25); ++GROUP_DECL(THRU1, AB23, U22); ++GROUP_DECL(TACH4, V21); ++GROUP_DECL(TACH5, N26); ++GROUP_DECL(TACH6, P25); ++GROUP_DECL(TACH7, N25); ++GROUP_DECL(NTCS5, V21); ++GROUP_DECL(NDCD5, N26); ++GROUP_DECL(NDSR5, P25); ++GROUP_DECL(NRI5, N25); ++GROUP_DECL(SALT12, AB26); ++GROUP_DECL(SALT13, AD26); ++GROUP_DECL(SALT14, P26); ++GROUP_DECL(SALT15, AE26); ++GROUP_DECL(NDTR5, V23); ++GROUP_DECL(NRTS5, W22); ++GROUP_DECL(NCTS6, AB26); ++GROUP_DECL(NDCD6, AD26); ++GROUP_DECL(NDSR6, P26); ++GROUP_DECL(NRI6, AE26); ++GROUP_DECL(NDTR6, AF26); ++GROUP_DECL(NRTS6, AF25); ++GROUP_DECL(TACH8, V23); ++GROUP_DECL(TACH9, W22); ++GROUP_DECL(TACH10, AB26); ++GROUP_DECL(TACH11, AD26); ++GROUP_DECL(TACH12, P26); ++GROUP_DECL(TACH13, AE26); ++GROUP_DECL(TACH14, AF26); ++GROUP_DECL(TACH15, AF25); ++GROUP_DECL(SPIM0, AE25, AD25, AF23, AF20, AF21, AE21, AE23); ++GROUP_DECL(PWM0, AE25); ++GROUP_DECL(PWM1, AD25); ++GROUP_DECL(PWM2, AF23); ++GROUP_DECL(PWM3, AF20); ++GROUP_DECL(PWM4, AF21); ++GROUP_DECL(PWM5, AE21); ++GROUP_DECL(PWM6, AE23); ++GROUP_DECL(PWM7, AD22); ++GROUP_DECL(SIOPBON0, AE25); ++GROUP_DECL(SIOPBIN0, AD25); ++GROUP_DECL(SIOSCIN0, AF23); ++GROUP_DECL(SIOS3N0, AF20); ++GROUP_DECL(SIOS5N0, AF21); ++GROUP_DECL(SIOPWREQN0, AE21); ++GROUP_DECL(SIOONCTRLN0, AE23); ++GROUP_DECL(SIOPWRGD0, AD22); ++GROUP_DECL(NCTS0, AF17); ++GROUP_DECL(NDCD0, AA16); ++GROUP_DECL(NDSR0, Y16); ++GROUP_DECL(NRI0, V17); ++GROUP_DECL(NDTR0, J13); ++GROUP_DECL(NRTS0, AB16); ++GROUP_DECL(TXD0, AC16); ++GROUP_DECL(RXD0, AF16); ++GROUP_DECL(NCTS1, AA15); ++GROUP_DECL(NDCD1, AB15); ++GROUP_DECL(NDSR1, AC15); ++GROUP_DECL(NRI1, AD15); ++GROUP_DECL(NDTR1, Y15); ++GROUP_DECL(NRTS1, AA14); ++GROUP_DECL(TXD1, W16); ++GROUP_DECL(RXD1, V16); ++GROUP_DECL(TXD2, AB18); ++GROUP_DECL(RXD2, AC18); ++GROUP_DECL(TXD3, K13); ++GROUP_DECL(RXD3, AA17); ++GROUP_DECL(NCTS5, V21); ++GROUP_DECL(TXD5, AB17); ++GROUP_DECL(RXD5, AD16); ++GROUP_DECL(TXD6, AC17); ++GROUP_DECL(RXD6, AD17); ++GROUP_DECL(TXD7, AE16); ++GROUP_DECL(RXD7, AE17); ++GROUP_DECL(TXD8, M15); ++GROUP_DECL(RXD8, B19); ++GROUP_DECL(TXD9, B26); ++GROUP_DECL(RXD9, A25); ++GROUP_DECL(TXD10, A24); ++GROUP_DECL(RXD10, B24); ++GROUP_DECL(TXD11, E26); ++GROUP_DECL(RXD11, A21); ++GROUP_DECL(SPIM1, K13, AA17, AB17, AD16, AC17, AD17, AE16, AE17); ++GROUP_DECL(WDTRST0N, K13); ++GROUP_DECL(WDTRST1N, AA17); ++GROUP_DECL(WDTRST2N, AB17); ++GROUP_DECL(WDTRST3N, AD16); ++GROUP_DECL(WDTRST4N, AC25); ++GROUP_DECL(WDTRST5N, AB25); ++GROUP_DECL(WDTRST6N, AC24); ++GROUP_DECL(WDTRST7N, AB24); ++GROUP_DECL(PWM8, K13); ++GROUP_DECL(PWM9, AA17); ++GROUP_DECL(PWM10, AB17); ++GROUP_DECL(PWM11, AD16); ++GROUP_DECL(PWM12, AC17); ++GROUP_DECL(PWM13, AD17); ++GROUP_DECL(PWM14, AE16); ++GROUP_DECL(PWM15, AE17); ++GROUP_DECL(SALT0, AC17); ++GROUP_DECL(SALT1, AD17); ++GROUP_DECL(SALT2, AC15); ++GROUP_DECL(SALT3, AD15); ++GROUP_DECL(FSI0, AD20, AC20); ++GROUP_DECL(FSI1, AA21, AB21); ++GROUP_DECL(FSI2, AC19, AE18); ++GROUP_DECL(FSI3, AD19, AD18); ++GROUP_DECL(SPIM2, W25, Y23, Y24, W21, AA23, AC22, AB22, Y21); ++GROUP_DECL(SALT4, W17); ++GROUP_DECL(SALT5, V18); ++GROUP_DECL(SALT6, W18); ++GROUP_DECL(SALT7, Y17); ++GROUP_DECL(SALT8, AA18); ++GROUP_DECL(SALT9, AA13); ++GROUP_DECL(SALT10, Y18); ++GROUP_DECL(SALT11, AA12); ++GROUP_DECL(ADC0, W17); ++GROUP_DECL(ADC1, V18); ++GROUP_DECL(ADC2, W18); ++GROUP_DECL(ADC3, Y17); ++GROUP_DECL(ADC4, AA18); ++GROUP_DECL(ADC5, AA13); ++GROUP_DECL(ADC6, Y18); ++GROUP_DECL(ADC7, AA12); ++GROUP_DECL(ADC8, W20); ++GROUP_DECL(ADC9, V20); ++GROUP_DECL(ADC10, Y11); ++GROUP_DECL(ADC11, V14); ++GROUP_DECL(ADC12, V19); ++GROUP_DECL(ADC13, W14); ++GROUP_DECL(ADC14, Y20); ++GROUP_DECL(ADC15, AB19); ++GROUP_DECL(AUXPWRGOOD0, W14); ++GROUP_DECL(AUXPWRGOOD1, Y20); ++GROUP_DECL(SGPM0, U21, T24, V22, T23); ++GROUP_DECL(SGPM1, AC25, AB25, AB24, W26); ++GROUP_DECL(I2C0, G11, H7); ++GROUP_DECL(I2C1, H8, H9); ++GROUP_DECL(I2C2, H10, H11); ++GROUP_DECL(I2C3, J9, J10); ++GROUP_DECL(I2C4, E9, F9); ++GROUP_DECL(I2C5, F8, M13); ++GROUP_DECL(I2C6, F7, D8); ++GROUP_DECL(I2C7, E8, L12); ++GROUP_DECL(I2C8, F12, E12); ++GROUP_DECL(I2C9, J12, G7); ++GROUP_DECL(I2C10, G8, G9); ++GROUP_DECL(I2C11, G10, K12); ++GROUP_DECL(I2C12, AC18, AA17); ++GROUP_DECL(I2C13, AB17, AD16); ++GROUP_DECL(I2C14, AC17, AD17); ++GROUP_DECL(I2C15, AE16, AE17); ++GROUP_DECL(DI2C0, C16, D9); ++GROUP_DECL(DI2C1, C14, F14); ++GROUP_DECL(DI2C2, D10, C12); ++GROUP_DECL(DI2C3, C11, C13); ++GROUP_DECL(DI2C8, U25, U26); ++GROUP_DECL(DI2C9, Y26, AA24); ++GROUP_DECL(DI2C10, R25, AA26); ++GROUP_DECL(DI2C11, R26, Y25); ++GROUP_DECL(DI2C12, W25, Y23); ++GROUP_DECL(DI2C13, Y24, W21); ++GROUP_DECL(DI2C14, AA23, AC22); ++GROUP_DECL(DI2C15, AB22, Y21); ++GROUP_DECL(LTPI_PS_I2C0, G11, H7); ++GROUP_DECL(LTPI_PS_I2C1, H8, H9); ++GROUP_DECL(LTPI_PS_I2C2, H10, H11); ++GROUP_DECL(LTPI_PS_I2C3, J9, J10); ++GROUP_DECL(SIOPBON1, AF17); ++GROUP_DECL(SIOPBIN1, AA16); ++GROUP_DECL(SIOSCIN1, Y16); ++GROUP_DECL(SIOS3N1, V17); ++GROUP_DECL(SIOS5N1, J13); ++GROUP_DECL(SIOPWREQN1, AB16); ++GROUP_DECL(SIOONCTRLN1, AA15); ++GROUP_DECL(SIOPWRGD1, AB15); ++GROUP_DECL(HVI3C12, W25, Y23); ++GROUP_DECL(HVI3C13, Y24, W21); ++GROUP_DECL(HVI3C14, AA23, AC22); ++GROUP_DECL(HVI3C15, AB22, Y21); ++GROUP_DECL(I3C4, AE20, AF19); ++GROUP_DECL(I3C5, Y22, AA20); ++GROUP_DECL(I3C6, AA22, AB20); ++GROUP_DECL(I3C7, AF18, AE19); ++GROUP_DECL(I3C8, AD20, AC20); ++GROUP_DECL(I3C9, AA21, AB21); ++GROUP_DECL(I3C10, AC19, AE18); ++GROUP_DECL(I3C11, AD19, AD18); ++GROUP_DECL(HVI3C0, U25, U26); ++GROUP_DECL(HVI3C1, Y26, AA24); ++GROUP_DECL(HVI3C2, R25, AA26); ++GROUP_DECL(HVI3C3, R26, Y25); ++GROUP_DECL(LTPI, U25, U26, Y26, AA24); ++GROUP_DECL(SPI0, D24, B23, B22); ++GROUP_DECL(QSPI0, C23, B18); ++GROUP_DECL(SPI0CS1, B21); ++GROUP_DECL(SPI0ABR, M15); ++GROUP_DECL(SPI0WPN, B19); ++GROUP_DECL(SPI1, B26, A25, A24); ++GROUP_DECL(QSPI1, B24, E26); ++GROUP_DECL(SPI1CS1, A21); ++GROUP_DECL(SPI1ABR, A19); ++GROUP_DECL(SPI1WPN, A18); ++GROUP_DECL(SPI2, D26, C26, A23, A22); ++GROUP_DECL(QSPI2, B25, F26); ++GROUP_DECL(SPI2CS1, A26); ++GROUP_DECL(THRU2, A19, A18); ++GROUP_DECL(THRU3, B25, F26); ++GROUP_DECL(JTAGM1, F10, E11, F11, F13); ++GROUP_DECL(JTAGM1TRST, D12); ++GROUP_DECL(MDIO0, B9, M14); ++GROUP_DECL(MDIO1, C9, C10); ++GROUP_DECL(MDIO2, E10, E13); ++GROUP_DECL(FWQSPI, M16, A15); ++GROUP_DECL(FWSPIABR, A14); ++GROUP_DECL(FWSPIWPN, N15); ++GROUP_DECL(RGMII0, C20, C19, A8, R14, A7, P14, D20, A6, B6, N14, B7, B8); ++GROUP_DECL(RGMII1, D19, B11, D15, B12, B10, P13, C18, C6, C7, D7, N13, C8); ++GROUP_DECL(RMII0, C20, A8, R14, A7, P14, A6, B6, N14); ++GROUP_DECL(RMII0RCLKO, D20); ++GROUP_DECL(RMII1, D19, D15, B12, B10, P13, C6, C7, D7); ++GROUP_DECL(RMII1RCLKO, C18); ++GROUP_DECL(VGA, J11, E7); ++GROUP_DECL(DSGPM0, D19, B10, C7, D7); ++GROUP_DECL(SGPS, B11, C18, N13, C8); ++GROUP_DECL(I2CF0, F12, E12, J12, G7); ++GROUP_DECL(I2CF1, E9, F9, F8, M13); ++GROUP_DECL(I2CF2, F7, D8, E8, L12); ++GROUP_DECL(CANBUS, G8, G9); ++GROUP_DECL(CANBUSSTBY, G7); ++GROUP_DECL(USBUART, G10, K12); ++GROUP_DECL(HBLED, V24); ++GROUP_DECL(MACLINK0, U21); ++GROUP_DECL(MACLINK1, AC24); ++GROUP_DECL(MACLINK2, T24); ++GROUP_DECL(NCTS2, U21); ++GROUP_DECL(NDCD2, T24); ++GROUP_DECL(NDSR2, V22); ++GROUP_DECL(NRI2, T23); ++GROUP_DECL(NDTR2, AC25); ++GROUP_DECL(NRTS2, AB25); ++GROUP_DECL(SMON0, U21, T24, V22, T23); ++GROUP_DECL(SMON1, AB24, W26, AC25, AB25); ++GROUP_DECL(SGMII, SGMII0); ++//PCIE RC PERST ++GROUP_DECL(PE2SGRSTN, PCIERC2_PERST, E10); ++GROUP_DECL(USB2CUD, PORTC_MODE); ++GROUP_DECL(USB2CD, PORTC_MODE); ++GROUP_DECL(USB2CH, PORTC_MODE); ++GROUP_DECL(USB2CU, PORTC_MODE); ++GROUP_DECL(USB2DD, PORTD_MODE); ++GROUP_DECL(USB2DH, PORTD_MODE); ++ ++static struct aspeed_pin_group aspeed_g7_soc1_pingroups[] = { ++ ASPEED_PINCTRL_GROUP(ESPI0), ++ ASPEED_PINCTRL_GROUP(ESPI1), ++ ASPEED_PINCTRL_GROUP(LPC0), ++ ASPEED_PINCTRL_GROUP(LPC1), ++ ASPEED_PINCTRL_GROUP(SD), ++ ASPEED_PINCTRL_GROUP(VPI), ++ ASPEED_PINCTRL_GROUP(OSCCLK), ++ ASPEED_PINCTRL_GROUP(TACH0), ++ ASPEED_PINCTRL_GROUP(TACH1), ++ ASPEED_PINCTRL_GROUP(TACH2), ++ ASPEED_PINCTRL_GROUP(TACH3), ++ ASPEED_PINCTRL_GROUP(THRU0), ++ ASPEED_PINCTRL_GROUP(THRU1), ++ ASPEED_PINCTRL_GROUP(TACH4), ++ ASPEED_PINCTRL_GROUP(TACH5), ++ ASPEED_PINCTRL_GROUP(TACH6), ++ ASPEED_PINCTRL_GROUP(TACH7), ++ ASPEED_PINCTRL_GROUP(NTCS5), ++ ASPEED_PINCTRL_GROUP(NDCD5), ++ ASPEED_PINCTRL_GROUP(NDSR5), ++ ASPEED_PINCTRL_GROUP(NRI5), ++ ASPEED_PINCTRL_GROUP(SALT12), ++ ASPEED_PINCTRL_GROUP(SALT13), ++ ASPEED_PINCTRL_GROUP(SALT14), ++ ASPEED_PINCTRL_GROUP(SALT15), ++ ASPEED_PINCTRL_GROUP(NDTR5), ++ ASPEED_PINCTRL_GROUP(NRTS5), ++ ASPEED_PINCTRL_GROUP(NCTS6), ++ ASPEED_PINCTRL_GROUP(NDCD6), ++ ASPEED_PINCTRL_GROUP(NDSR6), ++ ASPEED_PINCTRL_GROUP(NRI6), ++ ASPEED_PINCTRL_GROUP(NDTR6), ++ ASPEED_PINCTRL_GROUP(NRTS6), ++ ASPEED_PINCTRL_GROUP(TACH8), ++ ASPEED_PINCTRL_GROUP(TACH9), ++ ASPEED_PINCTRL_GROUP(TACH10), ++ ASPEED_PINCTRL_GROUP(TACH11), ++ ASPEED_PINCTRL_GROUP(TACH12), ++ ASPEED_PINCTRL_GROUP(TACH13), ++ ASPEED_PINCTRL_GROUP(TACH14), ++ ASPEED_PINCTRL_GROUP(TACH15), ++ ASPEED_PINCTRL_GROUP(SPIM0), ++ ASPEED_PINCTRL_GROUP(PWM0), ++ ASPEED_PINCTRL_GROUP(PWM1), ++ ASPEED_PINCTRL_GROUP(PWM2), ++ ASPEED_PINCTRL_GROUP(PWM3), ++ ASPEED_PINCTRL_GROUP(PWM4), ++ ASPEED_PINCTRL_GROUP(PWM5), ++ ASPEED_PINCTRL_GROUP(PWM6), ++ ASPEED_PINCTRL_GROUP(PWM7), ++ ASPEED_PINCTRL_GROUP(SIOPBON0), ++ ASPEED_PINCTRL_GROUP(SIOPBIN0), ++ ASPEED_PINCTRL_GROUP(SIOSCIN0), ++ ASPEED_PINCTRL_GROUP(SIOS3N0), ++ ASPEED_PINCTRL_GROUP(SIOS5N0), ++ ASPEED_PINCTRL_GROUP(SIOPWREQN0), ++ ASPEED_PINCTRL_GROUP(SIOONCTRLN0), ++ ASPEED_PINCTRL_GROUP(SIOPWRGD0), ++ ASPEED_PINCTRL_GROUP(NCTS0), ++ ASPEED_PINCTRL_GROUP(NDCD0), ++ ASPEED_PINCTRL_GROUP(NDSR0), ++ ASPEED_PINCTRL_GROUP(NRI0), ++ ASPEED_PINCTRL_GROUP(NDTR0), ++ ASPEED_PINCTRL_GROUP(NRTS0), ++ ASPEED_PINCTRL_GROUP(TXD0), ++ ASPEED_PINCTRL_GROUP(RXD0), ++ ASPEED_PINCTRL_GROUP(NCTS1), ++ ASPEED_PINCTRL_GROUP(NDCD1), ++ ASPEED_PINCTRL_GROUP(NDSR1), ++ ASPEED_PINCTRL_GROUP(NRI1), ++ ASPEED_PINCTRL_GROUP(NDTR1), ++ ASPEED_PINCTRL_GROUP(NRTS1), ++ ASPEED_PINCTRL_GROUP(TXD1), ++ ASPEED_PINCTRL_GROUP(RXD1), ++ ASPEED_PINCTRL_GROUP(TXD2), ++ ASPEED_PINCTRL_GROUP(RXD2), ++ ASPEED_PINCTRL_GROUP(TXD3), ++ ASPEED_PINCTRL_GROUP(RXD3), ++ ASPEED_PINCTRL_GROUP(NCTS5), ++ ASPEED_PINCTRL_GROUP(NDCD5), ++ ASPEED_PINCTRL_GROUP(NDSR5), ++ ASPEED_PINCTRL_GROUP(NRI5), ++ ASPEED_PINCTRL_GROUP(NDTR5), ++ ASPEED_PINCTRL_GROUP(NRTS5), ++ ASPEED_PINCTRL_GROUP(TXD5), ++ ASPEED_PINCTRL_GROUP(RXD5), ++ ASPEED_PINCTRL_GROUP(NCTS6), ++ ASPEED_PINCTRL_GROUP(NDCD6), ++ ASPEED_PINCTRL_GROUP(NDSR6), ++ ASPEED_PINCTRL_GROUP(NRI6), ++ ASPEED_PINCTRL_GROUP(NDTR6), ++ ASPEED_PINCTRL_GROUP(NRTS6), ++ ASPEED_PINCTRL_GROUP(TXD6), ++ ASPEED_PINCTRL_GROUP(RXD6), ++ ASPEED_PINCTRL_GROUP(TXD6), ++ ASPEED_PINCTRL_GROUP(RXD6), ++ ASPEED_PINCTRL_GROUP(TXD7), ++ ASPEED_PINCTRL_GROUP(RXD7), ++ ASPEED_PINCTRL_GROUP(TXD8), ++ ASPEED_PINCTRL_GROUP(RXD8), ++ ASPEED_PINCTRL_GROUP(TXD9), ++ ASPEED_PINCTRL_GROUP(RXD9), ++ ASPEED_PINCTRL_GROUP(TXD10), ++ ASPEED_PINCTRL_GROUP(RXD10), ++ ASPEED_PINCTRL_GROUP(TXD11), ++ ASPEED_PINCTRL_GROUP(RXD11), ++ ASPEED_PINCTRL_GROUP(SPIM1), ++ ASPEED_PINCTRL_GROUP(WDTRST0N), ++ ASPEED_PINCTRL_GROUP(WDTRST1N), ++ ASPEED_PINCTRL_GROUP(WDTRST2N), ++ ASPEED_PINCTRL_GROUP(WDTRST3N), ++ ASPEED_PINCTRL_GROUP(WDTRST4N), ++ ASPEED_PINCTRL_GROUP(WDTRST5N), ++ ASPEED_PINCTRL_GROUP(WDTRST6N), ++ ASPEED_PINCTRL_GROUP(WDTRST7N), ++ ASPEED_PINCTRL_GROUP(PWM8), ++ ASPEED_PINCTRL_GROUP(PWM9), ++ ASPEED_PINCTRL_GROUP(PWM10), ++ ASPEED_PINCTRL_GROUP(PWM11), ++ ASPEED_PINCTRL_GROUP(PWM12), ++ ASPEED_PINCTRL_GROUP(PWM13), ++ ASPEED_PINCTRL_GROUP(PWM14), ++ ASPEED_PINCTRL_GROUP(PWM15), ++ ASPEED_PINCTRL_GROUP(SALT0), ++ ASPEED_PINCTRL_GROUP(SALT1), ++ ASPEED_PINCTRL_GROUP(SALT2), ++ ASPEED_PINCTRL_GROUP(SALT3), ++ ASPEED_PINCTRL_GROUP(FSI0), ++ ASPEED_PINCTRL_GROUP(FSI1), ++ ASPEED_PINCTRL_GROUP(FSI2), ++ ASPEED_PINCTRL_GROUP(FSI3), ++ ASPEED_PINCTRL_GROUP(SPIM2), ++ ASPEED_PINCTRL_GROUP(SALT4), ++ ASPEED_PINCTRL_GROUP(SALT5), ++ ASPEED_PINCTRL_GROUP(SALT6), ++ ASPEED_PINCTRL_GROUP(SALT7), ++ ASPEED_PINCTRL_GROUP(SALT8), ++ ASPEED_PINCTRL_GROUP(SALT9), ++ ASPEED_PINCTRL_GROUP(SALT10), ++ ASPEED_PINCTRL_GROUP(SALT11), ++ ASPEED_PINCTRL_GROUP(ADC0), ++ ASPEED_PINCTRL_GROUP(ADC1), ++ ASPEED_PINCTRL_GROUP(ADC2), ++ ASPEED_PINCTRL_GROUP(ADC3), ++ ASPEED_PINCTRL_GROUP(ADC4), ++ ASPEED_PINCTRL_GROUP(ADC5), ++ ASPEED_PINCTRL_GROUP(ADC6), ++ ASPEED_PINCTRL_GROUP(ADC7), ++ ASPEED_PINCTRL_GROUP(ADC8), ++ ASPEED_PINCTRL_GROUP(ADC9), ++ ASPEED_PINCTRL_GROUP(ADC10), ++ ASPEED_PINCTRL_GROUP(ADC11), ++ ASPEED_PINCTRL_GROUP(ADC12), ++ ASPEED_PINCTRL_GROUP(ADC13), ++ ASPEED_PINCTRL_GROUP(ADC14), ++ ASPEED_PINCTRL_GROUP(ADC15), ++ ASPEED_PINCTRL_GROUP(AUXPWRGOOD0), ++ ASPEED_PINCTRL_GROUP(AUXPWRGOOD1), ++ ASPEED_PINCTRL_GROUP(SGPM0), ++ ASPEED_PINCTRL_GROUP(SGPM1), ++ ASPEED_PINCTRL_GROUP(I2C0), ++ ASPEED_PINCTRL_GROUP(I2C1), ++ ASPEED_PINCTRL_GROUP(I2C2), ++ ASPEED_PINCTRL_GROUP(I2C3), ++ ASPEED_PINCTRL_GROUP(I2C4), ++ ASPEED_PINCTRL_GROUP(I2C5), ++ ASPEED_PINCTRL_GROUP(I2C6), ++ ASPEED_PINCTRL_GROUP(I2C7), ++ ASPEED_PINCTRL_GROUP(I2C8), ++ ASPEED_PINCTRL_GROUP(I2C9), ++ ASPEED_PINCTRL_GROUP(I2C10), ++ ASPEED_PINCTRL_GROUP(I2C11), ++ ASPEED_PINCTRL_GROUP(I2C12), ++ ASPEED_PINCTRL_GROUP(I2C13), ++ ASPEED_PINCTRL_GROUP(I2C14), ++ ASPEED_PINCTRL_GROUP(I2C15), ++ ASPEED_PINCTRL_GROUP(LTPI_PS_I2C0), ++ ASPEED_PINCTRL_GROUP(LTPI_PS_I2C1), ++ ASPEED_PINCTRL_GROUP(LTPI_PS_I2C2), ++ ASPEED_PINCTRL_GROUP(LTPI_PS_I2C3), ++ ASPEED_PINCTRL_GROUP(DI2C0), ++ ASPEED_PINCTRL_GROUP(DI2C1), ++ ASPEED_PINCTRL_GROUP(DI2C2), ++ ASPEED_PINCTRL_GROUP(DI2C3), ++ ASPEED_PINCTRL_GROUP(DI2C8), ++ ASPEED_PINCTRL_GROUP(DI2C9), ++ ASPEED_PINCTRL_GROUP(DI2C10), ++ ASPEED_PINCTRL_GROUP(DI2C11), ++ ASPEED_PINCTRL_GROUP(DI2C12), ++ ASPEED_PINCTRL_GROUP(DI2C13), ++ ASPEED_PINCTRL_GROUP(DI2C14), ++ ASPEED_PINCTRL_GROUP(DI2C15), ++ ASPEED_PINCTRL_GROUP(SIOPBON1), ++ ASPEED_PINCTRL_GROUP(SIOPBIN1), ++ ASPEED_PINCTRL_GROUP(SIOSCIN1), ++ ASPEED_PINCTRL_GROUP(SIOS3N1), ++ ASPEED_PINCTRL_GROUP(SIOS5N1), ++ ASPEED_PINCTRL_GROUP(SIOPWREQN1), ++ ASPEED_PINCTRL_GROUP(SIOONCTRLN1), ++ ASPEED_PINCTRL_GROUP(SIOPWRGD1), ++ ASPEED_PINCTRL_GROUP(HVI3C12), ++ ASPEED_PINCTRL_GROUP(HVI3C13), ++ ASPEED_PINCTRL_GROUP(HVI3C14), ++ ASPEED_PINCTRL_GROUP(HVI3C15), ++ ASPEED_PINCTRL_GROUP(I3C4), ++ ASPEED_PINCTRL_GROUP(I3C5), ++ ASPEED_PINCTRL_GROUP(I3C6), ++ ASPEED_PINCTRL_GROUP(I3C7), ++ ASPEED_PINCTRL_GROUP(I3C8), ++ ASPEED_PINCTRL_GROUP(I3C9), ++ ASPEED_PINCTRL_GROUP(I3C10), ++ ASPEED_PINCTRL_GROUP(I3C11), ++ ASPEED_PINCTRL_GROUP(HVI3C0), ++ ASPEED_PINCTRL_GROUP(HVI3C1), ++ ASPEED_PINCTRL_GROUP(HVI3C2), ++ ASPEED_PINCTRL_GROUP(HVI3C3), ++ ASPEED_PINCTRL_GROUP(LTPI), ++ ASPEED_PINCTRL_GROUP(SPI0), ++ ASPEED_PINCTRL_GROUP(QSPI0), ++ ASPEED_PINCTRL_GROUP(SPI0CS1), ++ ASPEED_PINCTRL_GROUP(SPI0ABR), ++ ASPEED_PINCTRL_GROUP(SPI0WPN), ++ ASPEED_PINCTRL_GROUP(SPI1), ++ ASPEED_PINCTRL_GROUP(QSPI1), ++ ASPEED_PINCTRL_GROUP(SPI1CS1), ++ ASPEED_PINCTRL_GROUP(SPI1ABR), ++ ASPEED_PINCTRL_GROUP(SPI1WPN), ++ ASPEED_PINCTRL_GROUP(SPI2), ++ ASPEED_PINCTRL_GROUP(QSPI2), ++ ASPEED_PINCTRL_GROUP(SPI2CS1), ++ ASPEED_PINCTRL_GROUP(THRU2), ++ ASPEED_PINCTRL_GROUP(THRU3), ++ ASPEED_PINCTRL_GROUP(JTAGM1), ++ ASPEED_PINCTRL_GROUP(JTAGM1TRST), ++ ASPEED_PINCTRL_GROUP(MDIO0), ++ ASPEED_PINCTRL_GROUP(MDIO1), ++ ASPEED_PINCTRL_GROUP(MDIO2), ++ ASPEED_PINCTRL_GROUP(FWQSPI), ++ ASPEED_PINCTRL_GROUP(FWSPIABR), ++ ASPEED_PINCTRL_GROUP(FWSPIWPN), ++ ASPEED_PINCTRL_GROUP(RGMII0), ++ ASPEED_PINCTRL_GROUP(RGMII1), ++ ASPEED_PINCTRL_GROUP(RMII0), ++ ASPEED_PINCTRL_GROUP(RMII0RCLKO), ++ ASPEED_PINCTRL_GROUP(RMII1), ++ ASPEED_PINCTRL_GROUP(RMII1RCLKO), ++ ASPEED_PINCTRL_GROUP(VGA), ++ ASPEED_PINCTRL_GROUP(DSGPM0), ++ ASPEED_PINCTRL_GROUP(SGPS), ++ ASPEED_PINCTRL_GROUP(I2CF0), ++ ASPEED_PINCTRL_GROUP(I2CF1), ++ ASPEED_PINCTRL_GROUP(I2CF2), ++ ASPEED_PINCTRL_GROUP(CANBUS), ++ ASPEED_PINCTRL_GROUP(CANBUSSTBY), ++ ASPEED_PINCTRL_GROUP(USBUART), ++ ASPEED_PINCTRL_GROUP(HBLED), ++ ASPEED_PINCTRL_GROUP(MACLINK0), ++ ASPEED_PINCTRL_GROUP(MACLINK1), ++ ASPEED_PINCTRL_GROUP(MACLINK2), ++ ASPEED_PINCTRL_GROUP(NCTS2), ++ ASPEED_PINCTRL_GROUP(NDCD2), ++ ASPEED_PINCTRL_GROUP(NDSR2), ++ ASPEED_PINCTRL_GROUP(NRI2), ++ ASPEED_PINCTRL_GROUP(NDTR2), ++ ASPEED_PINCTRL_GROUP(NRTS2), ++ ASPEED_PINCTRL_GROUP(SMON0), ++ ASPEED_PINCTRL_GROUP(SMON1), ++ ASPEED_PINCTRL_GROUP(SGMII), ++ ASPEED_PINCTRL_GROUP(PE2SGRSTN), ++ ASPEED_PINCTRL_GROUP(USB2CUD), ++ ASPEED_PINCTRL_GROUP(USB2CD), ++ ASPEED_PINCTRL_GROUP(USB2CH), ++ ASPEED_PINCTRL_GROUP(USB2CU), ++ ASPEED_PINCTRL_GROUP(USB2DD), ++ ASPEED_PINCTRL_GROUP(USB2DH), ++}; ++ ++FUNC_DECL_(ESPI0, "ESPI0"); ++FUNC_DECL_(ESPI1, "ESPI1"); ++FUNC_DECL_(LPC0, "LPC0"); ++FUNC_DECL_(LPC1, "LPC1"); ++FUNC_DECL_(VPI, "VPI"); ++FUNC_DECL_(SD, "SD"); ++FUNC_DECL_(OSCCLK, "OSCCLK"); ++FUNC_DECL_(TACH0, "TACH0"); ++FUNC_DECL_(TACH1, "TACH1"); ++FUNC_DECL_(TACH2, "TACH2"); ++FUNC_DECL_(TACH3, "TACH3"); ++FUNC_DECL_(TACH4, "TACH4"); ++FUNC_DECL_(TACH5, "TACH5"); ++FUNC_DECL_(TACH6, "TACH6"); ++FUNC_DECL_(TACH7, "TACH7"); ++FUNC_DECL_(THRU0, "THRU0"); ++FUNC_DECL_(THRU1, "THRU1"); ++FUNC_DECL_(NTCS5, "NTCS5"); ++FUNC_DECL_(NDSR5, "NDSR5"); ++FUNC_DECL_(NRI5, "NRI5"); ++FUNC_DECL_(TACH8, "TACH8"); ++FUNC_DECL_(TACH9, "TACH9"); ++FUNC_DECL_(TACH10, "TACH10"); ++FUNC_DECL_(TACH11, "TACH11"); ++FUNC_DECL_(TACH12, "TACH12"); ++FUNC_DECL_(TACH13, "TACH13"); ++FUNC_DECL_(TACH14, "TACH14"); ++FUNC_DECL_(TACH15, "TACH15"); ++FUNC_DECL_(SALT12, "SALT12"); ++FUNC_DECL_(SALT13, "SALT13"); ++FUNC_DECL_(SALT14, "SALT14"); ++FUNC_DECL_(SALT15, "SALT15"); ++FUNC_DECL_(SPIM0, "SPIM0"); ++FUNC_DECL_(PWM0, "PWM0"); ++FUNC_DECL_(PWM1, "PWM1"); ++FUNC_DECL_(PWM2, "PWM2"); ++FUNC_DECL_(PWM3, "PWM3"); ++FUNC_DECL_(PWM4, "PWM4"); ++FUNC_DECL_(PWM5, "PWM5"); ++FUNC_DECL_(PWM6, "PWM6"); ++FUNC_DECL_(PWM7, "PWM7"); ++FUNC_DECL_(SIOPBON0, "SIOPBON0"); ++FUNC_DECL_(SIOPBIN0, "SIOPBIN0"); ++FUNC_DECL_(SIOSCIN0, "SIOSCIN0"); ++FUNC_DECL_(SIOS3N0, "SIOS3N0"); ++FUNC_DECL_(SIOS5N0, "SIOS5N0"); ++FUNC_DECL_(SIOPWREQN0, "SIOPWREQN0"); ++FUNC_DECL_(SIOONCTRLN0, "SIOONCTRLN0"); ++FUNC_DECL_(SIOPWRGD0, "SIOPWRGD0"); ++FUNC_DECL_(UART0, "NCTS0", "NDCD0", "NDSR0", "NRI0", "NDTR0", "NRTS0", "TXD0", "RXD0"); ++FUNC_DECL_(UART1, "NCTS1", "NDCD1", "NDSR1", "NRI1", "NDTR1", "NRTS1", "TXD1", "RXD1"); ++FUNC_DECL_(UART2, "TXD2", "RXD2"); ++FUNC_DECL_(UART3, "TXD3", "RXD3"); ++FUNC_DECL_(UART5, "NCTS5", "NDCD5", "NDSR5", "NRI5", "NDTR5", "NRTS5", "TXD5", "RXD5"); ++FUNC_DECL_(UART6, "NCTS6", "NDCD6", "NDSR6", "NRI6", "NDTR6", "NRTS6", "TXD6", "RXD6"); ++FUNC_DECL_(UART7, "TXD7", "RXD7"); ++FUNC_DECL_(UART8, "TXD8", "RXD8"); ++FUNC_DECL_(UART9, "TXD9", "RXD9"); ++FUNC_DECL_(UART10, "TXD10", "RXD10"); ++FUNC_DECL_(UART11, "TXD11", "RXD11"); ++FUNC_DECL_(SPIM1, "SPIM1"); ++FUNC_DECL_(SPIM2, "SPIM2"); ++FUNC_DECL_(PWM8, "PWM8"); ++FUNC_DECL_(PWM9, "PWM9"); ++FUNC_DECL_(PWM10, "PWM10"); ++FUNC_DECL_(PWM11, "PWM11"); ++FUNC_DECL_(PWM12, "PWM12"); ++FUNC_DECL_(PWM13, "PWM13"); ++FUNC_DECL_(PWM14, "PWM14"); ++FUNC_DECL_(PWM15, "PWM15"); ++FUNC_DECL_(WDTRST0N, "WDTRST0N"); ++FUNC_DECL_(WDTRST1N, "WDTRST1N"); ++FUNC_DECL_(WDTRST2N, "WDTRST2N"); ++FUNC_DECL_(WDTRST3N, "WDTRST3N"); ++FUNC_DECL_(WDTRST4N, "WDTRST4N"); ++FUNC_DECL_(WDTRST5N, "WDTRST5N"); ++FUNC_DECL_(WDTRST6N, "WDTRST6N"); ++FUNC_DECL_(WDTRST7N, "WDTRST7N"); ++FUNC_DECL_(FSI0, "FSI0"); ++FUNC_DECL_(FSI1, "FSI1"); ++FUNC_DECL_(FSI2, "FSI2"); ++FUNC_DECL_(FSI3, "FSI3"); ++FUNC_DECL_(SALT0, "SALT0"); ++FUNC_DECL_(SALT1, "SALT1"); ++FUNC_DECL_(SALT2, "SALT2"); ++FUNC_DECL_(SALT3, "SALT3"); ++FUNC_DECL_(SALT4, "SALT4"); ++FUNC_DECL_(SALT5, "SALT5"); ++FUNC_DECL_(SALT6, "SALT6"); ++FUNC_DECL_(SALT7, "SALT7"); ++FUNC_DECL_(SALT8, "SALT8"); ++FUNC_DECL_(SALT9, "SALT9"); ++FUNC_DECL_(SALT10, "SALT10"); ++FUNC_DECL_(SALT11, "SALT11"); ++FUNC_DECL_(ADC0, "ADC0"); ++FUNC_DECL_(ADC1, "ADC1"); ++FUNC_DECL_(ADC2, "ADC2"); ++FUNC_DECL_(ADC3, "ADC3"); ++FUNC_DECL_(ADC4, "ADC4"); ++FUNC_DECL_(ADC5, "ADC5"); ++FUNC_DECL_(ADC6, "ADC6"); ++FUNC_DECL_(ADC7, "ADC7"); ++FUNC_DECL_(ADC8, "ADC8"); ++FUNC_DECL_(ADC9, "ADC9"); ++FUNC_DECL_(ADC10, "ADC10"); ++FUNC_DECL_(ADC11, "ADC11"); ++FUNC_DECL_(ADC12, "ADC12"); ++FUNC_DECL_(ADC13, "ADC13"); ++FUNC_DECL_(ADC14, "ADC14"); ++FUNC_DECL_(ADC15, "ADC15"); ++FUNC_DECL_(AUXPWRGOOD0, "AUXPWRGOOD0"); ++FUNC_DECL_(AUXPWRGOOD1, "AUXPWRGOOD1"); ++FUNC_DECL_(SGPM0, "SGPM0", "DSGPM0"); ++FUNC_DECL_(SGPM1, "SGPM1"); ++FUNC_DECL_(LTPI_PS_I2C0, "LTPI_PS_I2C0"); ++FUNC_DECL_(LTPI_PS_I2C1, "LTPI_PS_I2C1"); ++FUNC_DECL_(LTPI_PS_I2C2, "LTPI_PS_I2C2"); ++FUNC_DECL_(LTPI_PS_I2C3, "LTPI_PS_I2C3"); ++FUNC_DECL_(I2C0, "I2C0", "DI2C0"); ++FUNC_DECL_(I2C1, "I2C1", "DI2C1"); ++FUNC_DECL_(I2C2, "I2C2", "DI2C2"); ++FUNC_DECL_(I2C3, "I2C3", "DI2C3"); ++FUNC_DECL_(I2C4, "I2C4"); ++FUNC_DECL_(I2C5, "I2C5"); ++FUNC_DECL_(I2C6, "I2C6"); ++FUNC_DECL_(I2C7, "I2C7"); ++FUNC_DECL_(I2C8, "I2C8", "DI2C8"); ++FUNC_DECL_(I2C9, "I2C9", "DI2C9"); ++FUNC_DECL_(I2C10, "I2C10", "DI2C10"); ++FUNC_DECL_(I2C11, "I2C11", "DI2C11"); ++FUNC_DECL_(I2C12, "I2C12", "DI2C12"); ++FUNC_DECL_(I2C13, "I2C13", "DI2C13"); ++FUNC_DECL_(I2C14, "I2C14", "DI2C14"); ++FUNC_DECL_(I2C15, "I2C15", "DI2C15"); ++FUNC_DECL_(SIOPBON1, "SIOPBON1"); ++FUNC_DECL_(SIOPBIN1, "SIOPBIN1"); ++FUNC_DECL_(SIOSCIN1, "SIOSCIN1"); ++FUNC_DECL_(SIOS3N1, "SIOS3N1"); ++FUNC_DECL_(SIOS5N1, "SIOS5N1"); ++FUNC_DECL_(SIOPWREQN1, "SIOPWREQN1"); ++FUNC_DECL_(SIOONCTRLN1, "SIOONCTRLN1"); ++FUNC_DECL_(SIOPWRGD1, "SIOPWRGD1"); ++FUNC_DECL_(I3C0, "HVI3C0"); ++FUNC_DECL_(I3C1, "HVI3C1"); ++FUNC_DECL_(I3C2, "HVI3C2"); ++FUNC_DECL_(I3C3, "HVI3C3"); ++FUNC_DECL_(I3C4, "I3C4"); ++FUNC_DECL_(I3C5, "I3C5"); ++FUNC_DECL_(I3C6, "I3C6"); ++FUNC_DECL_(I3C7, "I3C7"); ++FUNC_DECL_(I3C8, "I3C8"); ++FUNC_DECL_(I3C9, "I3C9"); ++FUNC_DECL_(I3C10, "I3C10"); ++FUNC_DECL_(I3C11, "I3C11"); ++FUNC_DECL_(I3C12, "HVI3C12"); ++FUNC_DECL_(I3C13, "HVI3C13"); ++FUNC_DECL_(I3C14, "HVI3C14"); ++FUNC_DECL_(I3C15, "HVI3C15"); ++FUNC_DECL_(LTPI, "LTPI"); ++FUNC_DECL_(SPI0, "SPI0"); ++FUNC_DECL_(QSPI0, "QSPI0"); ++FUNC_DECL_(SPI0CS1, "SPI0CS1"); ++FUNC_DECL_(SPI0ABR, "SPI0ABR"); ++FUNC_DECL_(SPI0WPN, "SPI0WPN"); ++FUNC_DECL_(SPI1, "SPI1"); ++FUNC_DECL_(QSPI1, "QSPI1"); ++FUNC_DECL_(SPI1CS1, "SPI1CS1"); ++FUNC_DECL_(SPI1ABR, "SPI1ABR"); ++FUNC_DECL_(SPI1WPN, "SPI1WPN"); ++FUNC_DECL_(SPI2, "SPI2"); ++FUNC_DECL_(QSPI2, "QSPI2"); ++FUNC_DECL_(SPI2CS1, "SPI2CS1"); ++FUNC_DECL_(THRU2, "THRU2"); ++FUNC_DECL_(THRU3, "THRU3"); ++FUNC_DECL_(JTAGM1, "JTAGM1", "JTAGM1TRST"); ++FUNC_DECL_(MDIO0, "MDIO0"); ++FUNC_DECL_(MDIO1, "MDIO1"); ++FUNC_DECL_(MDIO2, "MDIO2"); ++FUNC_DECL_(FWQSPI, "FWQSPI"); ++FUNC_DECL_(FWSPIABR, "FWSPIABR"); ++FUNC_DECL_(FWSPIWPN, "FWSPIWPN"); ++FUNC_DECL_(RGMII0, "RGMII0"); ++FUNC_DECL_(RGMII1, "RGMII1"); ++FUNC_DECL_(RMII0, "RMII0"); ++FUNC_DECL_(RMII0RCLKO, "RMII0RCLKO"); ++FUNC_DECL_(RMII1, "RMII1"); ++FUNC_DECL_(RMII1RCLKO, "RMII1RCLKO"); ++FUNC_DECL_(VGA, "VGA"); ++FUNC_DECL_(SGPS, "SGPS"); ++FUNC_DECL_(I2CF0, "I2CF0"); ++FUNC_DECL_(I2CF1, "I2CF1"); ++FUNC_DECL_(I2CF2, "I2CF2"); ++FUNC_DECL_(CANBUS, "CANBUS", "CANBUSSTBY"); ++FUNC_DECL_(USBUART, "USBUART"); ++FUNC_DECL_(HBLED, "HBLED"); ++FUNC_DECL_(MACLINK0, "MACLINK0"); ++FUNC_DECL_(MACLINK1, "MACLINK1"); ++FUNC_DECL_(MACLINK2, "MACLINK2"); ++FUNC_DECL_(SMON0, "SMON0"); ++FUNC_DECL_(SMON1, "SMON1"); ++FUNC_DECL_(SGMII, "SGMII"); ++FUNC_DECL_(PCIERC, "PE2SGRSTN"); ++FUNC_DECL_(USB2C, "USB2CUD", "USB2CD", "USB2CH", "USB2CU"); ++FUNC_DECL_(USB2D, "USB2DD", "USB2DH"); ++ ++static struct aspeed_pin_function aspeed_g7_soc1_funcs[] = { ++ ASPEED_PINCTRL_FUNC(ESPI0), ++ ASPEED_PINCTRL_FUNC(ESPI1), ++ ASPEED_PINCTRL_FUNC(LPC0), ++ ASPEED_PINCTRL_FUNC(LPC1), ++ ASPEED_PINCTRL_FUNC(VPI), ++ ASPEED_PINCTRL_FUNC(SD), ++ ASPEED_PINCTRL_FUNC(OSCCLK), ++ ASPEED_PINCTRL_FUNC(TACH0), ++ ASPEED_PINCTRL_FUNC(TACH1), ++ ASPEED_PINCTRL_FUNC(TACH2), ++ ASPEED_PINCTRL_FUNC(TACH3), ++ ASPEED_PINCTRL_FUNC(TACH4), ++ ASPEED_PINCTRL_FUNC(TACH5), ++ ASPEED_PINCTRL_FUNC(TACH6), ++ ASPEED_PINCTRL_FUNC(TACH7), ++ ASPEED_PINCTRL_FUNC(THRU0), ++ ASPEED_PINCTRL_FUNC(THRU1), ++ ASPEED_PINCTRL_FUNC(NTCS5), ++ ASPEED_PINCTRL_FUNC(NTCS5), ++ ASPEED_PINCTRL_FUNC(NDSR5), ++ ASPEED_PINCTRL_FUNC(NRI5), ++ ASPEED_PINCTRL_FUNC(NRI5), ++ ASPEED_PINCTRL_FUNC(SALT12), ++ ASPEED_PINCTRL_FUNC(SALT13), ++ ASPEED_PINCTRL_FUNC(SALT14), ++ ASPEED_PINCTRL_FUNC(SALT15), ++ ASPEED_PINCTRL_FUNC(TACH8), ++ ASPEED_PINCTRL_FUNC(TACH9), ++ ASPEED_PINCTRL_FUNC(TACH10), ++ ASPEED_PINCTRL_FUNC(TACH11), ++ ASPEED_PINCTRL_FUNC(TACH12), ++ ASPEED_PINCTRL_FUNC(TACH13), ++ ASPEED_PINCTRL_FUNC(TACH14), ++ ASPEED_PINCTRL_FUNC(TACH15), ++ ASPEED_PINCTRL_FUNC(SPIM0), ++ ASPEED_PINCTRL_FUNC(PWM0), ++ ASPEED_PINCTRL_FUNC(PWM1), ++ ASPEED_PINCTRL_FUNC(PWM2), ++ ASPEED_PINCTRL_FUNC(PWM3), ++ ASPEED_PINCTRL_FUNC(PWM4), ++ ASPEED_PINCTRL_FUNC(PWM5), ++ ASPEED_PINCTRL_FUNC(PWM6), ++ ASPEED_PINCTRL_FUNC(PWM7), ++ ASPEED_PINCTRL_FUNC(SIOPBON0), ++ ASPEED_PINCTRL_FUNC(SIOPBIN0), ++ ASPEED_PINCTRL_FUNC(SIOSCIN0), ++ ASPEED_PINCTRL_FUNC(SIOS3N0), ++ ASPEED_PINCTRL_FUNC(SIOS5N0), ++ ASPEED_PINCTRL_FUNC(SIOPWREQN0), ++ ASPEED_PINCTRL_FUNC(SIOONCTRLN0), ++ ASPEED_PINCTRL_FUNC(SIOPWRGD0), ++ ASPEED_PINCTRL_FUNC(UART0), ++ ASPEED_PINCTRL_FUNC(UART1), ++ ASPEED_PINCTRL_FUNC(UART2), ++ ASPEED_PINCTRL_FUNC(UART3), ++ ASPEED_PINCTRL_FUNC(UART5), ++ ASPEED_PINCTRL_FUNC(UART6), ++ ASPEED_PINCTRL_FUNC(UART7), ++ ASPEED_PINCTRL_FUNC(UART8), ++ ASPEED_PINCTRL_FUNC(UART9), ++ ASPEED_PINCTRL_FUNC(UART10), ++ ASPEED_PINCTRL_FUNC(UART11), ++ ASPEED_PINCTRL_FUNC(SPIM1), ++ ASPEED_PINCTRL_FUNC(PWM7), ++ ASPEED_PINCTRL_FUNC(PWM8), ++ ASPEED_PINCTRL_FUNC(PWM9), ++ ASPEED_PINCTRL_FUNC(PWM10), ++ ASPEED_PINCTRL_FUNC(PWM11), ++ ASPEED_PINCTRL_FUNC(PWM12), ++ ASPEED_PINCTRL_FUNC(PWM13), ++ ASPEED_PINCTRL_FUNC(PWM14), ++ ASPEED_PINCTRL_FUNC(PWM15), ++ ASPEED_PINCTRL_FUNC(WDTRST0N), ++ ASPEED_PINCTRL_FUNC(WDTRST1N), ++ ASPEED_PINCTRL_FUNC(WDTRST2N), ++ ASPEED_PINCTRL_FUNC(WDTRST3N), ++ ASPEED_PINCTRL_FUNC(WDTRST4N), ++ ASPEED_PINCTRL_FUNC(WDTRST5N), ++ ASPEED_PINCTRL_FUNC(WDTRST6N), ++ ASPEED_PINCTRL_FUNC(WDTRST7N), ++ ASPEED_PINCTRL_FUNC(FSI0), ++ ASPEED_PINCTRL_FUNC(FSI1), ++ ASPEED_PINCTRL_FUNC(FSI2), ++ ASPEED_PINCTRL_FUNC(FSI3), ++ ASPEED_PINCTRL_FUNC(SALT0), ++ ASPEED_PINCTRL_FUNC(SALT1), ++ ASPEED_PINCTRL_FUNC(SALT2), ++ ASPEED_PINCTRL_FUNC(SALT3), ++ ASPEED_PINCTRL_FUNC(SALT4), ++ ASPEED_PINCTRL_FUNC(SALT5), ++ ASPEED_PINCTRL_FUNC(SALT6), ++ ASPEED_PINCTRL_FUNC(SALT7), ++ ASPEED_PINCTRL_FUNC(SALT8), ++ ASPEED_PINCTRL_FUNC(SALT9), ++ ASPEED_PINCTRL_FUNC(SALT10), ++ ASPEED_PINCTRL_FUNC(SALT11), ++ ASPEED_PINCTRL_FUNC(ADC0), ++ ASPEED_PINCTRL_FUNC(ADC1), ++ ASPEED_PINCTRL_FUNC(ADC2), ++ ASPEED_PINCTRL_FUNC(ADC3), ++ ASPEED_PINCTRL_FUNC(ADC4), ++ ASPEED_PINCTRL_FUNC(ADC5), ++ ASPEED_PINCTRL_FUNC(ADC6), ++ ASPEED_PINCTRL_FUNC(ADC7), ++ ASPEED_PINCTRL_FUNC(ADC8), ++ ASPEED_PINCTRL_FUNC(ADC9), ++ ASPEED_PINCTRL_FUNC(ADC10), ++ ASPEED_PINCTRL_FUNC(ADC11), ++ ASPEED_PINCTRL_FUNC(ADC12), ++ ASPEED_PINCTRL_FUNC(ADC13), ++ ASPEED_PINCTRL_FUNC(ADC14), ++ ASPEED_PINCTRL_FUNC(ADC15), ++ ASPEED_PINCTRL_FUNC(AUXPWRGOOD0), ++ ASPEED_PINCTRL_FUNC(AUXPWRGOOD1), ++ ASPEED_PINCTRL_FUNC(SGPM0), ++ ASPEED_PINCTRL_FUNC(SGPM1), ++ ASPEED_PINCTRL_FUNC(SPIM2), ++ ASPEED_PINCTRL_FUNC(LTPI_PS_I2C0), ++ ASPEED_PINCTRL_FUNC(LTPI_PS_I2C1), ++ ASPEED_PINCTRL_FUNC(LTPI_PS_I2C2), ++ ASPEED_PINCTRL_FUNC(LTPI_PS_I2C3), ++ ASPEED_PINCTRL_FUNC(I2C0), ++ ASPEED_PINCTRL_FUNC(I2C1), ++ ASPEED_PINCTRL_FUNC(I2C2), ++ ASPEED_PINCTRL_FUNC(I2C3), ++ ASPEED_PINCTRL_FUNC(I2C4), ++ ASPEED_PINCTRL_FUNC(I2C5), ++ ASPEED_PINCTRL_FUNC(I2C6), ++ ASPEED_PINCTRL_FUNC(I2C7), ++ ASPEED_PINCTRL_FUNC(I2C8), ++ ASPEED_PINCTRL_FUNC(I2C9), ++ ASPEED_PINCTRL_FUNC(I2C10), ++ ASPEED_PINCTRL_FUNC(I2C11), ++ ASPEED_PINCTRL_FUNC(I2C12), ++ ASPEED_PINCTRL_FUNC(I2C13), ++ ASPEED_PINCTRL_FUNC(I2C14), ++ ASPEED_PINCTRL_FUNC(I2C15), ++ ASPEED_PINCTRL_FUNC(SIOPBON1), ++ ASPEED_PINCTRL_FUNC(SIOPBIN1), ++ ASPEED_PINCTRL_FUNC(SIOSCIN1), ++ ASPEED_PINCTRL_FUNC(SIOS3N1), ++ ASPEED_PINCTRL_FUNC(SIOS5N1), ++ ASPEED_PINCTRL_FUNC(SIOPWREQN1), ++ ASPEED_PINCTRL_FUNC(SIOONCTRLN1), ++ ASPEED_PINCTRL_FUNC(SIOPWRGD1), ++ ASPEED_PINCTRL_FUNC(I3C0), ++ ASPEED_PINCTRL_FUNC(I3C1), ++ ASPEED_PINCTRL_FUNC(I3C2), ++ ASPEED_PINCTRL_FUNC(I3C3), ++ ASPEED_PINCTRL_FUNC(I3C4), ++ ASPEED_PINCTRL_FUNC(I3C5), ++ ASPEED_PINCTRL_FUNC(I3C6), ++ ASPEED_PINCTRL_FUNC(I3C7), ++ ASPEED_PINCTRL_FUNC(I3C8), ++ ASPEED_PINCTRL_FUNC(I3C9), ++ ASPEED_PINCTRL_FUNC(I3C10), ++ ASPEED_PINCTRL_FUNC(I3C11), ++ ASPEED_PINCTRL_FUNC(I3C12), ++ ASPEED_PINCTRL_FUNC(I3C13), ++ ASPEED_PINCTRL_FUNC(I3C14), ++ ASPEED_PINCTRL_FUNC(I3C15), ++ ASPEED_PINCTRL_FUNC(LTPI), ++ ASPEED_PINCTRL_FUNC(SPI0), ++ ASPEED_PINCTRL_FUNC(QSPI0), ++ ASPEED_PINCTRL_FUNC(SPI0CS1), ++ ASPEED_PINCTRL_FUNC(SPI0ABR), ++ ASPEED_PINCTRL_FUNC(SPI0WPN), ++ ASPEED_PINCTRL_FUNC(SPI1), ++ ASPEED_PINCTRL_FUNC(QSPI1), ++ ASPEED_PINCTRL_FUNC(SPI1CS1), ++ ASPEED_PINCTRL_FUNC(SPI1ABR), ++ ASPEED_PINCTRL_FUNC(SPI1WPN), ++ ASPEED_PINCTRL_FUNC(SPI2), ++ ASPEED_PINCTRL_FUNC(QSPI2), ++ ASPEED_PINCTRL_FUNC(SPI2CS1), ++ ASPEED_PINCTRL_FUNC(THRU2), ++ ASPEED_PINCTRL_FUNC(THRU3), ++ ASPEED_PINCTRL_FUNC(JTAGM1), ++ ASPEED_PINCTRL_FUNC(MDIO0), ++ ASPEED_PINCTRL_FUNC(MDIO1), ++ ASPEED_PINCTRL_FUNC(MDIO2), ++ ASPEED_PINCTRL_FUNC(FWQSPI), ++ ASPEED_PINCTRL_FUNC(FWSPIABR), ++ ASPEED_PINCTRL_FUNC(FWSPIWPN), ++ ASPEED_PINCTRL_FUNC(RGMII0), ++ ASPEED_PINCTRL_FUNC(RGMII1), ++ ASPEED_PINCTRL_FUNC(RMII0), ++ ASPEED_PINCTRL_FUNC(RMII0RCLKO), ++ ASPEED_PINCTRL_FUNC(RMII1), ++ ASPEED_PINCTRL_FUNC(RMII1RCLKO), ++ ASPEED_PINCTRL_FUNC(VGA), ++ ASPEED_PINCTRL_FUNC(SGPS), ++ ASPEED_PINCTRL_FUNC(I2CF0), ++ ASPEED_PINCTRL_FUNC(I2CF1), ++ ASPEED_PINCTRL_FUNC(I2CF2), ++ ASPEED_PINCTRL_FUNC(CANBUS), ++ ASPEED_PINCTRL_FUNC(USBUART), ++ ASPEED_PINCTRL_FUNC(HBLED), ++ ASPEED_PINCTRL_FUNC(MACLINK0), ++ ASPEED_PINCTRL_FUNC(MACLINK1), ++ ASPEED_PINCTRL_FUNC(MACLINK2), ++ ASPEED_PINCTRL_FUNC(SMON0), ++ ASPEED_PINCTRL_FUNC(SMON1), ++ ASPEED_PINCTRL_FUNC(SGMII), ++ ASPEED_PINCTRL_FUNC(PCIERC), ++ ASPEED_PINCTRL_FUNC(USB2C), ++ ASPEED_PINCTRL_FUNC(USB2D), ++}; ++ ++/* number, name, drv_data */ ++static const struct pinctrl_pin_desc aspeed_g7_soc1_pins[] = { ++ PINCTRL_PIN(C16, "C16"), ++ PINCTRL_PIN(C14, "C14"), ++ PINCTRL_PIN(C11, "C11"), ++ PINCTRL_PIN(D9, "D9"), ++ PINCTRL_PIN(F14, "F14"), ++ PINCTRL_PIN(D10, "D10"), ++ PINCTRL_PIN(C12, "C12"), ++ PINCTRL_PIN(C13, "C13"), ++ PINCTRL_PIN(AC26, "AC26"), ++ PINCTRL_PIN(AA25, "AA25"), ++ PINCTRL_PIN(AB23, "AB23"), ++ PINCTRL_PIN(U22, "U22"), ++ PINCTRL_PIN(V21, "V21"), ++ PINCTRL_PIN(N26, "N26"), ++ PINCTRL_PIN(P25, "P25"), ++ PINCTRL_PIN(N25, "N25"), ++ PINCTRL_PIN(V23, "V23"), ++ PINCTRL_PIN(W22, "W22"), ++ PINCTRL_PIN(AB26, "AB26"), ++ PINCTRL_PIN(AD26, "AD26"), ++ PINCTRL_PIN(P26, "P26"), ++ PINCTRL_PIN(AE26, "AE26"), ++ PINCTRL_PIN(AF26, "AF26"), ++ PINCTRL_PIN(AF25, "AF25"), ++ PINCTRL_PIN(AE25, "AE25"), ++ PINCTRL_PIN(AD25, "AD25"), ++ PINCTRL_PIN(AF23, "AF23"), ++ PINCTRL_PIN(AF20, "AF20"), ++ PINCTRL_PIN(AF21, "AF21"), ++ PINCTRL_PIN(AE21, "AE21"), ++ PINCTRL_PIN(AE23, "AE23"), ++ PINCTRL_PIN(AD22, "AD22"), ++ PINCTRL_PIN(AF17, "AF17"), ++ PINCTRL_PIN(AA16, "AA16"), ++ PINCTRL_PIN(Y16, "Y16"), ++ PINCTRL_PIN(V17, "V17"), ++ PINCTRL_PIN(J13, "J13"), ++ PINCTRL_PIN(AB16, "AB16"), ++ PINCTRL_PIN(AC16, "AC16"), ++ PINCTRL_PIN(AF16, "AF16"), ++ PINCTRL_PIN(AA15, "AA15"), ++ PINCTRL_PIN(AB15, "AB15"), ++ PINCTRL_PIN(AC15, "AC15"), ++ PINCTRL_PIN(AD15, "AD15"), ++ PINCTRL_PIN(Y15, "Y15"), ++ PINCTRL_PIN(AA14, "AA14"), ++ PINCTRL_PIN(W16, "W16"), ++ PINCTRL_PIN(V16, "V16"), ++ PINCTRL_PIN(AB18, "AB18"), ++ PINCTRL_PIN(AC18, "AC18"), ++ PINCTRL_PIN(K13, "K13"), ++ PINCTRL_PIN(AA17, "AA17"), ++ PINCTRL_PIN(AB17, "AB17"), ++ PINCTRL_PIN(AD16, "AD16"), ++ PINCTRL_PIN(AC17, "AC17"), ++ PINCTRL_PIN(AD17, "AD17"), ++ PINCTRL_PIN(AE16, "AE16"), ++ PINCTRL_PIN(AE17, "AE17"), ++ PINCTRL_PIN(AB24, "AB24"), ++ PINCTRL_PIN(W26, "W26"), ++ PINCTRL_PIN(HOLE0, "HOLE0"), ++ PINCTRL_PIN(HOLE1, "HOLE1"), ++ PINCTRL_PIN(HOLE2, "HOLE2"), ++ PINCTRL_PIN(HOLE3, "HOLE3"), ++ PINCTRL_PIN(W25, "W25"), ++ PINCTRL_PIN(Y23, "Y23"), ++ PINCTRL_PIN(Y24, "Y24"), ++ PINCTRL_PIN(W21, "W21"), ++ PINCTRL_PIN(AA23, "AA23"), ++ PINCTRL_PIN(AC22, "AC22"), ++ PINCTRL_PIN(AB22, "AB22"), ++ PINCTRL_PIN(Y21, "Y21"), ++ PINCTRL_PIN(AE20, "AE20"), ++ PINCTRL_PIN(AF19, "AF19"), ++ PINCTRL_PIN(Y22, "Y22"), ++ PINCTRL_PIN(AA20, "AA20"), ++ PINCTRL_PIN(AA22, "AA22"), ++ PINCTRL_PIN(AB20, "AB20"), ++ PINCTRL_PIN(AF18, "AF18"), ++ PINCTRL_PIN(AE19, "AE19"), ++ PINCTRL_PIN(AD20, "AD20"), ++ PINCTRL_PIN(AC20, "AC20"), ++ PINCTRL_PIN(AA21, "AA21"), ++ PINCTRL_PIN(AB21, "AB21"), ++ PINCTRL_PIN(AC19, "AC19"), ++ PINCTRL_PIN(AE18, "AE18"), ++ PINCTRL_PIN(AD19, "AD19"), ++ PINCTRL_PIN(AD18, "AD18"), ++ PINCTRL_PIN(U25, "U25"), ++ PINCTRL_PIN(U26, "U26"), ++ PINCTRL_PIN(Y26, "Y26"), ++ PINCTRL_PIN(AA24, "AA24"), ++ PINCTRL_PIN(R25, "R25"), ++ PINCTRL_PIN(AA26, "AA26"), ++ PINCTRL_PIN(R26, "R26"), ++ PINCTRL_PIN(Y25, "Y25"), ++ PINCTRL_PIN(B16, "B16"), ++ PINCTRL_PIN(D14, "D14"), ++ PINCTRL_PIN(B15, "B15"), ++ PINCTRL_PIN(B14, "B14"), ++ PINCTRL_PIN(C17, "C17"), ++ PINCTRL_PIN(B13, "B13"), ++ PINCTRL_PIN(E14, "E14"), ++ PINCTRL_PIN(C15, "C15"), ++ PINCTRL_PIN(D24, "D24"), ++ PINCTRL_PIN(B23, "B23"), ++ PINCTRL_PIN(B22, "B22"), ++ PINCTRL_PIN(C23, "C23"), ++ PINCTRL_PIN(B18, "B18"), ++ PINCTRL_PIN(B21, "B21"), ++ PINCTRL_PIN(M15, "M15"), ++ PINCTRL_PIN(B19, "B19"), ++ PINCTRL_PIN(B26, "B26"), ++ PINCTRL_PIN(A25, "A25"), ++ PINCTRL_PIN(A24, "A24"), ++ PINCTRL_PIN(B24, "B24"), ++ PINCTRL_PIN(E26, "E26"), ++ PINCTRL_PIN(A21, "A21"), ++ PINCTRL_PIN(A19, "A19"), ++ PINCTRL_PIN(A18, "A18"), ++ PINCTRL_PIN(D26, "D26"), ++ PINCTRL_PIN(C26, "C26"), ++ PINCTRL_PIN(A23, "A23"), ++ PINCTRL_PIN(A22, "A22"), ++ PINCTRL_PIN(B25, "B25"), ++ PINCTRL_PIN(F26, "F26"), ++ PINCTRL_PIN(A26, "A26"), ++ PINCTRL_PIN(A14, "A14"), ++ PINCTRL_PIN(E10, "E10"), ++ PINCTRL_PIN(E13, "E13"), ++ PINCTRL_PIN(D12, "D12"), ++ PINCTRL_PIN(F10, "F10"), ++ PINCTRL_PIN(E11, "E11"), ++ PINCTRL_PIN(F11, "F11"), ++ PINCTRL_PIN(F13, "F13"), ++ PINCTRL_PIN(N15, "N15"), ++ PINCTRL_PIN(C20, "C20"), ++ PINCTRL_PIN(C19, "C19"), ++ PINCTRL_PIN(A8, "A8"), ++ PINCTRL_PIN(R14, "R14"), ++ PINCTRL_PIN(A7, "A7"), ++ PINCTRL_PIN(P14, "P14"), ++ PINCTRL_PIN(D20, "D20"), ++ PINCTRL_PIN(A6, "A6"), ++ PINCTRL_PIN(B6, "B6"), ++ PINCTRL_PIN(N14, "N14"), ++ PINCTRL_PIN(B7, "B7"), ++ PINCTRL_PIN(B8, "B8"), ++ PINCTRL_PIN(B9, "B9"), ++ PINCTRL_PIN(M14, "M14"), ++ PINCTRL_PIN(J11, "J11"), ++ PINCTRL_PIN(E7, "E7"), ++ PINCTRL_PIN(D19, "D19"), ++ PINCTRL_PIN(B11, "B11"), ++ PINCTRL_PIN(D15, "D15"), ++ PINCTRL_PIN(B12, "B12"), ++ PINCTRL_PIN(B10, "B10"), ++ PINCTRL_PIN(P13, "P13"), ++ PINCTRL_PIN(C18, "C18"), ++ PINCTRL_PIN(C6, "C6"), ++ PINCTRL_PIN(C7, "C7"), ++ PINCTRL_PIN(D7, "D7"), ++ PINCTRL_PIN(N13, "N13"), ++ PINCTRL_PIN(C8, "C8"), ++ PINCTRL_PIN(C9, "C9"), ++ PINCTRL_PIN(C10, "C10"), ++ PINCTRL_PIN(M16, "M16"), ++ PINCTRL_PIN(A15, "A15"), ++ PINCTRL_PIN(G11, "G11"), ++ PINCTRL_PIN(H7, "H7"), ++ PINCTRL_PIN(H8, "H8"), ++ PINCTRL_PIN(H9, "H9"), ++ PINCTRL_PIN(H10, "H10"), ++ PINCTRL_PIN(H11, "H11"), ++ PINCTRL_PIN(J9, "J9"), ++ PINCTRL_PIN(J10, "J10"), ++ PINCTRL_PIN(E9, "E9"), ++ PINCTRL_PIN(F9, "F9"), ++ PINCTRL_PIN(F8, "F8"), ++ PINCTRL_PIN(M13, "M13"), ++ PINCTRL_PIN(F7, "F7"), ++ PINCTRL_PIN(D8, "D8"), ++ PINCTRL_PIN(E8, "E8"), ++ PINCTRL_PIN(L12, "L12"), ++ PINCTRL_PIN(F12, "F12"), ++ PINCTRL_PIN(E12, "E12"), ++ PINCTRL_PIN(J12, "J12"), ++ PINCTRL_PIN(G7, "G7"), ++ PINCTRL_PIN(G8, "G8"), ++ PINCTRL_PIN(G9, "G9"), ++ PINCTRL_PIN(G10, "G10"), ++ PINCTRL_PIN(K12, "K12"), ++ PINCTRL_PIN(W17, "W17"), ++ PINCTRL_PIN(V18, "V18"), ++ PINCTRL_PIN(W18, "W18"), ++ PINCTRL_PIN(Y17, "Y17"), ++ PINCTRL_PIN(AA18, "AA18"), ++ PINCTRL_PIN(AA13, "AA13"), ++ PINCTRL_PIN(Y18, "Y18"), ++ PINCTRL_PIN(AA12, "AA12"), ++ PINCTRL_PIN(W20, "W20"), ++ PINCTRL_PIN(V20, "V20"), ++ PINCTRL_PIN(Y11, "Y11"), ++ PINCTRL_PIN(V14, "V14"), ++ PINCTRL_PIN(V19, "V19"), ++ PINCTRL_PIN(W14, "W14"), ++ PINCTRL_PIN(Y20, "Y20"), ++ PINCTRL_PIN(AB19, "AB19"), ++ PINCTRL_PIN(U21, "U21"), ++ PINCTRL_PIN(T24, "T24"), ++ PINCTRL_PIN(V24, "V24"), ++ PINCTRL_PIN(V22, "V22"), ++ PINCTRL_PIN(T23, "T23"), ++ PINCTRL_PIN(AC25, "AC25"), ++ PINCTRL_PIN(AB25, "AB25"), ++ PINCTRL_PIN(AC24, "AC24"), ++ PINCTRL_PIN(SGMII0, "SGMII0"), ++ PINCTRL_PIN(PCIERC2_PERST, "PCIERC2_PERST"), ++ PINCTRL_PIN(PORTC_MODE, "PORTC_MODE"), ++ PINCTRL_PIN(PORTD_MODE, "PORTD_MODE"), ++}; ++ ++FUNCFG_DESCL(C16, PIN_CFG(ESPI1, SCU400, GENMASK(2, 0), 1), ++ PIN_CFG(LPC1, SCU400, GENMASK(2, 0), 2), ++ PIN_CFG(SD, SCU400, GENMASK(2, 0), 3), ++ PIN_CFG(DI2C0, SCU400, GENMASK(2, 0), 4), ++ PIN_CFG(VPI, SCU400, GENMASK(2, 0), 5)); ++FUNCFG_DESCL(C14, PIN_CFG(ESPI1, SCU400, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(LPC1, SCU400, GENMASK(6, 4), (2 << 4)), ++ PIN_CFG(SD, SCU400, GENMASK(6, 4), (3 << 4)), ++ PIN_CFG(DI2C1, SCU400, GENMASK(6, 4), (4 << 4)), ++ PIN_CFG(VPI, SCU400, GENMASK(6, 4), (5 << 4))); ++FUNCFG_DESCL(C11, PIN_CFG(ESPI1, SCU400, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(LPC1, SCU400, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(SD, SCU400, GENMASK(10, 8), (3 << 8)), ++ PIN_CFG(DI2C3, SCU400, GENMASK(10, 8), (4 << 8)), ++ PIN_CFG(VPI, SCU400, GENMASK(10, 8), (5 << 8))); ++FUNCFG_DESCL(D9, PIN_CFG(ESPI1, SCU400, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(LPC1, SCU400, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(SD, SCU400, GENMASK(14, 12), (3 << 12)), ++ PIN_CFG(DI2C0, SCU400, GENMASK(14, 12), (4 << 12)), ++ PIN_CFG(VPI, SCU400, GENMASK(14, 12), (5 << 12))); ++FUNCFG_DESCL(F14, PIN_CFG(ESPI1, SCU400, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(LPC1, SCU400, GENMASK(18, 16), (2 << 16)), ++ PIN_CFG(SD, SCU400, GENMASK(18, 16), (3 << 16)), ++ PIN_CFG(DI2C1, SCU400, GENMASK(18, 16), (4 << 16)), ++ PIN_CFG(VPI, SCU400, GENMASK(18, 16), (5 << 16))); ++FUNCFG_DESCL(D10, PIN_CFG(ESPI1, SCU400, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(LPC1, SCU400, GENMASK(22, 20), (2 << 20)), ++ PIN_CFG(SD, SCU400, GENMASK(22, 20), (3 << 20)), ++ PIN_CFG(DI2C2, SCU400, GENMASK(22, 20), (4 << 20)), ++ PIN_CFG(VPI, SCU400, GENMASK(22, 20), (5 << 20))); ++FUNCFG_DESCL(C12, PIN_CFG(ESPI1, SCU400, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(LPC1, SCU400, GENMASK(26, 24), (2 << 24)), ++ PIN_CFG(SD, SCU400, GENMASK(26, 24), (3 << 24)), ++ PIN_CFG(DI2C2, SCU400, GENMASK(26, 24), (4 << 24))); ++FUNCFG_DESCL(C13, PIN_CFG(ESPI1, SCU400, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(LPC1, SCU400, GENMASK(30, 28), (2 << 28)), ++ PIN_CFG(SD, SCU400, GENMASK(30, 28), (3 << 28)), ++ PIN_CFG(DI2C3, SCU400, GENMASK(30, 28), (4 << 28))); ++FUNCFG_DESCL(AC26, PIN_CFG(TACH0, SCU404, GENMASK(2, 0), 1), ++ PIN_CFG(THRU0, SCU404, GENMASK(2, 0), 2), ++ PIN_CFG(VPI, SCU404, GENMASK(2, 0), 3)); ++FUNCFG_DESCL(AA25, PIN_CFG(TACH1, SCU404, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(THRU0, SCU404, GENMASK(6, 4), (2 << 4)), ++ PIN_CFG(VPI, SCU404, GENMASK(6, 4), (3 << 4))); ++FUNCFG_DESCL(AB23, PIN_CFG(TACH2, SCU404, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(THRU1, SCU404, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(VPI, SCU404, GENMASK(10, 8), (3 << 8))); ++FUNCFG_DESCL(U22, PIN_CFG(TACH3, SCU404, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(THRU1, SCU404, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(VPI, SCU404, GENMASK(14, 12), (3 << 12))); ++FUNCFG_DESCL(V21, PIN_CFG(TACH4, SCU404, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(VPI, SCU404, GENMASK(18, 16), (3 << 16)), ++ PIN_CFG(NCTS5, SCU404, GENMASK(18, 16), (4 << 16))); ++FUNCFG_DESCL(N26, PIN_CFG(TACH5, SCU404, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(VPI, SCU404, GENMASK(22, 20), (3 << 20)), ++ PIN_CFG(NDCD5, SCU404, GENMASK(22, 20), (4 << 20))); ++FUNCFG_DESCL(P25, PIN_CFG(TACH6, SCU404, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(VPI, SCU404, GENMASK(26, 24), (3 << 24)), ++ PIN_CFG(NDSR5, SCU404, GENMASK(26, 24), (4 << 24))); ++FUNCFG_DESCL(N25, PIN_CFG(TACH7, SCU404, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(VPI, SCU404, GENMASK(30, 28), (3 << 28)), ++ PIN_CFG(NRI5, SCU404, GENMASK(30, 28), (4 << 28))); ++FUNCFG_DESCL(V23, PIN_CFG(TACH8, SCU408, GENMASK(2, 0), 1), ++ PIN_CFG(VPI, SCU408, GENMASK(2, 0), 3), ++ PIN_CFG(NDTR5, SCU408, GENMASK(2, 0), 4)); ++FUNCFG_DESCL(W22, PIN_CFG(TACH9, SCU408, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(VPI, SCU408, GENMASK(6, 4), (3 << 4)), ++ PIN_CFG(NRTS5, SCU408, GENMASK(6, 4), (4 << 4))); ++FUNCFG_DESCL(AB26, PIN_CFG(TACH10, SCU408, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(SALT12, SCU408, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(VPI, SCU408, GENMASK(10, 8), (3 << 8)), ++ PIN_CFG(NCTS6, SCU408, GENMASK(10, 8), (4 << 8))); ++FUNCFG_DESCL(AD26, PIN_CFG(TACH11, SCU408, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SALT13, SCU408, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(VPI, SCU408, GENMASK(14, 12), (3 << 12)), ++ PIN_CFG(NDCD6, SCU408, GENMASK(14, 12), (4 << 12))); ++FUNCFG_DESCL(P26, PIN_CFG(TACH12, SCU408, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(SALT14, SCU408, GENMASK(18, 16), (2 << 16)), ++ PIN_CFG(VPI, SCU408, GENMASK(18, 16), (3 << 16)), ++ PIN_CFG(NDSR6, SCU408, GENMASK(18, 16), (4 << 16))); ++FUNCFG_DESCL(AE26, PIN_CFG(TACH13, SCU408, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(SALT15, SCU408, GENMASK(22, 20), (2 << 20)), ++ PIN_CFG(VPI, SCU408, GENMASK(22, 20), (3 << 20)), ++ PIN_CFG(NRI6, SCU408, GENMASK(22, 20), (4 << 20))); ++FUNCFG_DESCL(AF26, PIN_CFG(TACH14, SCU408, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(LPC0, SCU408, GENMASK(26, 24), (2 << 24)), ++ PIN_CFG(VPI, SCU408, GENMASK(26, 24), (3 << 24)), ++ PIN_CFG(NDTR6, SCU408, GENMASK(26, 24), (4 << 24))); ++FUNCFG_DESCL(AF25, PIN_CFG(TACH15, SCU408, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(LPC0, SCU408, GENMASK(30, 28), (2 << 28)), ++ PIN_CFG(VPI, SCU408, GENMASK(30, 28), (3 << 28)), ++ PIN_CFG(NRTS6, SCU408, GENMASK(30, 28), (4 << 28))); ++FUNCFG_DESCL(AE25, PIN_CFG(PWM0, SCU40C, GENMASK(2, 0), 1), ++ PIN_CFG(SIOPBON0, SCU40C, GENMASK(2, 0), 2), ++ PIN_CFG(VPI, SCU40C, GENMASK(2, 0), 3), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(2, 0), 4)); ++FUNCFG_DESCL(AD25, PIN_CFG(PWM1, SCU40C, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(SIOPBIN0, SCU40C, GENMASK(6, 4), (2 << 4)), ++ PIN_CFG(VPI, SCU40C, GENMASK(6, 4), (3 << 4)), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(6, 4), (4 << 4))); ++FUNCFG_DESCL(AF23, PIN_CFG(PWM2, SCU40C, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(SIOSCIN0, SCU40C, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(VPI, SCU40C, GENMASK(10, 8), (3 << 8)), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(10, 8), (4 << 8))); ++FUNCFG_DESCL(AF20, PIN_CFG(PWM3, SCU40C, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SIOS3N0, SCU40C, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(VPI, SCU40C, GENMASK(14, 12), (3 << 12)), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(14, 12), (4 << 12))); ++FUNCFG_DESCL(AF21, PIN_CFG(PWM4, SCU40C, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(SIOS5N0, SCU40C, GENMASK(18, 16), (2 << 16)), ++ PIN_CFG(VPI, SCU40C, GENMASK(18, 16), (3 << 16)), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(18, 16), (4 << 16))); ++FUNCFG_DESCL(AE21, PIN_CFG(PWM5, SCU40C, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(SIOPWREQN0, SCU40C, GENMASK(22, 20), (2 << 20)), ++ PIN_CFG(VPI, SCU40C, GENMASK(22, 20), (3 << 20)), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(22, 20), (4 << 20))); ++FUNCFG_DESCL(AE23, PIN_CFG(PWM6, SCU40C, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(SIOONCTRLN0, SCU40C, GENMASK(26, 24), (2 << 24)), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(26, 24), (4 << 24))); ++FUNCFG_DESCL(AD22, PIN_CFG(PWM7, SCU40C, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(SPIM0, SCU40C, GENMASK(30, 28), (4 << 28))); ++FUNCFG_DESCL(AF17, PIN_CFG(NCTS0, SCU410, GENMASK(2, 0), 1), ++ PIN_CFG(SIOPBON1, SCU410, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(AA16, PIN_CFG(NDCD0, SCU410, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(SIOPBIN1, SCU410, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(Y16, PIN_CFG(NDSR0, SCU410, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(SIOSCIN1, SCU410, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(V17, PIN_CFG(NRI0, SCU410, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SIOS3N1, SCU410, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(J13, PIN_CFG(NDTR0, SCU410, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(SIOS5N1, SCU410, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(AB16, PIN_CFG(NRTS0, SCU410, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(SIOPWREQN1, SCU410, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(AC16, PIN_CFG(TXD0, SCU410, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(AF16, PIN_CFG(RXD0, SCU410, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(AA15, PIN_CFG(NCTS1, SCU414, GENMASK(2, 0), 1), ++ PIN_CFG(SIOONCTRLN1, SCU414, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(AB15, PIN_CFG(NDCD1, SCU414, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(SIOPWRGD1, SCU414, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(AC15, PIN_CFG(NDSR1, SCU414, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(SALT2, SCU414, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(AD15, PIN_CFG(NRI1, SCU414, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SALT3, SCU414, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(Y15, PIN_CFG(NDTR1, SCU414, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(AA14, PIN_CFG(NRTS1, SCU414, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(W16, PIN_CFG(TXD1, SCU414, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(V16, PIN_CFG(RXD1, SCU414, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(AB18, PIN_CFG(TXD2, SCU418, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(AC18, PIN_CFG(RXD2, SCU418, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(I2C12, SCU418, GENMASK(6, 4), (4 << 4))); ++FUNCFG_DESCL(K13, PIN_CFG(TXD3, SCU418, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(WDTRST0N, SCU418, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(PWM8, SCU418, GENMASK(10, 8), (3 << 8)), ++ PIN_CFG(SPIM1, SCU418, GENMASK(10, 8), (5 << 8))); ++FUNCFG_DESCL(AA17, PIN_CFG(RXD3, SCU418, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(WDTRST1N, SCU418, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(PWM9, SCU418, GENMASK(14, 12), (3 << 12)), ++ PIN_CFG(I2C12, SCU418, GENMASK(14, 12), (4 << 12)), ++ PIN_CFG(SPIM1, SCU418, GENMASK(14, 12), (5 << 12))); ++FUNCFG_DESCL(AB17, PIN_CFG(TXD5, SCU418, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(WDTRST2N, SCU418, GENMASK(18, 16), (2 << 16)), ++ PIN_CFG(PWM10, SCU418, GENMASK(18, 16), (3 << 16)), ++ PIN_CFG(I2C13, SCU418, GENMASK(18, 16), (4 << 16)), ++ PIN_CFG(SPIM1, SCU418, GENMASK(18, 16), (5 << 16))); ++FUNCFG_DESCL(AD16, PIN_CFG(RXD5, SCU418, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(WDTRST3N, SCU418, GENMASK(22, 20), (2 << 20)), ++ PIN_CFG(PWM11, SCU418, GENMASK(22, 20), (3 << 20)), ++ PIN_CFG(I2C13, SCU418, GENMASK(22, 20), (4 << 20)), ++ PIN_CFG(SPIM1, SCU418, GENMASK(22, 20), (5 << 20))); ++FUNCFG_DESCL(AC17, PIN_CFG(TXD6, SCU418, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(SALT0, SCU418, GENMASK(26, 24), (2 << 24)), ++ PIN_CFG(PWM12, SCU418, GENMASK(26, 24), (3 << 24)), ++ PIN_CFG(I2C14, SCU418, GENMASK(26, 24), (4 << 24)), ++ PIN_CFG(SPIM1, SCU418, GENMASK(26, 24), (5 << 24))); ++FUNCFG_DESCL(AD17, PIN_CFG(RXD6, SCU418, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(SALT1, SCU418, GENMASK(30, 28), (2 << 28)), ++ PIN_CFG(PWM13, SCU418, GENMASK(30, 28), (3 << 28)), ++ PIN_CFG(I2C14, SCU418, GENMASK(30, 28), (4 << 28)), ++ PIN_CFG(SPIM1, SCU418, GENMASK(30, 28), (5 << 28))); ++FUNCFG_DESCL(AE16, PIN_CFG(TXD7, SCU41C, GENMASK(2, 0), 1), ++ PIN_CFG(I2C15, SCU41C, GENMASK(2, 0), 2), ++ PIN_CFG(PWM14, SCU41C, GENMASK(2, 0), 3), ++ PIN_CFG(LPC1, SCU41C, GENMASK(2, 0), 4), ++ PIN_CFG(SPIM1, SCU41C, GENMASK(2, 0), 5)); ++FUNCFG_DESCL(AE17, PIN_CFG(RXD7, SCU41C, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(I2C15, SCU41C, GENMASK(6, 4), (2 << 4)), ++ PIN_CFG(PWM15, SCU41C, GENMASK(6, 4), (3 << 4)), ++ PIN_CFG(LPC1, SCU41C, GENMASK(6, 4), (4 << 4)), ++ PIN_CFG(SPIM1, SCU41C, GENMASK(6, 4), (5 << 4))); ++FUNCFG_DESCL(AB24, PIN_CFG(SGPM1, SCU41C, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(WDTRST7N, SCU41C, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(PESGWAKEN, SCU41C, GENMASK(10, 8), (3 << 8)), ++ PIN_CFG(SMON1, SCU41C, GENMASK(10, 8), (5 << 8))); ++FUNCFG_DESCL(W26, PIN_CFG(SGPM1, SCU41C, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SMON1, SCU41C, GENMASK(14, 12), (5 << 12))); ++FUNCFG_DESCL(HOLE0); ++FUNCFG_DESCL(HOLE1); ++FUNCFG_DESCL(HOLE2); ++FUNCFG_DESCL(HOLE3); ++FUNCFG_DESCL(W25, PIN_CFG(HVI3C12, SCU420, GENMASK(2, 0), 1), ++ PIN_CFG(DI2C12, SCU420, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(Y23, PIN_CFG(HVI3C12, SCU420, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(DI2C12, SCU420, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(Y24, PIN_CFG(HVI3C13, SCU420, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(DI2C13, SCU420, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(W21, PIN_CFG(HVI3C13, SCU420, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(DI2C13, SCU420, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(AA23, PIN_CFG(HVI3C14, SCU420, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(DI2C14, SCU420, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(AC22, PIN_CFG(HVI3C14, SCU420, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(DI2C14, SCU420, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(AB22, PIN_CFG(HVI3C15, SCU420, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(DI2C15, SCU420, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(Y21, PIN_CFG(HVI3C15, SCU420, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(DI2C15, SCU420, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(AE20, PIN_CFG(I3C4, SCU424, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(AF19, PIN_CFG(I3C4, SCU424, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(Y22, PIN_CFG(I3C5, SCU424, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(AA20, PIN_CFG(I3C5, SCU424, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(AA22, PIN_CFG(I3C6, SCU424, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(AB20, PIN_CFG(I3C6, SCU424, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(AF18, PIN_CFG(I3C7, SCU424, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(AE19, PIN_CFG(I3C7, SCU424, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(AD20, PIN_CFG(I3C8, SCU428, GENMASK(2, 0), 1), ++ PIN_CFG(FSI0, SCU428, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(AC20, PIN_CFG(I3C8, SCU428, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(FSI0, SCU428, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(AA21, PIN_CFG(I3C9, SCU428, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(FSI1, SCU428, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(AB21, PIN_CFG(I3C9, SCU428, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(FSI1, SCU428, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(AC19, PIN_CFG(I3C10, SCU428, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(FSI2, SCU428, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(AE18, PIN_CFG(I3C10, SCU428, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(FSI2, SCU428, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(AD19, PIN_CFG(I3C11, SCU428, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(FSI3, SCU428, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(AD18, PIN_CFG(I3C11, SCU428, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(FSI3, SCU428, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(U25, PIN_CFG(HVI3C0, SCU42C, GENMASK(2, 0), 1), ++ PIN_CFG(DI2C8, SCU42C, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(U26, PIN_CFG(HVI3C0, SCU42C, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(DI2C8, SCU42C, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(Y26, PIN_CFG(HVI3C1, SCU42C, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(DI2C9, SCU42C, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(AA24, PIN_CFG(HVI3C1, SCU42C, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(DI2C9, SCU42C, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(R25, PIN_CFG(HVI3C2, SCU42C, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(DI2C10, SCU42C, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(AA26, PIN_CFG(HVI3C2, SCU42C, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(DI2C10, SCU42C, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(R26, PIN_CFG(HVI3C3, SCU42C, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(DI2C11, SCU42C, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(Y25, PIN_CFG(HVI3C3, SCU42C, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(DI2C11, SCU42C, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(B16, PIN_CFG(ESPI0, SCU430, GENMASK(2, 0), 1), ++ PIN_CFG(LPC0, SCU430, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(D14, PIN_CFG(ESPI0, SCU430, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(LPC0, SCU430, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(B15, PIN_CFG(ESPI0, SCU430, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(LPC0, SCU430, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(B14, PIN_CFG(ESPI0, SCU430, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(LPC0, SCU430, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(C17, PIN_CFG(ESPI0, SCU430, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(LPC0, SCU430, GENMASK(18, 16), (2 << 16)), ++ PIN_CFG(OSCCLK, SCU430, GENMASK(18, 16), (3 << 16))); ++FUNCFG_DESCL(B13, PIN_CFG(ESPI0, SCU430, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(LPC0, SCU430, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(E14, PIN_CFG(ESPI0, SCU430, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(LPC0, SCU430, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(C15, PIN_CFG(ESPI0, SCU430, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(LPC0, SCU430, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(D24, PIN_CFG(SPI0, SCU434, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(B23, PIN_CFG(SPI0, SCU434, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(B22, PIN_CFG(SPI0, SCU434, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(C23, PIN_CFG(QSPI0, SCU434, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(B18, PIN_CFG(QSPI0, SCU434, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(B21, PIN_CFG(SPI0CS1, SCU434, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(M15, PIN_CFG(SPI0ABR, SCU434, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(TXD8, SCU434, GENMASK(26, 24), (3 << 24))); ++FUNCFG_DESCL(B19, PIN_CFG(SPI0WPN, SCU434, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(RXD8, SCU434, GENMASK(30, 28), (3 << 28))); ++FUNCFG_DESCL(B26, PIN_CFG(SPI1, SCU438, GENMASK(2, 0), 1), ++ PIN_CFG(TXD9, SCU438, GENMASK(2, 0), 3)); ++FUNCFG_DESCL(A25, PIN_CFG(SPI1, SCU438, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(RXD9, SCU438, GENMASK(6, 4), (3 << 4))); ++FUNCFG_DESCL(A24, PIN_CFG(SPI1, SCU438, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(TXD10, SCU438, GENMASK(10, 8), (3 << 8))); ++FUNCFG_DESCL(B24, PIN_CFG(QSPI1, SCU438, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(RXD10, SCU438, GENMASK(14, 12), (3 << 12))); ++FUNCFG_DESCL(E26, PIN_CFG(QSPI1, SCU438, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(TXD11, SCU438, GENMASK(18, 16), (3 << 16))); ++FUNCFG_DESCL(A21, PIN_CFG(SPI1CS1, SCU438, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(RXD11, SCU438, GENMASK(22, 20), (3 << 20))); ++FUNCFG_DESCL(A19, PIN_CFG(SPI1ABR, SCU438, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(THRU2, SCU438, GENMASK(26, 24), (4 << 24))); ++FUNCFG_DESCL(A18, PIN_CFG(SPI1WPN, SCU438, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(THRU2, SCU438, GENMASK(30, 28), (4 << 28))); ++FUNCFG_DESCL(D26, PIN_CFG(SPI2, SCU43C, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(C26, PIN_CFG(SPI2, SCU43C, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(A23, PIN_CFG(SPI2, SCU43C, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(A22, PIN_CFG(SPI2, SCU43C, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(B25, PIN_CFG(QSPI2, SCU43C, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(THRU3, SCU43C, GENMASK(18, 16), (4 << 16))); ++FUNCFG_DESCL(F26, PIN_CFG(QSPI2, SCU43C, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(THRU3, SCU43C, GENMASK(22, 20), (4 << 20))); ++FUNCFG_DESCL(A26, PIN_CFG(SPI2CS1, SCU43C, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(A14, PIN_CFG(FWSPIABR, SCU43C, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(E10, PIN_CFG(MDIO2, SCU440, GENMASK(2, 0), 1), ++ PIN_CFG(PE2SGRSTN, SCU440, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(E13, PIN_CFG(MDIO2, SCU440, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(D12, PIN_CFG(JTAGM1TRST, SCU440, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(F10, PIN_CFG(JTAGM1, SCU440, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(E11, PIN_CFG(JTAGM1, SCU440, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(F11, PIN_CFG(JTAGM1, SCU440, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(F13, PIN_CFG(JTAGM1, SCU440, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(N15, PIN_CFG(FWSPIWPEN, SCU440, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(C20, PIN_CFG(RGMII0, SCU444, GENMASK(2, 0), 1), ++ PIN_CFG(RMII0, SCU444, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(C19, PIN_CFG(RGMII0, SCU444, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(A8, PIN_CFG(RGMII0, SCU444, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(RMII0, SCU444, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(R14, PIN_CFG(RGMII0, SCU444, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(RMII0, SCU444, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(A7, PIN_CFG(RGMII0, SCU444, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(RMII0, SCU444, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(P14, PIN_CFG(RGMII0, SCU444, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(RMII0, SCU444, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(D20, PIN_CFG(RGMII0, SCU444, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(RMII0RCLKO, SCU444, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(A6, PIN_CFG(RGMII0, SCU444, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(RMII0, SCU444, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(B6, PIN_CFG(RGMII0, SCU448, GENMASK(2, 0), 1), ++ PIN_CFG(RMII0, SCU448, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(N14, PIN_CFG(RGMII0, SCU448, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(RMII0, SCU448, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(B7, PIN_CFG(RGMII0, SCU448, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(B8, PIN_CFG(RGMII0, SCU448, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(B9, PIN_CFG(MDIO0, SCU448, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(M14, PIN_CFG(MDIO0, SCU448, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(J11, PIN_CFG(VGA, SCU448, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(E7, PIN_CFG(VGA, SCU448, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(D19, PIN_CFG(RGMII1, SCU44C, GENMASK(2, 0), 1), ++ PIN_CFG(RMII1, SCU44C, GENMASK(2, 0), 2), ++ PIN_CFG(DSGPM0, SCU44C, GENMASK(2, 0), 4)); ++FUNCFG_DESCL(B11, PIN_CFG(RGMII1, SCU44C, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(SGPS, SCU44C, GENMASK(6, 4), (5 << 4))); ++FUNCFG_DESCL(D15, PIN_CFG(RGMII1, SCU44C, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(RMII1, SCU44C, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(TXD3, SCU44C, GENMASK(10, 8), (4 << 8))); ++FUNCFG_DESCL(B12, PIN_CFG(RGMII1, SCU44C, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(RMII1, SCU44C, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(RXD3, SCU44C, GENMASK(14, 12), (4 << 12))); ++FUNCFG_DESCL(B10, PIN_CFG(RGMII1, SCU44C, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(RMII1, SCU44C, GENMASK(18, 16), (2 << 16)), ++ PIN_CFG(DSGPM0, SCU44C, GENMASK(18, 16), (4 << 16))); ++FUNCFG_DESCL(P13, PIN_CFG(RGMII1, SCU44C, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(RMII1, SCU44C, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(C18, PIN_CFG(RGMII1, SCU44C, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(RMII1RCLKO, SCU44C, GENMASK(26, 24), (2 << 24)), ++ PIN_CFG(SGPS, SCU44C, GENMASK(26, 24), (5 << 24))); ++FUNCFG_DESCL(C6, PIN_CFG(RGMII1, SCU44C, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(RMII1, SCU44C, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(C7, PIN_CFG(RGMII1, SCU450, GENMASK(2, 0), 1), ++ PIN_CFG(RMII1, SCU450, GENMASK(2, 0), 2), ++ PIN_CFG(DSGPM0, SCU450, GENMASK(2, 0), 4)); ++FUNCFG_DESCL(D7, PIN_CFG(RGMII1, SCU450, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(RMII1, SCU450, GENMASK(6, 4), (2 << 4)), ++ PIN_CFG(DSGPM0, SCU450, GENMASK(6, 4), (4 << 4))); ++FUNCFG_DESCL(N13, PIN_CFG(RGMII1, SCU450, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(SGPS, SCU450, GENMASK(10, 8), (5 << 8))); ++FUNCFG_DESCL(C8, PIN_CFG(RGMII1, SCU450, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SGPS, SCU450, GENMASK(14, 12), (5 << 12))); ++FUNCFG_DESCL(C9, PIN_CFG(MDIO1, SCU450, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(C10, PIN_CFG(MDIO1, SCU450, GENMASK(22, 20), (1 << 20))); ++FUNCFG_DESCL(M16, PIN_CFG(FWQSPI, SCU450, GENMASK(26, 24), (1 << 24))); ++FUNCFG_DESCL(A15, PIN_CFG(FWQSPI, SCU450, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(G11, PIN_CFG(I2C0, SCU454, GENMASK(2, 0), 1), ++ PIN_CFG(LTPI_PS_I2C0, SCU454, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(H7, PIN_CFG(I2C0, SCU454, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(LTPI_PS_I2C0, SCU454, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(H8, PIN_CFG(I2C1, SCU454, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(LTPI_PS_I2C1, SCU454, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(H9, PIN_CFG(I2C1, SCU454, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(LTPI_PS_I2C1, SCU454, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(H10, PIN_CFG(I2C2, SCU454, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(LTPI_PS_I2C2, SCU454, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(H11, PIN_CFG(I2C2, SCU454, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(LTPI_PS_I2C2, SCU454, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(J9, PIN_CFG(I2C3, SCU454, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(LTPI_PS_I2C3, SCU454, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(J10, PIN_CFG(I2C3, SCU454, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(LTPI_PS_I2C3, SCU454, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(E9, PIN_CFG(I2C4, SCU458, GENMASK(2, 0), 1), ++ PIN_CFG(I2CF1, SCU458, GENMASK(2, 0), 5)); ++FUNCFG_DESCL(F9, PIN_CFG(I2C4, SCU458, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(I2CF1, SCU458, GENMASK(6, 4), (5 << 4))); ++FUNCFG_DESCL(F8, PIN_CFG(I2C5, SCU458, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(I2CF1, SCU458, GENMASK(10, 8), (5 << 8))); ++FUNCFG_DESCL(M13, PIN_CFG(I2C5, SCU458, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(I2CF1, SCU458, GENMASK(14, 12), (5 << 12))); ++FUNCFG_DESCL(F7, PIN_CFG(I2C6, SCU458, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(I2CF2, SCU458, GENMASK(18, 16), (5 << 16))); ++FUNCFG_DESCL(D8, PIN_CFG(I2C6, SCU458, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(I2CF2, SCU458, GENMASK(22, 20), (5 << 20))); ++FUNCFG_DESCL(E8, PIN_CFG(I2C7, SCU458, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(I2CF2, SCU458, GENMASK(26, 24), (5 << 24))); ++FUNCFG_DESCL(L12, PIN_CFG(I2C7, SCU458, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(I2CF2, SCU458, GENMASK(30, 28), (5 << 28))); ++FUNCFG_DESCL(F12, PIN_CFG(I2C8, SCU45C, GENMASK(2, 0), 1), ++ PIN_CFG(I2CF0, SCU45C, GENMASK(2, 0), 5)); ++FUNCFG_DESCL(E12, PIN_CFG(I2C8, SCU45C, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(I2CF0, SCU45C, GENMASK(6, 4), (5 << 4))); ++FUNCFG_DESCL(J12, PIN_CFG(I2C9, SCU45C, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(I2CF0, SCU45C, GENMASK(10, 8), (5 << 8))); ++FUNCFG_DESCL(G7, PIN_CFG(I2C9, SCU45C, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(CANBUSSTBY, SCU45C, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(I2CF0, SCU45C, GENMASK(14, 12), (5 << 12))); ++FUNCFG_DESCL(G8, PIN_CFG(I2C10, SCU45C, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(CANBUS, SCU45C, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(G9, PIN_CFG(I2C10, SCU45C, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(CANBUS, SCU45C, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(G10, PIN_CFG(I2C11, SCU45C, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(USBUART, SCU45C, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(K12, PIN_CFG(I2C11, SCU45C, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(USBUART, SCU45C, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(W17, PIN_CFG(ADC0, SCU460, GENMASK(2, 0), 0), ++ PIN_CFG(GPIY0, SCU460, GENMASK(2, 0), 1), ++ PIN_CFG(SALT4, SCU460, GENMASK(2, 0), 2)); ++FUNCFG_DESCL(V18, PIN_CFG(ADC1, SCU460, GENMASK(6, 4), 0), ++ PIN_CFG(GPIY1, SCU460, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(SALT5, SCU460, GENMASK(6, 4), (2 << 4))); ++FUNCFG_DESCL(W18, PIN_CFG(ADC2, SCU460, GENMASK(10, 8), 0), ++ PIN_CFG(GPIY2, SCU460, GENMASK(10, 8), (1 << 8)), ++ PIN_CFG(SALT6, SCU460, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(Y17, PIN_CFG(ADC3, SCU460, GENMASK(14, 12), 0), ++ PIN_CFG(GPIY3, SCU460, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SALT7, SCU460, GENMASK(14, 12), (2 << 12))); ++FUNCFG_DESCL(AA18, PIN_CFG(ADC4, SCU460, GENMASK(18, 16), 0), ++ PIN_CFG(GPIY4, SCU460, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(SALT8, SCU460, GENMASK(18, 16), (2 << 16))); ++FUNCFG_DESCL(AA13, PIN_CFG(ADC5, SCU460, GENMASK(22, 20), 0), ++ PIN_CFG(GPIY5, SCU460, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(SALT9, SCU460, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(Y18, PIN_CFG(ADC6, SCU460, GENMASK(26, 24), 0), ++ PIN_CFG(GPIY6, SCU460, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(SALT10, SCU460, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(AA12, PIN_CFG(ADC7, SCU460, GENMASK(30, 28), 0), ++ PIN_CFG(GPIY7, SCU460, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(SALT11, SCU460, GENMASK(30, 28), (2 << 28))); ++FUNCFG_DESCL(W20, PIN_CFG(ADC8, SCU464, GENMASK(2, 0), 0), ++ PIN_CFG(GPIZ0, SCU464, GENMASK(2, 0), 1)); ++FUNCFG_DESCL(V20, PIN_CFG(ADC9, SCU464, GENMASK(6, 4), 0), ++ PIN_CFG(GPIZ1, SCU464, GENMASK(6, 4), (1 << 4))); ++FUNCFG_DESCL(Y11, PIN_CFG(ADC10, SCU464, GENMASK(10, 8), 0), ++ PIN_CFG(GPIZ2, SCU464, GENMASK(10, 8), (1 << 8))); ++FUNCFG_DESCL(V14, PIN_CFG(ADC11, SCU464, GENMASK(14, 12), 0), ++ PIN_CFG(GPIZ3, SCU464, GENMASK(14, 12), (1 << 12))); ++FUNCFG_DESCL(V19, PIN_CFG(ADC12, SCU464, GENMASK(18, 16), 0), ++ PIN_CFG(GPIZ4, SCU464, GENMASK(18, 16), (1 << 16))); ++FUNCFG_DESCL(W14, PIN_CFG(ADC13, SCU464, GENMASK(22, 20), 0), ++ PIN_CFG(GPIZ5, SCU464, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(AUXPWRGOOD0, SCU464, GENMASK(22, 20), (2 << 20))); ++FUNCFG_DESCL(Y20, PIN_CFG(ADC14, SCU464, GENMASK(26, 24), 0), ++ PIN_CFG(GPIZ6, SCU464, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(AUXPWRGOOD1, SCU464, GENMASK(26, 24), (2 << 24))); ++FUNCFG_DESCL(AB19, PIN_CFG(ADC15, SCU464, GENMASK(30, 28), 0), ++ PIN_CFG(GPIZ7, SCU464, GENMASK(30, 28), (1 << 28))); ++FUNCFG_DESCL(U21, PIN_CFG(SGPM0, SCU468, GENMASK(2, 0), 1), ++ PIN_CFG(SMON0, SCU468, GENMASK(2, 0), 2), ++ PIN_CFG(NCTS2, SCU468, GENMASK(2, 0), 3), ++ PIN_CFG(MACLINK0, SCU468, GENMASK(2, 0), 4)); ++FUNCFG_DESCL(T24, PIN_CFG(SGPM0, SCU468, GENMASK(6, 4), (1 << 4)), ++ PIN_CFG(SMON0, SCU468, GENMASK(6, 4), (2 << 4)), ++ PIN_CFG(NDCD2, SCU468, GENMASK(6, 4), (3 << 4)), ++ PIN_CFG(MACLINK2, SCU468, GENMASK(6, 4), (4 << 4))); ++FUNCFG_DESCL(V24, PIN_CFG(SGPM0LD_R, SCU468, GENMASK(10, 8), (2 << 8)), ++ PIN_CFG(HBLED, SCU468, GENMASK(10, 8), (2 << 8))); ++FUNCFG_DESCL(V22, PIN_CFG(SGPM0, SCU468, GENMASK(14, 12), (1 << 12)), ++ PIN_CFG(SMON0, SCU468, GENMASK(14, 12), (2 << 12)), ++ PIN_CFG(NDSR2, SCU468, GENMASK(14, 12), (3 << 12))); ++FUNCFG_DESCL(T23, PIN_CFG(SGPM0, SCU468, GENMASK(18, 16), (1 << 16)), ++ PIN_CFG(SMON0, SCU468, GENMASK(18, 16), (2 << 16)), ++ PIN_CFG(NRI2, SCU468, GENMASK(18, 16), (3 << 16))); ++FUNCFG_DESCL(AC25, PIN_CFG(SGPM1, SCU468, GENMASK(22, 20), (1 << 20)), ++ PIN_CFG(WDTRST4N, SCU468, GENMASK(22, 20), (2 << 20)), ++ PIN_CFG(NDTR2, SCU468, GENMASK(22, 20), (3 << 20)), ++ PIN_CFG(SMON1, SCU468, GENMASK(22, 20), (4 << 20))); ++FUNCFG_DESCL(AB25, PIN_CFG(SGPM1, SCU468, GENMASK(26, 24), (1 << 24)), ++ PIN_CFG(WDTRST5N, SCU468, GENMASK(26, 24), (2 << 24)), ++ PIN_CFG(NRTS2, SCU468, GENMASK(26, 24), (3 << 24)), ++ PIN_CFG(SMON1, SCU468, GENMASK(26, 24), (4 << 24))); ++FUNCFG_DESCL(AC24, PIN_CFG(SGPM1LD_R, SCU468, GENMASK(30, 28), (1 << 28)), ++ PIN_CFG(WDTRST6N, SCU468, GENMASK(30, 28), (2 << 28)), ++ PIN_CFG(MACLINK1, SCU468, GENMASK(30, 28), (3 << 28))); ++FUNCFG_DESCL(SGMII0, PIN_CFG(SGMII, SCU47C, BIT(0), 1 << 0)); ++FUNCFG_DESCL(PCIERC2_PERST, PIN_CFG(PE2SGRSTN, SCU908, BIT(1), 1 << 1)); ++FUNCFG_DESCL(PORTC_MODE, PIN_CFG(USB2CUD, SCU3B0, GENMASK(1, 0), 0), ++ PIN_CFG(USB2CD, SCU3B0, GENMASK(1, 0), 1 << 0), ++ PIN_CFG(USB2CH, SCU3B0, GENMASK(1, 0), 2 << 0), ++ PIN_CFG(USB2CU, SCU3B0, GENMASK(1, 0), 3 << 0)); ++FUNCFG_DESCL(PORTD_MODE, PIN_CFG(USB2DD, SCU3B0, GENMASK(3, 2), 1 << 2), ++ PIN_CFG(USB2DH, SCU3B0, GENMASK(3, 2), 2 << 2)); ++ ++static const struct aspeed_g7_pincfg pin_cfg[] = { ++ PINCFG_PIN(C16), PINCFG_PIN(C14), PINCFG_PIN(C11), ++ PINCFG_PIN(D9), PINCFG_PIN(F14), PINCFG_PIN(D10), ++ PINCFG_PIN(C12), PINCFG_PIN(C13), PINCFG_PIN(AC26), ++ PINCFG_PIN(AA25), PINCFG_PIN(AB23), PINCFG_PIN(U22), ++ PINCFG_PIN(V21), PINCFG_PIN(N26), PINCFG_PIN(P25), ++ PINCFG_PIN(N25), PINCFG_PIN(V23), PINCFG_PIN(W22), ++ PINCFG_PIN(AB26), PINCFG_PIN(AD26), PINCFG_PIN(P26), ++ PINCFG_PIN(AE26), PINCFG_PIN(AF26), PINCFG_PIN(AF25), ++ PINCFG_PIN(AE25), PINCFG_PIN(AD25), PINCFG_PIN(AF23), ++ PINCFG_PIN(AF20), PINCFG_PIN(AF21), PINCFG_PIN(AE21), ++ PINCFG_PIN(AE23), PINCFG_PIN(AD22), PINCFG_PIN(AF17), ++ PINCFG_PIN(AA16), PINCFG_PIN(Y16), PINCFG_PIN(V17), ++ PINCFG_PIN(J13), PINCFG_PIN(AB16), PINCFG_PIN(AC16), ++ PINCFG_PIN(AF16), PINCFG_PIN(AA15), PINCFG_PIN(AB15), ++ PINCFG_PIN(AC15), PINCFG_PIN(AD15), PINCFG_PIN(Y15), ++ PINCFG_PIN(AA14), PINCFG_PIN(W16), PINCFG_PIN(V16), ++ PINCFG_PIN(AB18), PINCFG_PIN(AC18), PINCFG_PIN(K13), ++ PINCFG_PIN(AA17), PINCFG_PIN(AB17), PINCFG_PIN(AD16), ++ PINCFG_PIN(AC17), PINCFG_PIN(AD17), PINCFG_PIN(AE16), ++ PINCFG_PIN(AE17), PINCFG_PIN(AB24), PINCFG_PIN(W26), ++ PINCFG_PIN(HOLE0), PINCFG_PIN(HOLE1), PINCFG_PIN(HOLE2), ++ PINCFG_PIN(HOLE3), PINCFG_PIN(W25), PINCFG_PIN(Y23), ++ PINCFG_PIN(Y24), PINCFG_PIN(W21), PINCFG_PIN(AA23), ++ PINCFG_PIN(AC22), PINCFG_PIN(AB22), PINCFG_PIN(Y21), ++ PINCFG_PIN(AE20), PINCFG_PIN(AF19), PINCFG_PIN(Y22), ++ PINCFG_PIN(AA20), PINCFG_PIN(AA22), PINCFG_PIN(AB20), ++ PINCFG_PIN(AF18), PINCFG_PIN(AE19), PINCFG_PIN(AD20), ++ PINCFG_PIN(AC20), PINCFG_PIN(AA21), PINCFG_PIN(AB21), ++ PINCFG_PIN(AC19), PINCFG_PIN(AE18), PINCFG_PIN(AD19), ++ PINCFG_PIN(AD18), PINCFG_PIN(U25), PINCFG_PIN(U26), ++ PINCFG_PIN(Y26), PINCFG_PIN(AA24), PINCFG_PIN(R25), ++ PINCFG_PIN(AA26), PINCFG_PIN(R26), PINCFG_PIN(Y25), ++ PINCFG_PIN(B16), PINCFG_PIN(D14), PINCFG_PIN(B15), ++ PINCFG_PIN(B14), PINCFG_PIN(C17), PINCFG_PIN(B13), ++ PINCFG_PIN(E14), PINCFG_PIN(C15), PINCFG_PIN(D24), ++ PINCFG_PIN(B23), PINCFG_PIN(B22), PINCFG_PIN(C23), ++ PINCFG_PIN(B18), PINCFG_PIN(B21), PINCFG_PIN(M15), ++ PINCFG_PIN(B19), PINCFG_PIN(B26), PINCFG_PIN(A25), ++ PINCFG_PIN(A24), PINCFG_PIN(B24), PINCFG_PIN(E26), ++ PINCFG_PIN(A21), PINCFG_PIN(A19), PINCFG_PIN(A18), ++ PINCFG_PIN(D26), PINCFG_PIN(C26), PINCFG_PIN(A23), ++ PINCFG_PIN(A22), PINCFG_PIN(B25), PINCFG_PIN(F26), ++ PINCFG_PIN(A26), PINCFG_PIN(A14), PINCFG_PIN(E10), ++ PINCFG_PIN(E13), PINCFG_PIN(D12), PINCFG_PIN(F10), ++ PINCFG_PIN(E11), PINCFG_PIN(F11), PINCFG_PIN(F13), ++ PINCFG_PIN(N15), PINCFG_PIN(C20), PINCFG_PIN(C19), ++ PINCFG_PIN(A8), PINCFG_PIN(R14), PINCFG_PIN(A7), ++ PINCFG_PIN(P14), PINCFG_PIN(D20), PINCFG_PIN(A6), ++ PINCFG_PIN(B6), PINCFG_PIN(N14), PINCFG_PIN(B7), ++ PINCFG_PIN(B8), PINCFG_PIN(B9), PINCFG_PIN(M14), ++ PINCFG_PIN(J11), PINCFG_PIN(E7), PINCFG_PIN(D19), ++ PINCFG_PIN(B11), PINCFG_PIN(D15), PINCFG_PIN(B12), ++ PINCFG_PIN(B10), PINCFG_PIN(P13), PINCFG_PIN(C18), ++ PINCFG_PIN(C6), PINCFG_PIN(C7), PINCFG_PIN(D7), ++ PINCFG_PIN(N13), PINCFG_PIN(C8), PINCFG_PIN(C9), ++ PINCFG_PIN(C10), PINCFG_PIN(M16), PINCFG_PIN(A15), ++ PINCFG_PIN(G11), PINCFG_PIN(H7), PINCFG_PIN(H8), ++ PINCFG_PIN(H9), PINCFG_PIN(H10), PINCFG_PIN(H11), ++ PINCFG_PIN(J9), PINCFG_PIN(J10), PINCFG_PIN(E9), ++ PINCFG_PIN(F9), PINCFG_PIN(F8), PINCFG_PIN(M13), ++ PINCFG_PIN(F7), PINCFG_PIN(D8), PINCFG_PIN(E8), ++ PINCFG_PIN(L12), PINCFG_PIN(F12), PINCFG_PIN(E12), ++ PINCFG_PIN(J12), PINCFG_PIN(G7), PINCFG_PIN(G8), ++ PINCFG_PIN(G9), PINCFG_PIN(G10), PINCFG_PIN(K12), ++ PINCFG_PIN(W17), PINCFG_PIN(V18), PINCFG_PIN(W18), ++ PINCFG_PIN(Y17), PINCFG_PIN(AA18), PINCFG_PIN(AA13), ++ PINCFG_PIN(Y18), PINCFG_PIN(AA12), PINCFG_PIN(W20), ++ PINCFG_PIN(V20), PINCFG_PIN(Y11), PINCFG_PIN(V14), ++ PINCFG_PIN(V19), PINCFG_PIN(W14), PINCFG_PIN(Y20), ++ PINCFG_PIN(AB19), PINCFG_PIN(U21), PINCFG_PIN(T24), ++ PINCFG_PIN(V24), PINCFG_PIN(V22), PINCFG_PIN(T23), ++ PINCFG_PIN(AC25), PINCFG_PIN(AB25), PINCFG_PIN(AC24), ++ PINCFG_PIN(SGMII0), PINCFG_PIN(PCIERC2_PERST), ++ PINCFG_PIN(PORTC_MODE), PINCFG_PIN(PORTD_MODE), ++}; ++ ++static int aspeed_g7_soc1_dt_node_to_map(struct pinctrl_dev *pctldev, ++ struct device_node *np_config, ++ struct pinctrl_map **map, u32 *num_maps) ++{ ++ return pinconf_generic_dt_node_to_map(pctldev, np_config, map, num_maps, ++ PIN_MAP_TYPE_INVALID); ++} ++ ++static void aspeed_g7_soc1_dt_free_map(struct pinctrl_dev *pctldev, ++ struct pinctrl_map *map, u32 num_maps) ++{ ++ kfree(map); ++} ++ ++static const struct pinctrl_ops aspeed_g7_soc1_pinctrl_ops = { ++ .get_groups_count = aspeed_pinctrl_get_groups_count, ++ .get_group_name = aspeed_pinctrl_get_group_name, ++ .get_group_pins = aspeed_pinctrl_get_group_pins, ++ .pin_dbg_show = aspeed_pinctrl_pin_dbg_show, ++ .dt_node_to_map = aspeed_g7_soc1_dt_node_to_map, ++ .dt_free_map = aspeed_g7_soc1_dt_free_map, ++}; ++ ++static const struct pinmux_ops aspeed_g7_soc1_pinmux_ops = { ++ .get_functions_count = aspeed_pinmux_get_fn_count, ++ .get_function_name = aspeed_pinmux_get_fn_name, ++ .get_function_groups = aspeed_pinmux_get_fn_groups, ++ .set_mux = aspeed_g7_pinmux_set_mux, ++ .gpio_request_enable = aspeed_g7_gpio_request_enable, ++ .strict = true, ++}; ++ ++static const struct pinconf_ops aspeed_g7_soc1_pinconf_ops = { ++ .is_generic = true, ++ .pin_config_get = aspeed_pin_config_get, ++ .pin_config_set = aspeed_pin_config_set, ++ .pin_config_group_get = aspeed_pin_config_group_get, ++ .pin_config_group_set = aspeed_pin_config_group_set, ++}; ++ ++/* pinctrl_desc */ ++static struct pinctrl_desc aspeed_g7_soc1_pinctrl_desc = { ++ .name = "aspeed-g7-soc1-pinctrl", ++ .pins = aspeed_g7_soc1_pins, ++ .npins = ARRAY_SIZE(aspeed_g7_soc1_pins), ++ .pctlops = &aspeed_g7_soc1_pinctrl_ops, ++ .pmxops = &aspeed_g7_soc1_pinmux_ops, ++ .confops = &aspeed_g7_soc1_pinconf_ops, ++ .owner = THIS_MODULE, ++}; ++ ++static struct aspeed_pin_config aspeed_g7_configs[] = { ++ /* GPIOA */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { C16, C16 }, SCU4C0, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C14, C14 }, SCU4C0, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C11, C11 }, SCU4C0, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D9, D9 }, SCU4C0, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F14, F14 }, SCU4C0, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D10, D10 }, SCU4C0, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C12, C12 }, SCU4C0, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C13, C13 }, SCU4C0, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { C16, C13 }, SCU4A0, BIT_MASK(4) }, ++ /* GPIOI */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { W25, W25 }, SCU4C0, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { Y23, Y23 }, SCU4C0, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { Y24, Y24 }, SCU4C0, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { W21, W21 }, SCU4C0, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AA23, AA23 }, SCU4C0, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AC22, AC22 }, SCU4C0, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AB22, AB22 }, SCU4C0, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { Y21, Y21 }, SCU4C0, GENMASK(31, 30) }, ++ { PIN_CONFIG_POWER_SOURCE, { W25, Y21 }, SCU4A0, BIT_MASK(12) }, ++ /* GPIOJ */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { AE20, AE20 }, SCU4C4, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AF19, AF19 }, SCU4C4, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { Y22, Y22 }, SCU4C4, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AA20, AA20 }, SCU4C4, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AA22, AA22 }, SCU4C4, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AB20, AB20 }, SCU4C4, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AF18, AF18 }, SCU4C4, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AE19, AE19 }, SCU4C4, GENMASK(15, 14) }, ++ /* GPIOK */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { AD20, AD20 }, SCU4C4, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AC20, AC20 }, SCU4C4, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AA21, AA21 }, SCU4C4, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AB21, AB21 }, SCU4C4, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AC19, AC19 }, SCU4C4, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AE18, AE18 }, SCU4C4, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AD19, AD19 }, SCU4C4, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AD18, AD18 }, SCU4C4, GENMASK(31, 30) }, ++ /* GPIOL */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { U25, U25 }, SCU4C8, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { U26, U26 }, SCU4C8, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { Y26, Y26 }, SCU4C8, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AA24, AA24 }, SCU4C8, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R25, R25 }, SCU4C8, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { AA26, AA26 }, SCU4C8, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R26, R26 }, SCU4C8, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { Y25, Y25 }, SCU4C8, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { U25, Y25 }, SCU4A0, BIT_MASK(15) }, ++ /* GPIOM */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { B16, B16 }, SCU4C8, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D14, D14 }, SCU4C8, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B15, B15 }, SCU4C8, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B14, B14 }, SCU4C8, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C17, C17 }, SCU4C8, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B13, B13 }, SCU4C8, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E14, E14 }, SCU4C8, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C15, C15 }, SCU4C8, GENMASK(31, 30) }, ++ { PIN_CONFIG_POWER_SOURCE, { B16, C15 }, SCU4A0, BIT_MASK(16) }, ++ /* GPION */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { D24, D24 }, SCU4CC, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B23, B23 }, SCU4CC, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B22, B22 }, SCU4CC, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C23, C23 }, SCU4CC, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B18, B18 }, SCU4CC, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B21, B21 }, SCU4CC, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M15, M15 }, SCU4CC, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B19, B19 }, SCU4CC, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { D24, B19 }, SCU4A0, BIT_MASK(17) }, ++ /* GPIOO */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { B26, B26 }, SCU4CC, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A25, A25 }, SCU4CC, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A24, A24 }, SCU4CC, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B24, B24 }, SCU4CC, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E26, E26 }, SCU4CC, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A21, A21 }, SCU4CC, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A19, A19 }, SCU4CC, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A18, A18 }, SCU4CC, GENMASK(31, 30) }, ++ { PIN_CONFIG_POWER_SOURCE, { B26, A18 }, SCU4A0, BIT_MASK(18) }, ++ /* GPIOP */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { D26, D26 }, SCU4D0, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C26, C26 }, SCU4D0, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A23, A23 }, SCU4D0, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A22, A22 }, SCU4D0, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B25, B25 }, SCU4D0, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F26, F26 }, SCU4D0, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A26, A26 }, SCU4D0, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A14, A14 }, SCU4D0, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { D26, A14 }, SCU4A0, BIT_MASK(19) }, ++ /* GPIOQ */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { E10, E10 }, SCU4D0, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E13, E13 }, SCU4D0, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D12, D12 }, SCU4D0, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F10, F10 }, SCU4D0, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E11, E11 }, SCU4D0, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F11, F11 }, SCU4D0, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F13, F13 }, SCU4D0, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { N15, N15 }, SCU4D0, GENMASK(31, 30) }, ++ /* GPIOR */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { C20, C20 }, SCU4D4, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C19, C19 }, SCU4D4, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A8, A8 }, SCU4D4, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { R14, R14 }, SCU4D4, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A7, A7 }, SCU4D4, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { P14, P14 }, SCU4D4, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D20, D20 }, SCU4D4, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A6, A6 }, SCU4D4, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { C20, A6 }, SCU4A0, BIT_MASK(21) }, ++ /* GPIOS */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { B6, B6 }, SCU4D4, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { N14, N14 }, SCU4D4, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B7, B7 }, SCU4D4, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B8, B8 }, SCU4D4, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B9, B9 }, SCU4D4, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M14, M14 }, SCU4D4, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { J11, J11 }, SCU4D4, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E7, E7 }, SCU4D4, GENMASK(31, 30) }, ++ { PIN_CONFIG_POWER_SOURCE, { B6, E7 }, SCU4A0, BIT_MASK(22) }, ++ /* GPIOT */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { D19, D19 }, SCU4D8, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B11, B11 }, SCU4D8, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D15, D15 }, SCU4D8, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B12, B12 }, SCU4D8, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { B10, B10 }, SCU4D8, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { P13, P13 }, SCU4D8, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C18, C18 }, SCU4D8, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C6, C6 }, SCU4D8, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { D19, C6 }, SCU4A0, BIT_MASK(23) }, ++ /* GPIOU */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { C7, C7 }, SCU4D8, GENMASK(17, 16) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D7, D7 }, SCU4D8, GENMASK(19, 18) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { N13, N13 }, SCU4D8, GENMASK(21, 20) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C8, C8 }, SCU4D8, GENMASK(23, 22) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C9, C9 }, SCU4D8, GENMASK(25, 24) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { C10, C10 }, SCU4D8, GENMASK(27, 26) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M16, M16 }, SCU4D8, GENMASK(29, 28) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { A15, A15 }, SCU4D8, GENMASK(31, 30) }, ++ { PIN_CONFIG_POWER_SOURCE, { C7, A15 }, SCU4A0, BIT_MASK(24) }, ++ /* GPIOW */ ++ { PIN_CONFIG_DRIVE_STRENGTH, { E9, E9 }, SCU4DC, GENMASK(1, 0) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F9, F9 }, SCU4DC, GENMASK(3, 2) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F8, F8 }, SCU4DC, GENMASK(5, 4) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { M13, M13 }, SCU4DC, GENMASK(7, 6) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { F7, F7 }, SCU4DC, GENMASK(9, 8) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { D8, D8 }, SCU4DC, GENMASK(11, 10) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { E8, E8 }, SCU4DC, GENMASK(13, 12) }, ++ { PIN_CONFIG_DRIVE_STRENGTH, { L12, L12 }, SCU4DC, GENMASK(15, 14) }, ++ { PIN_CONFIG_POWER_SOURCE, { E9, L12 }, SCU4A0, BIT_MASK(26) }, ++ ++ ASPEED_PULL_DOWN_PINCONF(C16, SCU480, 0), ++ ASPEED_PULL_DOWN_PINCONF(C14, SCU480, 1), ++ ASPEED_PULL_DOWN_PINCONF(C11, SCU480, 2), ++ ASPEED_PULL_DOWN_PINCONF(D9, SCU480, 3), ++ ASPEED_PULL_DOWN_PINCONF(F14, SCU480, 4), ++ ASPEED_PULL_DOWN_PINCONF(D10, SCU480, 5), ++ ASPEED_PULL_DOWN_PINCONF(C12, SCU480, 6), ++ ASPEED_PULL_DOWN_PINCONF(C13, SCU480, 7), ++ ASPEED_PULL_DOWN_PINCONF(AC26, SCU480, 8), ++ ASPEED_PULL_DOWN_PINCONF(AA25, SCU480, 9), ++ ASPEED_PULL_DOWN_PINCONF(AB23, SCU480, 10), ++ ASPEED_PULL_DOWN_PINCONF(U22, SCU480, 11), ++ ASPEED_PULL_DOWN_PINCONF(V21, SCU480, 12), ++ ASPEED_PULL_DOWN_PINCONF(N26, SCU480, 13), ++ ASPEED_PULL_DOWN_PINCONF(P25, SCU480, 14), ++ ASPEED_PULL_DOWN_PINCONF(N25, SCU480, 15), ++ ASPEED_PULL_DOWN_PINCONF(V23, SCU480, 16), ++ ASPEED_PULL_DOWN_PINCONF(W22, SCU480, 17), ++ ASPEED_PULL_DOWN_PINCONF(AB26, SCU480, 18), ++ ASPEED_PULL_DOWN_PINCONF(AD26, SCU480, 19), ++ ASPEED_PULL_DOWN_PINCONF(P26, SCU480, 20), ++ ASPEED_PULL_DOWN_PINCONF(AE26, SCU480, 21), ++ ASPEED_PULL_DOWN_PINCONF(AF26, SCU480, 22), ++ ASPEED_PULL_DOWN_PINCONF(AF25, SCU480, 23), ++ ASPEED_PULL_DOWN_PINCONF(AE25, SCU480, 24), ++ ASPEED_PULL_DOWN_PINCONF(AD25, SCU480, 25), ++ ASPEED_PULL_DOWN_PINCONF(AF23, SCU480, 26), ++ ASPEED_PULL_DOWN_PINCONF(AF20, SCU480, 27), ++ ASPEED_PULL_DOWN_PINCONF(AF21, SCU480, 28), ++ ASPEED_PULL_DOWN_PINCONF(AE21, SCU480, 29), ++ ASPEED_PULL_DOWN_PINCONF(AE23, SCU480, 30), ++ ASPEED_PULL_DOWN_PINCONF(AD22, SCU480, 31), ++ ASPEED_PULL_DOWN_PINCONF(AF17, SCU484, 0), ++ ASPEED_PULL_DOWN_PINCONF(AA16, SCU484, 1), ++ ASPEED_PULL_DOWN_PINCONF(Y16, SCU484, 2), ++ ASPEED_PULL_DOWN_PINCONF(V17, SCU484, 3), ++ ASPEED_PULL_DOWN_PINCONF(J13, SCU484, 4), ++ ASPEED_PULL_DOWN_PINCONF(AB16, SCU484, 5), ++ ASPEED_PULL_DOWN_PINCONF(AC16, SCU484, 6), ++ ASPEED_PULL_DOWN_PINCONF(AF16, SCU484, 7), ++ ASPEED_PULL_DOWN_PINCONF(AA15, SCU484, 8), ++ ASPEED_PULL_DOWN_PINCONF(AB15, SCU484, 9), ++ ASPEED_PULL_DOWN_PINCONF(AC15, SCU484, 10), ++ ASPEED_PULL_DOWN_PINCONF(AD15, SCU484, 11), ++ ASPEED_PULL_DOWN_PINCONF(Y15, SCU484, 12), ++ ASPEED_PULL_DOWN_PINCONF(AA14, SCU484, 13), ++ ASPEED_PULL_DOWN_PINCONF(W16, SCU484, 14), ++ ASPEED_PULL_DOWN_PINCONF(V16, SCU484, 15), ++ ASPEED_PULL_DOWN_PINCONF(AB18, SCU484, 16), ++ ASPEED_PULL_DOWN_PINCONF(AC18, SCU484, 17), ++ ASPEED_PULL_DOWN_PINCONF(K13, SCU484, 18), ++ ASPEED_PULL_DOWN_PINCONF(AA17, SCU484, 19), ++ ASPEED_PULL_DOWN_PINCONF(AB17, SCU484, 20), ++ ASPEED_PULL_DOWN_PINCONF(AD16, SCU484, 21), ++ ASPEED_PULL_DOWN_PINCONF(AC17, SCU484, 22), ++ ASPEED_PULL_DOWN_PINCONF(AD17, SCU484, 23), ++ ASPEED_PULL_DOWN_PINCONF(AE16, SCU484, 24), ++ ASPEED_PULL_DOWN_PINCONF(AE17, SCU484, 25), ++ ASPEED_PULL_DOWN_PINCONF(AB24, SCU484, 26), ++ ASPEED_PULL_DOWN_PINCONF(W26, SCU484, 27), ++ ASPEED_PULL_DOWN_PINCONF(HOLE0, SCU484, 28), ++ ASPEED_PULL_DOWN_PINCONF(HOLE1, SCU484, 29), ++ ASPEED_PULL_DOWN_PINCONF(HOLE2, SCU484, 30), ++ ASPEED_PULL_DOWN_PINCONF(HOLE3, SCU484, 31), ++ ASPEED_PULL_DOWN_PINCONF(W25, SCU488, 0), ++ ASPEED_PULL_DOWN_PINCONF(Y23, SCU488, 1), ++ ASPEED_PULL_DOWN_PINCONF(Y24, SCU488, 2), ++ ASPEED_PULL_DOWN_PINCONF(W21, SCU488, 3), ++ ASPEED_PULL_DOWN_PINCONF(AA23, SCU488, 4), ++ ASPEED_PULL_DOWN_PINCONF(AC22, SCU488, 5), ++ ASPEED_PULL_DOWN_PINCONF(AB22, SCU488, 6), ++ ASPEED_PULL_DOWN_PINCONF(Y21, SCU488, 7), ++ ASPEED_PULL_DOWN_PINCONF(AE20, SCU488, 8), ++ ASPEED_PULL_DOWN_PINCONF(AF19, SCU488, 9), ++ ASPEED_PULL_DOWN_PINCONF(Y22, SCU488, 10), ++ ASPEED_PULL_DOWN_PINCONF(AA20, SCU488, 11), ++ ASPEED_PULL_DOWN_PINCONF(AA22, SCU488, 12), ++ ASPEED_PULL_DOWN_PINCONF(AB20, SCU488, 13), ++ ASPEED_PULL_DOWN_PINCONF(AF18, SCU488, 14), ++ ASPEED_PULL_DOWN_PINCONF(AE19, SCU488, 15), ++ ASPEED_PULL_DOWN_PINCONF(AD20, SCU488, 16), ++ ASPEED_PULL_DOWN_PINCONF(AC20, SCU488, 17), ++ ASPEED_PULL_DOWN_PINCONF(AA21, SCU488, 18), ++ ASPEED_PULL_DOWN_PINCONF(AB21, SCU488, 19), ++ ASPEED_PULL_DOWN_PINCONF(AC19, SCU488, 20), ++ ASPEED_PULL_DOWN_PINCONF(AE18, SCU488, 21), ++ ASPEED_PULL_DOWN_PINCONF(AD19, SCU488, 22), ++ ASPEED_PULL_DOWN_PINCONF(AD18, SCU488, 23), ++ ASPEED_PULL_DOWN_PINCONF(U25, SCU488, 24), ++ ASPEED_PULL_DOWN_PINCONF(U26, SCU488, 25), ++ ASPEED_PULL_DOWN_PINCONF(Y26, SCU488, 26), ++ ASPEED_PULL_DOWN_PINCONF(AA24, SCU488, 27), ++ ASPEED_PULL_DOWN_PINCONF(R25, SCU488, 28), ++ ASPEED_PULL_DOWN_PINCONF(AA26, SCU488, 29), ++ ASPEED_PULL_DOWN_PINCONF(R26, SCU488, 30), ++ ASPEED_PULL_DOWN_PINCONF(Y25, SCU488, 31), ++ ASPEED_PULL_DOWN_PINCONF(B16, SCU48C, 0), ++ ASPEED_PULL_DOWN_PINCONF(D14, SCU48C, 1), ++ ASPEED_PULL_DOWN_PINCONF(B15, SCU48C, 2), ++ ASPEED_PULL_DOWN_PINCONF(B14, SCU48C, 3), ++ ASPEED_PULL_DOWN_PINCONF(C17, SCU48C, 4), ++ ASPEED_PULL_DOWN_PINCONF(B13, SCU48C, 5), ++ ASPEED_PULL_DOWN_PINCONF(E14, SCU48C, 6), ++ ASPEED_PULL_DOWN_PINCONF(C15, SCU48C, 7), ++ ASPEED_PULL_DOWN_PINCONF(D24, SCU48C, 8), ++ ASPEED_PULL_DOWN_PINCONF(B23, SCU48C, 9), ++ ASPEED_PULL_DOWN_PINCONF(B22, SCU48C, 10), ++ ASPEED_PULL_DOWN_PINCONF(C23, SCU48C, 11), ++ ASPEED_PULL_DOWN_PINCONF(B18, SCU48C, 12), ++ ASPEED_PULL_DOWN_PINCONF(B21, SCU48C, 13), ++ ASPEED_PULL_DOWN_PINCONF(M15, SCU48C, 14), ++ ASPEED_PULL_DOWN_PINCONF(B19, SCU48C, 15), ++ ASPEED_PULL_DOWN_PINCONF(B26, SCU48C, 16), ++ ASPEED_PULL_DOWN_PINCONF(A25, SCU48C, 17), ++ ASPEED_PULL_DOWN_PINCONF(A24, SCU48C, 18), ++ ASPEED_PULL_DOWN_PINCONF(B24, SCU48C, 19), ++ ASPEED_PULL_DOWN_PINCONF(E26, SCU48C, 20), ++ ASPEED_PULL_DOWN_PINCONF(A21, SCU48C, 21), ++ ASPEED_PULL_DOWN_PINCONF(A19, SCU48C, 22), ++ ASPEED_PULL_DOWN_PINCONF(A18, SCU48C, 23), ++ ASPEED_PULL_DOWN_PINCONF(D26, SCU48C, 24), ++ ASPEED_PULL_DOWN_PINCONF(C26, SCU48C, 25), ++ ASPEED_PULL_DOWN_PINCONF(A23, SCU48C, 26), ++ ASPEED_PULL_DOWN_PINCONF(A22, SCU48C, 27), ++ ASPEED_PULL_DOWN_PINCONF(B25, SCU48C, 28), ++ ASPEED_PULL_DOWN_PINCONF(F26, SCU48C, 29), ++ ASPEED_PULL_DOWN_PINCONF(A26, SCU48C, 30), ++ ASPEED_PULL_DOWN_PINCONF(A14, SCU48C, 31), ++ ASPEED_PULL_DOWN_PINCONF(E10, SCU490, 0), ++ ASPEED_PULL_DOWN_PINCONF(E13, SCU490, 1), ++ ASPEED_PULL_DOWN_PINCONF(D12, SCU490, 2), ++ ASPEED_PULL_DOWN_PINCONF(F10, SCU490, 3), ++ ASPEED_PULL_DOWN_PINCONF(E11, SCU490, 4), ++ ASPEED_PULL_DOWN_PINCONF(F11, SCU490, 5), ++ ASPEED_PULL_DOWN_PINCONF(F13, SCU490, 6), ++ ASPEED_PULL_DOWN_PINCONF(N15, SCU490, 7), ++ ASPEED_PULL_DOWN_PINCONF(C20, SCU490, 8), ++ ASPEED_PULL_DOWN_PINCONF(C19, SCU490, 9), ++ ASPEED_PULL_DOWN_PINCONF(A8, SCU490, 10), ++ ASPEED_PULL_DOWN_PINCONF(R14, SCU490, 11), ++ ASPEED_PULL_DOWN_PINCONF(A7, SCU490, 12), ++ ASPEED_PULL_DOWN_PINCONF(P14, SCU490, 13), ++ ASPEED_PULL_DOWN_PINCONF(D20, SCU490, 14), ++ ASPEED_PULL_DOWN_PINCONF(A6, SCU490, 15), ++ ASPEED_PULL_DOWN_PINCONF(B6, SCU490, 16), ++ ASPEED_PULL_DOWN_PINCONF(N14, SCU490, 17), ++ ASPEED_PULL_DOWN_PINCONF(B7, SCU490, 18), ++ ASPEED_PULL_DOWN_PINCONF(B8, SCU490, 19), ++ ASPEED_PULL_DOWN_PINCONF(B9, SCU490, 20), ++ ASPEED_PULL_DOWN_PINCONF(M14, SCU490, 21), ++ ASPEED_PULL_DOWN_PINCONF(J11, SCU490, 22), ++ ASPEED_PULL_DOWN_PINCONF(E7, SCU490, 23), ++ ASPEED_PULL_DOWN_PINCONF(D19, SCU490, 24), ++ ASPEED_PULL_DOWN_PINCONF(B11, SCU490, 25), ++ ASPEED_PULL_DOWN_PINCONF(D15, SCU490, 26), ++ ASPEED_PULL_DOWN_PINCONF(B12, SCU490, 27), ++ ASPEED_PULL_DOWN_PINCONF(B10, SCU490, 28), ++ ASPEED_PULL_DOWN_PINCONF(P13, SCU490, 29), ++ ASPEED_PULL_DOWN_PINCONF(C18, SCU490, 30), ++ ASPEED_PULL_DOWN_PINCONF(C6, SCU490, 31), ++ ASPEED_PULL_DOWN_PINCONF(C7, SCU494, 0), ++ ASPEED_PULL_DOWN_PINCONF(D7, SCU494, 1), ++ ASPEED_PULL_DOWN_PINCONF(N13, SCU494, 2), ++ ASPEED_PULL_DOWN_PINCONF(C8, SCU494, 3), ++ ASPEED_PULL_DOWN_PINCONF(C9, SCU494, 4), ++ ASPEED_PULL_DOWN_PINCONF(C10, SCU494, 5), ++ ASPEED_PULL_DOWN_PINCONF(M16, SCU494, 6), ++ ASPEED_PULL_DOWN_PINCONF(A15, SCU494, 7), ++ ASPEED_PULL_DOWN_PINCONF(G11, SCU494, 8), ++ ASPEED_PULL_DOWN_PINCONF(H7, SCU494, 9), ++ ASPEED_PULL_DOWN_PINCONF(H8, SCU494, 10), ++ ASPEED_PULL_DOWN_PINCONF(H9, SCU494, 11), ++ ASPEED_PULL_DOWN_PINCONF(H10, SCU494, 12), ++ ASPEED_PULL_DOWN_PINCONF(H11, SCU494, 13), ++ ASPEED_PULL_DOWN_PINCONF(J9, SCU494, 14), ++ ASPEED_PULL_DOWN_PINCONF(J10, SCU494, 15), ++ ASPEED_PULL_DOWN_PINCONF(E9, SCU494, 16), ++ ASPEED_PULL_DOWN_PINCONF(F9, SCU494, 17), ++ ASPEED_PULL_DOWN_PINCONF(F8, SCU494, 18), ++ ASPEED_PULL_DOWN_PINCONF(M13, SCU494, 19), ++ ASPEED_PULL_DOWN_PINCONF(F7, SCU494, 20), ++ ASPEED_PULL_DOWN_PINCONF(D8, SCU494, 21), ++ ASPEED_PULL_DOWN_PINCONF(E8, SCU494, 22), ++ ASPEED_PULL_DOWN_PINCONF(L12, SCU494, 23), ++ ASPEED_PULL_DOWN_PINCONF(F12, SCU494, 24), ++ ASPEED_PULL_DOWN_PINCONF(E12, SCU494, 25), ++ ASPEED_PULL_DOWN_PINCONF(J12, SCU494, 26), ++ ASPEED_PULL_DOWN_PINCONF(G7, SCU494, 27), ++ ASPEED_PULL_DOWN_PINCONF(G8, SCU494, 28), ++ ASPEED_PULL_DOWN_PINCONF(G9, SCU494, 29), ++ ASPEED_PULL_DOWN_PINCONF(G10, SCU494, 30), ++ ASPEED_PULL_DOWN_PINCONF(K12, SCU494, 31), ++ ASPEED_PULL_DOWN_PINCONF(W17, SCU498, 0), ++ ASPEED_PULL_DOWN_PINCONF(V18, SCU498, 1), ++ ASPEED_PULL_DOWN_PINCONF(W18, SCU498, 2), ++ ASPEED_PULL_DOWN_PINCONF(Y17, SCU498, 3), ++ ASPEED_PULL_DOWN_PINCONF(AA18, SCU498, 4), ++ ASPEED_PULL_DOWN_PINCONF(AA13, SCU498, 5), ++ ASPEED_PULL_DOWN_PINCONF(Y18, SCU498, 6), ++ ASPEED_PULL_DOWN_PINCONF(AA12, SCU498, 7), ++ ASPEED_PULL_DOWN_PINCONF(W20, SCU498, 8), ++ ASPEED_PULL_DOWN_PINCONF(V20, SCU498, 9), ++ ASPEED_PULL_DOWN_PINCONF(Y11, SCU498, 10), ++ ASPEED_PULL_DOWN_PINCONF(V14, SCU498, 11), ++ ASPEED_PULL_DOWN_PINCONF(V19, SCU498, 12), ++ ASPEED_PULL_DOWN_PINCONF(W14, SCU498, 13), ++ ASPEED_PULL_DOWN_PINCONF(Y20, SCU498, 14), ++ ASPEED_PULL_DOWN_PINCONF(AB19, SCU498, 15), ++ ASPEED_PULL_DOWN_PINCONF(U21, SCU498, 16), ++ ASPEED_PULL_DOWN_PINCONF(T24, SCU498, 17), ++ ASPEED_PULL_DOWN_PINCONF(V24, SCU498, 18), ++ ASPEED_PULL_DOWN_PINCONF(V22, SCU498, 19), ++ ASPEED_PULL_DOWN_PINCONF(T23, SCU498, 20), ++ ASPEED_PULL_DOWN_PINCONF(AC25, SCU498, 21), ++ ASPEED_PULL_DOWN_PINCONF(AB25, SCU498, 22), ++ ASPEED_PULL_DOWN_PINCONF(AC24, SCU498, 23), ++}; ++ ++static const struct aspeed_pin_config_map aspeed_g7_pin_config_map[] = { ++ { PIN_CONFIG_BIAS_PULL_DOWN, 0, 1, BIT_MASK(0)}, ++ { PIN_CONFIG_BIAS_PULL_DOWN, -1, 0, BIT_MASK(0)}, ++ { PIN_CONFIG_BIAS_PULL_UP, 0, 1, BIT_MASK(0)}, ++ { PIN_CONFIG_BIAS_PULL_UP, -1, 0, BIT_MASK(0)}, ++ { PIN_CONFIG_BIAS_DISABLE, -1, 1, BIT_MASK(0)}, ++ { PIN_CONFIG_DRIVE_STRENGTH, 0, 0, GENMASK(1, 0)}, ++ { PIN_CONFIG_DRIVE_STRENGTH, 1, 1, GENMASK(1, 0)}, ++ { PIN_CONFIG_DRIVE_STRENGTH, 2, 2, GENMASK(1, 0)}, ++ { PIN_CONFIG_DRIVE_STRENGTH, 3, 3, GENMASK(1, 0)}, ++ { PIN_CONFIG_POWER_SOURCE, 3300, 0, BIT_MASK(0)}, ++ { PIN_CONFIG_POWER_SOURCE, 1800, 1, BIT_MASK(0)}, ++}; ++ ++static struct aspeed_pinctrl_data aspeed_g7_pinctrl_data = { ++ .pins = aspeed_g7_soc1_pins, ++ .npins = ARRAY_SIZE(aspeed_g7_soc1_pins), ++ .pinmux = { ++ .groups = aspeed_g7_soc1_pingroups, ++ .ngroups = ARRAY_SIZE(aspeed_g7_soc1_pingroups), ++ .functions = aspeed_g7_soc1_funcs, ++ .nfunctions = ARRAY_SIZE(aspeed_g7_soc1_funcs), ++ .configs_g7 = pin_cfg, ++ .nconfigs_g7 = ARRAY_SIZE(pin_cfg), ++ }, ++ .configs = aspeed_g7_configs, ++ .nconfigs = ARRAY_SIZE(aspeed_g7_configs), ++ .confmaps = aspeed_g7_pin_config_map, ++ .nconfmaps = ARRAY_SIZE(aspeed_g7_pin_config_map), ++}; ++ ++static int aspeed_g7_soc1_pinctrl_probe(struct platform_device *pdev) ++{ ++ return aspeed_pinctrl_probe(pdev, &aspeed_g7_soc1_pinctrl_desc, ++ &aspeed_g7_pinctrl_data); ++} ++ ++static const struct of_device_id aspeed_g7_soc1_pinctrl_match[] = { ++ { .compatible = "aspeed,ast2700-soc1-pinctrl" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aspeed_g7_soc1_pinctrl_match); ++ ++static struct platform_driver aspeed_g7_soc1_pinctrl_driver = { ++ .probe = aspeed_g7_soc1_pinctrl_probe, ++ .driver = { ++ .name = "aspeed-g7-soc1-pinctrl", ++ .of_match_table = aspeed_g7_soc1_pinctrl_match, ++ .suppress_bind_attrs = true, ++ }, ++}; ++ ++static int __init aspeed_g7_soc1_pinctrl_register(void) ++{ ++ return platform_driver_register(&aspeed_g7_soc1_pinctrl_driver); ++} ++arch_initcall(aspeed_g7_soc1_pinctrl_register); +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed.c b/drivers/pinctrl/aspeed/pinctrl-aspeed.c +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed.c 2026-09-09 15:51:44.733237845 +0000 +@@ -285,6 +285,32 @@ + return 0; + } + ++int aspeed_g7_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int function, ++ unsigned int group) ++{ ++ int i, j; ++ int pin; ++ const struct aspeed_g7_funcfg *funcfg; ++ struct aspeed_pinctrl_data *pinctrl = pinctrl_dev_get_drvdata(pctldev); ++ const struct aspeed_pin_group *pingroup = ++ &pinctrl->pinmux.groups[group]; ++ const struct aspeed_g7_pincfg *pin_cfg = pinctrl->pinmux.configs_g7; ++ ++ for (i = 0; i < pingroup->npins; i++) { ++ pin = pingroup->pins[i]; ++ funcfg = pin_cfg[pin].funcfg; ++ ++ for (j = 0; j < pin_cfg[pin].nfuncfg; j++) { ++ if (strcmp(funcfg[j].name, pingroup->name) == 0) { ++ regmap_update_bits(pinctrl->scu, funcfg[j].reg, ++ funcfg[j].mask, ++ funcfg[j].val); ++ } ++ } ++ } ++ return 0; ++} ++ + static bool aspeed_expr_is_gpio(const struct aspeed_sig_expr *expr) + { + /* +@@ -440,6 +466,27 @@ + return 0; + } + ++int aspeed_g7_gpio_request_enable(struct pinctrl_dev *pctldev, ++ struct pinctrl_gpio_range *range, ++ unsigned int offset) ++{ ++ int i; ++ struct aspeed_pinctrl_data *pinctrl = pinctrl_dev_get_drvdata(pctldev); ++ const struct aspeed_g7_pincfg *pin_cfg = pinctrl->pinmux.configs_g7; ++ const struct aspeed_g7_funcfg *funcfg = pin_cfg[offset].funcfg; ++ ++ for (i = 0; i < pin_cfg[offset].nfuncfg; i++) { ++ if (!strncmp(funcfg[i].name, "GPI", 3)) { ++ regmap_update_bits(pinctrl->scu, funcfg[i].reg, ++ funcfg[i].mask, funcfg[i].val); ++ break; ++ } ++ regmap_update_bits(pinctrl->scu, funcfg[i].reg, funcfg[i].mask, ++ 0); ++ } ++ return 0; ++} ++ + int aspeed_pinctrl_probe(struct platform_device *pdev, + struct pinctrl_desc *pdesc, + struct aspeed_pinctrl_data *pdata) +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed.h b/drivers/pinctrl/aspeed/pinctrl-aspeed.h +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed.h 2026-09-09 15:51:44.733237845 +0000 +@@ -70,15 +70,15 @@ + struct regmap *scu; + + const struct pinctrl_pin_desc *pins; +- const unsigned int npins; ++ unsigned int npins; + + const struct aspeed_pin_config *configs; +- const unsigned int nconfigs; ++ unsigned int nconfigs; + + struct aspeed_pinmux_data pinmux; + + const struct aspeed_pin_config_map *confmaps; +- const unsigned int nconfmaps; ++ unsigned int nconfmaps; + }; + + /* Aspeed pinctrl helpers */ +@@ -101,6 +101,11 @@ + int aspeed_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int offset); ++int aspeed_g7_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int function, ++ unsigned int group); ++int aspeed_g7_gpio_request_enable(struct pinctrl_dev *pctldev, ++ struct pinctrl_gpio_range *range, ++ unsigned int offset); + int aspeed_pinctrl_probe(struct platform_device *pdev, + struct pinctrl_desc *pdesc, + struct aspeed_pinctrl_data *pdata); +diff --git a/drivers/pinctrl/aspeed/pinmux-aspeed.h b/drivers/pinctrl/aspeed/pinmux-aspeed.h +--- a/drivers/pinctrl/aspeed/pinmux-aspeed.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pinctrl/aspeed/pinmux-aspeed.h 2026-09-09 15:51:44.733237845 +0000 +@@ -792,6 +792,33 @@ + const struct aspeed_sig_expr *expr, bool enabled); + }; + ++struct aspeed_g7_funcfg { ++ char *name; ++ u32 reg; ++ u32 mask; ++ int val; ++}; ++ ++struct aspeed_g7_pincfg { ++ struct aspeed_g7_funcfg *funcfg; ++ unsigned int nfuncfg; ++}; ++ ++#define PIN_CFG(cfg_name, cfg_reg, cfg_mask, cfg_val) \ ++ { \ ++ .name = #cfg_name, .reg = cfg_reg, .mask = cfg_mask, \ ++ .val = cfg_val \ ++ } ++#define FUNCFG_SYM(pin) funcfg_ ## pin ++#define FUNCFG_PTR(pin) (&FUNCFG_SYM(pin)) ++ ++#define FUNCFG_DESCL(pin, ...) \ ++ static const struct aspeed_g7_funcfg FUNCFG_SYM(pin)[] = { __VA_ARGS__ } ++ ++#define PINCFG_PIN(pin) \ ++ [pin] = { .funcfg = (struct aspeed_g7_funcfg *)FUNCFG_PTR(pin), \ ++ .nfuncfg = ARRAY_SIZE(FUNCFG_SYM(pin)) } ++ + struct aspeed_pinmux_data { + struct device *dev; + struct regmap *maps[ASPEED_NR_PINMUX_IPS]; +@@ -799,10 +826,14 @@ + const struct aspeed_pinmux_ops *ops; + + const struct aspeed_pin_group *groups; +- const unsigned int ngroups; ++ unsigned int ngroups; + + const struct aspeed_pin_function *functions; +- const unsigned int nfunctions; ++ unsigned int nfunctions; ++ ++ const struct aspeed_g7_pincfg *configs_g7; ++ unsigned int nconfigs_g7; ++ + }; + + int aspeed_sig_desc_eval(const struct aspeed_sig_desc *desc, bool enabled, +diff --git a/drivers/reset/reset-aspeed.c b/drivers/reset/reset-aspeed.c +--- a/drivers/reset/reset-aspeed.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/reset/reset-aspeed.c 2026-09-09 15:51:44.708238223 +0000 +@@ -0,0 +1,310 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2024 ASPEED Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#define SCU0_RESET_CTRL1 0x200 ++#define SCU0_RESET_CTRL2 0x220 ++#define SCU1_RESET_CTRL1 0x200 ++#define SCU1_RESET_CTRL2 0x220 ++#define SCU1_PCIE3_CTRL 0x908 ++ ++struct aspeed_reset; ++ ++struct ast2700_reset_signal { ++ bool dedicated_clr; /* dedicated reset clr offset */ ++ u32 offset, bit; ++}; ++ ++struct aspeed_reset_info { ++ unsigned int nr_resets; ++ const struct ast2700_reset_signal *signal; ++}; ++ ++struct aspeed_reset { ++ struct reset_controller_dev rcdev; ++ struct aspeed_reset_info *info; ++ spinlock_t lock; /* Protect read-modify-write cycle */ ++ void __iomem *base; ++}; ++ ++static const struct ast2700_reset_signal ast2700_reset0_signals[] = { ++ [SCU0_RESET_SDRAM] = { true, SCU0_RESET_CTRL1, BIT(0) }, ++ [SCU0_RESET_DDRPHY] = { true, SCU0_RESET_CTRL1, BIT(1) }, ++ [SCU0_RESET_RSA] = { true, SCU0_RESET_CTRL1, BIT(2) }, ++ [SCU0_RESET_SHA3] = { true, SCU0_RESET_CTRL1, BIT(3) }, ++ [SCU0_RESET_HACE] = { true, SCU0_RESET_CTRL1, BIT(4) }, ++ [SCU0_RESET_SOC] = { true, SCU0_RESET_CTRL1, BIT(5) }, ++ [SCU0_RESET_VIDEO] = { true, SCU0_RESET_CTRL1, BIT(6) }, ++ [SCU0_RESET_2D] = { true, SCU0_RESET_CTRL1, BIT(7) }, ++ [SCU0_RESET_PCIS] = { true, SCU0_RESET_CTRL1, BIT(8) }, ++ [SCU0_RESET_RVAS0] = { true, SCU0_RESET_CTRL1, BIT(9) }, ++ [SCU0_RESET_RVAS1] = { true, SCU0_RESET_CTRL1, BIT(10) }, ++ [SCU0_RESET_SM3] = { true, SCU0_RESET_CTRL1, BIT(11) }, ++ [SCU0_RESET_SM4] = { true, SCU0_RESET_CTRL1, BIT(12) }, ++ [SCU0_RESET_CRT0] = { true, SCU0_RESET_CTRL1, BIT(13) }, ++ [SCU0_RESET_ECC] = { true, SCU0_RESET_CTRL1, BIT(14) }, ++ [SCU0_RESET_DP_PCI] = { true, SCU0_RESET_CTRL1, BIT(15) }, ++ [SCU0_RESET_UFS] = { true, SCU0_RESET_CTRL1, BIT(16) }, ++ [SCU0_RESET_EMMC] = { true, SCU0_RESET_CTRL1, BIT(17) }, ++ [SCU0_RESET_PCIE1RST] = { true, SCU0_RESET_CTRL1, BIT(18) }, ++ [SCU0_RESET_PCIE1RSTOE] = { true, SCU0_RESET_CTRL1, BIT(19) }, ++ [SCU0_RESET_PCIE0RST] = { true, SCU0_RESET_CTRL1, BIT(20) }, ++ [SCU0_RESET_PCIE0RSTOE] = { true, SCU0_RESET_CTRL1, BIT(21) }, ++ [SCU0_RESET_JTAG] = { true, SCU0_RESET_CTRL1, BIT(22) }, ++ [SCU0_RESET_MCTP0] = { true, SCU0_RESET_CTRL1, BIT(23) }, ++ [SCU0_RESET_MCTP1] = { true, SCU0_RESET_CTRL1, BIT(24) }, ++ [SCU0_RESET_XDMA0] = { true, SCU0_RESET_CTRL1, BIT(25) }, ++ [SCU0_RESET_XDMA1] = { true, SCU0_RESET_CTRL1, BIT(26) }, ++ [SCU0_RESET_H2X1] = { true, SCU0_RESET_CTRL1, BIT(27) }, ++ [SCU0_RESET_DP] = { true, SCU0_RESET_CTRL1, BIT(28) }, ++ [SCU0_RESET_DP_MCU] = { true, SCU0_RESET_CTRL1, BIT(29) }, ++ [SCU0_RESET_SSP] = { true, SCU0_RESET_CTRL1, BIT(30) }, ++ [SCU0_RESET_H2X0] = { true, SCU0_RESET_CTRL1, BIT(31) }, ++ [SCU0_RESET_PORTA_VHUB] = { true, SCU0_RESET_CTRL2, BIT(0) }, ++ [SCU0_RESET_PORTA_PHY3] = { true, SCU0_RESET_CTRL2, BIT(1) }, ++ [SCU0_RESET_PORTA_XHCI] = { true, SCU0_RESET_CTRL2, BIT(2) }, ++ [SCU0_RESET_PORTB_VHUB] = { true, SCU0_RESET_CTRL2, BIT(3) }, ++ [SCU0_RESET_PORTB_PHY3] = { true, SCU0_RESET_CTRL2, BIT(4) }, ++ [SCU0_RESET_PORTB_XHCI] = { true, SCU0_RESET_CTRL2, BIT(5) }, ++ [SCU0_RESET_PORTA_VHUB_EHCI] = { true, SCU0_RESET_CTRL2, BIT(6) }, ++ [SCU0_RESET_PORTB_VHUB_EHCI] = { true, SCU0_RESET_CTRL2, BIT(7) }, ++ [SCU0_RESET_UHCI] = { true, SCU0_RESET_CTRL2, BIT(8) }, ++ [SCU0_RESET_TSP] = { true, SCU0_RESET_CTRL2, BIT(9) }, ++ [SCU0_RESET_E2M0] = { true, SCU0_RESET_CTRL2, BIT(10) }, ++ [SCU0_RESET_E2M1] = { true, SCU0_RESET_CTRL2, BIT(11) }, ++ [SCU0_RESET_VLINK] = { true, SCU0_RESET_CTRL2, BIT(12) }, ++}; ++ ++static const struct ast2700_reset_signal ast2700_reset1_signals[] = { ++ [SCU1_RESET_LPC0] = { true, SCU1_RESET_CTRL1, BIT(0) }, ++ [SCU1_RESET_LPC1] = { true, SCU1_RESET_CTRL1, BIT(1) }, ++ [SCU1_RESET_MII] = { true, SCU1_RESET_CTRL1, BIT(2) }, ++ [SCU1_RESET_PECI] = { true, SCU1_RESET_CTRL1, BIT(3) }, ++ [SCU1_RESET_PWM] = { true, SCU1_RESET_CTRL1, BIT(4) }, ++ [SCU1_RESET_MAC0] = { true, SCU1_RESET_CTRL1, BIT(5) }, ++ [SCU1_RESET_MAC1] = { true, SCU1_RESET_CTRL1, BIT(6) }, ++ [SCU1_RESET_MAC2] = { true, SCU1_RESET_CTRL1, BIT(7) }, ++ [SCU1_RESET_ADC] = { true, SCU1_RESET_CTRL1, BIT(8) }, ++ [SCU1_RESET_SD] = { true, SCU1_RESET_CTRL1, BIT(9) }, ++ [SCU1_RESET_ESPI0] = { true, SCU1_RESET_CTRL1, BIT(10) }, ++ [SCU1_RESET_ESPI1] = { true, SCU1_RESET_CTRL1, BIT(11) }, ++ [SCU1_RESET_JTAG1] = { true, SCU1_RESET_CTRL1, BIT(12) }, ++ [SCU1_RESET_SPI0] = { true, SCU1_RESET_CTRL1, BIT(13) }, ++ [SCU1_RESET_SPI1] = { true, SCU1_RESET_CTRL1, BIT(14) }, ++ [SCU1_RESET_SPI2] = { true, SCU1_RESET_CTRL1, BIT(15) }, ++ [SCU1_RESET_I3C0] = { true, SCU1_RESET_CTRL1, BIT(16) }, ++ [SCU1_RESET_I3C1] = { true, SCU1_RESET_CTRL1, BIT(17) }, ++ [SCU1_RESET_I3C2] = { true, SCU1_RESET_CTRL1, BIT(18) }, ++ [SCU1_RESET_I3C3] = { true, SCU1_RESET_CTRL1, BIT(19) }, ++ [SCU1_RESET_I3C4] = { true, SCU1_RESET_CTRL1, BIT(20) }, ++ [SCU1_RESET_I3C5] = { true, SCU1_RESET_CTRL1, BIT(21) }, ++ [SCU1_RESET_I3C6] = { true, SCU1_RESET_CTRL1, BIT(22) }, ++ [SCU1_RESET_I3C7] = { true, SCU1_RESET_CTRL1, BIT(23) }, ++ [SCU1_RESET_I3C8] = { true, SCU1_RESET_CTRL1, BIT(24) }, ++ [SCU1_RESET_I3C9] = { true, SCU1_RESET_CTRL1, BIT(25) }, ++ [SCU1_RESET_I3C10] = { true, SCU1_RESET_CTRL1, BIT(26) }, ++ [SCU1_RESET_I3C11] = { true, SCU1_RESET_CTRL1, BIT(27) }, ++ [SCU1_RESET_I3C12] = { true, SCU1_RESET_CTRL1, BIT(28) }, ++ [SCU1_RESET_I3C13] = { true, SCU1_RESET_CTRL1, BIT(29) }, ++ [SCU1_RESET_I3C14] = { true, SCU1_RESET_CTRL1, BIT(30) }, ++ [SCU1_RESET_I3C15] = { true, SCU1_RESET_CTRL1, BIT(31) }, ++ [SCU1_RESET_MCU0] = { true, SCU1_RESET_CTRL2, BIT(0) }, ++ [SCU1_RESET_MCU1] = { true, SCU1_RESET_CTRL2, BIT(1) }, ++ [SCU1_RESET_H2A_SPI1] = { true, SCU1_RESET_CTRL2, BIT(2) }, ++ [SCU1_RESET_H2A_SPI2] = { true, SCU1_RESET_CTRL2, BIT(3) }, ++ [SCU1_RESET_UART0] = { true, SCU1_RESET_CTRL2, BIT(4) }, ++ [SCU1_RESET_UART1] = { true, SCU1_RESET_CTRL2, BIT(5) }, ++ [SCU1_RESET_UART2] = { true, SCU1_RESET_CTRL2, BIT(6) }, ++ [SCU1_RESET_UART3] = { true, SCU1_RESET_CTRL2, BIT(7) }, ++ [SCU1_RESET_I2C_FILTER] = { true, SCU1_RESET_CTRL2, BIT(8) }, ++ [SCU1_RESET_CALIPTRA] = { true, SCU1_RESET_CTRL2, BIT(9) }, ++ [SCU1_RESET_XDMA] = { true, SCU1_RESET_CTRL2, BIT(10) }, ++ [SCU1_RESET_FSI] = { true, SCU1_RESET_CTRL2, BIT(12) }, ++ [SCU1_RESET_CAN] = { true, SCU1_RESET_CTRL2, BIT(13) }, ++ [SCU1_RESET_MCTP] = { true, SCU1_RESET_CTRL2, BIT(14) }, ++ [SCU1_RESET_I2C] = { true, SCU1_RESET_CTRL2, BIT(15) }, ++ [SCU1_RESET_UART6] = { true, SCU1_RESET_CTRL2, BIT(16) }, ++ [SCU1_RESET_UART7] = { true, SCU1_RESET_CTRL2, BIT(17) }, ++ [SCU1_RESET_UART8] = { true, SCU1_RESET_CTRL2, BIT(18) }, ++ [SCU1_RESET_UART9] = { true, SCU1_RESET_CTRL2, BIT(19) }, ++ [SCU1_RESET_LTPI0] = { true, SCU1_RESET_CTRL2, BIT(20) }, ++ [SCU1_RESET_VGAL] = { true, SCU1_RESET_CTRL2, BIT(21) }, ++ [SCU1_RESET_LTPI1] = { true, SCU1_RESET_CTRL2, BIT(22) }, ++ [SCU1_RESET_ACE] = { true, SCU1_RESET_CTRL2, BIT(23) }, ++ [SCU1_RESET_E2M] = { true, SCU1_RESET_CTRL2, BIT(24) }, ++ [SCU1_RESET_UHCI] = { true, SCU1_RESET_CTRL2, BIT(25) }, ++ [SCU1_RESET_PORTC_USB2UART] = { true, SCU1_RESET_CTRL2, BIT(26) }, ++ [SCU1_RESET_PORTC_VHUB_EHCI] = { true, SCU1_RESET_CTRL2, BIT(27) }, ++ [SCU1_RESET_PORTD_USB2UART] = { true, SCU1_RESET_CTRL2, BIT(28) }, ++ [SCU1_RESET_PORTD_VHUB_EHCI] = { true, SCU1_RESET_CTRL2, BIT(29) }, ++ [SCU1_RESET_H2X] = { true, SCU1_RESET_CTRL2, BIT(30) }, ++ [SCU1_RESET_I3CDMA] = { true, SCU1_RESET_CTRL2, BIT(31) }, ++ [SCU1_RESET_PCIE2RST] = { false, SCU1_PCIE3_CTRL, BIT(0) }, ++}; ++ ++static inline struct aspeed_reset *to_aspeed_reset(struct reset_controller_dev *rcdev) ++{ ++ return container_of(rcdev, struct aspeed_reset, rcdev); ++} ++ ++static int aspeed_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) ++{ ++ struct aspeed_reset *rc = to_aspeed_reset(rcdev); ++ void __iomem *reg_offset = rc->base + rc->info->signal[id].offset; ++ ++ if (rc->info->signal[id].dedicated_clr) { ++ writel(rc->info->signal[id].bit, reg_offset); ++ } else { ++ guard(spinlock_irqsave)(&rc->lock); ++ writel(readl(reg_offset) & ~rc->info->signal[id].bit, reg_offset); ++ } ++ ++ return 0; ++} ++ ++static int aspeed_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) ++{ ++ struct aspeed_reset *rc = to_aspeed_reset(rcdev); ++ void __iomem *reg_offset = rc->base + rc->info->signal[id].offset; ++ ++ if (rc->info->signal[id].dedicated_clr) { ++ writel(rc->info->signal[id].bit, reg_offset + 0x04); ++ } else { ++ guard(spinlock_irqsave)(&rc->lock); ++ writel(readl(reg_offset) | rc->info->signal[id].bit, reg_offset); ++ } ++ ++ return 0; ++} ++ ++static int aspeed_reset_status(struct reset_controller_dev *rcdev, unsigned long id) ++{ ++ struct aspeed_reset *rc = to_aspeed_reset(rcdev); ++ void __iomem *reg_offset = rc->base + rc->info->signal[id].offset; ++ ++ return (readl(reg_offset) & rc->info->signal[id].bit) ? 1 : 0; ++} ++ ++static const struct reset_control_ops aspeed_reset_ops = { ++ .assert = aspeed_reset_assert, ++ .deassert = aspeed_reset_deassert, ++ .status = aspeed_reset_status, ++}; ++ ++static int aspeed_reset_probe(struct auxiliary_device *adev, ++ const struct auxiliary_device_id *id) ++{ ++ struct aspeed_reset *reset; ++ struct device *dev = &adev->dev; ++ ++ reset = devm_kzalloc(dev, sizeof(*reset), GFP_KERNEL); ++ if (!reset) ++ return -ENOMEM; ++ ++ spin_lock_init(&reset->lock); ++ ++ reset->info = (struct aspeed_reset_info *)id->driver_data; ++ reset->rcdev.owner = THIS_MODULE; ++ reset->rcdev.nr_resets = reset->info->nr_resets; ++ reset->rcdev.ops = &aspeed_reset_ops; ++ reset->rcdev.of_node = dev->parent->of_node; ++ reset->rcdev.dev = dev; ++ reset->rcdev.of_reset_n_cells = 1; ++ reset->base = (void __iomem *)adev->dev.platform_data; ++ ++ dev_set_drvdata(dev, reset); ++ ++ return devm_reset_controller_register(dev, &reset->rcdev); ++} ++ ++static void aspeed_reset_unregister_adev(void *_adev) ++{ ++ struct auxiliary_device *adev = _adev; ++ ++ auxiliary_device_delete(adev); ++ auxiliary_device_uninit(adev); ++} ++ ++static void aspeed_reset_adev_release(struct device *dev) ++{ ++ struct auxiliary_device *adev = to_auxiliary_dev(dev); ++ ++ kfree(adev); ++} ++ ++int aspeed_reset_controller_register(struct device *clk_dev, void __iomem *base, ++ const char *adev_name) ++{ ++ struct auxiliary_device *adev; ++ int ret; ++ ++ adev = kzalloc(sizeof(*adev), GFP_KERNEL); ++ if (!adev) ++ return -ENOMEM; ++ ++ adev->name = adev_name; ++ adev->dev.parent = clk_dev; ++ adev->dev.release = aspeed_reset_adev_release; ++ adev->id = 666u; ++ ++ ret = auxiliary_device_init(adev); ++ if (ret) { ++ kfree(adev); ++ return ret; ++ } ++ ++ ret = auxiliary_device_add(adev); ++ if (ret) { ++ auxiliary_device_uninit(adev); ++ return ret; ++ } ++ ++ adev->dev.platform_data = (__force void *)base; ++ ++ return devm_add_action_or_reset(clk_dev, aspeed_reset_unregister_adev, adev); ++} ++EXPORT_SYMBOL_GPL(aspeed_reset_controller_register); ++ ++static const struct aspeed_reset_info ast2700_reset0_info = { ++ .nr_resets = ARRAY_SIZE(ast2700_reset0_signals), ++ .signal = ast2700_reset0_signals, ++}; ++ ++static const struct aspeed_reset_info ast2700_reset1_info = { ++ .nr_resets = ARRAY_SIZE(ast2700_reset1_signals), ++ .signal = ast2700_reset1_signals, ++}; ++ ++static const struct auxiliary_device_id aspeed_reset_ids[] = { ++ { .name = "reset_aspeed.reset0", .driver_data = (kernel_ulong_t)&ast2700_reset0_info }, ++ { .name = "reset_aspeed.reset1", .driver_data = (kernel_ulong_t)&ast2700_reset1_info }, ++ { } ++}; ++MODULE_DEVICE_TABLE(auxiliary, aspeed_reset_ids); ++ ++static struct auxiliary_driver aspeed_reset_driver = { ++ .probe = aspeed_reset_probe, ++ .id_table = aspeed_reset_ids, ++}; ++ ++static int __init rest_aspeed_init(void) ++{ ++ return auxiliary_driver_register(&aspeed_reset_driver); ++} ++subsys_initcall(rest_aspeed_init); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_DESCRIPTION("ASPEED SoC Reset Controller Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/rtc/rtc-aspeed.c b/drivers/rtc/rtc-aspeed.c +--- a/drivers/rtc/rtc-aspeed.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/rtc/rtc-aspeed.c 2026-09-09 15:51:44.679238661 +0000 +@@ -10,14 +10,64 @@ + struct aspeed_rtc { + struct rtc_device *rtc_dev; + void __iomem *base; ++ spinlock_t irq_lock; /* interrupt enable register lock */ ++ struct mutex write_mutex; /* serialize registers write */ + }; + + #define RTC_TIME 0x00 + #define RTC_YEAR 0x04 ++#define RTC_ALARM 0x08 + #define RTC_CTRL 0x10 ++#define RTC_ALARM_STATUS 0x14 + +-#define RTC_UNLOCK BIT(1) +-#define RTC_ENABLE BIT(0) ++#define RTC_ENABLE BIT(0) ++#define RTC_UNLOCK BIT(1) ++#define RTC_ALARM_MODE BIT(2) ++#define RTC_ALARM_SEC_ENABLE BIT(3) ++#define RTC_ALARM_MIN_ENABLE BIT(4) ++#define RTC_ALARM_HOUR_ENABLE BIT(5) ++#define RTC_ALARM_MDAY_ENABLE BIT(6) ++ ++#define RTC_ALARM_SEC_CB_STATUS BIT(0) ++#define RTC_ALARM_MIN_STATUS BIT(1) ++#define RTC_ALARM_HOUR_STATUS BIT(2) ++#define RTC_ALARM_MDAY_STATUS BIT(3) ++ ++/* ++ * enable a rtc interrupt ++ */ ++static void aspeed_rtc_int_enable(struct aspeed_rtc *rtc, u32 intr) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&rtc->irq_lock, flags); ++ writel(readl(rtc->base + RTC_CTRL) | intr, rtc->base + RTC_CTRL); ++ spin_unlock_irqrestore(&rtc->irq_lock, flags); ++} ++ ++/* ++ * disable a rtc interrupt ++ */ ++static void aspeed_rtc_int_disable(struct aspeed_rtc *rtc, u32 intr) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&rtc->irq_lock, flags); ++ writel(readl(rtc->base + RTC_CTRL) & ~intr, rtc->base + RTC_CTRL); ++ spin_unlock_irqrestore(&rtc->irq_lock, flags); ++} ++ ++/* ++ * clean a rtc interrupt status ++ */ ++static void aspeed_rtc_clean_alarm(struct aspeed_rtc *rtc) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&rtc->irq_lock, flags); ++ writel(readl(rtc->base + RTC_ALARM_STATUS), rtc->base + RTC_ALARM_STATUS); ++ spin_unlock_irqrestore(&rtc->irq_lock, flags); ++} + + static int aspeed_rtc_read_time(struct device *dev, struct rtc_time *tm) + { +@@ -45,7 +95,7 @@ + tm->tm_mon = ((reg2 >> 0) & 0x0f) - 1; + tm->tm_year = year + (cent * 100) - 1900; + +- dev_dbg(dev, "%s %ptR", __func__, tm); ++ dev_dbg(dev, "%s %ptR\n", __func__, tm); + + return 0; + } +@@ -56,6 +106,8 @@ + u32 reg1, reg2, ctrl; + int year, cent; + ++ dev_dbg(dev, "%s %ptR\n", __func__, tm); ++ + cent = (tm->tm_year + 1900) / 100; + year = tm->tm_year % 100; + +@@ -77,40 +129,215 @@ + return 0; + } + ++static int aspeed_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) ++{ ++ struct aspeed_rtc *rtc = dev_get_drvdata(dev); ++ unsigned int alarm_enable; ++ ++ dev_dbg(dev, "%s, enabled:%x\n", __func__, enabled); ++ ++ alarm_enable = RTC_ALARM_MODE | RTC_ALARM_SEC_ENABLE | RTC_ALARM_MIN_ENABLE | ++ RTC_ALARM_HOUR_ENABLE | RTC_ALARM_MDAY_ENABLE; ++ if (enabled) ++ aspeed_rtc_int_enable(rtc, alarm_enable); ++ else ++ aspeed_rtc_int_disable(rtc, alarm_enable); ++ ++ return 0; ++} ++ ++static int aspeed_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) ++{ ++ struct aspeed_rtc *rtc = dev_get_drvdata(dev); ++ u32 reg1, reg2; ++ unsigned int alarm_enable; ++ unsigned int alarm_status; ++ ++ if (!(readl(rtc->base + RTC_CTRL) & RTC_ENABLE)) { ++ dev_dbg(dev, "%s failing as rtc disabled\n", __func__); ++ return -EINVAL; ++ } ++ ++ do { ++ reg2 = readl(rtc->base + RTC_YEAR); ++ reg1 = readl(rtc->base + RTC_TIME); ++ } while (reg1 != readl(rtc->base + RTC_TIME)); ++ ++ /* read alarm value */ ++ alarm->time.tm_mday = (reg1 >> 24) & 0x1f; ++ alarm->time.tm_hour = (reg1 >> 16) & 0x1f; ++ alarm->time.tm_min = (reg1 >> 8) & 0x3f; ++ alarm->time.tm_sec = (reg1 >> 0) & 0x3f; ++ ++ dev_dbg(dev, "%s %ptR\n", __func__, &alarm->time); ++ ++ alarm_enable = RTC_ALARM_SEC_ENABLE | RTC_ALARM_MIN_ENABLE | ++ RTC_ALARM_HOUR_ENABLE | RTC_ALARM_MDAY_ENABLE; ++ alarm_status = RTC_ALARM_SEC_CB_STATUS | RTC_ALARM_MIN_STATUS | ++ RTC_ALARM_HOUR_STATUS | RTC_ALARM_MDAY_STATUS; ++ ++ /* don't allow the ALARM read to mess up ALARM_STATUS */ ++ mutex_lock(&rtc->write_mutex); ++ ++ /* alarm is enabled if the interrupt is enabled */ ++ if (readl(rtc->base + RTC_CTRL) & alarm_enable) ++ alarm->enabled = true; ++ else ++ alarm->enabled = false; ++ ++ /* alarm interrupt asserted or not */ ++ if (readl(rtc->base + RTC_ALARM_STATUS) & alarm_status) ++ alarm->pending = true; ++ else ++ alarm->pending = false; ++ ++ mutex_unlock(&rtc->write_mutex); ++ ++ return 0; ++} ++ ++static int aspeed_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) ++{ ++ struct aspeed_rtc *rtc = dev_get_drvdata(dev); ++ unsigned int alarm_enable; ++ u32 reg; ++ ++ if (!(readl(rtc->base + RTC_CTRL) & RTC_ENABLE)) { ++ dev_dbg(dev, "%s failing as rtc disabled\n", __func__); ++ return -EINVAL; ++ } ++ ++ dev_dbg(dev, "%s %ptR\n", __func__, &alarm->time); ++ ++ alarm_enable = RTC_ALARM_MODE | RTC_ALARM_SEC_ENABLE | RTC_ALARM_MIN_ENABLE | ++ RTC_ALARM_HOUR_ENABLE | RTC_ALARM_MDAY_ENABLE; ++ ++ /* don't allow the ALARM read to mess up ALARM_STATUS */ ++ mutex_lock(&rtc->write_mutex); ++ ++ /* write the new alarm time */ ++ reg = (alarm->time.tm_mday << 24) | (alarm->time.tm_hour << 16) | ++ (alarm->time.tm_min << 8) | alarm->time.tm_sec; ++ writel(reg, rtc->base + RTC_ALARM); ++ ++ /* alarm is enabled if the interrupt is enabled */ ++ if (alarm->enabled) ++ aspeed_rtc_int_enable(rtc, alarm_enable); ++ else ++ aspeed_rtc_int_disable(rtc, alarm_enable); ++ ++ mutex_unlock(&rtc->write_mutex); ++ ++ return 0; ++} ++ + static const struct rtc_class_ops aspeed_rtc_ops = { +- .read_time = aspeed_rtc_read_time, +- .set_time = aspeed_rtc_set_time, ++ .read_time = aspeed_rtc_read_time, ++ .set_time = aspeed_rtc_set_time, ++ .alarm_irq_enable = aspeed_rtc_alarm_irq_enable, ++ .read_alarm = aspeed_rtc_read_alarm, ++ .set_alarm = aspeed_rtc_set_alarm, + }; + ++static irqreturn_t aspeed_rtc_irq(int irq, void *dev_id) ++{ ++ struct aspeed_rtc *rtc = dev_id; ++ unsigned int alarm_enable; ++ ++ alarm_enable = RTC_ALARM_MODE | RTC_ALARM_SEC_ENABLE | RTC_ALARM_MIN_ENABLE | ++ RTC_ALARM_HOUR_ENABLE | RTC_ALARM_MDAY_ENABLE; ++ aspeed_rtc_int_disable(rtc, alarm_enable); ++ aspeed_rtc_clean_alarm(rtc); ++ ++ return IRQ_HANDLED; ++} ++ + static int aspeed_rtc_probe(struct platform_device *pdev) + { + struct aspeed_rtc *rtc; ++ unsigned int irq; ++ int rc; ++ u32 ctrl; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + rtc->base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(rtc->base)) ++ if (IS_ERR(rtc->base)) { ++ dev_err(&pdev->dev, "cannot ioremap resource for rtc\n"); + return PTR_ERR(rtc->base); ++ } + + rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(rtc->rtc_dev)) + return PTR_ERR(rtc->rtc_dev); + ++ spin_lock_init(&rtc->irq_lock); ++ mutex_init(&rtc->write_mutex); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ rc = devm_request_irq(&pdev->dev, irq, aspeed_rtc_irq, ++ 0, pdev->name, rtc); ++ if (rc) { ++ dev_err(&pdev->dev, "interrupt number %d is not available.\n", irq); ++ goto err; ++ } ++ + platform_set_drvdata(pdev, rtc); + ++ device_init_wakeup(&pdev->dev, true); ++ + rtc->rtc_dev->ops = &aspeed_rtc_ops; + rtc->rtc_dev->range_min = RTC_TIMESTAMP_BEGIN_1900; + rtc->rtc_dev->range_max = 38814989399LL; /* 3199-12-31 23:59:59 */ + +- return devm_rtc_register_device(rtc->rtc_dev); ++ /* ++ * In devm_rtc_register_device, ++ * rtc_hctosys read time from RTC to check hardware status. ++ * In rtc_read_time, run aspeed_rtc_read_time and check the rtc_time. ++ * As a result, need to enable and initialize RTC time. ++ * ++ * If the RTC_ENABLE has been set, it means that the RTC has been initialed ++ * Enable and unlock RTC to initialize RTC time to 1970-01-01T01:01:01 ++ * and re-lock and ensure enable is set now that a time is programmed. ++ */ ++ ctrl = readl(rtc->base + RTC_CTRL); ++ ++ if (!(ctrl & RTC_ENABLE)) { ++ writel(ctrl | RTC_UNLOCK, rtc->base + RTC_CTRL); ++ ++ /* ++ * Initial value set to year:70,mon:0,mday:1,hour:1,min:1,sec:1 ++ * rtc_valid_tm check whether in suitable range or not. ++ */ ++ writel(0x01010101, rtc->base + RTC_TIME); ++ writel(0x00134601, rtc->base + RTC_YEAR); ++ ++ /* Re-lock and ensure enable is set now that a time is programmed */ ++ writel(ctrl | RTC_ENABLE, rtc->base + RTC_CTRL); ++ } ++ ++ rc = devm_rtc_register_device(rtc->rtc_dev); ++ if (rc) { ++ dev_err(&pdev->dev, "can't register rtc device\n"); ++ goto err; ++ } ++ ++ return 0; ++ ++err: ++ return rc; + } + + static const struct of_device_id aspeed_rtc_match[] = { + { .compatible = "aspeed,ast2400-rtc", }, + { .compatible = "aspeed,ast2500-rtc", }, + { .compatible = "aspeed,ast2600-rtc", }, ++ { .compatible = "aspeed,ast2700-rtc", }, + {} + }; + MODULE_DEVICE_TABLE(of, aspeed_rtc_match); +diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig +--- a/drivers/soc/aspeed/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/soc/aspeed/Kconfig 2026-09-09 15:51:44.717238087 +0000 +@@ -4,6 +4,12 @@ + + menu "ASPEED SoC drivers" + ++config ASPEED_BMC_DEV ++ tristate "ASPEED BMC Device" ++ ++config ASPEED_HOST_BMC_DEV ++ tristate "ASPEED Host BMC Device" ++ + config ASPEED_LPC_CTRL + tristate "ASPEED LPC firmware cycle control" + select REGMAP +@@ -24,6 +30,13 @@ + allows the BMC to listen on and save the data written by + the host to an arbitrary LPC I/O port. + ++config ASPEED_SSP ++ tristate "ASPEED SSP loader" ++ default n ++ help ++ Driver for loading secondary-service-processor binary ++ ++ + config ASPEED_UART_ROUTING + tristate "ASPEED uart routing control" + select REGMAP +@@ -34,6 +47,16 @@ + users to perform runtime configuration of the RX muxes among + the UART controllers and I/O pins. + ++config ASPEED_LPC_MAILBOX ++ tristate "ASPEED LPC mailbox support" ++ select REGMAP ++ select MFD_SYSCON ++ default ARCH_ASPEED ++ help ++ Provides a driver to control the LPC mailbox which possesses ++ up to 32 data registers for the communication between the Host ++ and the BMC over LPC. ++ + config ASPEED_P2A_CTRL + tristate "ASPEED P2A (VGA MMIO to BMC) bridge control" + select REGMAP +@@ -52,6 +75,98 @@ + help + Say yes to support decoding of ASPEED BMC information. + ++config ASPEED_SBC ++ bool "ASPEED Secure Boot Controller driver" ++ default MACH_ASPEED_G6 ++ help ++ Say yes to provide information about the secure boot controller in ++ debugfs. ++ ++config ASPEED_XDMA ++ tristate "ASPEED XDMA Engine Driver" ++ select REGMAP ++ select MFD_SYSCON ++ depends on HAS_DMA ++ help ++ Enables support for the XDMA Engine found on the ASPEED BMC ++ SoCs. The XDMA engine can perform PCIe DMA operations between the BMC ++ and a host processor. The driver provides ioctl() interface to reset ++ and initialize engine. ++ ++config ASPEED_LPC_PCC ++ tristate "Aspeed Post Code Capture support" ++ depends on ARCH_ASPEED && REGMAP && MFD_SYSCON ++ help ++ Provides a driver to control the LPC PCC interface, ++ allowing the BMC to snoop data bytes written by the ++ the host to an arbitrary LPC I/O port. ++ ++config ASPEED_UDMA ++ tristate "Aspeed UDMA Engine Driver" ++ depends on ARCH_ASPEED && REGMAP && MFD_SYSCON && HAS_DMA ++ help ++ Enable support for the Aspeed UDMA Engine found on the Aspeed AST2XXX ++ SOCs. The UDMA engine can perform UART DMA operations between the memory ++ buffer and the UART/VUART devices. ++ ++config ASPEED_MBOX ++ bool "Enable support for the ASPEED MBOX driver" ++ depends on ARCH_ASPEED ++ help ++ Enable support for the ASPEED MBOX driver. This driver ++ provides a mailbox client to the ASPEED BMC SoC IPC. ++ ++config ASPEED_MCTP ++ tristate "Aspeed MCTP Controller support" ++ depends on REGMAP && MFD_SYSCON ++ help ++ Enable support for Aspeed MCTP Controller. ++ The MCTP controller allows the BMC to communicate with devices on ++ the host PCIe network. ++ ++config ASPEED_DISP_INTF ++ bool "ASPEED Display Interface driver" ++ select REGMAP ++ select MFD_SYSCON ++ default ARCH_ASPEED ++ help ++ Say yes to support control the display interface of ASPEED BMC. ++ ++config ASPEED_UDMA ++ tristate "Aspeed UDMA Engine Driver" ++ depends on ARCH_ASPEED && REGMAP && MFD_SYSCON && HAS_DMA ++ help ++ Enable support for the Aspeed UDMA Engine found on the Aspeed AST2XXX ++ SOCs. The UDMA engine can perform UART DMA operations between the memory ++ buffer and the UART/VUART devices. ++ ++config ASPEED_OTP ++ tristate ++ help ++ Enable driver support for Aspeed OTP driver. Each bit in One ++ Time Prgrammable (OTP) memory is capable to be programmed once. ++ The OTP driver performs basic read/program operations of ++ OTP memory. ++ ++config AST2600_OTP ++ tristate "AST2600 OTP Driver" ++ select ASPEED_OTP ++ depends on ARCH_ASPEED ++ help ++ Enable driver support for Aspeed AST2600 OTP driver. ++ ++config AST2700_OTP ++ tristate "AST2700 OTP Driver" ++ select ASPEED_OTP ++ depends on ARCH_ASPEED ++ help ++ Enable driver support for Aspeed AST2700 OTP driver. ++ ++source "drivers/soc/aspeed/espi/Kconfig" ++source "drivers/soc/aspeed/rvas/Kconfig" ++ ++source "drivers/soc/aspeed/mmbi/Kconfig" ++ + endmenu + + endif +diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile +--- a/drivers/soc/aspeed/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/soc/aspeed/Makefile 2026-09-09 15:51:44.771237271 +0000 +@@ -1,6 +1,23 @@ + # SPDX-License-Identifier: GPL-2.0-only ++obj-$(CONFIG_ASPEED_BMC_DEV) += aspeed-bmc-dev.o ++obj-$(CONFIG_ASPEED_HOST_BMC_DEV) += aspeed-host-bmc-dev.o + obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o ++obj-$(CONFIG_ASPEED_LPC_MAILBOX) += aspeed-lpc-mbox.o ++obj-$(CONFIG_ASPEED_LPC_PCC) += aspeed-lpc-pcc.o ++obj-$(CONFIG_ASPEED_UDMA) += aspeed-udma.o + obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o ++obj-$(CONFIG_ASPEED_SSP) += aspeed-ssp.o + obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o + obj-$(CONFIG_ASPEED_SOCINFO) += aspeed-socinfo.o ++obj-$(CONFIG_ASPEED_XDMA) += aspeed-xdma.o ++obj-$(CONFIG_ASPEED_RVAS) += rvas/ ++obj-$(CONFIG_ARCH_ASPEED) += aspeed-usb-phy.o ++obj-$(CONFIG_ARCH_ASPEED) += aspeed-usb-hp.o ++obj-$(CONFIG_ASPEED_MCTP) += aspeed-mctp.o ++obj-$(CONFIG_ASPEED_DISP_INTF) += aspeed-disp-intf.o ++obj-$(CONFIG_ASPEED_MBOX) += aspeed-mbox.o ++obj-$(CONFIG_AST2600_OTP) += ast2600-otp.o ++obj-$(CONFIG_AST2700_OTP) += ast2700-otp.o ++obj-y += espi/ ++obj-y += mmbi/ +diff --git a/drivers/soc/aspeed/aspeed-bmc-dev.c b/drivers/soc/aspeed/aspeed-bmc-dev.c +--- a/drivers/soc/aspeed/aspeed-bmc-dev.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-bmc-dev.c 2026-09-09 15:51:44.718238072 +0000 +@@ -0,0 +1,701 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright (C) ASPEED Technology Inc. ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define SCU_TRIGGER_MSI ++ ++/* AST2600 SCU */ ++#define ASPEED_SCU04 0x04 ++#define AST2600A3_SCU04 0x05030303 ++#define ASPEED_SCUC20 0xC20 ++#define ASPEED_SCUC24 0xC24 ++#define MSI_ROUTING_MASK GENMASK(11, 10) ++#define PCIDEV1_INTX_MSI_HOST2BMC_EN BIT(18) ++#define MSI_ROUTING_PCIe2LPC_PCIDEV0 (0x1 << 10) ++#define MSI_ROUTING_PCIe2LPC_PCIDEV1 (0x2 << 10) ++/* AST2700 SCU */ ++#define SCU0_REVISION_ID 0x0 ++#define REVISION_ID GENMASK(23, 16) ++#define SCU0_PCIE_CONF_CTRL 0x970 ++/* Host2BMC */ ++#define ASPEED_BMC_MEM_BAR 0xF10 ++#define PCIE2PCI_MEM_BAR_ENABLE BIT(1) ++#define HOST2BMC_MEM_BAR_ENABLE BIT(0) ++#define ASPEED_BMC_MEM_BAR_REMAP 0xF18 ++ ++#define ASPEED_BMC_SHADOW_CTRL 0xF50 ++#define READ_ONLY_MASK BIT(31) ++#define MASK_BAR1 BIT(2) ++#define MASK_BAR0 BIT(1) ++#define SHADOW_CFG BIT(0) ++ ++#define ASPEED_BMC_HOST2BMC_Q1 0xA000 ++#define ASPEED_BMC_HOST2BMC_Q2 0xA010 ++#define ASPEED_BMC_BMC2HOST_Q1 0xA020 ++#define ASPEED_BMC_BMC2HOST_Q2 0xA030 ++#define ASPEED_BMC_BMC2HOST_STS 0xA040 ++#define BMC2HOST_INT_STS_DOORBELL BIT(31) ++#define BMC2HOST_ENABLE_INTB BIT(30) ++#define BMC2HOST_Q1_FULL BIT(27) ++#define BMC2HOST_Q1_EMPTY BIT(26) ++#define BMC2HOST_Q2_FULL BIT(25) ++#define BMC2HOST_Q2_EMPTY BIT(24) ++#define BMC2HOST_Q1_FULL_UNMASK BIT(23) ++#define BMC2HOST_Q1_EMPTY_UNMASK BIT(22) ++#define BMC2HOST_Q2_FULL_UNMASK BIT(21) ++#define BMC2HOST_Q2_EMPTY_UNMASK BIT(20) ++ ++#define ASPEED_BMC_HOST2BMC_STS 0xA044 ++#define HOST2BMC_INT_STS_DOORBELL BIT(31) ++#define HOST2BMC_ENABLE_INTB BIT(30) ++#define HOST2BMC_Q1_FULL BIT(27) ++#define HOST2BMC_Q1_EMPTY BIT(26) ++#define HOST2BMC_Q2_FULL BIT(25) ++#define HOST2BMC_Q2_EMPTY BIT(24) ++#define HOST2BMC_Q1_FULL_UNMASK BIT(23) ++#define HOST2BMC_Q1_EMPTY_UNMASK BIT(22) ++#define HOST2BMC_Q2_FULL_UNMASK BIT(21) ++#define HOST2BMC_Q2_EMPTY_UNMASK BIT(20) ++ ++#define ASPEED_SCU_PCIE_CONF_CTRL 0xC20 ++#define SCU_PCIE_CONF_BMC_DEV_EN BIT(8) ++#define SCU_PCIE_CONF_BMC_DEV_EN_MMIO BIT(9) ++#define SCU_PCIE_CONF_BMC_DEV_EN_MSI BIT(11) ++#define SCU_PCIE_CONF_BMC_DEV_EN_IRQ BIT(13) ++#define SCU_PCIE_CONF_BMC_DEV_EN_DMA BIT(14) ++#define SCU_PCIE_CONF_BMC_DEV_EN_E2L BIT(15) ++#define SCU_PCIE_CONF_BMC_DEV_EN_LPC_DECODE BIT(21) ++ ++#define ASPEED_SCU_BMC_DEV_CLASS 0xC68 ++ ++#define ASPEED_QUEUE_NUM 2 ++enum queue_index { ++ QUEUE1 = 0, ++ QUEUE2, ++}; ++ ++struct aspeed_platform { ++ int (*init)(struct platform_device *pdev); ++ ssize_t (*queue_rx)(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, ++ char *buf, loff_t off, size_t count); ++ ssize_t (*queue_tx)(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, ++ char *buf, loff_t off, size_t count); ++}; ++ ++struct aspeed_queue_message { ++ /* Queue waiters for idle engine */ ++ wait_queue_head_t tx_wait; ++ wait_queue_head_t rx_wait; ++ struct kernfs_node *kn; ++ struct bin_attribute bin; ++ int index; ++ struct aspeed_bmc_device *bmc_device; ++}; ++ ++struct aspeed_bmc_device { ++ unsigned char *host2bmc_base_virt; ++ struct device *dev; ++ struct miscdevice miscdev; ++ int id; ++ void __iomem *reg_base; ++ dma_addr_t bmc_mem_phy; ++ phys_addr_t bmc_mem_size; ++ void *bmc_mem_cpu; ++ ++ int pcie2lpc; ++ int irq; ++ ++ struct aspeed_queue_message queue[ASPEED_QUEUE_NUM]; ++ ++ const struct aspeed_platform *platform; ++ ++ /* AST2700 */ ++ struct regmap *device; ++ struct regmap *e2m; ++ ++ struct regmap *scu; ++ int pcie_irq; ++}; ++ ++static struct aspeed_bmc_device *file_aspeed_bmc_device(struct file *file) ++{ ++ return container_of(file->private_data, struct aspeed_bmc_device, ++ miscdev); ++} ++ ++static int aspeed_bmc_device_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ struct aspeed_bmc_device *bmc_device = file_aspeed_bmc_device(file); ++ unsigned long vsize = vma->vm_end - vma->vm_start; ++ ++ if (vsize > bmc_device->bmc_mem_size) ++ return -EINVAL; ++ ++ return dma_mmap_coherent(bmc_device->dev, vma, ++ bmc_device->bmc_mem_cpu, ++ bmc_device->bmc_mem_phy, ++ bmc_device->bmc_mem_size); ++ ++} ++ ++static const struct file_operations aspeed_bmc_device_fops = { ++ .owner = THIS_MODULE, ++ .mmap = aspeed_bmc_device_mmap, ++}; ++ ++static ssize_t aspeed_ast2600_queue_rx(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct aspeed_queue_message *queue = attr->private; ++ struct aspeed_bmc_device *bmc_device = queue->bmc_device; ++ int index = queue->index; ++ u32 *data = (u32 *)buf; ++ u32 scu_id; ++ int ret; ++ ++ ret = wait_event_interruptible(queue->rx_wait, ++ !(readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS) & ++ ((index == QUEUE1) ? HOST2BMC_Q1_EMPTY : HOST2BMC_Q2_EMPTY))); ++ if (ret) ++ return -EINTR; ++ ++ data[0] = readl(bmc_device->reg_base + ++ ((index == QUEUE1) ? ASPEED_BMC_HOST2BMC_Q1 : ASPEED_BMC_HOST2BMC_Q2)); ++ ++ regmap_read(bmc_device->scu, ASPEED_SCU04, &scu_id); ++ if (scu_id == AST2600A3_SCU04) { ++ writel(BMC2HOST_INT_STS_DOORBELL | BMC2HOST_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS); ++ } else { ++ //A0 : BIT(12) A1 : BIT(15) ++ regmap_update_bits(bmc_device->scu, 0x560, BIT(15), BIT(15)); ++ regmap_update_bits(bmc_device->scu, 0x560, BIT(15), 0); ++ } ++ ++ return sizeof(u32); ++} ++ ++static ssize_t aspeed_ast2600_queue_tx(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct aspeed_queue_message *queue = attr->private; ++ struct aspeed_bmc_device *bmc_device = queue->bmc_device; ++ int index = queue->index; ++ u32 tx_buff; ++ u32 scu_id; ++ int ret; ++ ++ if (count != sizeof(u32)) ++ return -EINVAL; ++ ++ ret = wait_event_interruptible(queue->tx_wait, ++ !(readl(bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS) & ++ ((index == QUEUE1) ? BMC2HOST_Q1_FULL : BMC2HOST_Q2_FULL))); ++ if (ret) ++ return -EINTR; ++ ++ memcpy(&tx_buff, buf, 4); ++ writel(tx_buff, bmc_device->reg_base + ((index == QUEUE1) ? ASPEED_BMC_BMC2HOST_Q1 : ++ ASPEED_BMC_BMC2HOST_Q2)); ++ ++ /* trigger to host ++ * Only After AST2600A3 support DoorBell MSI ++ */ ++ regmap_read(bmc_device->scu, ASPEED_SCU04, &scu_id); ++ if (scu_id == AST2600A3_SCU04) { ++ writel(BMC2HOST_INT_STS_DOORBELL | BMC2HOST_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS); ++ } else { ++ //A0 : BIT(12) A1 : BIT(15) ++ regmap_update_bits(bmc_device->scu, 0x560, BIT(15), BIT(15)); ++ regmap_update_bits(bmc_device->scu, 0x560, BIT(15), 0); ++ } ++ ++ return sizeof(u32); ++} ++ ++static ssize_t aspeed_ast2700_queue_rx(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct aspeed_queue_message *queue = attr->private; ++ struct aspeed_bmc_device *bmc_device = queue->bmc_device; ++ int index = queue->index; ++ u32 *data = (u32 *)buf; ++ int ret; ++ ++ ret = wait_event_interruptible(queue->rx_wait, ++ !(readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS) & ++ ((index == QUEUE1) ? HOST2BMC_Q1_EMPTY : HOST2BMC_Q2_EMPTY))); ++ if (ret) ++ return -EINTR; ++ ++ data[0] = readl(bmc_device->reg_base + ++ ((index == QUEUE1) ? ASPEED_BMC_HOST2BMC_Q1 : ASPEED_BMC_HOST2BMC_Q2)); ++ ++ writel(BMC2HOST_INT_STS_DOORBELL | BMC2HOST_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS); ++ ++ return sizeof(u32); ++} ++ ++static ssize_t aspeed_ast2700_queue_tx(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct aspeed_queue_message *queue = attr->private; ++ struct aspeed_bmc_device *bmc_device = queue->bmc_device; ++ int index = queue->index; ++ u32 tx_buff; ++ int ret; ++ ++ if (count != sizeof(u32)) ++ return -EINVAL; ++ ++ ret = wait_event_interruptible(queue->tx_wait, ++ !(readl(bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS) & ++ ((index == QUEUE1) ? BMC2HOST_Q1_FULL : BMC2HOST_Q2_FULL))); ++ if (ret) ++ return -EINTR; ++ ++ memcpy(&tx_buff, buf, 4); ++ writel(tx_buff, bmc_device->reg_base + ((index == QUEUE1) ? ASPEED_BMC_BMC2HOST_Q1 : ++ ASPEED_BMC_BMC2HOST_Q2)); ++ ++ writel(BMC2HOST_INT_STS_DOORBELL | BMC2HOST_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS); ++ ++ return sizeof(u32); ++} ++ ++/* AST2600 */ ++static irqreturn_t aspeed_bmc_dev_pcie_isr(int irq, void *dev_id) ++{ ++ struct aspeed_bmc_device *bmc_device = dev_id; ++ ++ while (!(readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS) & HOST2BMC_Q1_EMPTY)) ++ readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_Q1); ++ ++ while (!(readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS) & HOST2BMC_Q2_EMPTY)) ++ readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_Q2); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t aspeed_bmc_dev_isr(int irq, void *dev_id) ++{ ++ struct aspeed_bmc_device *bmc_device = dev_id; ++ u32 host2bmc_q_sts = readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS); ++ ++ if (host2bmc_q_sts & HOST2BMC_INT_STS_DOORBELL) ++ writel(HOST2BMC_INT_STS_DOORBELL, bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS); ++ ++ if (host2bmc_q_sts & HOST2BMC_ENABLE_INTB) ++ writel(HOST2BMC_ENABLE_INTB, bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS); ++ ++ if (host2bmc_q_sts & HOST2BMC_Q1_FULL) ++ dev_info(bmc_device->dev, "Q1 Full\n"); ++ ++ if (host2bmc_q_sts & HOST2BMC_Q2_FULL) ++ dev_info(bmc_device->dev, "Q2 Full\n"); ++ ++ if (!(readl(bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS) & BMC2HOST_Q1_FULL)) ++ wake_up_interruptible(&bmc_device->queue[QUEUE1].tx_wait); ++ ++ if (!(readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS) & HOST2BMC_Q1_EMPTY)) ++ wake_up_interruptible(&bmc_device->queue[QUEUE1].rx_wait); ++ ++ if (!(readl(bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS) & BMC2HOST_Q2_FULL)) ++ wake_up_interruptible(&bmc_device->queue[QUEUE2].tx_wait); ++ ++ if (!(readl(bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS) & HOST2BMC_Q2_EMPTY)) ++ wake_up_interruptible(&bmc_device->queue[QUEUE2].rx_wait); ++ ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_ast2600_init(struct platform_device *pdev) ++{ ++ struct aspeed_bmc_device *bmc_device = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u32 pcie_config_ctl = SCU_PCIE_CONF_BMC_DEV_EN_IRQ | ++ SCU_PCIE_CONF_BMC_DEV_EN_MMIO | SCU_PCIE_CONF_BMC_DEV_EN; ++ u32 scu_id; ++ ++ bmc_device->scu = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,scu"); ++ if (IS_ERR(bmc_device->scu)) { ++ dev_err(&pdev->dev, "failed to find SCU regmap\n"); ++ return PTR_ERR(bmc_device->scu); ++ } ++ ++ if (bmc_device->pcie2lpc) ++ pcie_config_ctl |= SCU_PCIE_CONF_BMC_DEV_EN_E2L | ++ SCU_PCIE_CONF_BMC_DEV_EN_LPC_DECODE; ++ ++ regmap_update_bits(bmc_device->scu, ASPEED_SCU_PCIE_CONF_CTRL, ++ pcie_config_ctl, pcie_config_ctl); ++ ++ /* update class code to others as it is a MFD device */ ++ regmap_write(bmc_device->scu, ASPEED_SCU_BMC_DEV_CLASS, 0xff000000); ++ ++#ifdef SCU_TRIGGER_MSI ++ //SCUC24[17]: Enable PCI device 1 INTx/MSI from SCU560[15]. Will be added in next version ++ regmap_update_bits(bmc_device->scu, ASPEED_SCUC20, BIT(11) | BIT(14), BIT(11) | BIT(14)); ++ ++ regmap_read(bmc_device->scu, ASPEED_SCU04, &scu_id); ++ if (scu_id == AST2600A3_SCU04) ++ regmap_update_bits(bmc_device->scu, ASPEED_SCUC24, ++ PCIDEV1_INTX_MSI_HOST2BMC_EN | MSI_ROUTING_MASK, ++ PCIDEV1_INTX_MSI_HOST2BMC_EN | MSI_ROUTING_PCIe2LPC_PCIDEV1); ++ else ++ regmap_update_bits(bmc_device->scu, ASPEED_SCUC24, ++ BIT(17) | BIT(14) | BIT(11), BIT(17) | BIT(14) | BIT(11)); ++#else ++ //SCUC24[18]: Enable PCI device 1 INTx/MSI from Host-to-BMC controller. ++ regmap_update_bits(bmc_device->scu, 0xc24, BIT(18) | BIT(14), BIT(18) | BIT(14)); ++#endif ++ ++ writel((~(bmc_device->bmc_mem_size - 1) & 0xFFFFFFFF) | HOST2BMC_MEM_BAR_ENABLE, ++ bmc_device->reg_base + ASPEED_BMC_MEM_BAR); ++ writel(bmc_device->bmc_mem_phy, bmc_device->reg_base + ASPEED_BMC_MEM_BAR_REMAP); ++ ++ //Setting BMC to Host Q register ++ writel(BMC2HOST_Q2_FULL_UNMASK | BMC2HOST_Q1_FULL_UNMASK | BMC2HOST_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS); ++ writel(HOST2BMC_Q2_FULL_UNMASK | HOST2BMC_Q1_FULL_UNMASK | HOST2BMC_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS); ++ ++ return 0; ++} ++ ++static int aspeed_ast2700_init(struct platform_device *pdev) ++{ ++ struct aspeed_bmc_device *bmc_device = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u32 pcie_config_ctl; ++ u32 scu_id; ++ int i; ++ ++ bmc_device->device = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,device"); ++ if (IS_ERR(bmc_device->device)) { ++ dev_err(&pdev->dev, "failed to find device regmap\n"); ++ return PTR_ERR(bmc_device->device); ++ } ++ ++ bmc_device->e2m = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,e2m"); ++ if (IS_ERR(bmc_device->e2m)) { ++ dev_err(&pdev->dev, "failed to find e2m regmap\n"); ++ return PTR_ERR(bmc_device->e2m); ++ } ++ ++ bmc_device->scu = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,scu"); ++ if (IS_ERR(bmc_device->scu)) { ++ dev_err(&pdev->dev, "failed to find SCU regmap\n"); ++ return PTR_ERR(bmc_device->scu); ++ } ++ ++ if (bmc_device->pcie2lpc) { ++ pcie_config_ctl = SCU_PCIE_CONF_BMC_DEV_EN_E2L | ++ SCU_PCIE_CONF_BMC_DEV_EN_LPC_DECODE; ++ regmap_update_bits(bmc_device->scu, SCU0_PCIE_CONF_CTRL, ++ pcie_config_ctl, pcie_config_ctl); ++ } ++ ++ /* update class code to others as it is a MFD device */ ++ regmap_write(bmc_device->device, 0x18, 0xff000027); ++ ++ /* MSI */ ++ regmap_update_bits(bmc_device->device, 0x74, GENMASK(7, 4), BIT(7) | (5 << 4)); ++ /* EnPCIaMSI:BIT(25), EnPCIaIntA:BIT(17), EnPCIaMst:BIT(9), EnPCIaDev:BIT(1) */ ++ regmap_read(bmc_device->scu, SCU0_REVISION_ID, &scu_id); ++ if (scu_id & REVISION_ID) ++ regmap_update_bits(bmc_device->device, 0x70, ++ BIT(25) | BIT(17) | BIT(9) | BIT(1), ++ BIT(25) | BIT(17) | BIT(9) | BIT(1)); ++ else ++ /* Disable MSI[bit25] in ast2700A0 int only */ ++ regmap_update_bits(bmc_device->device, 0x70, ++ BIT(17) | BIT(9) | BIT(1), ++ BIT(25) | BIT(17) | BIT(9) | BIT(1)); ++ ++ /* bar size check for 4k align */ ++ for (i = 1; i < 16; i++) { ++ if ((bmc_device->bmc_mem_size / 4096) == (1 << (i - 1))) ++ break; ++ } ++ if (i == 16) { ++ i = 0; ++ dev_warn(bmc_device->dev, ++ "Bar size not align for 4K : %dK\n", (u32)bmc_device->bmc_mem_size / 1024); ++ } ++ ++ /* ++ * BAR assign in scu ++ * ((bar_mem / 4k) << 8) | per_size ++ */ ++ regmap_write(bmc_device->device, 0x1c, ((bmc_device->bmc_mem_phy) >> 4) | i); ++ ++ if (bmc_device->id == 0) ++ /* Node 0 Bar 0 */ ++ regmap_write(bmc_device->e2m, 0x108, ((bmc_device->bmc_mem_phy) >> 4) | i); ++ else ++ /* Node 1 Bar 0 */ ++ regmap_write(bmc_device->e2m, 0x128, ((bmc_device->bmc_mem_phy) >> 4) | i); ++ ++ /* Setting BMC to Host Q register */ ++ writel(BMC2HOST_Q2_FULL_UNMASK | BMC2HOST_Q1_FULL_UNMASK | BMC2HOST_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_BMC2HOST_STS); ++ writel(HOST2BMC_Q2_FULL_UNMASK | HOST2BMC_Q1_FULL_UNMASK | HOST2BMC_ENABLE_INTB, ++ bmc_device->reg_base + ASPEED_BMC_HOST2BMC_STS); ++ ++ return 0; ++} ++ ++static int aspeed_bmc_device_setup_queue(struct platform_device *pdev) ++{ ++ struct aspeed_bmc_device *bmc_device = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ int ret, i; ++ ++ for (i = 0; i < ASPEED_QUEUE_NUM; i++) { ++ struct aspeed_queue_message *queue = &bmc_device->queue[i]; ++ ++ init_waitqueue_head(&queue->tx_wait); ++ init_waitqueue_head(&queue->rx_wait); ++ ++ sysfs_bin_attr_init(&queue->bin); ++ ++ /* Queue name index starts from 1 */ ++ queue->bin.attr.name = ++ devm_kasprintf(dev, GFP_KERNEL, "bmc-dev-queue%d", (i + 1)); ++ queue->bin.attr.mode = 0600; ++ queue->bin.read = bmc_device->platform->queue_rx; ++ queue->bin.write = bmc_device->platform->queue_tx; ++ queue->bin.size = 4; ++ queue->bin.private = queue; ++ ++ ret = sysfs_create_bin_file(&pdev->dev.kobj, &queue->bin); ++ if (ret) { ++ dev_err(dev, "error for bin%d file\n", i); ++ return ret; ++ } ++ ++ queue->kn = kernfs_find_and_get(dev->kobj.sd, queue->bin.attr.name); ++ if (!queue->kn) { ++ sysfs_remove_bin_file(&dev->kobj, &queue->bin); ++ return ret; ++ } ++ ++ queue->index = i; ++ queue->bmc_device = bmc_device; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_bmc_device_setup_memory_mapping(struct platform_device *pdev) ++{ ++ struct aspeed_bmc_device *bmc_device = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ int ret; ++ ++ bmc_device->miscdev.minor = MISC_DYNAMIC_MINOR; ++ bmc_device->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "bmc-device%d", bmc_device->id); ++ bmc_device->miscdev.fops = &aspeed_bmc_device_fops; ++ bmc_device->miscdev.parent = dev; ++ ret = misc_register(&bmc_device->miscdev); ++ if (ret) { ++ dev_err(dev, "Unable to register device\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static struct aspeed_platform ast2600_plaform = { ++ .init = aspeed_ast2600_init, ++ .queue_rx = aspeed_ast2600_queue_rx, ++ .queue_tx = aspeed_ast2600_queue_tx ++}; ++ ++static struct aspeed_platform ast2700_plaform = { ++ .init = aspeed_ast2700_init, ++ .queue_rx = aspeed_ast2700_queue_rx, ++ .queue_tx = aspeed_ast2700_queue_tx ++}; ++ ++static const struct of_device_id aspeed_bmc_device_of_matches[] = { ++ { .compatible = "aspeed,ast2600-bmc-device", .data = &ast2600_plaform }, ++ { .compatible = "aspeed,ast2700-bmc-device", .data = &ast2700_plaform }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_bmc_device_of_matches); ++ ++static int aspeed_bmc_device_probe(struct platform_device *pdev) ++{ ++ struct aspeed_bmc_device *bmc_device; ++ struct device *dev = &pdev->dev; ++ struct resource res; ++ const void *md = of_device_get_match_data(dev); ++ struct device_node *np; ++ int ret = 0, i; ++ ++ if (!md) ++ return -ENODEV; ++ ++ bmc_device = devm_kzalloc(&pdev->dev, sizeof(struct aspeed_bmc_device), GFP_KERNEL); ++ if (!bmc_device) ++ return -ENOMEM; ++ dev_set_drvdata(dev, bmc_device); ++ ++ bmc_device->platform = md; ++ ++ bmc_device->id = of_alias_get_id(dev->of_node, "bmcdev"); ++ if (bmc_device->id < 0) ++ bmc_device->id = 0; ++ ++ bmc_device->dev = dev; ++ bmc_device->reg_base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(bmc_device->reg_base)) ++ return PTR_ERR(bmc_device->reg_base); ++ ++ ret = of_reserved_mem_device_init(dev); ++ if (ret) { ++ dev_err(dev, "of_reserved_mem_device_init failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (ret) { ++ dev_err(dev, "cannot set 64-bits DMA mask\n"); ++ return ret; ++ } ++ ++ np = of_parse_phandle(dev->of_node, "memory-region", 0); ++ if (!np || of_address_to_resource(np, 0, &res)) { ++ dev_err(dev, "Failed to find memory-region.\n"); ++ return -ENOMEM; ++ } ++ ++ of_node_put(np); ++ ++ bmc_device->bmc_mem_size = resource_size(&res); ++ bmc_device->bmc_mem_cpu = dmam_alloc_coherent(dev, bmc_device->bmc_mem_size, ++ &bmc_device->bmc_mem_phy, GFP_KERNEL); ++ if (!bmc_device->bmc_mem_cpu) { ++ dev_err(dev, "Failed to allocate BMC memory.\n"); ++ return -ENOMEM; ++ } ++ ++ bmc_device->irq = platform_get_irq(pdev, 0); ++ if (bmc_device->irq < 0) { ++ dev_err(&pdev->dev, "platform get of irq[=%d] failed!\n", bmc_device->irq); ++ return bmc_device->irq; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, bmc_device->irq, aspeed_bmc_dev_isr, 0, ++ dev_name(&pdev->dev), bmc_device); ++ if (ret) { ++ dev_err(dev, "aspeed bmc device Unable to get IRQ"); ++ return ret; ++ } ++ ++ ret = aspeed_bmc_device_setup_queue(pdev); ++ if (ret) { ++ dev_err(dev, "Cannot setup queue message"); ++ goto out; ++ } ++ ++ ret = aspeed_bmc_device_setup_memory_mapping(pdev); ++ if (ret) { ++ dev_err(dev, "Cannot setup memory mapping misc"); ++ goto out_free_queue; ++ } ++ ++ if (of_property_read_bool(dev->of_node, "pcie2lpc")) ++ bmc_device->pcie2lpc = 1; ++ ++ ret = bmc_device->platform->init(pdev); ++ if (ret) { ++ dev_err(dev, "Initialize bmc device failed\n"); ++ goto out_free_misc; ++ } ++ ++ bmc_device->pcie_irq = platform_get_irq(pdev, 1); ++ if (bmc_device->pcie_irq < 0) { ++ dev_warn(&pdev->dev, ++ "platform get of pcie irq[=%d] failed!\n", bmc_device->pcie_irq); ++ } else { ++ ret = devm_request_irq(&pdev->dev, bmc_device->pcie_irq, ++ aspeed_bmc_dev_pcie_isr, IRQF_SHARED, ++ dev_name(&pdev->dev), bmc_device); ++ if (ret < 0) { ++ dev_warn(dev, "Failed to request PCI-E IRQ %d.\n", ret); ++ bmc_device->pcie_irq = -1; ++ } ++ } ++ ++ dev_info(dev, "aspeed bmc device: driver successfully loaded.\n"); ++ ++ return 0; ++ ++out_free_misc: ++ misc_deregister(&bmc_device->miscdev); ++out_free_queue: ++ for (i = 0; i < ASPEED_QUEUE_NUM; i++) ++ sysfs_remove_bin_file(&pdev->dev.kobj, &bmc_device->queue[i].bin); ++out: ++ dev_warn(dev, "aspeed bmc device: driver init failed (ret=%d)!\n", ret); ++ return ret; ++} ++ ++static void aspeed_bmc_device_remove(struct platform_device *pdev) ++{ ++ struct aspeed_bmc_device *bmc_device = platform_get_drvdata(pdev); ++ int i; ++ ++ for (i = 0; i < ASPEED_QUEUE_NUM; i++) ++ sysfs_remove_bin_file(&pdev->dev.kobj, &bmc_device->queue[i].bin); ++ misc_deregister(&bmc_device->miscdev); ++ devm_free_irq(&pdev->dev, bmc_device->irq, bmc_device); ++ devm_free_irq(&pdev->dev, bmc_device->pcie_irq, bmc_device); ++ ++ devm_kfree(&pdev->dev, bmc_device); ++} ++ ++static struct platform_driver aspeed_bmc_device_driver = { ++ .probe = aspeed_bmc_device_probe, ++ .remove = aspeed_bmc_device_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_bmc_device_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_bmc_device_driver); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_DESCRIPTION("ASPEED BMC DEVICE Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/aspeed-disp-intf.c b/drivers/soc/aspeed/aspeed-disp-intf.c +--- a/drivers/soc/aspeed/aspeed-disp-intf.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-disp-intf.c 2026-09-09 15:51:44.718238072 +0000 +@@ -0,0 +1,255 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright (C) ASPEED Technology Inc. ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVICE_NAME "aspeed-disp-intf" ++ ++#define AST2700_SCU_CHIP_ID 0x0 ++#define SCU_CPU_REVISION_ID_HW GENMASK(23, 16) ++ ++#define AST2700_SCU_PIN_SEL 0x414 ++#define AST2700_SCU_D1PLL_SEL GENMASK(13, 12) ++#define AST2700_SCU_DAC_SRC_SEL GENMASK(11, 10) ++#define AST2700_SCU_DP_SRC_SEL GENMASK(9, 8) ++ ++#define AST2600_SCU_PIN_SEL 0x0C0 ++#define AST2600_SCU_DP_SRC_SEL BIT(18) ++#define AST2600_SCU_DAC_SRC_SEL BIT(16) ++ ++struct aspeed_disp_intf_config { ++ u8 version; ++ u32 dac_src_sel; ++ u32 dac_src_max; ++ u32 dac_src_min; ++ u32 dp_src_sel; ++ u32 dp_src_max; ++ u32 dp_src_min; ++}; ++ ++struct aspeed_disp_intf { ++ struct device *dev; ++ struct miscdevice miscdev; ++ struct regmap *scu; ++ const struct aspeed_disp_intf_config *config; ++}; ++ ++static int dac_src, dp_src; ++ ++static const struct aspeed_disp_intf_config ast2600_config = { ++ .version = 6, ++ .dac_src_sel = AST2600_SCU_PIN_SEL, ++ .dac_src_max = 1, ++ .dac_src_min = 0, ++ .dp_src_sel = AST2600_SCU_PIN_SEL, ++ .dp_src_max = 1, ++ .dp_src_min = 0, ++}; ++ ++static const struct aspeed_disp_intf_config ast2700_config = { ++ .version = 7, ++ .dac_src_sel = AST2700_SCU_PIN_SEL, ++ .dac_src_max = 2, ++ .dac_src_min = 0, ++ .dp_src_sel = AST2700_SCU_PIN_SEL, ++ .dp_src_max = 2, ++ .dp_src_min = 0, ++}; ++ ++static ssize_t dac_src_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct aspeed_disp_intf *intf = dev_get_drvdata(dev); ++ const struct aspeed_disp_intf_config *config = intf->config; ++ u32 val; ++ ++ regmap_read(intf->scu, config->dac_src_sel, &val); ++ dac_src = (config->version == 6) ++ ? FIELD_GET(AST2600_SCU_DAC_SRC_SEL, val) ++ : FIELD_GET(AST2700_SCU_DAC_SRC_SEL, val); ++ return sysfs_emit(buf, "%d\n", dac_src); ++} ++ ++static ssize_t dac_src_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct aspeed_disp_intf *intf = dev_get_drvdata(dev); ++ const struct aspeed_disp_intf_config *config = intf->config; ++ int src, res; ++ ++ res = kstrtoint(buf, 0, &src); ++ if (res) ++ return res; ++ ++ if (src < config->dac_src_min || src > config->dac_src_max) { ++ dev_err(intf->dev, "Invalid dac_src(max:%d, min:%d)\n", ++ config->dac_src_max, config->dac_src_min); ++ return -1; ++ } ++ ++ dac_src = src; ++ if (config->version == 6) { ++ regmap_update_bits(intf->scu, config->dac_src_sel, AST2600_SCU_DAC_SRC_SEL, ++ FIELD_PREP(AST2600_SCU_DAC_SRC_SEL, src)); ++ } else { ++ u32 id; ++ u32 mask = AST2700_SCU_DAC_SRC_SEL; ++ u32 val = FIELD_PREP(AST2700_SCU_DAC_SRC_SEL, src); ++ ++ // D1PLL used in A0 only ++ regmap_read(intf->scu, AST2700_SCU_CHIP_ID, &id); ++ if (FIELD_GET(SCU_CPU_REVISION_ID_HW, id) != 0) { ++ mask |= AST2700_SCU_D1PLL_SEL; ++ val |= FIELD_PREP(AST2700_SCU_D1PLL_SEL, src); ++ } ++ ++ regmap_update_bits(intf->scu, config->dac_src_sel, mask, val); ++ } ++ return count; ++} ++ ++static DEVICE_ATTR_RW(dac_src); ++ ++static ssize_t dp_src_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct aspeed_disp_intf *intf = dev_get_drvdata(dev); ++ const struct aspeed_disp_intf_config *config = intf->config; ++ u32 val; ++ ++ regmap_read(intf->scu, config->dp_src_sel, &val); ++ dp_src = (config->version == 6) ++ ? FIELD_GET(AST2600_SCU_DP_SRC_SEL, val) ++ : FIELD_GET(AST2700_SCU_DP_SRC_SEL, val); ++ return sysfs_emit(buf, "%d\n", dp_src); ++} ++ ++static ssize_t dp_src_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct aspeed_disp_intf *intf = dev_get_drvdata(dev); ++ const struct aspeed_disp_intf_config *config = intf->config; ++ int src, res; ++ ++ res = kstrtoint(buf, 0, &src); ++ if (res) ++ return res; ++ ++ if (src < config->dp_src_min || src > config->dp_src_max) { ++ dev_err(intf->dev, "Invalid dp_src(max:%d, min:%d)\n", ++ config->dp_src_max, config->dp_src_min); ++ return -1; ++ } ++ ++ dp_src = src; ++ if (config->version == 6) { ++ regmap_update_bits(intf->scu, config->dp_src_sel, AST2600_SCU_DP_SRC_SEL, ++ FIELD_PREP(AST2600_SCU_DP_SRC_SEL, src)); ++ } else { ++ u32 val; ++ ++ regmap_update_bits(intf->scu, config->dp_src_sel, AST2700_SCU_DP_SRC_SEL, ++ FIELD_PREP(AST2700_SCU_DP_SRC_SEL, src)); ++ ++ // D1PLL used in A0 only ++ regmap_read(intf->scu, AST2700_SCU_CHIP_ID, &val); ++ if (FIELD_GET(SCU_CPU_REVISION_ID_HW, val) == 0) { ++ regmap_update_bits(intf->scu, config->dp_src_sel, AST2700_SCU_D1PLL_SEL, ++ FIELD_PREP(AST2700_SCU_D1PLL_SEL, src)); ++ } ++ } ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(dp_src); ++ ++static struct attribute *aspeed_disp_intf_attrs[] = { ++ &dev_attr_dac_src.attr, ++ &dev_attr_dp_src.attr, ++ NULL, ++}; ++ ++static const struct attribute_group aspeed_disp_intf_attgrp = { ++ .name = NULL, ++ .attrs = aspeed_disp_intf_attrs, ++}; ++ ++static int aspeed_disp_intf_probe(struct platform_device *pdev) ++{ ++ struct aspeed_disp_intf *intf; ++ struct device *dev = &pdev->dev; ++ int ret; ++ ++ intf = devm_kzalloc(&pdev->dev, sizeof(struct aspeed_disp_intf), GFP_KERNEL); ++ if (!intf) ++ return -ENOMEM; ++ ++ dev_set_drvdata(&pdev->dev, intf); ++ ++ intf->config = of_device_get_match_data(&pdev->dev); ++ if (!intf->config) ++ return -ENODEV; ++ ++ intf->dev = dev; ++ intf->scu = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); ++ if (IS_ERR(intf->scu)) { ++ dev_err(dev, "failed to find SCU regmap\n"); ++ return PTR_ERR(intf->scu); ++ } ++ ++ intf->miscdev.minor = MISC_DYNAMIC_MINOR; ++ intf->miscdev.name = DEVICE_NAME; ++ intf->miscdev.parent = dev; ++ ret = misc_register(&intf->miscdev); ++ if (ret) { ++ dev_err(dev, "Unable to register device\n"); ++ return ret; ++ } ++ ++ ret = sysfs_create_group(&dev->kobj, &aspeed_disp_intf_attgrp); ++ if (ret != 0) ++ dev_warn(dev, "failed to register attributes\n"); ++ ++ return 0; ++} ++ ++static void aspeed_disp_intf_remove(struct platform_device *pdev) ++{ ++ struct aspeed_disp_intf *intf = platform_get_drvdata(pdev); ++ ++ sysfs_remove_group(&intf->dev->kobj, &aspeed_disp_intf_attgrp); ++ misc_deregister(&intf->miscdev); ++ devm_kfree(&pdev->dev, intf); ++} ++ ++static const struct of_device_id aspeed_disp_intf_of_matches[] = { ++ { .compatible = "aspeed,ast2600-disp-intf", .data = &ast2600_config }, ++ { .compatible = "aspeed,ast2700-disp-intf", .data = &ast2700_config }, ++ {}, ++}; ++ ++static struct platform_driver aspeed_disp_intf_driver = { ++ .probe = aspeed_disp_intf_probe, ++ .remove = aspeed_disp_intf_remove, ++ .driver = { ++ .name = DEVICE_NAME, ++ .of_match_table = aspeed_disp_intf_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_disp_intf_driver); ++ ++MODULE_DEVICE_TABLE(of, aspeed_disp_intf_of_matches); ++MODULE_AUTHOR("Jammy Huang "); ++MODULE_DESCRIPTION("ASPEED Display Interface Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/aspeed-host-bmc-dev.c b/drivers/soc/aspeed/aspeed-host-bmc-dev.c +--- a/drivers/soc/aspeed/aspeed-host-bmc-dev.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-host-bmc-dev.c 2026-09-09 15:51:44.718238072 +0000 +@@ -0,0 +1,828 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright (C) ASPEED Technology Inc. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#if IS_ENABLED(CONFIG_ASPEED_MMBI) ++#include "mmbi/aspeed-mmbi.h" ++#endif ++ ++#define PCI_BMC_HOST2BMC_Q1 0x30000 ++#define PCI_BMC_HOST2BMC_Q2 0x30010 ++#define PCI_BMC_BMC2HOST_Q1 0x30020 ++#define PCI_BMC_BMC2HOST_Q2 0x30030 ++#define PCI_BMC_BMC2HOST_STS 0x30040 ++#define BMC2HOST_INT_STS_DOORBELL BIT(31) ++#define BMC2HOST_ENABLE_INTB BIT(30) ++ ++#define BMC2HOST_Q1_FULL BIT(27) ++#define BMC2HOST_Q1_EMPTY BIT(26) ++#define BMC2HOST_Q2_FULL BIT(25) ++#define BMC2HOST_Q2_EMPTY BIT(24) ++#define BMC2HOST_Q1_FULL_UNMASK BIT(23) ++#define BMC2HOST_Q1_EMPTY_UNMASK BIT(22) ++#define BMC2HOST_Q2_FULL_UNMASK BIT(21) ++#define BMC2HOST_Q2_EMPTY_UNMASK BIT(20) ++ ++#define PCI_BMC_HOST2BMC_STS 0x30044 ++#define HOST2BMC_INT_STS_DOORBELL BIT(31) ++#define HOST2BMC_ENABLE_INTB BIT(30) ++ ++#define HOST2BMC_Q1_FULL BIT(27) ++#define HOST2BMC_Q1_EMPTY BIT(26) ++#define HOST2BMC_Q2_FULL BIT(25) ++#define HOST2BMC_Q2_EMPTY BIT(24) ++#define HOST2BMC_Q1_FULL_UNMASK BIT(23) ++#define HOST2BMC_Q1_EMPTY_UNMASK BIT(22) ++#define HOST2BMC_Q2_FULL_UNMASK BIT(21) ++#define HOST2BMC_Q2_EMPTY_UNMASK BIT(20) ++ ++static DEFINE_IDA(bmc_device_ida); ++ ++#define VUART_MAX_PARMS 2 ++#define ASPEED_QUEUE_NUM 2 ++#define MAX_MSI_NUM 8 ++ ++enum queue_index { ++ QUEUE1 = 0, ++ QUEUE2, ++}; ++ ++enum msi_index { ++ BMC_MSI, ++ MBX_MSI, ++ VUART0_MSI, ++ VUART1_MSI, ++}; ++ ++/* Match msi_index */ ++static int ast2600_msi_idx_table[MAX_MSI_NUM] = { 4, 21, 16, 15 }; ++static int ast2700_soc0_msi_idx_table[MAX_MSI_NUM] = { 0, 11, 6, 5}; ++ ++struct aspeed_platform { ++ int (*setup)(struct pci_dev *pdev); ++}; ++ ++struct aspeed_queue_message { ++ /* Queue waiters for idle engine */ ++ wait_queue_head_t tx_wait; ++ wait_queue_head_t rx_wait; ++ struct kernfs_node *kn; ++ struct bin_attribute bin; ++ int index; ++ struct aspeed_pci_bmc_dev *pci_bmc_device; ++}; ++ ++#if IS_ENABLED(CONFIG_ASPEED_MMBI) ++struct aspeed_host_pcie_mmbi_ins { ++ struct mmbi_ins_desc mmbi_desc; ++ int irq; ++ int msi_index; ++ int instance_index; ++ int bar_index; ++ const char *dev_name; ++ bool valid; ++}; ++ ++struct aspeed_host_pcie_mmbi { ++ struct device *dev; ++ struct aspeed_host_pcie_mmbi_ins instances[PCI_STD_NUM_BARS]; ++ int msi_nums; ++}; ++ ++/* ++ * app_interface: per-channel application interface selection. ++ * 0 = ioctl (MMBI_APP_INTF_IOCTL) ++ * 1 = mctp-netdev (MMBI_APP_INTF_MCTP_NETDEV) ++ * ++ * One value applies to every channel. Multiple values map by channel index, ++ * and omitted channels keep their defaults. ++ * Examples: ++ * app_interface=1 -> all channels use mctp-netdev ++ * app_interface=1,0,0,0,1 -> ch0/ch4 use mctp-netdev ++ * Invalid values fall back to the default map. ++ */ ++static const uint app_interface_defaults[MMBI_MAX_CHANNELS] = { ++ [0] = MMBI_APP_INTF_MCTP_NETDEV, ++}; ++ ++static uint app_interface[MMBI_MAX_CHANNELS] = { [0] = MMBI_APP_INTF_MCTP_NETDEV }; ++static int app_interface_count; ++module_param_array(app_interface, uint, &app_interface_count, 0444); ++MODULE_PARM_DESC(app_interface, ++ "App interface per channel: 0=ioctl, 1=mctp-netdev; one value applies to all channels"); ++ ++#endif ++ ++struct aspeed_pci_bmc_dev { ++ struct device *dev; ++ struct miscdevice miscdev; ++ struct aspeed_platform *platform; ++ kernel_ulong_t driver_data; ++ int id; ++ ++ unsigned long mem_bar_base; ++ unsigned long mem_bar_size; ++ void __iomem *mem_bar_reg; ++ ++ unsigned long message_bar_base; ++ unsigned long message_bar_size; ++ void __iomem *msg_bar_reg; ++ ++ void __iomem *pcie_sio_decode_addr; ++ ++ struct aspeed_queue_message queue[ASPEED_QUEUE_NUM]; ++ ++ void __iomem *sio_mbox_reg; ++ struct uart_8250_port uart[VUART_MAX_PARMS]; ++ int uart_line[VUART_MAX_PARMS]; ++ ++ /* Interrupt ++ * The index of array is using to enum msi_index ++ */ ++ int *msi_idx_table; ++#if IS_ENABLED(CONFIG_ASPEED_MMBI) ++ struct aspeed_host_pcie_mmbi mmbi; ++#endif ++}; ++ ++#define PCIE_DEVICE_SIO_ADDR (0x2E * 4) ++#define BMC_MULTI_MSI 32 ++ ++#define DRIVER_NAME "aspeed-host-bmc-dev" ++ ++static struct aspeed_pci_bmc_dev *file_aspeed_bmc_device(struct file *file) ++{ ++ return container_of(file->private_data, struct aspeed_pci_bmc_dev, miscdev); ++} ++ ++static int aspeed_pci_bmc_dev_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = file_aspeed_bmc_device(file); ++ unsigned long vsize = vma->vm_end - vma->vm_start; ++ pgprot_t prot = vma->vm_page_prot; ++ ++ if (vma->vm_pgoff + vsize > pci_bmc_dev->mem_bar_base + 0x100000) ++ return -EINVAL; ++ ++ prot = pgprot_noncached(prot); ++ ++ if (remap_pfn_range(vma, vma->vm_start, ++ (pci_bmc_dev->mem_bar_base >> PAGE_SHIFT) + vma->vm_pgoff, ++ vsize, prot)) ++ return -EAGAIN; ++ ++ return 0; ++} ++ ++static const struct file_operations aspeed_pci_bmc_dev_fops = { ++ .owner = THIS_MODULE, ++ .mmap = aspeed_pci_bmc_dev_mmap, ++}; ++ ++static ssize_t aspeed_queue_rx(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, ++ char *buf, loff_t off, size_t count) ++{ ++ struct aspeed_queue_message *queue = attr->private; ++ struct aspeed_pci_bmc_dev *pci_bmc_device = queue->pci_bmc_device; ++ int index = queue->index; ++ u32 *data = (u32 *)buf; ++ int ret; ++ ++ ret = wait_event_interruptible(queue->rx_wait, ++ !(readl(pci_bmc_device->msg_bar_reg + PCI_BMC_BMC2HOST_STS) & ++ ((index == QUEUE1) ? BMC2HOST_Q1_EMPTY : BMC2HOST_Q2_EMPTY))); ++ if (ret) ++ return -EINTR; ++ ++ data[0] = readl(pci_bmc_device->msg_bar_reg + ++ ((index == QUEUE1) ? PCI_BMC_BMC2HOST_Q1 : PCI_BMC_BMC2HOST_Q2)); ++ ++ writel(HOST2BMC_INT_STS_DOORBELL | HOST2BMC_ENABLE_INTB, ++ pci_bmc_device->msg_bar_reg + PCI_BMC_HOST2BMC_STS); ++ ++ return sizeof(u32); ++} ++ ++static ssize_t aspeed_queue_tx(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, ++ char *buf, loff_t off, size_t count) ++{ ++ struct aspeed_queue_message *queue = attr->private; ++ struct aspeed_pci_bmc_dev *pci_bmc_device = queue->pci_bmc_device; ++ int index = queue->index; ++ u32 tx_buff; ++ int ret; ++ ++ if (count != sizeof(u32)) ++ return -EINVAL; ++ ++ ret = wait_event_interruptible(queue->tx_wait, ++ !(readl(pci_bmc_device->msg_bar_reg + PCI_BMC_HOST2BMC_STS) & ++ ((index == QUEUE1) ? HOST2BMC_Q1_FULL : HOST2BMC_Q2_FULL))); ++ if (ret) ++ return -EINTR; ++ ++ memcpy(&tx_buff, buf, 4); ++ writel(tx_buff, pci_bmc_device->msg_bar_reg + ++ ((index == QUEUE1) ? PCI_BMC_HOST2BMC_Q1 : PCI_BMC_HOST2BMC_Q2)); ++ //trigger to host ++ writel(HOST2BMC_INT_STS_DOORBELL | HOST2BMC_ENABLE_INTB, ++ pci_bmc_device->msg_bar_reg + PCI_BMC_HOST2BMC_STS); ++ ++ return sizeof(u32); ++} ++ ++static irqreturn_t aspeed_pci_host_bmc_device_interrupt(int irq, void *dev_id) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_device = dev_id; ++ u32 bmc2host_q_sts = readl(pci_bmc_device->msg_bar_reg + PCI_BMC_BMC2HOST_STS); ++ ++ if (bmc2host_q_sts & BMC2HOST_INT_STS_DOORBELL) ++ writel(BMC2HOST_INT_STS_DOORBELL, ++ pci_bmc_device->msg_bar_reg + PCI_BMC_BMC2HOST_STS); ++ ++ if (bmc2host_q_sts & BMC2HOST_ENABLE_INTB) ++ writel(BMC2HOST_ENABLE_INTB, pci_bmc_device->msg_bar_reg + PCI_BMC_BMC2HOST_STS); ++ ++ if (bmc2host_q_sts & BMC2HOST_Q1_FULL) ++ dev_info(pci_bmc_device->dev, "Q1 Full\n"); ++ ++ if (bmc2host_q_sts & BMC2HOST_Q2_FULL) ++ dev_info(pci_bmc_device->dev, "Q2 Full\n"); ++ ++ //check q1 ++ if (!(readl(pci_bmc_device->msg_bar_reg + PCI_BMC_HOST2BMC_STS) & HOST2BMC_Q1_FULL)) ++ wake_up_interruptible(&pci_bmc_device->queue[QUEUE1].tx_wait); ++ ++ if (!(readl(pci_bmc_device->msg_bar_reg + PCI_BMC_BMC2HOST_STS) & BMC2HOST_Q1_EMPTY)) ++ wake_up_interruptible(&pci_bmc_device->queue[QUEUE1].rx_wait); ++ //chech q2 ++ if (!(readl(pci_bmc_device->msg_bar_reg + PCI_BMC_HOST2BMC_STS) & HOST2BMC_Q2_FULL)) ++ wake_up_interruptible(&pci_bmc_device->queue[QUEUE2].tx_wait); ++ ++ if (!(readl(pci_bmc_device->msg_bar_reg + PCI_BMC_BMC2HOST_STS) & BMC2HOST_Q2_EMPTY)) ++ wake_up_interruptible(&pci_bmc_device->queue[QUEUE2].rx_wait); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t aspeed_pci_host_mbox_interrupt(int irq, void *dev_id) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_device = dev_id; ++ u32 isr = readl(pci_bmc_device->sio_mbox_reg + 0x94); ++ ++ if (isr & BIT(7)) ++ writel(BIT(7), pci_bmc_device->sio_mbox_reg + 0x94); ++ ++ return IRQ_HANDLED; ++} ++ ++static void aspeed_pci_setup_irq_resource(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ ++ /* Assign static msi index table by platform */ ++ if (pdev->revision == 0x27) { ++ pci_bmc_dev->msi_idx_table = ast2700_soc0_msi_idx_table; ++ } else { ++ pci_bmc_dev->msi_idx_table = ast2600_msi_idx_table; ++ } ++ ++ if (pci_alloc_irq_vectors(pdev, 1, BMC_MULTI_MSI, PCI_IRQ_INTX | PCI_IRQ_MSI) <= 1) ++ /* Set all msi index to the first vector */ ++ memset(pci_bmc_dev->msi_idx_table, 0, sizeof(int) * MAX_MSI_NUM); ++} ++ ++static int aspeed_pci_bmc_device_setup_queue(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_device = pci_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ int ret, i; ++ ++ for (i = 0; i < ASPEED_QUEUE_NUM; i++) { ++ struct aspeed_queue_message *queue = &pci_bmc_device->queue[i]; ++ ++ init_waitqueue_head(&queue->tx_wait); ++ init_waitqueue_head(&queue->rx_wait); ++ ++ sysfs_bin_attr_init(&queue->bin); ++ ++ /* Queue name index starts from 1 */ ++ queue->bin.attr.name = ++ devm_kasprintf(dev, GFP_KERNEL, "pci-bmc-dev-queue%d", (i + 1)); ++ queue->bin.attr.mode = 0600; ++ queue->bin.read = aspeed_queue_rx; ++ queue->bin.write = aspeed_queue_tx; ++ queue->bin.size = 4; ++ queue->bin.private = queue; ++ ++ ret = sysfs_create_bin_file(&pdev->dev.kobj, &queue->bin); ++ if (ret) { ++ dev_err(dev, "error for bin%d file\n", i); ++ return ret; ++ } ++ ++ queue->kn = kernfs_find_and_get(dev->kobj.sd, queue->bin.attr.name); ++ if (!queue->kn) { ++ sysfs_remove_bin_file(&dev->kobj, &queue->bin); ++ return ret; ++ } ++ ++ queue->index = i; ++ queue->pci_bmc_device = pci_bmc_device; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_pci_bmc_device_setup_vuart(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u16 vuart_ioport; ++ int ret, i; ++ ++ for (i = 0; i < VUART_MAX_PARMS; i++) { ++ /* Assign the line to non-exist device */ ++ pci_bmc_dev->uart_line[i] = -ENOENT; ++ vuart_ioport = 0x3F8 - (i * 0x100); ++ pci_bmc_dev->uart[i].port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ; ++ pci_bmc_dev->uart[i].port.uartclk = 115200 * 16; ++ pci_bmc_dev->uart[i].port.irq = ++ pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[VUART0_MSI + i]); ++ pci_bmc_dev->uart[i].port.dev = dev; ++ pci_bmc_dev->uart[i].port.iotype = UPIO_MEM32; ++ pci_bmc_dev->uart[i].port.iobase = 0; ++ pci_bmc_dev->uart[i].port.mapbase = ++ pci_bmc_dev->message_bar_base + (vuart_ioport << 2); ++ pci_bmc_dev->uart[i].port.membase = 0; ++ pci_bmc_dev->uart[i].port.type = PORT_16550A; ++ pci_bmc_dev->uart[i].port.flags |= (UPF_IOREMAP | UPF_FIXED_PORT | UPF_FIXED_TYPE); ++ pci_bmc_dev->uart[i].port.regshift = 2; ++ ret = serial8250_register_8250_port(&pci_bmc_dev->uart[i]); ++ if (ret < 0) { ++ dev_err_probe(dev, ret, "Can't setup PCIe VUART\n"); ++ return ret; ++ } ++ pci_bmc_dev->uart_line[i] = ret; ++ } ++ return 0; ++} ++ ++static int aspeed_pci_bmc_device_setup_memory_mapping(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ int ret; ++ ++ pci_bmc_dev->miscdev.minor = MISC_DYNAMIC_MINOR; ++ pci_bmc_dev->miscdev.name = ++ devm_kasprintf(dev, GFP_KERNEL, "%s%d", DRIVER_NAME, pci_bmc_dev->id); ++ pci_bmc_dev->miscdev.fops = &aspeed_pci_bmc_dev_fops; ++ pci_bmc_dev->miscdev.parent = dev; ++ ++ ret = misc_register(&pci_bmc_dev->miscdev); ++ if (ret) { ++ pr_err("host bmc register fail %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_pci_bmc_device_setup_mbox(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ int ret; ++ ++ /* setup mbox */ ++ pci_bmc_dev->pcie_sio_decode_addr = pci_bmc_dev->msg_bar_reg + PCIE_DEVICE_SIO_ADDR; ++ writel(0xaa, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0xa5, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0xa5, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0x07, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0x0e, pci_bmc_dev->pcie_sio_decode_addr + 0x04); ++ /* disable */ ++ writel(0x30, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0x00, pci_bmc_dev->pcie_sio_decode_addr + 0x04); ++ /* set decode address 0x100 */ ++ writel(0x60, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0x01, pci_bmc_dev->pcie_sio_decode_addr + 0x04); ++ writel(0x61, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0x00, pci_bmc_dev->pcie_sio_decode_addr + 0x04); ++ /* enable */ ++ writel(0x30, pci_bmc_dev->pcie_sio_decode_addr); ++ writel(0x01, pci_bmc_dev->pcie_sio_decode_addr + 0x04); ++ pci_bmc_dev->sio_mbox_reg = pci_bmc_dev->msg_bar_reg + 0x400; ++ ++ ret = devm_request_irq(dev, ++ pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[MBX_MSI]), ++ aspeed_pci_host_mbox_interrupt, IRQF_SHARED, ++ devm_kasprintf(dev, GFP_KERNEL, "aspeed-sio-mbox%d", pci_bmc_dev->id), ++ pci_bmc_dev); ++ if (ret) { ++ pr_err("host bmc device Unable to get IRQ %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void aspeed_pci_host_bmc_device_release_queue(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ int i; ++ ++ for (i = 0; i < ASPEED_QUEUE_NUM; i++) ++ sysfs_remove_bin_file(&pdev->dev.kobj, &pci_bmc_dev->queue[i].bin); ++} ++ ++static void aspeed_pci_host_bmc_device_release_vuart(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ int i; ++ ++ for (i = 0; i < VUART_MAX_PARMS; i++) { ++ if (pci_bmc_dev->uart_line[i] >= 0) ++ serial8250_unregister_port(pci_bmc_dev->uart_line[i]); ++ } ++} ++ ++static void aspeed_pci_host_bmc_device_release_memory_mapping(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ ++ if (!list_empty(&pci_bmc_dev->miscdev.list)) ++ misc_deregister(&pci_bmc_dev->miscdev); ++} ++ ++static int aspeed_pci_host_setup(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ int rc = 0; ++ ++ /* Get share memory BAR */ ++ pci_bmc_dev->mem_bar_base = pci_resource_start(pdev, 0); ++ pci_bmc_dev->mem_bar_size = pci_resource_len(pdev, 0); ++ pci_bmc_dev->mem_bar_reg = pci_ioremap_bar(pdev, 0); ++ if (!pci_bmc_dev->mem_bar_reg) ++ return -ENOMEM; ++ ++ /* Get Message BAR */ ++ pci_bmc_dev->message_bar_base = pci_resource_start(pdev, 1); ++ pci_bmc_dev->message_bar_size = pci_resource_len(pdev, 1); ++ pci_bmc_dev->msg_bar_reg = pci_ioremap_bar(pdev, 1); ++ if (!pci_bmc_dev->msg_bar_reg) { ++ rc = -ENOMEM; ++ goto out_free0; ++ } ++ ++ /* AST2600 ERRTA40: dummy read */ ++ if (pdev->revision < 0x27) ++ (void)__raw_readl((void __iomem *)pci_bmc_dev->msg_bar_reg); ++ ++ rc = aspeed_pci_bmc_device_setup_queue(pdev); ++ if (rc) { ++ pr_err("Cannot setup Queue Message"); ++ goto out_free1; ++ } ++ ++ rc = aspeed_pci_bmc_device_setup_memory_mapping(pdev); ++ if (rc) { ++ pr_err("Cannot setup Memory Mapping"); ++ goto out_free_queue; ++ } ++ ++ rc = aspeed_pci_bmc_device_setup_mbox(pdev); ++ if (rc) { ++ pr_err("Cannot setup Mailnbox"); ++ goto out_free_mmapping; ++ } ++ ++ rc = aspeed_pci_bmc_device_setup_vuart(pdev); ++ if (rc) { ++ pr_err("Cannot setup Virtual UART"); ++ goto out_free_mbox; ++ } ++ ++ rc = devm_request_irq(&pdev->dev, pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[BMC_MSI]), ++ aspeed_pci_host_bmc_device_interrupt, IRQF_SHARED, ++ pci_bmc_dev->miscdev.name, pci_bmc_dev); ++ if (rc) { ++ pr_err("Get BMC DEVICE IRQ failed. (err=%d)\n", rc); ++ goto out_free_uart; ++ } ++ ++ return 0; ++ ++out_free_uart: ++ aspeed_pci_host_bmc_device_release_vuart(pdev); ++out_free_mbox: ++ devm_free_irq(&pdev->dev, pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[MBX_MSI]), ++ pci_bmc_dev); ++out_free_mmapping: ++ aspeed_pci_host_bmc_device_release_memory_mapping(pdev); ++out_free_queue: ++ aspeed_pci_host_bmc_device_release_queue(pdev); ++out_free1: ++ pci_iounmap(pdev, pci_bmc_dev->msg_bar_reg); ++out_free0: ++ pci_iounmap(pdev, pci_bmc_dev->mem_bar_reg); ++ ++ pci_release_regions(pdev); ++ return rc; ++} ++ ++static struct aspeed_platform aspeed_pcie_host[] = { ++ { .setup = aspeed_pci_host_setup }, ++ { 0 } ++}; ++ ++#if IS_ENABLED(CONFIG_ASPEED_MMBI) ++static const char * ++aspeed_host_pcie_mmbi_app_interface_name(enum mmbi_app_interface intf) ++{ ++ switch (intf) { ++ case MMBI_APP_INTF_MCTP_NETDEV: ++ return "mctp-netdev"; ++ case MMBI_APP_INTF_IOCTL: ++ default: ++ return "ioctl"; ++ } ++} ++ ++static int aspeed_host_pcie_mmbi_validate_app_interface(struct device *dev) ++{ ++ int i; ++ ++ for (i = 0; i < app_interface_count; i++) { ++ if (app_interface[i] > MMBI_APP_INTF_MCTP_NETDEV) { ++ dev_warn(dev, ++ "invalid app_interface[%d]=%u, using defaults\n", ++ i, app_interface[i]); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static enum mmbi_app_interface ++aspeed_host_pcie_mmbi_get_app_interface(int channel, bool use_defaults) ++{ ++ if (use_defaults) ++ return app_interface_defaults[channel]; ++ ++ if (app_interface_count == 1) ++ return app_interface[0]; ++ ++ if (channel < app_interface_count) ++ return app_interface[channel]; ++ ++ return app_interface_defaults[channel]; ++} ++ ++static void ++aspeed_host_pcie_mmbi_init_app_interfaces(struct mmbi_ins_desc *mmbi_desc, ++ bool use_defaults) ++{ ++ int i; ++ ++ for (i = 0; i < MMBI_MAX_CHANNELS; i++) ++ mmbi_desc->chan_desc[i].app_interface = ++ aspeed_host_pcie_mmbi_get_app_interface(i, ++ use_defaults); ++} ++ ++static void aspeed_host_pcie_mmbi_log_app_interfaces(struct device *dev, ++ struct mmbi_ins_desc *mmbi_desc, ++ int bar) ++{ ++ struct mmbi_chan_desc *chan_desc; ++ const char *interface; ++ int i; ++ ++ for (i = 0; i < mmbi_desc->num_of_channels; i++) { ++ chan_desc = &mmbi_desc->chan_desc[i]; ++ interface = aspeed_host_pcie_mmbi_app_interface_name(chan_desc->app_interface); ++ dev_info(dev, "BAR %d channel %d application interface: %s\n", ++ bar, i, interface); ++ } ++} ++ ++static int aspeed_pci_host_mmbi_setup(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++ struct aspeed_host_pcie_mmbi *mmbi = &pci_bmc_dev->mmbi; ++ struct aspeed_host_pcie_mmbi_ins *ins; ++ struct device *dev = &pdev->dev; ++ struct mmbi_ins_desc *mmbi_desc; ++ int i, ret, instance_id; ++ int last_ret = -ENODEV; ++ bool use_default_app_interfaces; ++ resource_size_t start, size; ++ ++ ret = aspeed_host_pcie_mmbi_validate_app_interface(dev); ++ use_default_app_interfaces = ret != 0; ++ ++ instance_id = 0; ++ for (i = 2; i < PCI_STD_NUM_BARS; i++) { ++ ins = &mmbi->instances[i]; ++ mmbi_desc = &ins->mmbi_desc; ++ start = pci_resource_start(pdev, i); ++ size = pci_resource_len(pdev, i); ++ ++ if (!start || !size) { ++ dev_err(dev, ++ "Invalid BAR %d with start 0x%llx and size 0x%llx\n", ++ i, (unsigned long long)start, ++ (unsigned long long)size); ++ continue; ++ } ++ ++ mmbi_desc->desc_virt = pci_ioremap_bar(pdev, i); ++ if (!mmbi_desc->desc_virt) { ++ dev_err(dev, "Failed to ioremap BAR %d\n", i); ++ continue; ++ } ++ mmbi_desc->dev = dev; ++ mmbi_desc->role = MMBI_ROLE_HOST; ++ ++ aspeed_host_pcie_mmbi_init_app_interfaces(mmbi_desc, ++ use_default_app_interfaces); ++ ret = mmbi_instance_init(mmbi_desc); ++ if (ret) { ++ dev_info(dev, ++ "Failed to initialize MMBI at bar %d, error %d\n", ++ i, ret); ++ pci_iounmap(pdev, mmbi_desc->desc_virt); ++ last_ret = ret; ++ } else { ++ ins->instance_index = instance_id++; ++ ins->bar_index = i; ++ ins->valid = true; ++ ins->dev_name = devm_kasprintf(dev, GFP_KERNEL, ++ "mmbi_host_ins%d", ++ ins->instance_index); ++ aspeed_host_pcie_mmbi_log_app_interfaces(dev, mmbi_desc, i); ++ ++ ins->msi_index = mmbi_desc->host_int_location; ++ } ++ } ++ ++ if (!instance_id) ++ return last_ret; ++ ++ return 0; ++} ++#endif ++ ++static int aspeed_pci_host_bmc_device_probe(struct pci_dev *pdev, const struct pci_device_id *ent) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev; ++ int rc = 0; ++ ++ pr_info("ASPEED BMC PCI ID %04x:%04x, IRQ=%u\n", pdev->vendor, pdev->device, pdev->irq); ++ ++ pci_bmc_dev = devm_kzalloc(&pdev->dev, sizeof(*pci_bmc_dev), GFP_KERNEL); ++ if (!pci_bmc_dev) ++ return -ENOMEM; ++ ++ /* Get platform id */ ++ pci_bmc_dev->driver_data = ent->driver_data; ++ pci_bmc_dev->platform = &aspeed_pcie_host[ent->driver_data]; ++ ++ pci_bmc_dev->id = ida_simple_get(&bmc_device_ida, 0, 0, GFP_KERNEL); ++ if (pci_bmc_dev->id < 0) ++ return pci_bmc_dev->id; ++ ++ rc = pci_enable_device(pdev); ++ if (rc) { ++ dev_err(&pdev->dev, "pci_enable_device() returned error %d\n", rc); ++ return rc; ++ } ++ ++ pci_set_master(pdev); ++ pci_set_drvdata(pdev, pci_bmc_dev); ++ ++ /* Prepare IRQ resource */ ++ aspeed_pci_setup_irq_resource(pdev); ++ ++ /* Setup BMC PCI device */ ++ rc = pci_bmc_dev->platform->setup(pdev); ++ if (rc) { ++ dev_err(&pdev->dev, "ASPEED PCIe Host device returned error %d\n", rc); ++ pci_free_irq_vectors(pdev); ++ pci_disable_device(pdev); ++ return rc; ++ } ++ ++#if IS_ENABLED(CONFIG_ASPEED_MMBI) ++ pci_bmc_dev->mmbi.dev = &pdev->dev; ++ rc = aspeed_pci_host_mmbi_setup(pdev); ++ if (rc) { ++ dev_err(&pdev->dev, "Failed to setup MMBI instances\n"); ++ return rc; ++ } ++#endif ++ ++ return 0; ++} ++ ++static void aspeed_pci_host_bmc_device_remove(struct pci_dev *pdev) ++{ ++ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev); ++#if IS_ENABLED(CONFIG_ASPEED_MMBI) ++ struct aspeed_host_pcie_mmbi *mmbi = &pci_bmc_dev->mmbi; ++ struct aspeed_host_pcie_mmbi_ins *ins; ++ int i; ++ ++ for (i = 2; i < PCI_STD_NUM_BARS; i++) { ++ ins = &mmbi->instances[i]; ++ if (ins->valid) { ++ mmbi_instance_remove(&ins->mmbi_desc); ++ pci_iounmap(pdev, ins->mmbi_desc.desc_virt); ++ } ++ } ++#endif ++ aspeed_pci_host_bmc_device_release_queue(pdev); ++ aspeed_pci_host_bmc_device_release_memory_mapping(pdev); ++ aspeed_pci_host_bmc_device_release_vuart(pdev); ++ ++ devm_free_irq(&pdev->dev, ++ pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[BMC_MSI]), ++ pci_bmc_dev); ++ devm_free_irq(&pdev->dev, ++ pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[MBX_MSI]), ++ pci_bmc_dev); ++ ++ ida_simple_remove(&bmc_device_ida, pci_bmc_dev->id); ++ ++ pci_iounmap(pdev, pci_bmc_dev->msg_bar_reg); ++ pci_iounmap(pdev, pci_bmc_dev->mem_bar_reg); ++ ++ pci_free_irq_vectors(pdev); ++ pci_release_regions(pdev); ++ pci_disable_device(pdev); ++} ++ ++/** ++ * This table holds the list of (VendorID,DeviceID) supported by this driver ++ * ++ */ ++static struct pci_device_id aspeed_host_bmc_dev_pci_ids[] = { ++ { PCI_DEVICE(0x1A03, 0x2402), .class = 0xFF0000, .class_mask = 0xFFFF00 }, ++ { PCI_DEVICE(0x1A03, 0x2402), .class = 0x0C0C00, .class_mask = 0xFFFF00 }, ++ { ++ 0, ++ } ++}; ++ ++MODULE_DEVICE_TABLE(pci, aspeed_host_bmc_dev_pci_ids); ++ ++static struct pci_driver aspeed_host_bmc_dev_driver = { ++ .name = DRIVER_NAME, ++ .id_table = aspeed_host_bmc_dev_pci_ids, ++ .probe = aspeed_pci_host_bmc_device_probe, ++ .remove = aspeed_pci_host_bmc_device_remove, ++}; ++ ++static int __init aspeed_host_bmc_device_init(void) ++{ ++ return pci_register_driver(&aspeed_host_bmc_dev_driver); ++} ++ ++static void aspeed_host_bmc_device_exit(void) ++{ ++ /* unregister pci driver */ ++ pci_unregister_driver(&aspeed_host_bmc_dev_driver); ++} ++ ++late_initcall(aspeed_host_bmc_device_init); ++module_exit(aspeed_host_bmc_device_exit); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_DESCRIPTION("ASPEED Host BMC DEVICE Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/aspeed-lpc-mbox.c b/drivers/soc/aspeed/aspeed-lpc-mbox.c +--- a/drivers/soc/aspeed/aspeed-lpc-mbox.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-lpc-mbox.c 2026-09-09 15:51:44.718238072 +0000 +@@ -0,0 +1,406 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright 2017 IBM Corporation ++ * Copyright 2021 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVICE_NAME "aspeed-mbox" ++ ++#define ASPEED_MBOX_DR(dr, n) (dr + (n * 4)) ++#define ASPEED_MBOX_STR(str, n) (str + (n / 8) * 4) ++#define ASPEED_MBOX_BIE(bie, n) (bie + (n / 8) * 4) ++#define ASPEED_MBOX_HIE(hie, n) (hie + (n / 8) * 4) ++ ++#define ASPEED_MBOX_BCR_RECV BIT(7) ++#define ASPEED_MBOX_BCR_MASK BIT(1) ++#define ASPEED_MBOX_BCR_SEND BIT(0) ++ ++/* ioctl code */ ++#define ASPEED_MBOX_IOCTL 0xA3 ++#define ASPEED_MBOX_IOCTL_GET_SIZE \ ++ _IOR(ASPEED_MBOX_IOCTL, 0, struct aspeed_mbox_ioctl_data) ++ ++struct aspeed_mbox_ioctl_data { ++ unsigned int data; ++}; ++ ++struct aspeed_mbox_model { ++ unsigned int dr_num; ++ ++ /* offsets to the MBOX registers */ ++ unsigned int dr; ++ unsigned int str; ++ unsigned int bcr; ++ unsigned int hcr; ++ unsigned int bie; ++ unsigned int hie; ++}; ++ ++struct aspeed_mbox { ++ struct miscdevice miscdev; ++ struct regmap *map; ++ unsigned int base; ++ wait_queue_head_t queue; ++ struct mutex mutex; ++ const struct aspeed_mbox_model *model; ++}; ++ ++static atomic_t aspeed_mbox_open_count = ATOMIC_INIT(0); ++ ++static u8 aspeed_mbox_inb(struct aspeed_mbox *mbox, int reg) ++{ ++ /* ++ * The mbox registers are actually only one byte but are addressed ++ * four bytes apart. The other three bytes are marked 'reserved', ++ * they *should* be zero but lets not rely on it. ++ * I am going to rely on the fact we can casually read/write to them... ++ */ ++ unsigned int val = 0xff; /* If regmap throws an error return 0xff */ ++ int rc = regmap_read(mbox->map, mbox->base + reg, &val); ++ ++ if (rc) ++ dev_err(mbox->miscdev.parent, "regmap_read() failed with " ++ "%d (reg: 0x%08x)\n", rc, reg); ++ ++ return val & 0xff; ++} ++ ++static void aspeed_mbox_outb(struct aspeed_mbox *mbox, u8 data, int reg) ++{ ++ int rc = regmap_write(mbox->map, mbox->base + reg, data); ++ ++ if (rc) ++ dev_err(mbox->miscdev.parent, "regmap_write() failed with " ++ "%d (data: %u reg: 0x%08x)\n", rc, data, reg); ++} ++ ++static struct aspeed_mbox *file_mbox(struct file *file) ++{ ++ return container_of(file->private_data, struct aspeed_mbox, miscdev); ++} ++ ++static int aspeed_mbox_open(struct inode *inode, struct file *file) ++{ ++ struct aspeed_mbox *mbox = file_mbox(file); ++ const struct aspeed_mbox_model *model = mbox->model; ++ ++ if (atomic_inc_return(&aspeed_mbox_open_count) == 1) { ++ /* ++ * Clear the interrupt status bit if it was left on and unmask ++ * interrupts. ++ * ASPEED_MBOX_BCR_RECV bit is W1C, this also unmasks in 1 step ++ */ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_BCR_RECV, model->bcr); ++ return 0; ++ } ++ ++ atomic_dec(&aspeed_mbox_open_count); ++ return -EBUSY; ++} ++ ++static ssize_t aspeed_mbox_read(struct file *file, char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct aspeed_mbox *mbox = file_mbox(file); ++ const struct aspeed_mbox_model *model = mbox->model; ++ char __user *p = buf; ++ ssize_t ret; ++ int i; ++ ++ if (!access_ok(buf, count)) ++ return -EFAULT; ++ ++ if (count + *ppos > model->dr_num) ++ return -EINVAL; ++ ++ if (file->f_flags & O_NONBLOCK) { ++ if (!(aspeed_mbox_inb(mbox, model->bcr) & ++ ASPEED_MBOX_BCR_RECV)) ++ return -EAGAIN; ++ } else if (wait_event_interruptible(mbox->queue, ++ aspeed_mbox_inb(mbox, model->bcr) & ++ ASPEED_MBOX_BCR_RECV)) { ++ return -ERESTARTSYS; ++ } ++ ++ mutex_lock(&mbox->mutex); ++ ++ for (i = *ppos; count > 0 && i < model->dr_num; i++) { ++ uint8_t reg = aspeed_mbox_inb(mbox, ASPEED_MBOX_DR(model->dr, i)); ++ ++ ret = __put_user(reg, p); ++ if (ret) ++ goto out_unlock; ++ ++ p++; ++ count--; ++ } ++ ++ /* ASPEED_MBOX_BCR_RECV bit is write to clear, this also unmasks in 1 step */ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_BCR_RECV, model->bcr); ++ ret = p - buf; ++ ++out_unlock: ++ mutex_unlock(&mbox->mutex); ++ return ret; ++} ++ ++static ssize_t aspeed_mbox_write(struct file *file, const char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct aspeed_mbox *mbox = file_mbox(file); ++ const struct aspeed_mbox_model *model = mbox->model; ++ const char __user *p = buf; ++ ssize_t ret; ++ char c; ++ int i; ++ ++ if (!access_ok(buf, count)) ++ return -EFAULT; ++ ++ if (count + *ppos > model->dr_num) ++ return -EINVAL; ++ ++ mutex_lock(&mbox->mutex); ++ ++ for (i = *ppos; count > 0 && i < model->dr_num; i++) { ++ ret = __get_user(c, p); ++ if (ret) ++ goto out_unlock; ++ ++ aspeed_mbox_outb(mbox, c, ASPEED_MBOX_DR(model->dr, i)); ++ p++; ++ count--; ++ } ++ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_BCR_SEND, model->bcr); ++ ret = p - buf; ++ ++out_unlock: ++ mutex_unlock(&mbox->mutex); ++ return ret; ++} ++ ++static __poll_t aspeed_mbox_poll(struct file *file, poll_table *wait) ++{ ++ struct aspeed_mbox *mbox = file_mbox(file); ++ const struct aspeed_mbox_model *model = mbox->model; ++ __poll_t mask = 0; ++ ++ poll_wait(file, &mbox->queue, wait); ++ ++ if (aspeed_mbox_inb(mbox, model->bcr) & ASPEED_MBOX_BCR_RECV) ++ mask |= POLLIN; ++ ++ return mask; ++} ++ ++static int aspeed_mbox_release(struct inode *inode, struct file *file) ++{ ++ atomic_dec(&aspeed_mbox_open_count); ++ return 0; ++} ++ ++static long aspeed_mbox_ioctl(struct file *file, unsigned int cmd, ++ unsigned long param) ++{ ++ long ret = 0; ++ struct aspeed_mbox *mbox = file_mbox(file); ++ const struct aspeed_mbox_model *model = mbox->model; ++ struct aspeed_mbox_ioctl_data data; ++ ++ switch (cmd) { ++ case ASPEED_MBOX_IOCTL_GET_SIZE: ++ data.data = model->dr_num; ++ if (copy_to_user((void __user *)param, &data, sizeof(data))) ++ ret = -EFAULT; ++ break; ++ default: ++ ret = -ENOTTY; ++ break; ++ } ++ ++ return ret; ++} ++ ++static const struct file_operations aspeed_mbox_fops = { ++ .owner = THIS_MODULE, ++ .llseek = no_seek_end_llseek, ++ .read = aspeed_mbox_read, ++ .write = aspeed_mbox_write, ++ .open = aspeed_mbox_open, ++ .release = aspeed_mbox_release, ++ .poll = aspeed_mbox_poll, ++ .unlocked_ioctl = aspeed_mbox_ioctl, ++}; ++ ++static irqreturn_t aspeed_mbox_irq(int irq, void *arg) ++{ ++ struct aspeed_mbox *mbox = arg; ++ const struct aspeed_mbox_model *model = mbox->model; ++ ++ if (!(aspeed_mbox_inb(mbox, model->bcr) & ASPEED_MBOX_BCR_RECV)) ++ return IRQ_NONE; ++ ++ /* ++ * Leave the status bit set so that we know the data is for us, ++ * clear it once it has been read. ++ */ ++ ++ /* Mask it off, we'll clear it when we the data gets read */ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_BCR_MASK, model->bcr); ++ ++ wake_up(&mbox->queue); ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_mbox_config_irq(struct aspeed_mbox *mbox, ++ struct platform_device *pdev) ++{ ++ const struct aspeed_mbox_model *model = mbox->model; ++ struct device *dev = &pdev->dev; ++ int i, rc, irq; ++ ++ irq = irq_of_parse_and_map(dev->of_node, 0); ++ if (!irq) ++ return -ENODEV; ++ ++ rc = devm_request_irq(dev, irq, aspeed_mbox_irq, ++ IRQF_SHARED, DEVICE_NAME, mbox); ++ if (rc < 0) { ++ dev_err(dev, "Unable to request IRQ %d\n", irq); ++ return rc; ++ } ++ ++ /* ++ * Disable all register based interrupts. ++ */ ++ for (i = 0; i < model->dr_num / 8; ++i) ++ aspeed_mbox_outb(mbox, 0x00, ASPEED_MBOX_BIE(model->bie, i)); ++ ++ /* These registers are write one to clear. Clear them. */ ++ for (i = 0; i < model->dr_num / 8; ++i) ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STR(model->str, i)); ++ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_BCR_RECV, model->bcr); ++ return 0; ++} ++ ++static int aspeed_mbox_probe(struct platform_device *pdev) ++{ ++ struct aspeed_mbox *mbox; ++ struct device *dev; ++ int rc; ++ ++ dev = &pdev->dev; ++ ++ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); ++ if (!mbox) ++ return -ENOMEM; ++ ++ dev_set_drvdata(&pdev->dev, mbox); ++ ++ rc = of_property_read_u32(dev->of_node, "reg", &mbox->base); ++ if (rc) { ++ dev_err(dev, "Couldn't read reg device tree property\n"); ++ return rc; ++ } ++ ++ mbox->model = of_device_get_match_data(dev); ++ if (IS_ERR(mbox->model)) { ++ dev_err(dev, "Couldn't get model data\n"); ++ return -ENODEV; ++ } ++ ++ mbox->map = syscon_node_to_regmap( ++ pdev->dev.parent->of_node); ++ if (IS_ERR(mbox->map)) { ++ dev_err(dev, "Couldn't get regmap\n"); ++ return -ENODEV; ++ } ++ ++ mutex_init(&mbox->mutex); ++ init_waitqueue_head(&mbox->queue); ++ ++ mbox->miscdev.minor = MISC_DYNAMIC_MINOR; ++ mbox->miscdev.name = DEVICE_NAME; ++ mbox->miscdev.fops = &aspeed_mbox_fops; ++ mbox->miscdev.parent = dev; ++ rc = misc_register(&mbox->miscdev); ++ if (rc) { ++ dev_err(dev, "Unable to register device\n"); ++ return rc; ++ } ++ ++ rc = aspeed_mbox_config_irq(mbox, pdev); ++ if (rc) { ++ dev_err(dev, "Failed to configure IRQ\n"); ++ misc_deregister(&mbox->miscdev); ++ return rc; ++ } ++ ++ return 0; ++} ++ ++static void aspeed_mbox_remove(struct platform_device *pdev) ++{ ++ struct aspeed_mbox *mbox = dev_get_drvdata(&pdev->dev); ++ ++ misc_deregister(&mbox->miscdev); ++} ++ ++static const struct aspeed_mbox_model ast2400_model = { ++ .dr_num = 16, ++ .dr = 0x0, ++ .str = 0x40, ++ .bcr = 0x48, ++ .hcr = 0x4c, ++ .bie = 0x50, ++ .hie = 0x58, ++}; ++ ++static const struct aspeed_mbox_model ast2600_model = { ++ .dr_num = 32, ++ .dr = 0x0, ++ .str = 0x80, ++ .bcr = 0x90, ++ .hcr = 0x94, ++ .bie = 0xa0, ++ .hie = 0xb0, ++}; ++ ++static const struct of_device_id aspeed_mbox_match[] = { ++ { .compatible = "aspeed,ast2400-mbox", ++ .data = &ast2400_model }, ++ { .compatible = "aspeed,ast2500-mbox", ++ .data = &ast2400_model }, ++ { .compatible = "aspeed,ast2600-mbox", ++ .data = &ast2600_model }, ++ { }, ++}; ++ ++static struct platform_driver aspeed_mbox_driver = { ++ .driver = { ++ .name = DEVICE_NAME, ++ .of_match_table = aspeed_mbox_match, ++ }, ++ .probe = aspeed_mbox_probe, ++ .remove = aspeed_mbox_remove, ++}; ++ ++module_platform_driver(aspeed_mbox_driver); ++MODULE_DEVICE_TABLE(of, aspeed_mbox_match); ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Cyril Bur "); ++MODULE_AUTHOR("Chia-Wei Wang ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVICE_NAME "aspeed-lpc-pcc" ++ ++static DEFINE_IDA(aspeed_pcc_ida); ++ ++#define HICR5 0x80 ++#define HICR5_EN_SNP0W BIT(0) ++#define HICR5_EN_SNP1W BIT(2) ++#define HICR6 0x084 ++#define HICR6_EN2BMODE BIT(19) ++#define SNPWADR 0x090 ++#define PCCR6 0x0c4 ++#define PCCR6_DMA_CUR_ADDR GENMASK(27, 0) ++#define PCCR4 0x0d0 ++#define PCCR4_DMA_ADDRL_MASK GENMASK(31, 0) ++#define PCCR4_DMA_ADDRL_SHIFT 0 ++#define PCCR5 0x0d4 ++#define PCCR5_DMA_ADDRH_MASK GENMASK(27, 24) ++#define PCCR5_DMA_ADDRH_SHIFT 24 ++#define PCCR5_DMA_LEN_MASK GENMASK(23, 0) ++#define PCCR5_DMA_LEN_SHIFT 0 ++#define HICRB 0x100 ++#define HICRB_ENSNP0D BIT(14) ++#define HICRB_ENSNP1D BIT(15) ++#define PCCR0 0x130 ++#define PCCR0_EN_DMA_INT BIT(31) ++#define PCCR0_EN_DMA_MODE BIT(14) ++#define PCCR0_ADDR_SEL_MASK GENMASK(13, 12) ++#define PCCR0_ADDR_SEL_SHIFT 12 ++#define PCCR0_RX_TRIG_LVL_MASK GENMASK(10, 8) ++#define PCCR0_RX_TRIG_LVL_SHIFT 8 ++#define PCCR0_CLR_RX_FIFO BIT(7) ++#define PCCR0_MODE_SEL_MASK GENMASK(5, 4) ++#define PCCR0_MODE_SEL_SHIFT 4 ++#define PCCR0_EN_RX_TMOUT_INT BIT(2) ++#define PCCR0_EN_RX_AVAIL_INT BIT(1) ++#define PCCR0_EN BIT(0) ++#define PCCR1 0x134 ++#define PCCR1_BASE_ADDR_MASK GENMASK(15, 0) ++#define PCCR1_BASE_ADDR_SHIFT 0 ++#define PCCR1_DONT_CARE_BITS_MASK GENMASK(21, 16) ++#define PCCR1_DONT_CARE_BITS_SHIFT 16 ++#define PCCR2 0x138 ++#define PCCR2_INT_STATUS_PATTERN_B BIT(16) ++#define PCCR2_INT_STATUS_PATTERN_A BIT(8) ++#define PCCR2_INT_STATUS_DMA_DONE BIT(4) ++#define PCCR2_INT_STATUS_DATA_RDY PCCR2_INT_STATUS_DMA_DONE ++#define PCCR2_INT_STATUS_RX_OVER BIT(3) ++#define PCCR2_INT_STATUS_RX_TMOUT BIT(2) ++#define PCCR2_INT_STATUS_RX_AVAIL BIT(1) ++#define PCCR3 0x13c ++#define PCCR3_FIFO_DATA_MASK GENMASK(7, 0) ++ ++#define PCC_DMA_BUFSZ (256 * SZ_1K) ++ ++/* Except for the 1-byte threshold, the rest represent fractions of the FIFO. ++ * Ex. PCC_FIFO_THR_1_EIGHTH means 1/8th of the FIFO size. ++ */ ++enum pcc_fifo_threshold { ++ PCC_FIFO_THR_1_BYTE, ++ PCC_FIFO_THR_1_EIGHTH, ++ PCC_FIFO_THR_2_EIGHTH, ++ PCC_FIFO_THR_3_EIGHTH, ++ PCC_FIFO_THR_4_EIGHTH, ++ PCC_FIFO_THR_5_EIGHTH, ++ PCC_FIFO_THR_6_EIGHTH, ++ PCC_FIFO_THR_7_EIGHTH, ++}; ++ ++enum pcc_record_mode { ++ PCC_REC_1B, ++ PCC_REC_2B, ++ PCC_REC_4B, ++ PCC_REC_FULL, ++}; ++ ++enum pcc_port_hbits_select { ++ PCC_PORT_HBITS_SEL_NONE, ++ PCC_PORT_HBITS_SEL_45, ++ PCC_PORT_HBITS_SEL_67, ++ PCC_PORT_HBITS_SEL_89, ++}; ++ ++struct aspeed_pcc_dma { ++ uint32_t rptr; ++ uint8_t *virt; ++ dma_addr_t addr; ++ uint32_t size; ++}; ++ ++struct aspeed_pcc_ctrl { ++ struct device *dev; ++ struct regmap *regmap; ++ int irq; ++ uint32_t port; ++ struct aspeed_pcc_dma dma; ++ struct kfifo fifo; ++ wait_queue_head_t wq; ++ struct miscdevice mdev; ++ int mdev_id; ++ spinlock_t lock; /* protects access to the FIFO and DMA pointer */ ++}; ++ ++static inline bool is_valid_rec_mode(uint32_t mode) ++{ ++ return (mode > PCC_REC_FULL) ? false : true; ++} ++ ++static inline bool is_valid_high_bits_select(uint32_t sel) ++{ ++ return (sel > PCC_PORT_HBITS_SEL_89) ? false : true; ++} ++ ++static ssize_t aspeed_pcc_file_read(struct file *file, char __user *buffer, ++ size_t count, loff_t *ppos) ++{ ++ int rc; ++ unsigned int copied; ++ struct aspeed_pcc_ctrl *pcc = container_of(file->private_data, ++ struct aspeed_pcc_ctrl, ++ mdev); ++ ++ if (kfifo_is_empty(&pcc->fifo)) { ++ if (file->f_flags & O_NONBLOCK) ++ return -EAGAIN; ++ ++ rc = wait_event_interruptible(pcc->wq, ++ !kfifo_is_empty(&pcc->fifo)); ++ if (rc == -ERESTARTSYS) ++ return -EINTR; ++ } ++ ++ rc = kfifo_to_user(&pcc->fifo, buffer, count, &copied); ++ ++ return rc ? rc : copied; ++} ++ ++static __poll_t aspeed_pcc_file_poll(struct file *file, ++ struct poll_table_struct *pt) ++{ ++ struct aspeed_pcc_ctrl *pcc = container_of(file->private_data, ++ struct aspeed_pcc_ctrl, ++ mdev); ++ ++ poll_wait(file, &pcc->wq, pt); ++ ++ return !kfifo_is_empty(&pcc->fifo) ? POLLIN : 0; ++} ++ ++static const struct file_operations pcc_fops = { ++ .owner = THIS_MODULE, ++ .read = aspeed_pcc_file_read, ++ .poll = aspeed_pcc_file_poll, ++}; ++ ++static irqreturn_t aspeed_pcc_dma_isr(int irq, void *arg) ++{ ++ uint32_t reg, rptr, wptr; ++ struct aspeed_pcc_ctrl *pcc = (struct aspeed_pcc_ctrl *)arg; ++ struct kfifo *fifo = &pcc->fifo; ++ ++ spin_lock(&pcc->lock); ++ regmap_write_bits(pcc->regmap, PCCR2, PCCR2_INT_STATUS_DMA_DONE, PCCR2_INT_STATUS_DMA_DONE); ++ ++ regmap_read(pcc->regmap, PCCR6, ®); ++ wptr = (reg & PCCR6_DMA_CUR_ADDR) - (pcc->dma.addr & PCCR6_DMA_CUR_ADDR); ++ rptr = pcc->dma.rptr; ++ ++ /* If kfifo is empty or has enough space, insert new data; ++ * otherwise, discard the new data. ++ */ ++ if (rptr <= wptr) { ++ kfifo_in(fifo, pcc->dma.virt + rptr, wptr - rptr); ++ } else { ++ /* Handle wrap-around case */ ++ kfifo_in(fifo, pcc->dma.virt + rptr, pcc->dma.size - rptr); ++ kfifo_in(fifo, pcc->dma.virt, wptr); ++ } ++ ++ pcc->dma.rptr = wptr; ++ spin_unlock(&pcc->lock); ++ ++ wake_up_interruptible(&pcc->wq); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t aspeed_pcc_isr(int irq, void *arg) ++{ ++ uint32_t sts; ++ struct aspeed_pcc_ctrl *pcc = (struct aspeed_pcc_ctrl *)arg; ++ ++ regmap_read(pcc->regmap, PCCR2, &sts); ++ ++ if (!(sts & (PCCR2_INT_STATUS_RX_TMOUT | ++ PCCR2_INT_STATUS_RX_AVAIL | ++ PCCR2_INT_STATUS_DMA_DONE))) ++ return IRQ_NONE; ++ ++ return aspeed_pcc_dma_isr(irq, arg); ++} ++ ++/* ++ * A2600-15 AP note ++ * ++ * SW workaround to prevent generating Non-Fatal-Error (NFE) ++ * eSPI response when PCC is used for port I/O byte snooping ++ * over eSPI. ++ */ ++static int aspeed_a2600_15(struct aspeed_pcc_ctrl *pcc, struct device *dev) ++{ ++ u32 hicr5_en, hicrb_en; ++ ++ /* abort if snoop is enabled */ ++ regmap_read(pcc->regmap, HICR5, &hicr5_en); ++ if (hicr5_en & (HICR5_EN_SNP0W | HICR5_EN_SNP1W)) { ++ dev_err(dev, "A2600-15 should be applied with snoop disabled\n"); ++ return -EPERM; ++ } ++ ++ /* set SNPWADR of snoop device */ ++ regmap_write(pcc->regmap, SNPWADR, pcc->port | ((pcc->port + 2) << 16)); ++ ++ /* set HICRB[15:14]=11b to enable ACCEPT response for SNPWADR */ ++ hicrb_en = HICRB_ENSNP0D | HICRB_ENSNP1D; ++ regmap_update_bits(pcc->regmap, HICRB, hicrb_en, hicrb_en); ++ ++ /* set HICR6[19] to extend SNPWADR to 2x range */ ++ regmap_update_bits(pcc->regmap, HICR6, HICR6_EN2BMODE, HICR6_EN2BMODE); ++ ++ return 0; ++} ++ ++static int aspeed_pcc_enable(struct aspeed_pcc_ctrl *pcc, struct device *dev) ++{ ++ int rc; ++ ++ rc = aspeed_a2600_15(pcc, dev); ++ if (rc) ++ return rc; ++ ++ /* record mode: Set 2-Byte mode. */ ++ regmap_update_bits(pcc->regmap, PCCR0, ++ PCCR0_MODE_SEL_MASK, ++ PCC_REC_2B << PCCR0_MODE_SEL_SHIFT); ++ ++ /* port address */ ++ regmap_update_bits(pcc->regmap, PCCR1, ++ PCCR1_BASE_ADDR_MASK, ++ pcc->port << PCCR1_BASE_ADDR_SHIFT); ++ ++ /* Set address high bits selection to 0b01 for address bit[5:4] */ ++ regmap_update_bits(pcc->regmap, PCCR0, ++ PCCR0_ADDR_SEL_MASK, ++ PCC_PORT_HBITS_SEL_45 << PCCR0_ADDR_SEL_SHIFT); ++ ++ /* Set LPC don't care address to 0x3 for port 80~83h */ ++ regmap_update_bits(pcc->regmap, PCCR1, ++ PCCR1_DONT_CARE_BITS_MASK, ++ 0x3 << PCCR1_DONT_CARE_BITS_SHIFT); ++ ++ /* set DMA ring buffer size and enable interrupts */ ++ regmap_write(pcc->regmap, PCCR4, pcc->dma.addr & 0xffffffff); ++#ifdef CONFIG_ARM64 ++ regmap_update_bits(pcc->regmap, PCCR5, PCCR5_DMA_ADDRH_MASK, ++ (pcc->dma.addr >> 32) << PCCR5_DMA_ADDRH_SHIFT); ++#endif ++ regmap_update_bits(pcc->regmap, PCCR5, PCCR5_DMA_LEN_MASK, ++ (pcc->dma.size / 4) << PCCR5_DMA_LEN_SHIFT); ++ regmap_update_bits(pcc->regmap, PCCR0, ++ PCCR0_EN_DMA_INT | PCCR0_EN_DMA_MODE, ++ PCCR0_EN_DMA_INT | PCCR0_EN_DMA_MODE); ++ ++ regmap_update_bits(pcc->regmap, PCCR0, ++ PCCR0_RX_TRIG_LVL_MASK, ++ PCC_FIFO_THR_2_EIGHTH << PCCR0_RX_TRIG_LVL_SHIFT); ++ ++ regmap_update_bits(pcc->regmap, PCCR0, PCCR0_EN, PCCR0_EN); ++ ++ return 0; ++} ++ ++static int aspeed_pcc_disable(struct aspeed_pcc_ctrl *pcc) ++{ ++ /* Disable PCC and DMA Mode for safety */ ++ regmap_update_bits(pcc->regmap, PCCR0, PCCR0_EN | PCCR0_EN_DMA_MODE, 0); ++ ++ /* Clear Rx FIFO. */ ++ regmap_update_bits(pcc->regmap, PCCR0, PCCR0_CLR_RX_FIFO, 1); ++ ++ /* Clear All interrupts status. */ ++ regmap_write(pcc->regmap, PCCR2, ++ PCCR2_INT_STATUS_RX_OVER | PCCR2_INT_STATUS_DMA_DONE | ++ PCCR2_INT_STATUS_PATTERN_A | PCCR2_INT_STATUS_PATTERN_B); ++ ++ return 0; ++} ++ ++static int aspeed_pcc_probe(struct platform_device *pdev) ++{ ++ int rc; ++ struct aspeed_pcc_ctrl *pcc; ++ struct device *dev = &pdev->dev; ++ uint32_t fifo_size = PAGE_SIZE; ++ ++ pcc = devm_kzalloc(dev, sizeof(*pcc), GFP_KERNEL); ++ if (!pcc) ++ return -ENOMEM; ++ ++ pcc->regmap = syscon_node_to_regmap(dev->parent->of_node); ++ if (IS_ERR(pcc->regmap)) ++ return dev_err_probe(dev, PTR_ERR(pcc->regmap), "Couldn't get regmap\n"); ++ ++ rc = of_property_read_u32(dev->of_node, "pcc-ports", &pcc->port); ++ if (rc) { ++ dev_err(dev, "no pcc ports configured\n"); ++ return rc; ++ } ++ ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_err(dev, "cannot set 64-bits DMA mask\n"); ++ return rc; ++ } ++ ++ pcc->dma.size = PCC_DMA_BUFSZ; ++ pcc->dma.virt = dmam_alloc_coherent(dev, ++ pcc->dma.size, ++ &pcc->dma.addr, ++ GFP_KERNEL); ++ if (!pcc->dma.virt) { ++ dev_err(dev, "cannot allocate DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ fifo_size = roundup(pcc->dma.size, PAGE_SIZE); ++ rc = kfifo_alloc(&pcc->fifo, fifo_size, GFP_KERNEL); ++ if (rc) ++ return rc; ++ ++ spin_lock_init(&pcc->lock); ++ ++ /* Disable PCC to clean up DMA buffer before request IRQ. */ ++ rc = aspeed_pcc_disable(pcc); ++ if (rc) { ++ dev_err(dev, "Couldn't disable PCC\n"); ++ goto err_free_kfifo; ++ } ++ ++ pcc->irq = platform_get_irq(pdev, 0); ++ if (pcc->irq < 0) { ++ rc = pcc->irq; ++ goto err_free_kfifo; ++ } ++ ++ rc = devm_request_irq(dev, pcc->irq, aspeed_pcc_isr, 0, DEVICE_NAME, pcc); ++ if (rc < 0) { ++ dev_err(dev, "Couldn't request IRQ %d\n", pcc->irq); ++ goto err_free_kfifo; ++ } ++ ++ init_waitqueue_head(&pcc->wq); ++ ++ pcc->mdev_id = ida_alloc(&aspeed_pcc_ida, GFP_KERNEL); ++ if (pcc->mdev_id < 0) { ++ dev_err(dev, "Couldn't allocate ID\n"); ++ goto err_free_kfifo; ++ } ++ ++ pcc->mdev.parent = dev; ++ pcc->mdev.minor = MISC_DYNAMIC_MINOR; ++ pcc->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, ++ pcc->mdev_id); ++ pcc->mdev.fops = &pcc_fops; ++ rc = misc_register(&pcc->mdev); ++ if (rc) { ++ dev_err(dev, "Couldn't register misc device\n"); ++ goto err_free_ida; ++ } ++ ++ rc = aspeed_pcc_enable(pcc, dev); ++ if (rc) { ++ dev_err(dev, "Couldn't enable PCC\n"); ++ goto err_dereg_mdev; ++ } ++ ++ dev_set_drvdata(dev, pcc); ++ ++ return 0; ++ ++err_dereg_mdev: ++ misc_deregister(&pcc->mdev); ++ ++err_free_ida: ++ ida_free(&aspeed_pcc_ida, pcc->mdev_id); ++ ++err_free_kfifo: ++ kfifo_free(&pcc->fifo); ++ ++ return rc; ++} ++ ++static void aspeed_pcc_remove(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct aspeed_pcc_ctrl *pcc = dev_get_drvdata(dev); ++ ++ kfifo_free(&pcc->fifo); ++ ida_free(&aspeed_pcc_ida, pcc->mdev_id); ++ misc_deregister(&pcc->mdev); ++} ++ ++static const struct of_device_id aspeed_pcc_table[] = { ++ { .compatible = "aspeed,ast2600-lpc-pcc" }, ++ { }, ++}; ++ ++static struct platform_driver aspeed_pcc_driver = { ++ .driver = { ++ .name = "aspeed-pcc", ++ .of_match_table = aspeed_pcc_table, ++ }, ++ .probe = aspeed_pcc_probe, ++ .remove = aspeed_pcc_remove, ++}; ++ ++module_platform_driver(aspeed_pcc_driver); ++ ++MODULE_AUTHOR("Chia-Wei Wang "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Driver for Aspeed Post Code Capture"); +diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c +--- a/drivers/soc/aspeed/aspeed-lpc-snoop.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c 2026-09-09 15:51:44.718238072 +0000 +@@ -11,7 +11,7 @@ + */ + + #include +-#include ++#include + #include + #include + #include +@@ -25,7 +25,6 @@ + + #define DEVICE_NAME "aspeed-lpc-snoop" + +-#define NUM_SNOOP_CHANNELS 2 + #define SNOOP_FIFO_SIZE 2048 + + #define HICR5 0x80 +@@ -36,6 +35,7 @@ + #define HICR6 0x84 + #define HICR6_STR_SNP0W BIT(0) + #define HICR6_STR_SNP1W BIT(1) ++#define HICR6_NFE_WA BIT(20) + #define SNPWADR 0x90 + #define SNPWADR_CH0_MASK GENMASK(15, 0) + #define SNPWADR_CH0_SHIFT 0 +@@ -57,7 +57,22 @@ + unsigned int has_hicrb_ensnp; + }; + ++enum aspeed_lpc_snoop_index { ++ ASPEED_LPC_SNOOP_INDEX_0 = 0, ++ ASPEED_LPC_SNOOP_INDEX_1 = 1, ++ ASPEED_LPC_SNOOP_INDEX_MAX = ASPEED_LPC_SNOOP_INDEX_1, ++}; ++ ++struct aspeed_lpc_snoop_channel_cfg { ++ enum aspeed_lpc_snoop_index index; ++ u32 hicr5_en; ++ u32 snpwadr_mask; ++ u32 snpwadr_shift; ++ u32 hicrb_en; ++}; ++ + struct aspeed_lpc_snoop_channel { ++ const struct aspeed_lpc_snoop_channel_cfg *cfg; + bool enabled; + struct kfifo fifo; + wait_queue_head_t wq; +@@ -67,10 +82,28 @@ + struct aspeed_lpc_snoop { + struct regmap *regmap; + int irq; +- struct clk *clk; +- struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; ++ struct aspeed_lpc_snoop_channel chan[ASPEED_LPC_SNOOP_INDEX_MAX + 1]; + }; + ++static const struct aspeed_lpc_snoop_channel_cfg channel_cfgs[ASPEED_LPC_SNOOP_INDEX_MAX + 1] = { ++ { ++ .index = ASPEED_LPC_SNOOP_INDEX_0, ++ .hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, ++ .snpwadr_mask = SNPWADR_CH0_MASK, ++ .snpwadr_shift = SNPWADR_CH0_SHIFT, ++ .hicrb_en = HICRB_ENSNP0D, ++ }, ++ { ++ .index = ASPEED_LPC_SNOOP_INDEX_1, ++ .hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, ++ .snpwadr_mask = SNPWADR_CH1_MASK, ++ .snpwadr_shift = SNPWADR_CH1_SHIFT, ++ .hicrb_en = HICRB_ENSNP1D, ++ }, ++}; ++ ++static DEFINE_IDA(aspeed_lpc_snoop_ida); ++ + static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file) + { + return container_of(file->private_data, +@@ -136,12 +169,19 @@ + return IRQ_NONE; + + /* Check if one of the snoop channels is interrupting */ +- reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W); +- if (!reg) ++ if (!(reg & (HICR6_STR_SNP0W | HICR6_STR_SNP1W))) + return IRQ_NONE; + +- /* Ack pending IRQs */ +- regmap_write(lpc_snoop->regmap, HICR6, reg); ++ /* Check if NFE WA is set */ ++ if (reg & HICR6_NFE_WA) { ++ /* Ack pending IRQs with keeping NFE WA */ ++ regmap_write(lpc_snoop->regmap, HICR6, ++ (HICR6_STR_SNP0W | HICR6_STR_SNP1W | HICR6_NFE_WA)); ++ } else { ++ /* Ack pending IRQs */ ++ regmap_write(lpc_snoop->regmap, HICR6, ++ (HICR6_STR_SNP0W | HICR6_STR_SNP1W)); ++ } + + /* Read and save most recent snoop'ed data byte to FIFO */ + regmap_read(lpc_snoop->regmap, SNPWDR, &data); +@@ -182,108 +222,92 @@ + return 0; + } + +-static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, +- struct device *dev, +- int channel, u16 lpc_port) ++__attribute__((nonnull)) ++static int aspeed_lpc_enable_snoop(struct device *dev, ++ struct aspeed_lpc_snoop *lpc_snoop, ++ struct aspeed_lpc_snoop_channel *channel, ++ const struct aspeed_lpc_snoop_channel_cfg *cfg, ++ u16 lpc_port) + { +- int rc = 0; +- u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en; +- const struct aspeed_lpc_snoop_model_data *model_data = +- of_device_get_match_data(dev); ++ const struct aspeed_lpc_snoop_model_data *model_data; ++ int rc = 0, id; + +- if (WARN_ON(lpc_snoop->chan[channel].enabled)) ++ if (WARN_ON(channel->enabled)) + return -EBUSY; + +- init_waitqueue_head(&lpc_snoop->chan[channel].wq); +- /* Create FIFO datastructure */ +- rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, +- SNOOP_FIFO_SIZE, GFP_KERNEL); ++ init_waitqueue_head(&channel->wq); ++ ++ channel->cfg = cfg; ++ channel->miscdev.minor = MISC_DYNAMIC_MINOR; ++ channel->miscdev.fops = &snoop_fops; ++ channel->miscdev.parent = dev; ++ ++ id = ida_alloc(&aspeed_lpc_snoop_ida, GFP_KERNEL); ++ if (id < 0) ++ return id; ++ ++ channel->miscdev.name = ++ devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, id); ++ if (!channel->miscdev.name) ++ return -ENOMEM; ++ ++ rc = kfifo_alloc(&channel->fifo, SNOOP_FIFO_SIZE, GFP_KERNEL); + if (rc) + return rc; + +- lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; +- lpc_snoop->chan[channel].miscdev.name = +- devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); +- if (!lpc_snoop->chan[channel].miscdev.name) { +- rc = -ENOMEM; +- goto err_free_fifo; +- } +- lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; +- lpc_snoop->chan[channel].miscdev.parent = dev; +- rc = misc_register(&lpc_snoop->chan[channel].miscdev); ++ rc = misc_register(&channel->miscdev); + if (rc) + goto err_free_fifo; + + /* Enable LPC snoop channel at requested port */ +- switch (channel) { +- case 0: +- hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W; +- snpwadr_mask = SNPWADR_CH0_MASK; +- snpwadr_shift = SNPWADR_CH0_SHIFT; +- hicrb_en = HICRB_ENSNP0D; +- break; +- case 1: +- hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W; +- snpwadr_mask = SNPWADR_CH1_MASK; +- snpwadr_shift = SNPWADR_CH1_SHIFT; +- hicrb_en = HICRB_ENSNP1D; +- break; +- default: +- rc = -EINVAL; +- goto err_misc_deregister; +- } +- +- regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); +- regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask, +- lpc_port << snpwadr_shift); +- if (model_data->has_hicrb_ensnp) +- regmap_update_bits(lpc_snoop->regmap, HICRB, +- hicrb_en, hicrb_en); ++ regmap_set_bits(lpc_snoop->regmap, HICR5, cfg->hicr5_en); ++ regmap_update_bits(lpc_snoop->regmap, SNPWADR, cfg->snpwadr_mask, ++ lpc_port << cfg->snpwadr_shift); ++ ++ model_data = of_device_get_match_data(dev); ++ if (model_data && model_data->has_hicrb_ensnp) ++ regmap_set_bits(lpc_snoop->regmap, HICRB, cfg->hicrb_en); + +- lpc_snoop->chan[channel].enabled = true; ++ channel->enabled = true; + + return 0; + +-err_misc_deregister: +- misc_deregister(&lpc_snoop->chan[channel].miscdev); + err_free_fifo: +- kfifo_free(&lpc_snoop->chan[channel].fifo); ++ kfifo_free(&channel->fifo); + return rc; + } + ++__attribute__((nonnull)) + static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, +- int channel) ++ struct aspeed_lpc_snoop_channel *channel) + { +- if (!lpc_snoop->chan[channel].enabled) ++ if (!channel->enabled) + return; + +- switch (channel) { +- case 0: +- regmap_update_bits(lpc_snoop->regmap, HICR5, +- HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, +- 0); +- break; +- case 1: +- regmap_update_bits(lpc_snoop->regmap, HICR5, +- HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, +- 0); +- break; +- default: +- return; +- } ++ /* Disable interrupts along with the device */ ++ regmap_clear_bits(lpc_snoop->regmap, HICR5, channel->cfg->hicr5_en); + +- lpc_snoop->chan[channel].enabled = false; ++ channel->enabled = false; + /* Consider improving safety wrt concurrent reader(s) */ +- misc_deregister(&lpc_snoop->chan[channel].miscdev); +- kfifo_free(&lpc_snoop->chan[channel].fifo); ++ misc_deregister(&channel->miscdev); ++ kfifo_free(&channel->fifo); ++} ++ ++static void aspeed_lpc_snoop_remove(struct platform_device *pdev) ++{ ++ struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); ++ ++ /* Disable both snoop channels */ ++ aspeed_lpc_disable_snoop(lpc_snoop, &lpc_snoop->chan[0]); ++ aspeed_lpc_disable_snoop(lpc_snoop, &lpc_snoop->chan[1]); + } + + static int aspeed_lpc_snoop_probe(struct platform_device *pdev) + { + struct aspeed_lpc_snoop *lpc_snoop; +- struct device *dev; + struct device_node *np; +- u32 port; ++ struct device *dev; ++ int idx; + int rc; + + dev = &pdev->dev; +@@ -293,77 +317,40 @@ + return -ENOMEM; + + np = pdev->dev.parent->of_node; +- if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") && +- !of_device_is_compatible(np, "aspeed,ast2500-lpc-v2") && +- !of_device_is_compatible(np, "aspeed,ast2600-lpc-v2")) { +- dev_err(dev, "unsupported LPC device binding\n"); +- return -ENODEV; +- } + + lpc_snoop->regmap = syscon_node_to_regmap(np); +- if (IS_ERR(lpc_snoop->regmap)) { +- dev_err(dev, "Couldn't get regmap\n"); +- return -ENODEV; +- } ++ if (IS_ERR(lpc_snoop->regmap)) ++ return dev_err_probe(dev, PTR_ERR(lpc_snoop->regmap), "Couldn't get regmap\n"); + + dev_set_drvdata(&pdev->dev, lpc_snoop); + +- rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); +- if (rc) { +- dev_err(dev, "no snoop ports configured\n"); +- return -ENODEV; +- } +- +- lpc_snoop->clk = devm_clk_get(dev, NULL); +- if (IS_ERR(lpc_snoop->clk)) { +- rc = PTR_ERR(lpc_snoop->clk); +- if (rc != -EPROBE_DEFER) +- dev_err(dev, "couldn't get clock\n"); +- return rc; +- } +- rc = clk_prepare_enable(lpc_snoop->clk); +- if (rc) { +- dev_err(dev, "couldn't enable clock\n"); +- return rc; +- } +- + rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); + if (rc) +- goto err; ++ return rc; + +- rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port); +- if (rc) +- goto err; ++ static_assert(ARRAY_SIZE(channel_cfgs) == ARRAY_SIZE(lpc_snoop->chan), ++ "Broken implementation assumption regarding cfg count"); ++ for (idx = ASPEED_LPC_SNOOP_INDEX_0; idx <= ASPEED_LPC_SNOOP_INDEX_MAX; idx++) { ++ u32 port; ++ ++ rc = of_property_read_u32_index(dev->of_node, "snoop-ports", idx, &port); ++ if (rc) ++ break; + +- /* Configuration of 2nd snoop channel port is optional */ +- if (of_property_read_u32_index(dev->of_node, "snoop-ports", +- 1, &port) == 0) { +- rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port); +- if (rc) { +- aspeed_lpc_disable_snoop(lpc_snoop, 0); +- goto err; +- } ++ rc = aspeed_lpc_enable_snoop(dev, lpc_snoop, &lpc_snoop->chan[idx], ++ &channel_cfgs[idx], port); ++ if (rc) ++ goto cleanup_channels; + } + +- return 0; ++ return idx == ASPEED_LPC_SNOOP_INDEX_0 ? -ENODEV : 0; + +-err: +- clk_disable_unprepare(lpc_snoop->clk); ++cleanup_channels: ++ aspeed_lpc_snoop_remove(pdev); + + return rc; + } + +-static void aspeed_lpc_snoop_remove(struct platform_device *pdev) +-{ +- struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); +- +- /* Disable both snoop channels */ +- aspeed_lpc_disable_snoop(lpc_snoop, 0); +- aspeed_lpc_disable_snoop(lpc_snoop, 1); +- +- clk_disable_unprepare(lpc_snoop->clk); +-} +- + static const struct aspeed_lpc_snoop_model_data ast2400_model_data = { + .has_hicrb_ensnp = 0, + }; +diff --git a/drivers/soc/aspeed/aspeed-mbox.c b/drivers/soc/aspeed/aspeed-mbox.c +--- a/drivers/soc/aspeed/aspeed-mbox.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-mbox.c 2026-09-09 15:51:44.718238072 +0000 +@@ -0,0 +1,312 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright Aspeed Technology Inc. (C) 2025. All rights reserved ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define __ASPEED_MBOX_IOCTL_MAGIC 'X' ++#define ASPEED_MBOX_IOCTL_CAPS _IOR(__ASPEED_MBOX_IOCTL_MAGIC, 0, uint32_t[4]) ++#define ASPEED_MBOX_IOCTL_SEND _IOW(__ASPEED_MBOX_IOCTL_MAGIC, 1, uint32_t[8]) ++#define ASPEED_MBOX_IOCTL_RECV _IOR(__ASPEED_MBOX_IOCTL_MAGIC, 2, uint32_t[8]) ++ ++/** ++ * struct aspeed_mem - Description of a ASPEED memory buffer ++ * @buf: Shared memory base address ++ * @size: Shared memory byte size ++ */ ++struct aspeed_mem { ++ void __iomem *buf; ++ phys_addr_t phys_addr; ++ resource_size_t size; ++}; ++ ++/* ++ * Message prototype of IPC. It should be defined per your usage. ++ * cmd: type of message ++ * len: length of message ++ */ ++struct ast_mbox_msg { ++ u32 cmd; ++ u32 len; ++}; ++ ++/* ++ * struct ast_mbox_chan - Description of a ASPEED mailbox channel ++ * @cl: Mailbox client ++ * @mdev: Misc device ++ * @chan: Mailbox channel ++ * @tx_base: Information of tx shmem ++ * @rx_base: Information of rx shmem ++ * @rx_buffer: buffer to store data on callback ++ * @rx_wait: Wait queue for receiving messages ++ * @rx_msg_lock: Spinlock to protect rx_buffer ++ */ ++struct ast_mbox_info { ++ struct device *dev; ++ struct mbox_client cl; ++ struct miscdevice mdev; ++ struct mbox_chan *chan; ++ struct aspeed_mem tx_base; ++ struct aspeed_mem rx_base; ++ char *rx_buffer; ++ wait_queue_head_t rx_wait; ++ spinlock_t rx_msg_lock; /* spinlock to protect rx_buffer */ ++}; ++ ++static ssize_t mbox_read(struct file *fp, char __user *buf, size_t nbytes, loff_t *off) ++{ ++ struct ast_mbox_info *info = container_of(fp->private_data, struct ast_mbox_info, mdev); ++ int ret; ++ ++ if (nbytes == 0) ++ return 0; ++ ++ if (!info->rx_base.buf) { ++ dev_err(info->dev, "No RX shmem\n"); ++ return -EINVAL; ++ } ++ ++ if (nbytes > info->rx_base.size) { ++ dev_warn(info->dev, "Read size %zu exceeds RX shmem size %llu\n", ++ nbytes, info->rx_base.size); ++ nbytes = info->rx_base.size; ++ } ++ ++ ret = copy_to_user((void __user *)buf, info->rx_base.buf, nbytes); ++ if (ret) ++ return -EFAULT; ++ ++ return nbytes; ++} ++ ++static ssize_t mbox_write(struct file *fp, const char *buf, size_t nbytes, loff_t *off) ++{ ++ struct ast_mbox_info *info = container_of(fp->private_data, struct ast_mbox_info, mdev); ++ int ret; ++ ++ if (nbytes == 0) ++ return 0; ++ ++ if (!info->tx_base.buf) { ++ dev_err(info->dev, "No TX shmem\n"); ++ return -EINVAL; ++ } ++ ++ if (nbytes > info->tx_base.size) { ++ dev_warn(info->dev, "Write size %zu exceeds TX shmem size %llu\n", ++ nbytes, info->tx_base.size); ++ nbytes = info->tx_base.size; ++ } ++ ++ ret = copy_from_user(info->tx_base.buf, (void __user *)buf, nbytes); ++ if (ret) ++ return -EFAULT; ++ ++ return nbytes; ++} ++ ++static __poll_t mbox_poll(struct file *fp, struct poll_table_struct *wait) ++{ ++ struct ast_mbox_info *info = container_of(fp->private_data, struct ast_mbox_info, mdev); ++ __poll_t mask = 0; ++ ++ poll_wait(fp, &info->rx_wait, wait); ++ ++ if (info->rx_buffer) ++ mask |= POLLIN | POLLRDNORM; ++ ++ return mask; ++} ++ ++static long mbox_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct ast_mbox_info *info = container_of(fp->private_data, struct ast_mbox_info, mdev); ++ u32 *data; ++ u32 msg[8]; ++ unsigned long flags; ++ int ret = 0; ++ ++ switch (cmd) { ++ case ASPEED_MBOX_IOCTL_CAPS: ++ data = (u32 *)arg; ++ data[0] = (info->tx_base.phys_addr) >> 8; ++ data[1] = info->tx_base.size; ++ data[2] = (info->rx_base.phys_addr) >> 8; ++ data[3] = info->rx_base.size; ++ break; ++ case ASPEED_MBOX_IOCTL_SEND: ++ ret = copy_from_user(msg, (void __user *)arg, sizeof(msg)); ++ if (ret) { ++ dev_dbg(info->dev, "Failed to copy message from user\n"); ++ return -EFAULT; ++ } ++ ++ ret = mbox_send_message(info->chan, msg); ++ if (ret < 0) ++ dev_dbg(info->dev, "Failed to send message via mailbox\n"); ++ break; ++ case ASPEED_MBOX_IOCTL_RECV: ++ spin_lock_irqsave(&info->rx_msg_lock, flags); ++ if (!info->rx_buffer) { ++ spin_unlock_irqrestore(&info->rx_msg_lock, flags); ++ dev_dbg(info->dev, "No message received\n"); ++ return -EAGAIN; ++ } ++ ret = copy_to_user((void __user *)arg, info->rx_buffer, sizeof(msg)); ++ info->rx_buffer = NULL; ++ spin_unlock_irqrestore(&info->rx_msg_lock, flags); ++ if (ret) { ++ dev_dbg(info->dev, "Failed to copy message to user\n"); ++ return -EFAULT; ++ } ++ break; ++ default: ++ dev_err(info->dev, "Unsupported cmd: %x\n", cmd); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static const struct file_operations aspeed_mbox_fops = { ++ .owner = THIS_MODULE, ++ .read = mbox_read, ++ .write = mbox_write, ++ .poll = mbox_poll, ++ .unlocked_ioctl = mbox_ioctl, ++}; ++ ++static void mbox_rx_callback(struct mbox_client *client, void *message) ++{ ++ struct ast_mbox_info *info = container_of(client, struct ast_mbox_info, cl); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&info->rx_msg_lock, flags); ++ info->rx_buffer = message; ++ spin_unlock_irqrestore(&info->rx_msg_lock, flags); ++ ++ wake_up_interruptible(&info->rx_wait); ++} ++ ++static int aspeed_mbox_probe(struct platform_device *pdev) ++{ ++ struct ast_mbox_info *info; ++ struct resource *res; ++ struct device *dev = &pdev->dev; ++ int ret; ++ u32 tx_tout = -1; ++ ++ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ info->tx_base.buf = devm_platform_get_and_ioremap_resource(pdev, 0, &res); ++ if (PTR_ERR(info->tx_base.buf) == -EBUSY) { ++ /* if reserved area in SRAM, try just ioremap */ ++ info->tx_base.size = resource_size(res); ++ info->tx_base.buf = devm_ioremap(dev, res->start, info->tx_base.size); ++ } ++ if (IS_ERR(info->tx_base.buf)) { ++ info->tx_base.buf = NULL; ++ } else { ++ info->tx_base.phys_addr = res->start; ++ info->tx_base.size = resource_size(res); ++ } ++ ++ info->rx_base.buf = devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (PTR_ERR(info->rx_base.buf) == -EBUSY) { ++ /* if reserved area in SRAM, try just ioremap */ ++ info->rx_base.size = resource_size(res); ++ info->rx_base.buf = devm_ioremap(dev, res->start, info->rx_base.size); ++ } ++ if (IS_ERR(info->rx_base.buf)) { ++ info->rx_base = info->tx_base; ++ } else { ++ info->rx_base.phys_addr = res->start; ++ info->rx_base.size = resource_size(res); ++ } ++ ++ if (device_property_read_u32(dev, "aspeed,tx-timeout", &tx_tout)) ++ tx_tout = -1; ++ ++ dev_info(dev, "TX shmem: phys 0x%pa size %llu\n", ++ &info->tx_base.phys_addr, info->tx_base.size); ++ dev_info(dev, "RX shmem: phys 0x%pa size %llu\n", ++ &info->rx_base.phys_addr, info->rx_base.size); ++ dev_info(dev, "TX timeout: %u ms\n", tx_tout); ++ ++ info->cl.dev = dev; ++ info->cl.rx_callback = mbox_rx_callback; ++ info->cl.tx_block = true; ++ info->cl.knows_txdone = false; ++ info->cl.tx_tout = tx_tout; ++ ++ info->chan = mbox_request_channel(&info->cl, 0); ++ if (IS_ERR(info->chan)) { ++ dev_err(dev, "failed to request channel err %ld\n", ++ PTR_ERR(info->chan)); ++ return -EPROBE_DEFER; ++ } ++ ++ info->rx_buffer = NULL; ++ init_waitqueue_head(&info->rx_wait); ++ spin_lock_init(&info->rx_msg_lock); ++ info->dev = dev; ++ platform_set_drvdata(pdev, info); ++ ++ info->mdev.parent = dev; ++ info->mdev.minor = MISC_DYNAMIC_MINOR; ++ info->mdev.name = dev_name(dev); ++ info->mdev.fops = &aspeed_mbox_fops; ++ ret = misc_register(&info->mdev); ++ if (ret) { ++ dev_err(dev, "failed to register misc device\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void aspeed_mbox_remove(struct platform_device *pdev) ++{ ++ struct ast_mbox_info *info = platform_get_drvdata(pdev); ++ ++ mbox_free_channel(info->chan); ++ misc_deregister(&info->mdev); ++} ++ ++static const struct of_device_id mbox_cl_match[] = { ++ { .compatible = "aspeed,aspeed-mbox" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, mbox_cl_match); ++ ++static struct platform_driver aspeed_mbox_driver = { ++ .driver = { ++ .name = "aspeed_mbox_client", ++ .of_match_table = mbox_cl_match, ++ }, ++ .probe = aspeed_mbox_probe, ++ .remove = aspeed_mbox_remove, ++}; ++module_platform_driver(aspeed_mbox_driver); ++ ++MODULE_AUTHOR("Jammy Huang "); ++MODULE_DESCRIPTION("ASPEED MBOX CLIENT driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/aspeed-mctp.c b/drivers/soc/aspeed/aspeed-mctp.c +--- a/drivers/soc/aspeed/aspeed-mctp.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-mctp.c 2026-09-09 15:51:44.718238072 +0000 +@@ -0,0 +1,2693 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2020, Intel Corporation. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++#include ++#endif ++ ++/* AST2600 MCTP Controller registers */ ++#define ASPEED_MCTP_CTRL 0x000 ++#define TX_CMD_TRIGGER BIT(0) ++#define RX_CMD_READY BIT(4) ++#define MATCHING_EID BIT(9) ++ ++#define ASPEED_MCTP_TX_CMD 0x004 ++#define ASPEED_MCTP_RX_CMD 0x008 ++ ++#define ASPEED_MCTP_INT_STS 0x00c ++#define ASPEED_MCTP_INT_EN 0x010 ++#define TX_CMD_SENT_INT BIT(0) ++#define TX_CMD_LAST_INT BIT(1) ++#define TX_CMD_WRONG_INT BIT(2) ++#define RX_CMD_RECEIVE_INT BIT(8) ++#define RX_CMD_NO_MORE_INT BIT(9) ++ ++#define ASPEED_MCTP_EID 0x014 ++#define MEMORY_SPACE_MAPPING GENMASK(31, 28) ++#define MCTP_EID GENMASK(7, 0) ++#define ASPEED_MCTP_OBFF_CTRL 0x018 ++ ++#define ASPEED_MCTP_ENGINE_CTRL 0x01c ++#define TX_MAX_PAYLOAD_SIZE_SHIFT 0 ++#define TX_MAX_PAYLOAD_SIZE_MASK GENMASK(1, TX_MAX_PAYLOAD_SIZE_SHIFT) ++#define TX_MAX_PAYLOAD_SIZE(x) \ ++ (((x) << TX_MAX_PAYLOAD_SIZE_SHIFT) & TX_MAX_PAYLOAD_SIZE_MASK) ++#define RX_MAX_PAYLOAD_SIZE_SHIFT 4 ++#define RX_MAX_PAYLOAD_SIZE_MASK GENMASK(5, RX_MAX_PAYLOAD_SIZE_SHIFT) ++#define RX_MAX_PAYLOAD_SIZE(x) \ ++ (((x) << RX_MAX_PAYLOAD_SIZE_SHIFT) & RX_MAX_PAYLOAD_SIZE_MASK) ++#define FIFO_LAYOUT_SHIFT 8 ++#define FIFO_LAYOUT_MASK GENMASK(9, FIFO_LAYOUT_SHIFT) ++#define FIFO_LAYOUT(x) \ ++ (((x) << FIFO_LAYOUT_SHIFT) & FIFO_LAYOUT_MASK) ++ ++#define ASPEED_MCTP_RX_BUF_ADDR 0x08 ++#define ASPEED_MCTP_RX_BUF_HI_ADDR 0x020 ++#define ASPEED_MCTP_RX_BUF_SIZE 0x024 ++#define ASPEED_MCTP_RX_BUF_RD_PTR 0x028 ++#define UPDATE_RX_RD_PTR BIT(31) ++#define RX_BUF_RD_PTR_MASK GENMASK(11, 0) ++#define ASPEED_MCTP_RX_BUF_WR_PTR 0x02c ++#define RX_BUF_WR_PTR_MASK GENMASK(11, 0) ++ ++#define ASPEED_MCTP_TX_BUF_ADDR 0x04 ++#define ASPEED_MCTP_TX_BUF_HI_ADDR 0x030 ++#define ASPEED_MCTP_TX_BUF_SIZE 0x034 ++#define ASPEED_MCTP_TX_BUF_RD_PTR 0x038 ++#define UPDATE_TX_RD_PTR BIT(31) ++#define TX_BUF_RD_PTR_MASK GENMASK(11, 0) ++#define ASPEED_MCTP_TX_BUF_WR_PTR 0x03c ++#define TX_BUF_WR_PTR_MASK GENMASK(11, 0) ++#define ASPEED_G7_MCTP_PCIE_BDF 0x04c ++ ++#define ADDR_LEN GENMASK(26, 0) ++#define DATA_ADDR(x) (((x) >> 4) & ADDR_LEN) ++ ++/* TX command */ ++#define TX_LAST_CMD BIT(31) ++#define TX_DATA_ADDR_SHIFT 4 ++#define TX_DATA_ADDR_MASK GENMASK(30, TX_DATA_ADDR_SHIFT) ++#define TX_DATA_ADDR(x) \ ++ ((DATA_ADDR(x) << TX_DATA_ADDR_SHIFT) & TX_DATA_ADDR_MASK) ++#define TX_RESERVED_1_MASK GENMASK(1, 0) /* must be 1 */ ++#define TX_RESERVED_1 1 ++#define TX_STOP_AFTER_CMD BIT(16) ++#define TX_INTERRUPT_AFTER_CMD BIT(15) ++#define TX_PACKET_SIZE_SHIFT 2 ++#define TX_PACKET_SIZE_MASK GENMASK(12, TX_PACKET_SIZE_SHIFT) ++#define TX_PACKET_SIZE(x) \ ++ (((x) << TX_PACKET_SIZE_SHIFT) & TX_PACKET_SIZE_MASK) ++#define TX_RESERVED_0_MASK GENMASK(1, 0) /* MBZ */ ++#define TX_RESERVED_0 0 ++ ++/* RX command */ ++#define RX_INTERRUPT_AFTER_CMD BIT(2) ++#define RX_DATA_ADDR_SHIFT 4 ++#define RX_DATA_ADDR_MASK GENMASK(30, RX_DATA_ADDR_SHIFT) ++#define RX_DATA_ADDR(x) \ ++ ((DATA_ADDR(x) << RX_DATA_ADDR_SHIFT) & RX_DATA_ADDR_MASK) ++ ++#define ADDR_LEN_2500 GENMASK(23, 0) ++#define DATA_ADDR_2500(x) (((x) >> 7) & ADDR_LEN_2500) ++ ++/* TX command for ast2500 */ ++#define TX_DATA_ADDR_MASK_2500 GENMASK(30, 8) ++#define TX_DATA_ADDR_2500(x) \ ++ FIELD_PREP(TX_DATA_ADDR_MASK_2500, DATA_ADDR_2500(x)) ++#define TX_PACKET_SIZE_2500(x) \ ++ FIELD_PREP(GENMASK(11, 2), x) ++#define TX_PACKET_DEST_EID GENMASK(7, 0) ++#define TX_PACKET_TARGET_ID GENMASK(31, 16) ++#define TX_PACKET_ROUTING_TYPE BIT(14) ++#define TX_PACKET_TAG_OWNER BIT(13) ++#define TX_PACKET_PADDING_LEN GENMASK(1, 0) ++ ++/* Rx command for ast2500 */ ++#define RX_LAST_CMD BIT(31) ++#define RX_DATA_ADDR_MASK_2500 GENMASK(29, 7) ++#define RX_DATA_ADDR_2500(x) \ ++ FIELD_PREP(RX_DATA_ADDR_MASK_2500, DATA_ADDR_2500(x)) ++#define RX_PACKET_SIZE GENMASK(30, 24) ++#define RX_PACKET_SRC_EID GENMASK(23, 16) ++#define RX_PACKET_ROUTING_TYPE GENMASK(15, 14) ++#define RX_PACKET_TAG_OWNER BIT(13) ++#define RX_PACKET_SEQ_NUMBER GENMASK(12, 11) ++#define RX_PACKET_MSG_TAG GENMASK(10, 8) ++#define RX_PACKET_SOM BIT(7) ++#define RX_PACKET_EOM BIT(6) ++#define RX_PACKET_PADDING_LEN GENMASK(5, 4) ++ ++/* HW buffer sizes */ ++#define TX_PACKET_COUNT 48 ++#define RX_PACKET_COUNT 96 ++#if (RX_PACKET_COUNT % 4 != 0) ++#error The Rx buffer size should be 4-aligned. ++#error 1.Make runaway wrap boundary can be determined in Ast2600 A1/A2. ++#error 2.Fix the runaway read pointer bug in Ast2600 A3. ++#endif ++#define TX_MAX_PACKET_COUNT (TX_BUF_RD_PTR_MASK + 1) ++#define RX_MAX_PACKET_COUNT (RX_BUF_RD_PTR_MASK + 1) ++ ++/* Per client packet cache sizes */ ++#define RX_RING_COUNT 64 ++#define TX_RING_COUNT 64 ++ ++/* PCIe Host Controller registers */ ++#define ASPEED_PCIE_LINK 0x0c0 ++#define PCIE_LINK_STS BIT(5) ++#define ASPEED_PCIE_MISC_STS_1 0x0c4 ++ ++/* PCIe Host Controller registers */ ++#define ASPEED_G7_PCIE_LOCATE 0x300 ++#define PCIE_LOCATE_IO BIT(0) ++#define ASPEED_G7_PCIE_LINK 0x358 ++#define PCIE_G7_LINK_STS BIT(8) ++#define ASPEED_G7_IO_PCIE_LINK 0x344 ++#define PCIE_G7_IO_LINK_STS BIT(18) ++ ++/* PCI address definitions */ ++#define PCI_DEV_NUM_MASK GENMASK(4, 0) ++#define PCI_BUS_NUM_SHIFT 5 ++#define PCI_BUS_NUM_MASK GENMASK(12, PCI_BUS_NUM_SHIFT) ++#define GET_PCI_DEV_NUM(x) ((x) & PCI_DEV_NUM_MASK) ++#define GET_PCI_BUS_NUM(x) (((x) & PCI_BUS_NUM_MASK) >> PCI_BUS_NUM_SHIFT) ++ ++/* MCTP header definitions */ ++#define MCTP_HDR_SRC_EID_OFFSET 14 ++#define MCTP_HDR_TAG_OFFSET 15 ++#define MCTP_HDR_SOM BIT(7) ++#define MCTP_HDR_EOM BIT(6) ++#define MCTP_HDR_SOM_EOM (MCTP_HDR_SOM | MCTP_HDR_EOM) ++#define MCTP_PAYLOAD_TYPE_OFFSET 0 ++#define MCTP_HDR_TYPE_CONTROL 0 ++#define MCTP_HDR_TYPE_VDM_PCI 0x7e ++#define MCTP_HDR_TYPE_SPDM 0x5 ++#define MCTP_HDR_TYPE_BASE_LAST MCTP_HDR_TYPE_SPDM ++#define MCTP_PAYLOAD_VENDOR_OFFSET 1 ++#define MCTP_PAYLOAD_VDM_TYPE_OFFSET 3 ++ ++/* MCTP header DW little endian mask definitions */ ++/* 0th DW */ ++#define MCTP_HDR_DW_LE_ROUTING_TYPE GENMASK(26, 24) ++#define MCTP_HDR_DW_LE_PACKET_SIZE GENMASK(9, 0) ++/* 1st DW */ ++#define MCTP_HDR_DW_LE_PADDING_LEN GENMASK(13, 12) ++/* 2nd DW */ ++#define MCTP_HDR_DW_LE_TARGET_ID GENMASK(31, 16) ++/* 3rd DW */ ++#define MCTP_HDR_DW_LE_TAG_OWNER BIT(3) ++#define MCTP_HDR_DW_LE_DEST_EID GENMASK(23, 16) ++ ++#define ASPEED_MCTP_2600 0 ++#define ASPEED_MCTP_2600A3 1 ++ ++#define ASPEED_REVISION_ID0 0x04 ++#define ASPEED_REVISION_ID1 0x14 ++#define ID0_AST2600A0 0x05000303 ++#define ID1_AST2600A0 0x05000303 ++#define ID0_AST2600A1 0x05010303 ++#define ID1_AST2600A1 0x05010303 ++#define ID0_AST2600A2 0x05010303 ++#define ID1_AST2600A2 0x05020303 ++#define ID0_AST2600A3 0x05030303 ++#define ID1_AST2600A3 0x05030303 ++#define ID0_AST2620A1 0x05010203 ++#define ID1_AST2620A1 0x05010203 ++#define ID0_AST2620A2 0x05010203 ++#define ID1_AST2620A2 0x05020203 ++#define ID0_AST2620A3 0x05030203 ++#define ID1_AST2620A3 0x05030203 ++#define ID0_AST2605A2 0x05010103 ++#define ID1_AST2605A2 0x05020103 ++#define ID0_AST2605A3 0x05030103 ++#define ID1_AST2605A3 0x05030103 ++#define ID0_AST2625A3 0x05030403 ++#define ID1_AST2625A3 0x05030403 ++ ++#define ASPEED_G7_SCU_PCIE0_CTRL_OFFSET 0xa60 ++#define ASPEED_G7_SCU_PCIE1_CTRL_OFFSET 0xae0 ++#define ASPEED_G7_SCU_PCIE_CTRL_VDM_EN BIT(1) ++ ++struct aspeed_mctp_match_data { ++ u32 rx_cmd_size; ++ u32 tx_cmd_size; ++ u32 packet_unit_size; ++ bool need_address_mapping; ++ bool vdm_hdr_direct_xfer; ++ bool fifo_auto_surround; ++ bool dma_need_64bits_width; ++ u32 scu_pcie_ctrl_offset; ++}; ++ ++struct aspeed_mctp_rx_cmd { ++ u32 rx_lo; ++ u32 rx_hi; ++}; ++ ++struct aspeed_mctp_tx_cmd { ++ u32 tx_lo; ++ u32 tx_hi; ++}; ++ ++struct aspeed_g7_mctp_tx_cmd { ++ u32 tx_lo; ++ u32 tx_mid; ++ u32 tx_hi; ++ u32 reserved; ++}; ++ ++struct mctp_buffer { ++ void *vaddr; ++ dma_addr_t dma_handle; ++}; ++ ++struct mctp_channel { ++ struct mctp_buffer data; ++ struct mctp_buffer cmd; ++ struct tasklet_struct tasklet; ++ u32 buffer_count; ++ u32 rd_ptr; ++ u32 wr_ptr; ++ bool stopped; ++}; ++ ++struct aspeed_mctp { ++ struct device *dev; ++ struct miscdevice mctp_miscdev; ++ const struct aspeed_mctp_match_data *match_data; ++ struct regmap *map; ++ struct reset_control *reset; ++ /* ++ * The reset of the dma block in the MCTP-RC is connected to ++ * another reset pin. ++ */ ++ struct reset_control *reset_dma; ++ struct mctp_channel tx; ++ struct mctp_channel rx; ++ struct list_head clients; ++ struct mctp_client *default_client; ++ struct list_head mctp_type_handlers; ++ /* ++ * clients_lock protects list of clients, list of type handlers ++ * and default client ++ */ ++ spinlock_t clients_lock; ++ struct list_head endpoints; ++ size_t endpoints_count; ++ /* ++ * endpoints_lock protects list of endpoints ++ */ ++ struct mutex endpoints_lock; ++ struct { ++ struct regmap *map; ++ struct delayed_work rst_dwork; ++ bool need_uevent; ++ } pcie; ++ struct { ++ bool enable; ++ bool first_loop; ++ int packet_counter; ++ } rx_runaway_wa; ++ bool rx_warmup; ++ /* Device ID for the device instance */ ++ u8 dev_id; ++ u8 eid; ++ struct platform_device *peci_mctp; ++ /* Use the flag to identify RC or EP */ ++ bool rc_f; ++ /* Use the flag to identify the support of MCTP interrupt */ ++ bool miss_mctp_int; ++ /* Rx hardware buffer size */ ++ u32 rx_packet_count; ++ /* Rx pointer ring size */ ++ u32 rx_ring_count; ++ /* Tx pointer ring size */ ++ u32 tx_ring_count; ++ /* Delayed work for periodic detection of Rx packets */ ++ struct delayed_work rx_det_dwork; ++ u32 rx_det_period_us; ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++ struct net_device *ndev; ++ bool pcie_vdm_enabled; ++#endif ++}; ++ ++struct mctp_client { ++ struct kref ref; ++ struct aspeed_mctp *priv; ++ struct ptr_ring tx_queue; ++ struct ptr_ring rx_queue; ++ struct list_head link; ++ wait_queue_head_t wait_queue; ++}; ++ ++struct mctp_type_handler { ++ u8 mctp_type; ++ u16 pci_vendor_id; ++ u16 vdm_type; ++ u16 vdm_mask; ++ struct mctp_client *client; ++ struct list_head link; ++}; ++ ++union aspeed_mctp_eid_data_info { ++ struct aspeed_mctp_eid_info eid_info; ++ struct aspeed_mctp_eid_ext_info eid_ext_info; ++}; ++ ++enum mctp_address_type { ++ ASPEED_MCTP_GENERIC_ADDR_FORMAT = 0, ++ ASPEED_MCTP_EXTENDED_ADDR_FORMAT = 1 ++}; ++ ++struct aspeed_mctp_endpoint { ++ union aspeed_mctp_eid_data_info data; ++ struct list_head link; ++}; ++ ++struct kmem_cache *packet_cache; ++ ++static void data_dump(struct aspeed_mctp *priv, struct mctp_pcie_packet_data *data) ++{ ++ int i; ++ ++ dev_dbg(priv->dev, "Address %zu", (size_t)data); ++ dev_dbg(priv->dev, "VDM header:"); ++ for (i = 0; i < PCIE_VDM_HDR_SIZE_DW; i++) { ++ dev_dbg(priv->dev, "%02x %02x %02x %02x", data->hdr[i] & 0xff, ++ (data->hdr[i] >> 8) & 0xff, (data->hdr[i] >> 16) & 0xff, ++ (data->hdr[i] >> 24) & 0xff); ++ } ++ dev_dbg(priv->dev, "Data payload:"); ++ for (i = 0; i < PCIE_VDM_DATA_SIZE_DW; i++) { ++ dev_dbg(priv->dev, "%02x %02x %02x %02x", ++ data->payload[i] & 0xff, (data->payload[i] >> 8) & 0xff, ++ (data->payload[i] >> 16) & 0xff, ++ (data->payload[i] >> 24) & 0xff); ++ } ++} ++ ++void *aspeed_mctp_packet_alloc(gfp_t flags) ++{ ++ return kmem_cache_alloc(packet_cache, flags); ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_packet_alloc); ++ ++void aspeed_mctp_packet_free(void *packet) ++{ ++ kmem_cache_free(packet_cache, packet); ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_packet_free); ++ ++static int _get_bdf(struct aspeed_mctp *priv) ++{ ++ u32 reg; ++ u16 bdf, devfn; ++ ++ if (priv->match_data->dma_need_64bits_width) { ++ regmap_read(priv->pcie.map, ASPEED_G7_PCIE_LOCATE, ®); ++ if (!(reg & PCIE_LOCATE_IO)) { ++ regmap_read(priv->pcie.map, ASPEED_G7_PCIE_LINK, ®); ++ if (!(reg & PCIE_G7_LINK_STS)) ++ return -ENETDOWN; ++ regmap_read(priv->map, ASPEED_G7_MCTP_PCIE_BDF, ®); ++ bdf = PCI_DEVID(PCI_BUS_NUM(reg), reg & 0xff); ++ } else { ++ regmap_read(priv->pcie.map, ASPEED_G7_IO_PCIE_LINK, ++ ®); ++ if (!(reg & PCIE_G7_IO_LINK_STS)) ++ return -ENETDOWN; ++ regmap_read(priv->map, ASPEED_G7_MCTP_PCIE_BDF, ®); ++ bdf = PCI_DEVID(PCI_BUS_NUM(reg), reg & 0xff); ++ } ++ } else { ++ regmap_read(priv->pcie.map, ASPEED_PCIE_LINK, ®); ++ if (!(reg & PCIE_LINK_STS)) ++ return -ENETDOWN; ++ regmap_read(priv->pcie.map, ASPEED_PCIE_MISC_STS_1, ®); ++ ++ reg = reg & (PCI_BUS_NUM_MASK | PCI_DEV_NUM_MASK); ++ /* only support function 0 */ ++ devfn = GET_PCI_DEV_NUM(reg) << 3; ++ bdf = PCI_DEVID(GET_PCI_BUS_NUM(reg), devfn); ++ } ++ ++ if (!priv->rc_f && bdf == 0) ++ return -ENODEV; ++ ++ return bdf; ++} ++ ++static uint32_t chip_version(struct device *dev) ++{ ++ struct regmap *scu; ++ u32 revid0, revid1; ++ ++ scu = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,scu"); ++ if (IS_ERR(scu)) { ++ dev_err(dev, "failed to find 2600 SCU regmap\n"); ++ return PTR_ERR(scu); ++ } ++ regmap_read(scu, ASPEED_REVISION_ID0, &revid0); ++ regmap_read(scu, ASPEED_REVISION_ID1, &revid1); ++ if (revid0 == ID0_AST2600A3 && revid1 == ID1_AST2600A3) { ++ /* AST2600-A3 */ ++ return ASPEED_MCTP_2600A3; ++ } else if (revid0 == ID0_AST2620A3 && revid1 == ID1_AST2620A3) { ++ /* AST2620-A3 */ ++ return ASPEED_MCTP_2600A3; ++ } else if (revid0 == ID0_AST2605A3 && revid1 == ID1_AST2605A3) { ++ /* AST2605-A3 */ ++ return ASPEED_MCTP_2600A3; ++ } else if (revid0 == ID0_AST2625A3 && revid1 == ID1_AST2625A3) { ++ /* AST2605-A3 */ ++ return ASPEED_MCTP_2600A3; ++ } ++ return ASPEED_MCTP_2600; ++} ++ ++static int pcie_vdm_enable(struct device *dev) ++{ ++ int ret = 0; ++ struct regmap *scu; ++ const struct aspeed_mctp_match_data *match_data = ++ of_device_get_match_data(dev); ++ ++ scu = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,scu"); ++ if (IS_ERR(scu)) { ++ dev_err(dev, "failed to find SCU regmap\n"); ++ return PTR_ERR(scu); ++ } ++ ret = regmap_update_bits(scu, match_data->scu_pcie_ctrl_offset, ++ ASPEED_G7_SCU_PCIE_CTRL_VDM_EN, ++ ASPEED_G7_SCU_PCIE_CTRL_VDM_EN); ++ return ret; ++} ++ ++/* ++ * HW produces and expects VDM header in little endian and payload in network order. ++ * To allow userspace to use network order for the whole packet, PCIe VDM header needs ++ * to be swapped. ++ */ ++static void aspeed_mctp_swap_pcie_vdm_hdr(struct mctp_pcie_packet_data *data) ++{ ++ int i; ++ ++ for (i = 0; i < PCIE_VDM_HDR_SIZE_DW; i++) ++ data->hdr[i] = swab32(data->hdr[i]); ++} ++ ++static void aspeed_mctp_rx_trigger(struct mctp_channel *rx) ++{ ++ struct aspeed_mctp *priv = container_of(rx, typeof(*priv), rx); ++ u32 reg; ++ ++ /* ++ * Even though rx_buf_addr doesn't change, if we don't do the write ++ * here, the HW doesn't trigger RX. We're also clearing the ++ * RX_CMD_READY bit, otherwise we're observing a rare case where ++ * trigger isn't registered by the HW, and we're ending up with stuck ++ * HW (not reacting to wr_ptr writes). ++ * Also, note that we're writing 0 as wr_ptr. If we're writing other ++ * value, the HW behaves in a bizarre way that's hard to explain... ++ */ ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, RX_CMD_READY, 0); ++ if (priv->match_data->fifo_auto_surround) { ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_ADDR, ++ rx->cmd.dma_handle); ++ if (priv->match_data->dma_need_64bits_width) ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_HI_ADDR, ++ upper_32_bits(rx->cmd.dma_handle)); ++ } else { ++ regmap_read(priv->map, ASPEED_MCTP_RX_BUF_ADDR, ®); ++ if (!reg) { ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_ADDR, ++ rx->cmd.dma_handle); ++ } else if (reg == (rx->cmd.dma_handle & GENMASK(28, 3))) { ++ dev_info(priv->dev, ++ "Already initialized - skipping rx dma set\n"); ++ } else { ++ dev_err(priv->dev, ++ "The memory of rx dma can't be changed after the controller is activated\n"); ++ return; ++ } ++ } ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_WR_PTR, 0); ++ ++ /* After re-enabling RX we need to restart WA logic */ ++ if (priv->rx_runaway_wa.enable) ++ priv->rx.buffer_count = priv->rx_packet_count; ++ /* ++ * When Rx warmup MCTP controller may store first packet into the 0th to the ++ * 3rd cmd. In ast2600 A3, If the packet isn't stored in the 0th cmd we need ++ * to change the rx buffer size to avoid rx runaway in first loop. In ast2600 ++ * A1/A2, after first loop hardware is guaranteed to use (RX_PACKET_COUNT - 4) ++ * buffers. ++ */ ++ priv->rx_warmup = true; ++ priv->rx_runaway_wa.first_loop = true; ++ priv->rx_runaway_wa.packet_counter = 0; ++ ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, RX_CMD_READY, ++ RX_CMD_READY); ++} ++ ++static void aspeed_mctp_tx_trigger(struct mctp_channel *tx, bool notify) ++{ ++ struct aspeed_mctp *priv = container_of(tx, typeof(*priv), tx); ++ u32 ctrl_val; ++ int ret; ++ ++ if (notify) { ++ if (priv->match_data->dma_need_64bits_width) { ++ struct aspeed_g7_mctp_tx_cmd *last_cmd; ++ ++ last_cmd = (struct aspeed_g7_mctp_tx_cmd *)tx->cmd.vaddr + ++ (tx->wr_ptr - 1) % TX_PACKET_COUNT; ++ last_cmd->tx_lo |= TX_INTERRUPT_AFTER_CMD; ++ } else { ++ struct aspeed_mctp_tx_cmd *last_cmd; ++ ++ last_cmd = (struct aspeed_mctp_tx_cmd *)tx->cmd.vaddr + ++ (tx->wr_ptr - 1) % TX_PACKET_COUNT; ++ last_cmd->tx_lo |= TX_INTERRUPT_AFTER_CMD; ++ } ++ } ++ if (priv->match_data->fifo_auto_surround) ++ regmap_write(priv->map, ASPEED_MCTP_TX_BUF_WR_PTR, tx->wr_ptr); ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, TX_CMD_TRIGGER, ++ TX_CMD_TRIGGER); ++ ret = regmap_read_poll_timeout_atomic(priv->map, ASPEED_MCTP_CTRL, ++ ctrl_val, ++ !(ctrl_val & TX_CMD_TRIGGER), 0, ++ 1000000); ++ if (ret) { ++ u32 rd_ptr, wr_ptr; ++ ++ regmap_write(priv->map, ASPEED_MCTP_TX_BUF_RD_PTR, UPDATE_RX_RD_PTR); ++ regmap_read(priv->map, ASPEED_MCTP_TX_BUF_RD_PTR, &rd_ptr); ++ rd_ptr &= RX_BUF_RD_PTR_MASK; ++ regmap_read(priv->map, ASPEED_MCTP_TX_BUF_WR_PTR, &wr_ptr); ++ wr_ptr &= TX_BUF_RD_PTR_MASK; ++ dev_warn(priv->dev, ++ "Wait tx completed timeout rd_ptr = %x, wr_ptr = %x\n", ++ rd_ptr, wr_ptr); ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, TX_CMD_TRIGGER, ++ 0); ++ } ++} ++ ++static void aspeed_mctp_tx_cmd_prep(u32 *tx_hdr, struct aspeed_mctp_tx_cmd *tx_cmd) ++{ ++ u32 packet_size, target_id; ++ u8 dest_eid, padding_len, routing_type, tag_owner; ++ ++ packet_size = FIELD_GET(MCTP_HDR_DW_LE_PACKET_SIZE, tx_hdr[0]); ++ routing_type = FIELD_GET(MCTP_HDR_DW_LE_ROUTING_TYPE, tx_hdr[0]); ++ routing_type = routing_type ? routing_type - 1 : 0; ++ padding_len = FIELD_GET(MCTP_HDR_DW_LE_PADDING_LEN, tx_hdr[1]); ++ target_id = FIELD_GET(MCTP_HDR_DW_LE_TARGET_ID, tx_hdr[2]); ++ tag_owner = FIELD_GET(MCTP_HDR_DW_LE_TAG_OWNER, tx_hdr[3]); ++ dest_eid = FIELD_GET(MCTP_HDR_DW_LE_DEST_EID, tx_hdr[3]); ++ ++ tx_cmd->tx_hi = FIELD_PREP(TX_PACKET_DEST_EID, dest_eid); ++ tx_cmd->tx_lo = FIELD_PREP(TX_PACKET_TARGET_ID, target_id) | ++ TX_INTERRUPT_AFTER_CMD | ++ FIELD_PREP(TX_PACKET_ROUTING_TYPE, routing_type) | ++ FIELD_PREP(TX_PACKET_TAG_OWNER, tag_owner) | ++ TX_PACKET_SIZE_2500(packet_size) | ++ FIELD_PREP(TX_PACKET_PADDING_LEN, padding_len); ++} ++ ++static void aspeed_mctp_emit_tx_cmd(struct mctp_channel *tx, ++ struct mctp_pcie_packet *packet) ++{ ++ struct aspeed_mctp *priv = container_of(tx, typeof(*priv), tx); ++ struct aspeed_mctp_tx_cmd *tx_cmd = ++ (struct aspeed_mctp_tx_cmd *)tx->cmd.vaddr + tx->wr_ptr; ++ struct aspeed_g7_mctp_tx_cmd *tx_cmd_g7 = ++ (struct aspeed_g7_mctp_tx_cmd *)tx->cmd.vaddr + tx->wr_ptr; ++ u32 packet_sz_dw = packet->size / sizeof(u32) - ++ sizeof(packet->data.hdr) / sizeof(u32); ++ u32 offset; ++ ++ data_dump(priv, &packet->data); ++ aspeed_mctp_swap_pcie_vdm_hdr(&packet->data); ++ ++ if (priv->match_data->vdm_hdr_direct_xfer) { ++ offset = tx->wr_ptr * sizeof(packet->data); ++ memcpy((u8 *)tx->data.vaddr + offset, &packet->data, ++ sizeof(packet->data)); ++ if (priv->match_data->dma_need_64bits_width) { ++ tx_cmd_g7->tx_lo = TX_PACKET_SIZE(packet_sz_dw); ++ tx_cmd_g7->tx_mid = TX_RESERVED_1; ++ tx_cmd_g7->tx_mid |= ((tx->data.dma_handle + offset) & ++ GENMASK(31, 4)); ++ tx_cmd_g7->tx_hi = upper_32_bits((tx->data.dma_handle + offset)); ++ } else { ++ tx_cmd->tx_lo = TX_PACKET_SIZE(packet_sz_dw); ++ tx_cmd->tx_hi = TX_RESERVED_1; ++ tx_cmd->tx_hi |= TX_DATA_ADDR(tx->data.dma_handle + offset); ++ } ++ } else { ++ offset = tx->wr_ptr * sizeof(struct mctp_pcie_packet_data_2500); ++ memcpy((u8 *)tx->data.vaddr + offset, packet->data.payload, ++ sizeof(packet->data.payload)); ++ aspeed_mctp_tx_cmd_prep(packet->data.hdr, tx_cmd); ++ tx_cmd->tx_hi |= TX_DATA_ADDR_2500(tx->data.dma_handle + offset); ++ if (tx->wr_ptr == TX_PACKET_COUNT - 1) ++ tx_cmd->tx_hi |= TX_LAST_CMD; ++ } ++ dev_dbg(priv->dev, "tx->wr_prt: %d, tx_cmd: hi:%08x lo:%08x\n", ++ tx->wr_ptr, tx_cmd->tx_hi, tx_cmd->tx_lo); ++ ++ tx->wr_ptr = (tx->wr_ptr + 1) % TX_PACKET_COUNT; ++} ++ ++static struct mctp_client *aspeed_mctp_client_alloc(struct aspeed_mctp *priv) ++{ ++ struct mctp_client *client; ++ ++ client = kzalloc(sizeof(*client), GFP_KERNEL); ++ if (!client) ++ goto out; ++ ++ kref_init(&client->ref); ++ client->priv = priv; ++ ptr_ring_init(&client->tx_queue, priv->tx_ring_count, GFP_KERNEL); ++ ptr_ring_init(&client->rx_queue, priv->rx_ring_count, GFP_ATOMIC); ++ ++out: ++ return client; ++} ++ ++static void aspeed_mctp_client_free(struct kref *ref) ++{ ++ struct mctp_client *client = container_of(ref, typeof(*client), ref); ++ ++ ptr_ring_cleanup(&client->rx_queue, &aspeed_mctp_packet_free); ++ ptr_ring_cleanup(&client->tx_queue, &aspeed_mctp_packet_free); ++ ++ kfree(client); ++} ++ ++static void aspeed_mctp_client_get(struct mctp_client *client) ++{ ++ lockdep_assert_held(&client->priv->clients_lock); ++ ++ kref_get(&client->ref); ++} ++ ++static void aspeed_mctp_client_put(struct mctp_client *client) ++{ ++ kref_put(&client->ref, &aspeed_mctp_client_free); ++} ++ ++static struct mctp_client * ++aspeed_mctp_find_handler(struct aspeed_mctp *priv, ++ struct mctp_pcie_packet *packet) ++{ ++ struct mctp_type_handler *handler; ++ u8 *payload = (u8 *)packet->data.payload; ++ struct mctp_client *client = NULL; ++ u8 mctp_type; ++ u16 vendor = 0; ++ u16 vdm_type = 0; ++ ++ lockdep_assert_held(&priv->clients_lock); ++ ++ mctp_type = payload[MCTP_PAYLOAD_TYPE_OFFSET]; ++ if (mctp_type == MCTP_HDR_TYPE_VDM_PCI) { ++ vendor = *((u16 *)&payload[MCTP_PAYLOAD_VENDOR_OFFSET]); ++ vdm_type = *((u16 *)&payload[MCTP_PAYLOAD_VDM_TYPE_OFFSET]); ++ } ++ ++ list_for_each_entry(handler, &priv->mctp_type_handlers, link) { ++ if (handler->mctp_type == mctp_type && ++ handler->pci_vendor_id == vendor && ++ handler->vdm_type == (vdm_type & handler->vdm_mask)) { ++ dev_dbg(priv->dev, "Found client for type %x vdm %x\n", ++ mctp_type, handler->vdm_type); ++ client = handler->client; ++ break; ++ } ++ } ++ return client; ++} ++ ++static void aspeed_mctp_dispatch_packet(struct aspeed_mctp *priv, ++ struct mctp_pcie_packet *packet) ++{ ++ struct mctp_client *client; ++ int ret; ++ ++ spin_lock(&priv->clients_lock); ++ ++ client = aspeed_mctp_find_handler(priv, packet); ++ ++ if (!client) ++ client = priv->default_client; ++ ++ if (client) ++ aspeed_mctp_client_get(client); ++ ++ spin_unlock(&priv->clients_lock); ++ ++ if (client) { ++ ret = ptr_ring_produce(&client->rx_queue, packet); ++ if (ret) { ++ /* ++ * This can happen if client process does not ++ * consume packets fast enough ++ */ ++ dev_dbg(priv->dev, "Failed to store packet in client RX queue\n"); ++ aspeed_mctp_packet_free(packet); ++ } else { ++ wake_up_all(&client->wait_queue); ++ } ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++ mctp_pcie_vdm_receive_packet(priv->ndev); ++#endif ++ aspeed_mctp_client_put(client); ++ } else { ++ dev_dbg(priv->dev, "Failed to dispatch RX packet\n"); ++ aspeed_mctp_packet_free(packet); ++ } ++} ++ ++static void aspeed_mctp_tx_tasklet(unsigned long data) ++{ ++ struct mctp_channel *tx = (struct mctp_channel *)data; ++ struct aspeed_mctp *priv = container_of(tx, typeof(*priv), tx); ++ struct mctp_client *client; ++ bool trigger = false; ++ bool full = false; ++ u32 rd_ptr; ++ ++ if (priv->match_data->fifo_auto_surround) { ++ regmap_write(priv->map, ASPEED_MCTP_TX_BUF_RD_PTR, UPDATE_RX_RD_PTR); ++ regmap_read(priv->map, ASPEED_MCTP_TX_BUF_RD_PTR, &rd_ptr); ++ rd_ptr &= TX_BUF_RD_PTR_MASK; ++ } else { ++ rd_ptr = tx->rd_ptr; ++ } ++ ++ spin_lock(&priv->clients_lock); ++ ++ list_for_each_entry(client, &priv->clients, link) { ++ while (!(full = (tx->wr_ptr + 1) % TX_PACKET_COUNT == rd_ptr)) { ++ struct mctp_pcie_packet *packet; ++ ++ packet = ptr_ring_consume(&client->tx_queue); ++ if (!packet) ++ break; ++ ++ aspeed_mctp_emit_tx_cmd(tx, packet); ++ aspeed_mctp_packet_free(packet); ++ trigger = true; ++ } ++ } ++ ++ spin_unlock(&priv->clients_lock); ++ ++ if (trigger) ++ aspeed_mctp_tx_trigger(tx, full); ++} ++ ++static void aspeed_mctp_rx_hdr_prep(struct aspeed_mctp *priv, u8 *hdr, u32 rx_lo) ++{ ++ u16 bdf; ++ u8 routing_type; ++ ++ /* ++ * MCTP controller will map the routing type to reduce one bit ++ * 0 (Route to RC) -> 0, ++ * 2 (Route by ID) -> 1, ++ * 3 (Broadcast from RC) -> 2 ++ */ ++ routing_type = FIELD_GET(RX_PACKET_ROUTING_TYPE, rx_lo); ++ routing_type = routing_type ? routing_type + 1 : 0; ++ bdf = _get_bdf(priv); ++ /* Length[7:0] */ ++ hdr[0] = FIELD_GET(RX_PACKET_SIZE, rx_lo); ++ /* TD:EP:ATTR[1:0]:R or AT[1:0]:Length[9:8] */ ++ hdr[1] = 0; ++ /* R or T9:TC[2:0]:R[3:0] */ ++ hdr[2] = 0; ++ /* R or Fmt[2]:Fmt[1:0]=b'11:Type[4:3]=b'10:Type[2:0] */ ++ hdr[3] = 0x70 | routing_type; ++ /* VDM message code = 0x7f */ ++ hdr[4] = 0x7f; ++ /* R[1:0]:Pad len[1:0]:MCTP VDM Code[3:0] */ ++ hdr[5] = FIELD_GET(RX_PACKET_PADDING_LEN, rx_lo) << 4; ++ /* TODO: PCI Requester ID: HW didn't get this information */ ++ hdr[6] = 0; ++ hdr[7] = 5; ++ /* Vendor ID: 0x1AB4 */ ++ hdr[8] = 0xb4; ++ hdr[9] = 0x1a; ++ /* PCI Target ID */ ++ hdr[10] = bdf & 0xff; ++ hdr[11] = bdf >> 8 & 0xff; ++ /* SOM:EOM:Pkt Seq#[1:0]:TO:Msg Tag[2:0]*/ ++ hdr[12] = FIELD_GET(RX_PACKET_SOM, rx_lo) << 7 | ++ FIELD_GET(RX_PACKET_EOM, rx_lo) << 6 | ++ FIELD_GET(RX_PACKET_SEQ_NUMBER, rx_lo) << 4 | ++ FIELD_GET(RX_PACKET_TAG_OWNER, rx_lo) << 3 | ++ FIELD_GET(RX_PACKET_MSG_TAG, rx_lo); ++ /* Source Endpoint ID */ ++ hdr[13] = FIELD_GET(RX_PACKET_SRC_EID, rx_lo); ++ /* Destination Endpoint ID: HW didn't get this information*/ ++ hdr[14] = priv->eid; ++ /* TODO: R[3:0]: header version[3:0] */ ++ hdr[15] = 1; ++} ++ ++static void aspeed_mctp_rx_tasklet(unsigned long data) ++{ ++ struct mctp_channel *rx = (struct mctp_channel *)data; ++ struct aspeed_mctp *priv = container_of(rx, typeof(*priv), rx); ++ struct mctp_pcie_packet *rx_packet; ++ struct aspeed_mctp_rx_cmd *rx_cmd; ++ u32 hw_read_ptr; ++ u32 *hdr, *payload; ++ bool rx_full; ++ ++ /* initialized as false */ ++ rx_full = false; ++ ++ if (priv->match_data->vdm_hdr_direct_xfer && priv->match_data->fifo_auto_surround) { ++ struct mctp_pcie_packet_data *rx_buf; ++ u32 residual_cmds = 0; ++ ++ /* Trigger HW read pointer update, must be done before RX loop */ ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_RD_PTR, UPDATE_RX_RD_PTR); ++ ++ /* ++ * rx->stopped indicates if rx ring is full or not. ++ * Use rx_full to note ring status before consuming packet. ++ */ ++ rx_full = rx->stopped; ++ ++ /* ++ * XXX: Using rd_ptr obtained from HW is unreliable so we need to ++ * maintain the state of buffer on our own by peeking into the buffer ++ * and checking where the packet was written. ++ */ ++ rx_buf = (struct mctp_pcie_packet_data *)rx->data.vaddr; ++ hdr = (u32 *)&rx_buf[rx->wr_ptr]; ++ if (!*hdr && priv->rx_warmup) { ++ u32 tmp_wr_ptr = rx->wr_ptr; ++ ++ /* ++ * HACK: Right after start the RX hardware can put received ++ * packet into an unexpected offset - in order to locate ++ * received packet driver has to scan all RX data buffers. ++ */ ++ do { ++ tmp_wr_ptr = (tmp_wr_ptr + 1) % priv->rx_packet_count; ++ ++ hdr = (u32 *)&rx_buf[tmp_wr_ptr]; ++ } while (!*hdr && tmp_wr_ptr != rx->wr_ptr); ++ ++ if (tmp_wr_ptr != rx->wr_ptr) { ++ dev_warn(priv->dev, ++ "Runaway RX packet found %d -> %d\n", ++ rx->wr_ptr, tmp_wr_ptr); ++ residual_cmds = abs(tmp_wr_ptr - rx->wr_ptr); ++ rx->wr_ptr = tmp_wr_ptr; ++ if (!priv->rx_runaway_wa.enable && ++ priv->rx_warmup) ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_SIZE, ++ rx->buffer_count - residual_cmds); ++ priv->rx_warmup = false; ++ } ++ } else { ++ priv->rx_warmup = false; ++ } ++ ++ if (priv->rx_runaway_wa.packet_counter > priv->rx_packet_count && ++ priv->rx_runaway_wa.first_loop) { ++ if (priv->rx_runaway_wa.enable) ++ /* ++ * Once we receive RX_PACKET_COUNT packets, hardware is ++ * guaranteed to use (RX_PACKET_COUNT - 4) buffers. Decrease ++ * buffer count by 4, then we can turn off scanning of RX ++ * buffers. RX buffer scanning should be enabled every time ++ * RX hardware is started. ++ * This is just a performance optimization - we could keep ++ * scanning RX buffers forever, but under heavy traffic it is ++ * fairly common that rx_tasklet is executed while RX buffer ++ * ring is empty. ++ */ ++ rx->buffer_count = priv->rx_packet_count - 4; ++ else ++ /* ++ * Once we receive RX_PACKET_COUNT packets, we need to restore the ++ * RX buffer size to 4 byte aligned value to avoid rx runaway. ++ */ ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_SIZE, ++ rx->buffer_count); ++ priv->rx_runaway_wa.first_loop = false; ++ } ++ ++ while (*hdr != 0) { ++ if (FIELD_GET(MCTP_HDR_DW_LE_PACKET_SIZE, hdr[0]) * 4 > ++ ASPEED_MCTP_MTU) ++ dev_warn(priv->dev, ++ "Rx length %ld > MTU size %d\n", ++ FIELD_GET(MCTP_HDR_DW_LE_PACKET_SIZE, ++ hdr[0]) * ++ 4, ++ ASPEED_MCTP_MTU); ++ rx_packet = aspeed_mctp_packet_alloc(GFP_ATOMIC); ++ if (rx_packet) { ++ memcpy(&rx_packet->data, hdr, sizeof(rx_packet->data)); ++ aspeed_mctp_swap_pcie_vdm_hdr(&rx_packet->data); ++ ++ aspeed_mctp_dispatch_packet(priv, rx_packet); ++ } else { ++ dev_dbg(priv->dev, "Failed to allocate RX packet\n"); ++ } ++ data_dump(priv, &rx_packet->data); ++ *hdr = 0; ++ rx->wr_ptr = (rx->wr_ptr + 1) % rx->buffer_count; ++ hdr = (u32 *)&rx_buf[rx->wr_ptr]; ++ ++ priv->rx_runaway_wa.packet_counter++; ++ } ++ ++ /* ++ * Update HW write pointer, this can be done only after driver consumes ++ * packets from RX ring. ++ */ ++ regmap_read(priv->map, ASPEED_MCTP_RX_BUF_RD_PTR, &hw_read_ptr); ++ hw_read_ptr &= RX_BUF_RD_PTR_MASK; ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_WR_PTR, (hw_read_ptr)); ++ ++ dev_dbg(priv->dev, "RX hw ptr %02d, sw ptr %2d\n", ++ hw_read_ptr, rx->wr_ptr); ++ } else { ++ struct mctp_pcie_packet_data_2500 *rx_buf; ++ ++ rx_buf = (struct mctp_pcie_packet_data_2500 *)rx->data.vaddr; ++ payload = (u32 *)&rx_buf[rx->wr_ptr]; ++ rx_cmd = (struct aspeed_mctp_rx_cmd *)rx->cmd.vaddr; ++ hdr = (u32 *)&((rx_cmd + rx->wr_ptr)->rx_lo); ++ ++ if (!*hdr) { ++ u32 tmp_wr_ptr = rx->wr_ptr; ++ ++ /* ++ * HACK: Right after start the RX hardware can put received ++ * packet into an unexpected offset - in order to locate ++ * received packet driver has to scan all RX data buffers. ++ */ ++ do { ++ tmp_wr_ptr = (tmp_wr_ptr + 1) % rx->buffer_count; ++ ++ hdr = (u32 *)&((rx_cmd + tmp_wr_ptr)->rx_lo); ++ } while (!*hdr && tmp_wr_ptr != rx->wr_ptr); ++ ++ if (tmp_wr_ptr != rx->wr_ptr) { ++ dev_warn(priv->dev, ++ "Runaway RX packet found %d -> %d\n", ++ rx->wr_ptr, tmp_wr_ptr); ++ rx->wr_ptr = tmp_wr_ptr; ++ } ++ } ++ ++ while (*hdr != 0) { ++ rx_packet = aspeed_mctp_packet_alloc(GFP_ATOMIC); ++ if (rx_packet) { ++ memcpy(rx_packet->data.payload, payload, ++ sizeof(rx_packet->data.payload)); ++ ++ aspeed_mctp_rx_hdr_prep(priv, (u8 *)rx_packet->data.hdr, *hdr); ++ ++ aspeed_mctp_swap_pcie_vdm_hdr(&rx_packet->data); ++ ++ aspeed_mctp_dispatch_packet(priv, rx_packet); ++ } else { ++ dev_dbg(priv->dev, "Failed to allocate RX packet\n"); ++ } ++ dev_dbg(priv->dev, ++ "rx->wr_ptr = %d, rx_cmd->rx_lo = %08x", ++ rx->wr_ptr, *hdr); ++ data_dump(priv, &rx_packet->data); ++ *hdr = 0; ++ rx->wr_ptr = (rx->wr_ptr + 1) % rx->buffer_count; ++ payload = (u32 *)&rx_buf[rx->wr_ptr]; ++ hdr = (u32 *)&((rx_cmd + rx->wr_ptr)->rx_lo); ++ } ++ } ++ ++ /* Kick RX if it was stopped due to ring full condition */ ++ if (rx->stopped) { ++ if (!rx_full) { ++ /* ++ * RX ring may still be full in here as the HW keeps producing when Tasklet consumes the packets. ++ * Use rx_full to detect if RX ring is already full before or after Tasklet consumption. ++ * Schedule another tasklet here to consume RX ring before restarting reception if ring is full after the while loop, ++ * in case that RX_CMD_NO_MORE_INT interrupts tasklet after tasklet consumes packets. ++ * Use flag cause we cannot control if ASPEED_MCTP_RX_BUF_WR_PTR can be updated before ring full occurs. ++ * Example of problematic scenario: ++ * 1. Tasklet executing, found *hdr==0 at wr_ptr=14, break the while loop and going forward. ++ * 2. After leaving the loop, Tasklet spend time doing some time-consuming stuffs like printing log. ++ * 3. HW keep receiving during step2, and triggered RX_CMD_NO_MORE_INT to set rx->stopped to true in the IRQ handler. ++ * 4. CPU returns to Tasklet, after step2 the tasklet sees rx->stopped == true, therefore kick RX_READY to restart RX. ++ * 5. Issue reproduced, RX restarted without stored packets consumed, and get overwritten later. ++ */ ++ tasklet_hi_schedule(&priv->rx.tasklet); ++ } else { ++ rx->stopped = false; ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, RX_CMD_READY, ++ RX_CMD_READY); ++ } ++ } ++} ++ ++static void aspeed_mctp_rx_chan_init(struct mctp_channel *rx) ++{ ++ struct aspeed_mctp *priv = container_of(rx, typeof(*priv), rx); ++ u32 *rx_cmd = (u32 *)rx->cmd.vaddr; ++ struct aspeed_mctp_rx_cmd *rx_cmd_64 = ++ (struct aspeed_mctp_rx_cmd *)rx->cmd.vaddr; ++ u32 data_size = priv->match_data->packet_unit_size; ++ u32 hw_rx_count = priv->rx_packet_count; ++ struct mctp_pcie_packet_data *rx_buf = (struct mctp_pcie_packet_data *)rx->data.vaddr; ++ int i; ++ ++ if (priv->match_data->vdm_hdr_direct_xfer) { ++ if (priv->match_data->dma_need_64bits_width) { ++ for (i = 0; i < priv->rx_packet_count; i++) { ++ rx_cmd_64->rx_hi = ++ upper_32_bits((rx->data.dma_handle + data_size * i)); ++ rx_cmd_64->rx_lo = ++ (rx->data.dma_handle + data_size * i) & ++ GENMASK(31, 4); ++ rx_cmd_64->rx_lo |= RX_INTERRUPT_AFTER_CMD; ++ rx_cmd_64++; ++ } ++ } else { ++ for (i = 0; i < priv->rx_packet_count; i++) { ++ *rx_cmd = RX_DATA_ADDR(rx->data.dma_handle + data_size * i); ++ *rx_cmd |= RX_INTERRUPT_AFTER_CMD; ++ rx_cmd++; ++ } ++ } ++ } else { ++ for (i = 0; i < priv->rx_packet_count; i++) { ++ rx_cmd_64->rx_hi = RX_DATA_ADDR_2500(rx->data.dma_handle + data_size * i); ++ rx_cmd_64->rx_lo = 0; ++ if (i == priv->rx_packet_count - 1) ++ rx_cmd_64->rx_hi |= RX_LAST_CMD; ++ rx_cmd_64++; ++ } ++ } ++ /* Clear the header of rx data */ ++ for (i = 0; i < priv->rx_packet_count; i++) ++ *(u32 *)&rx_buf[i] = 0; ++ rx->wr_ptr = 0; ++ rx->buffer_count = priv->rx_packet_count; ++ if (priv->match_data->fifo_auto_surround) { ++ /* ++ * TODO: Once read pointer runaway bug is fixed in some future AST2x00 ++ * stepping then add chip revision detection and turn on this ++ * workaround only when needed ++ */ ++ if (priv->match_data->dma_need_64bits_width) ++ priv->rx_runaway_wa.enable = false; ++ else ++ priv->rx_runaway_wa.enable = ++ (chip_version(priv->dev) == ASPEED_MCTP_2600) ? ++ true : ++ false; ++ ++ /* ++ * Hardware does not wrap around ASPEED_MCTP_RX_BUF_SIZE ++ * correctly - we have to set number of buffers to n/4 -1 ++ */ ++ if (priv->rx_runaway_wa.enable) ++ hw_rx_count = (priv->rx_packet_count / 4 - 1); ++ ++ regmap_write(priv->map, ASPEED_MCTP_RX_BUF_SIZE, hw_rx_count); ++ } ++} ++ ++static void aspeed_mctp_tx_chan_init(struct mctp_channel *tx) ++{ ++ struct aspeed_mctp *priv = container_of(tx, typeof(*priv), tx); ++ ++ tx->wr_ptr = 0; ++ tx->rd_ptr = 0; ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, TX_CMD_TRIGGER, 0); ++ regmap_write(priv->map, ASPEED_MCTP_TX_BUF_ADDR, tx->cmd.dma_handle); ++ if (priv->match_data->dma_need_64bits_width) ++ regmap_write(priv->map, ASPEED_MCTP_TX_BUF_HI_ADDR, ++ upper_32_bits(tx->cmd.dma_handle)); ++ if (priv->match_data->fifo_auto_surround) { ++ regmap_write(priv->map, ASPEED_MCTP_TX_BUF_SIZE, TX_PACKET_COUNT); ++ regmap_write(priv->map, ASPEED_MCTP_TX_BUF_WR_PTR, 0); ++ } ++} ++ ++struct mctp_client *aspeed_mctp_create_client(struct aspeed_mctp *priv) ++{ ++ struct mctp_client *client; ++ ++ client = aspeed_mctp_client_alloc(priv); ++ if (!client) ++ return NULL; ++ ++ init_waitqueue_head(&client->wait_queue); ++ ++ spin_lock_bh(&priv->clients_lock); ++ list_add_tail(&client->link, &priv->clients); ++ spin_unlock_bh(&priv->clients_lock); ++ ++ return client; ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_create_client); ++ ++static int aspeed_mctp_open(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *misc = file->private_data; ++ struct platform_device *pdev = to_platform_device(misc->parent); ++ struct aspeed_mctp *priv = platform_get_drvdata(pdev); ++ struct mctp_client *client; ++ ++ client = aspeed_mctp_create_client(priv); ++ if (!client) ++ return -ENOMEM; ++ ++ file->private_data = client; ++ ++ return 0; ++} ++ ++void aspeed_mctp_delete_client(struct mctp_client *client) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ struct mctp_type_handler *handler, *tmp; ++ ++ spin_lock_bh(&priv->clients_lock); ++ ++ list_del(&client->link); ++ ++ if (priv->default_client == client) ++ priv->default_client = NULL; ++ ++ list_for_each_entry_safe(handler, tmp, &priv->mctp_type_handlers, ++ link) { ++ if (handler->client == client) { ++ list_del(&handler->link); ++ kfree(handler); ++ } ++ } ++ spin_unlock_bh(&priv->clients_lock); ++ ++ /* Disable the tasklet to appease lockdep */ ++ local_bh_disable(); ++ aspeed_mctp_client_put(client); ++ local_bh_enable(); ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_delete_client); ++ ++static int aspeed_mctp_release(struct inode *inode, struct file *file) ++{ ++ struct mctp_client *client = file->private_data; ++ ++ aspeed_mctp_delete_client(client); ++ ++ return 0; ++} ++ ++#define LEN_MASK_HI GENMASK(9, 8) ++#define LEN_MASK_LO GENMASK(7, 0) ++#define PCI_VDM_HDR_LEN_MASK_LO GENMASK(31, 24) ++#define PCI_VDM_HDR_LEN_MASK_HI GENMASK(17, 16) ++#define PCIE_VDM_HDR_REQUESTER_BDF_MASK GENMASK(31, 16) ++ ++int aspeed_mctp_send_packet(struct mctp_client *client, ++ struct mctp_pcie_packet *packet) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ u32 *hdr_dw = (u32 *)packet->data.hdr; ++ u8 *hdr = (u8 *)packet->data.hdr; ++ u8 *payload = (u8 *)packet->data.payload; ++ u16 packet_data_sz_dw; ++ u16 pci_data_len_dw; ++ int ret; ++ u16 bdf; ++ ++ ret = _get_bdf(priv); ++ if (ret < 0) ++ return ret; ++ bdf = ret; ++ ++ /* ++ * If the data size is different from contents of PCIe VDM header, ++ * aspeed_mctp_tx_cmd will be programmed incorrectly. This may cause ++ * MCTP HW to stop working. ++ */ ++ pci_data_len_dw = FIELD_PREP(LEN_MASK_LO, FIELD_GET(PCI_VDM_HDR_LEN_MASK_LO, hdr_dw[0])) | ++ FIELD_PREP(LEN_MASK_HI, FIELD_GET(PCI_VDM_HDR_LEN_MASK_HI, hdr_dw[0])); ++ if (pci_data_len_dw == 0) /* According to PCIe Spec, 0 means 1024 DW */ ++ pci_data_len_dw = SZ_1K; ++ ++ packet_data_sz_dw = packet->size / sizeof(u32) - sizeof(packet->data.hdr) / sizeof(u32); ++ if (packet_data_sz_dw != pci_data_len_dw) ++ return -EINVAL; ++ ++ be32p_replace_bits(&hdr_dw[1], bdf, PCIE_VDM_HDR_REQUESTER_BDF_MASK); ++ ++ /* ++ * XXX Don't update EID for MCTP Control messages - old EID may ++ * interfere with MCTP discovery flow. ++ */ ++ if (priv->eid && payload[MCTP_PAYLOAD_TYPE_OFFSET] != MCTP_HDR_TYPE_CONTROL) ++ hdr[MCTP_HDR_SRC_EID_OFFSET] = priv->eid; ++ ++ ret = ptr_ring_produce_bh(&client->tx_queue, packet); ++ if (!ret) ++ tasklet_hi_schedule(&priv->tx.tasklet); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_send_packet); ++ ++struct mctp_pcie_packet *aspeed_mctp_receive_packet(struct mctp_client *client, ++ unsigned long timeout) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ int ret; ++ ++ ret = _get_bdf(priv); ++ if (ret < 0) ++ return ERR_PTR(ret); ++ ++ ret = wait_event_interruptible_timeout(client->wait_queue, ++ __ptr_ring_peek(&client->rx_queue), ++ timeout); ++ if (ret < 0) ++ return ERR_PTR(ret); ++ else if (ret == 0) ++ return ERR_PTR(-ETIME); ++ ++ return ptr_ring_consume_bh(&client->rx_queue); ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_receive_packet); ++ ++void aspeed_mctp_flush_rx_queue(struct mctp_client *client) ++{ ++ struct mctp_pcie_packet *packet; ++ ++ while ((packet = ptr_ring_consume_bh(&client->rx_queue))) ++ aspeed_mctp_packet_free(packet); ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_flush_rx_queue); ++ ++static ssize_t aspeed_mctp_read(struct file *file, char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_client *client = file->private_data; ++ struct aspeed_mctp *priv = client->priv; ++ struct mctp_pcie_packet *rx_packet; ++ u32 mctp_ctrl; ++ u32 mctp_int_sts; ++ ++ if (count < PCIE_MCTP_MIN_PACKET_SIZE) ++ return -EINVAL; ++ ++ if (count > sizeof(rx_packet->data)) ++ count = sizeof(rx_packet->data); ++ ++ if (priv->miss_mctp_int) { ++ regmap_read(priv->map, ASPEED_MCTP_CTRL, &mctp_ctrl); ++ if (!(mctp_ctrl & RX_CMD_READY)) ++ priv->rx.stopped = true; ++ /* Polling the RX_CMD_RECEIVE_INT to ensure rx_tasklet can find the data */ ++ regmap_read(priv->map, ASPEED_MCTP_INT_STS, &mctp_int_sts); ++ if (mctp_int_sts & RX_CMD_RECEIVE_INT) ++ regmap_write(priv->map, ASPEED_MCTP_INT_STS, ++ mctp_int_sts); ++ } ++ ++ tasklet_hi_schedule(&priv->rx.tasklet); ++ rx_packet = ptr_ring_consume_bh(&client->rx_queue); ++ if (!rx_packet) ++ return -EAGAIN; ++ ++ if (copy_to_user(buf, &rx_packet->data, count)) { ++ dev_err(priv->dev, "copy to user failed\n"); ++ count = -EFAULT; ++ } ++ ++ aspeed_mctp_packet_free(rx_packet); ++ ++ return count; ++} ++ ++static void aspeed_mctp_flush_tx_queue(struct mctp_client *client) ++{ ++ struct mctp_pcie_packet *packet; ++ ++ while ((packet = ptr_ring_consume_bh(&client->tx_queue))) ++ aspeed_mctp_packet_free(packet); ++} ++ ++static void aspeed_mctp_flush_all_tx_queues(struct aspeed_mctp *priv) ++{ ++ struct mctp_client *client; ++ ++ spin_lock_bh(&priv->clients_lock); ++ list_for_each_entry(client, &priv->clients, link) ++ aspeed_mctp_flush_tx_queue(client); ++ spin_unlock_bh(&priv->clients_lock); ++} ++ ++static ssize_t aspeed_mctp_write(struct file *file, const char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_client *client = file->private_data; ++ struct aspeed_mctp *priv = client->priv; ++ struct mctp_pcie_packet *tx_packet; ++ int ret; ++ ++ if (count < PCIE_MCTP_MIN_PACKET_SIZE) ++ return -EINVAL; ++ ++ if (count > sizeof(tx_packet->data)) ++ return -ENOSPC; ++ ++ tx_packet = aspeed_mctp_packet_alloc(GFP_KERNEL); ++ if (!tx_packet) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ if (copy_from_user(&tx_packet->data, buf, count)) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ ret = -EFAULT; ++ goto out_packet; ++ } ++ ++ tx_packet->size = count; ++ ++ ret = aspeed_mctp_send_packet(client, tx_packet); ++ if (ret) ++ goto out_packet; ++ ++ return count; ++ ++out_packet: ++ aspeed_mctp_packet_free(tx_packet); ++out: ++ return ret; ++} ++ ++int aspeed_mctp_add_type_handler(struct mctp_client *client, u8 mctp_type, ++ u16 pci_vendor_id, u16 vdm_type, u16 vdm_mask) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ struct mctp_type_handler *handler, *new_handler; ++ int ret = 0; ++ ++ if (mctp_type <= MCTP_HDR_TYPE_BASE_LAST) { ++ /* Vendor, type and type mask must be zero for types 0-5 */ ++ if (pci_vendor_id != 0 || vdm_type != 0 || vdm_mask != 0) ++ return -EINVAL; ++ } else if (mctp_type == MCTP_HDR_TYPE_VDM_PCI) { ++ /* For Vendor Defined PCI type the vendor ID must be nonzero */ ++ if (pci_vendor_id == 0 || pci_vendor_id == 0xffff) ++ return -EINVAL; ++ } else { ++ return -EINVAL; ++ } ++ ++ new_handler = kzalloc(sizeof(*new_handler), GFP_KERNEL); ++ if (!new_handler) ++ return -ENOMEM; ++ new_handler->mctp_type = mctp_type; ++ new_handler->pci_vendor_id = pci_vendor_id; ++ new_handler->vdm_type = vdm_type & vdm_mask; ++ new_handler->vdm_mask = vdm_mask; ++ new_handler->client = client; ++ ++ spin_lock_bh(&priv->clients_lock); ++ list_for_each_entry(handler, &priv->mctp_type_handlers, link) { ++ if (handler->mctp_type == new_handler->mctp_type && ++ handler->pci_vendor_id == new_handler->pci_vendor_id && ++ handler->vdm_type == new_handler->vdm_type) { ++ if (handler->client != new_handler->client) ++ ret = -EBUSY; ++ kfree(new_handler); ++ goto out_unlock; ++ } ++ } ++ list_add_tail(&new_handler->link, &priv->mctp_type_handlers); ++out_unlock: ++ spin_unlock_bh(&priv->clients_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_add_type_handler); ++ ++static int aspeed_mctp_remove_type_handler(struct mctp_client *client, ++ u8 mctp_type, u16 pci_vendor_id, ++ u16 vdm_type, u16 vdm_mask) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ struct mctp_type_handler *handler, *tmp; ++ int ret = -EINVAL; ++ ++ vdm_type &= vdm_mask; ++ ++ spin_lock_bh(&priv->clients_lock); ++ list_for_each_entry_safe(handler, tmp, &priv->mctp_type_handlers, ++ link) { ++ if (handler->client == client && ++ handler->mctp_type == mctp_type && ++ handler->pci_vendor_id == pci_vendor_id && ++ handler->vdm_type == vdm_type) { ++ list_del(&handler->link); ++ kfree(handler); ++ ret = 0; ++ break; ++ } ++ } ++ spin_unlock_bh(&priv->clients_lock); ++ return ret; ++} ++ ++int aspeed_mctp_register_default_handler(struct mctp_client *client) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ int ret = 0; ++ ++ spin_lock_bh(&priv->clients_lock); ++ ++ if (!priv->default_client) ++ priv->default_client = client; ++ else if (priv->default_client != client) ++ ret = -EBUSY; ++ ++ spin_unlock_bh(&priv->clients_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_register_default_handler); ++ ++static int ++aspeed_mctp_register_type_handler(struct mctp_client *client, ++ void __user *userbuf) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ struct aspeed_mctp_type_handler_ioctl handler; ++ ++ if (copy_from_user(&handler, userbuf, sizeof(handler))) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ return -EFAULT; ++ } ++ ++ return aspeed_mctp_add_type_handler(client, handler.mctp_type, ++ handler.pci_vendor_id, ++ handler.vendor_type, ++ handler.vendor_type_mask); ++} ++ ++static int ++aspeed_mctp_unregister_type_handler(struct mctp_client *client, ++ void __user *userbuf) ++{ ++ struct aspeed_mctp *priv = client->priv; ++ struct aspeed_mctp_type_handler_ioctl handler; ++ ++ if (copy_from_user(&handler, userbuf, sizeof(handler))) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ return -EFAULT; ++ } ++ ++ return aspeed_mctp_remove_type_handler(client, handler.mctp_type, ++ handler.pci_vendor_id, ++ handler.vendor_type, ++ handler.vendor_type_mask); ++} ++ ++static int ++aspeed_mctp_filter_eid(struct aspeed_mctp *priv, void __user *userbuf) ++{ ++ struct aspeed_mctp_filter_eid eid; ++ ++ if (copy_from_user(&eid, userbuf, sizeof(eid))) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ return -EFAULT; ++ } ++ ++ if (eid.enable) { ++ regmap_update_bits(priv->map, ASPEED_MCTP_EID, ++ MCTP_EID, eid.eid); ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, ++ MATCHING_EID, MATCHING_EID); ++ } else { ++ regmap_update_bits(priv->map, ASPEED_MCTP_CTRL, ++ MATCHING_EID, 0); ++ } ++ return 0; ++} ++ ++static int aspeed_mctp_get_bdf(struct aspeed_mctp *priv, void __user *userbuf) ++{ ++ struct aspeed_mctp_get_bdf bdf = { _get_bdf(priv) }; ++ ++ if (copy_to_user(userbuf, &bdf, sizeof(bdf))) { ++ dev_err(priv->dev, "copy to user failed\n"); ++ return -EFAULT; ++ } ++ return 0; ++} ++ ++static int ++aspeed_mctp_get_medium_id(struct aspeed_mctp *priv, void __user *userbuf) ++{ ++ struct aspeed_mctp_get_medium_id id = { 0x09 }; /* PCIe revision 2.0 */ ++ ++ if (copy_to_user(userbuf, &id, sizeof(id))) { ++ dev_err(priv->dev, "copy to user failed\n"); ++ return -EFAULT; ++ } ++ return 0; ++} ++ ++static int ++aspeed_mctp_get_mtu(struct aspeed_mctp *priv, void __user *userbuf) ++{ ++ struct aspeed_mctp_get_mtu id = { ASPEED_MCTP_MTU }; ++ ++ if (copy_to_user(userbuf, &id, sizeof(id))) { ++ dev_err(priv->dev, "copy to user failed\n"); ++ return -EFAULT; ++ } ++ return 0; ++} ++ ++int aspeed_mctp_get_eid_bdf(struct mctp_client *client, u8 eid, u16 *bdf) ++{ ++ struct aspeed_mctp_endpoint *endpoint; ++ int ret = -ENOENT; ++ ++ mutex_lock(&client->priv->endpoints_lock); ++ list_for_each_entry(endpoint, &client->priv->endpoints, link) { ++ if (endpoint->data.eid_info.eid == eid) { ++ *bdf = endpoint->data.eid_info.bdf; ++ ret = 0; ++ break; ++ } ++ } ++ mutex_unlock(&client->priv->endpoints_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_get_eid_bdf); ++ ++int aspeed_mctp_get_eid(struct mctp_client *client, u16 bdf, ++ u8 domain_id, u8 *eid) ++{ ++ struct aspeed_mctp_endpoint *endpoint; ++ int ret = -ENOENT; ++ ++ mutex_lock(&client->priv->endpoints_lock); ++ ++ list_for_each_entry(endpoint, &client->priv->endpoints, link) { ++ if (endpoint->data.eid_ext_info.domain_id == domain_id && ++ endpoint->data.eid_ext_info.bdf == bdf) { ++ *eid = endpoint->data.eid_ext_info.eid; ++ ret = 0; ++ break; ++ } ++ } ++ ++ mutex_unlock(&client->priv->endpoints_lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aspeed_mctp_get_eid); ++ ++static int ++aspeed_mctp_get_eid_info(struct aspeed_mctp *priv, void __user *userbuf, ++ enum mctp_address_type addr_format) ++{ ++ int count = 0; ++ int ret = 0; ++ struct aspeed_mctp_get_eid_info get_eid; ++ struct aspeed_mctp_endpoint *endpoint; ++ void *user_ptr; ++ size_t count_to_copy; ++ ++ if (copy_from_user(&get_eid, userbuf, sizeof(get_eid))) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ return -EFAULT; ++ } ++ ++ mutex_lock(&priv->endpoints_lock); ++ ++ if (get_eid.count == 0) { ++ count = priv->endpoints_count; ++ goto out_unlock; ++ } ++ ++ user_ptr = u64_to_user_ptr(get_eid.ptr); ++ count_to_copy = get_eid.count > priv->endpoints_count ? ++ priv->endpoints_count : get_eid.count; ++ list_for_each_entry(endpoint, &priv->endpoints, link) { ++ if (endpoint->data.eid_info.eid < get_eid.start_eid) ++ continue; ++ if (count >= count_to_copy) ++ break; ++ ++ if (addr_format == ASPEED_MCTP_EXTENDED_ADDR_FORMAT) ++ ret = copy_to_user(&(((struct aspeed_mctp_eid_ext_info *) ++ user_ptr)[count]), ++ &endpoint->data, ++ sizeof(struct aspeed_mctp_eid_ext_info)); ++ else ++ ret = copy_to_user(&(((struct aspeed_mctp_eid_info *) ++ user_ptr)[count]), ++ &endpoint->data, ++ sizeof(struct aspeed_mctp_eid_info)); ++ ++ if (ret) { ++ dev_err(priv->dev, "copy to user failed\n"); ++ ret = -EFAULT; ++ goto out_unlock; ++ } ++ count++; ++ } ++ ++out_unlock: ++ get_eid.count = count; ++ if (copy_to_user(userbuf, &get_eid, sizeof(get_eid))) { ++ dev_err(priv->dev, "copy to user failed\n"); ++ ret = -EFAULT; ++ } ++ ++ mutex_unlock(&priv->endpoints_lock); ++ return ret; ++} ++ ++static int ++eid_info_cmp(void *priv, const struct list_head *a, const struct list_head *b) ++{ ++ struct aspeed_mctp_endpoint *endpoint_a; ++ struct aspeed_mctp_endpoint *endpoint_b; ++ ++ if (a == b) ++ return 0; ++ ++ endpoint_a = list_entry(a, typeof(*endpoint_a), link); ++ endpoint_b = list_entry(b, typeof(*endpoint_b), link); ++ ++ if (endpoint_a->data.eid_info.eid < endpoint_b->data.eid_info.eid) ++ return -1; ++ else if (endpoint_a->data.eid_info.eid > endpoint_b->data.eid_info.eid) ++ return 1; ++ ++ return 0; ++} ++ ++static void aspeed_mctp_eid_info_list_remove(struct list_head *list) ++{ ++ struct aspeed_mctp_endpoint *endpoint; ++ struct aspeed_mctp_endpoint *tmp; ++ ++ list_for_each_entry_safe(endpoint, tmp, list, link) { ++ list_del(&endpoint->link); ++ kfree(endpoint); ++ } ++} ++ ++static bool ++aspeed_mctp_eid_info_list_valid(struct list_head *list) ++{ ++ struct aspeed_mctp_endpoint *endpoint; ++ struct aspeed_mctp_endpoint *next; ++ ++ list_for_each_entry(endpoint, list, link) { ++ next = list_next_entry(endpoint, link); ++ if (&next->link == list) ++ break; ++ ++ /* duplicted eids */ ++ if (next->data.eid_info.eid == endpoint->data.eid_info.eid) ++ return false; ++ } ++ ++ return true; ++} ++ ++static int ++aspeed_mctp_set_eid_info(struct aspeed_mctp *priv, void __user *userbuf, ++ enum mctp_address_type addr_format) ++{ ++ struct list_head list = LIST_HEAD_INIT(list); ++ struct aspeed_mctp_set_eid_info set_eid; ++ void *user_ptr; ++ struct aspeed_mctp_endpoint *endpoint; ++ int ret = 0; ++ u8 eid = 0; ++ size_t i; ++ ++ if (copy_from_user(&set_eid, userbuf, sizeof(set_eid))) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ return -EFAULT; ++ } ++ ++ if (set_eid.count > ASPEED_MCTP_EID_INFO_MAX) ++ return -EINVAL; ++ ++ user_ptr = u64_to_user_ptr(set_eid.ptr); ++ for (i = 0; i < set_eid.count; i++) { ++ endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL); ++ if (!endpoint) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ memset(endpoint, 0, sizeof(*endpoint)); ++ ++ if (addr_format == ASPEED_MCTP_EXTENDED_ADDR_FORMAT) ++ ret = copy_from_user(&endpoint->data, ++ &(((struct aspeed_mctp_eid_ext_info *) ++ user_ptr)[i]), ++ sizeof(struct aspeed_mctp_eid_ext_info)); ++ else ++ ret = copy_from_user(&endpoint->data, ++ &(((struct aspeed_mctp_eid_info *) ++ user_ptr)[i]), ++ sizeof(struct aspeed_mctp_eid_info)); ++ ++ if (ret) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ kfree(endpoint); ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ /* Detect self EID */ ++ if (_get_bdf(priv) == endpoint->data.eid_info.bdf) { ++ /* ++ * XXX Use smallest EID with matching BDF. ++ * On some platforms there could be multiple endpoints ++ * with same BDF in routing table. ++ */ ++ if (eid == 0 || endpoint->data.eid_info.eid < eid) ++ eid = endpoint->data.eid_info.eid; ++ } ++ ++ list_add_tail(&endpoint->link, &list); ++ } ++ ++ list_sort(NULL, &list, eid_info_cmp); ++ if (!aspeed_mctp_eid_info_list_valid(&list)) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ mutex_lock(&priv->endpoints_lock); ++ if (list_empty(&priv->endpoints)) ++ list_splice_init(&list, &priv->endpoints); ++ else ++ list_swap(&list, &priv->endpoints); ++ priv->endpoints_count = set_eid.count; ++ priv->eid = eid; ++ mutex_unlock(&priv->endpoints_lock); ++out: ++ aspeed_mctp_eid_info_list_remove(&list); ++ return ret; ++} ++ ++static int aspeed_mctp_set_own_eid(struct aspeed_mctp *priv, void __user *userbuf) ++{ ++ struct aspeed_mctp_set_own_eid data; ++ ++ if (copy_from_user(&data, userbuf, sizeof(data))) { ++ dev_err(priv->dev, "copy from user failed\n"); ++ return -EFAULT; ++ } ++ ++ priv->eid = data.eid; ++ ++ return 0; ++} ++ ++static long ++aspeed_mctp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct mctp_client *client = file->private_data; ++ struct aspeed_mctp *priv = client->priv; ++ void __user *userbuf = (void __user *)arg; ++ int ret; ++ ++ switch (cmd) { ++ case ASPEED_MCTP_IOCTL_FILTER_EID: ++ ret = aspeed_mctp_filter_eid(priv, userbuf); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_GET_BDF: ++ ret = aspeed_mctp_get_bdf(priv, userbuf); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_GET_MEDIUM_ID: ++ ret = aspeed_mctp_get_medium_id(priv, userbuf); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_GET_MTU: ++ ret = aspeed_mctp_get_mtu(priv, userbuf); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_REGISTER_DEFAULT_HANDLER: ++ ret = aspeed_mctp_register_default_handler(client); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_REGISTER_TYPE_HANDLER: ++ ret = aspeed_mctp_register_type_handler(client, userbuf); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_UNREGISTER_TYPE_HANDLER: ++ ret = aspeed_mctp_unregister_type_handler(client, userbuf); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_GET_EID_INFO: ++ ret = aspeed_mctp_get_eid_info(priv, userbuf, ASPEED_MCTP_GENERIC_ADDR_FORMAT); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_GET_EID_EXT_INFO: ++ ret = aspeed_mctp_get_eid_info(priv, userbuf, ASPEED_MCTP_EXTENDED_ADDR_FORMAT); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_SET_EID_INFO: ++ ret = aspeed_mctp_set_eid_info(priv, userbuf, ASPEED_MCTP_GENERIC_ADDR_FORMAT); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_SET_EID_EXT_INFO: ++ ret = aspeed_mctp_set_eid_info(priv, userbuf, ASPEED_MCTP_EXTENDED_ADDR_FORMAT); ++ break; ++ ++ case ASPEED_MCTP_IOCTL_SET_OWN_EID: ++ ret = aspeed_mctp_set_own_eid(priv, userbuf); ++ break; ++ ++ default: ++ dev_err(priv->dev, "Command not found\n"); ++ ret = -ENOTTY; ++ } ++ ++ return ret; ++} ++ ++static __poll_t aspeed_mctp_poll(struct file *file, ++ struct poll_table_struct *pt) ++{ ++ struct mctp_client *client = file->private_data; ++ __poll_t ret = 0; ++ struct aspeed_mctp *priv = client->priv; ++ struct mctp_channel *rx = &priv->rx; ++ u32 mctp_ctrl; ++ u32 mctp_int_sts; ++ ++ if (priv->miss_mctp_int) { ++ regmap_read(priv->map, ASPEED_MCTP_CTRL, &mctp_ctrl); ++ if (!(mctp_ctrl & RX_CMD_READY)) ++ rx->stopped = true; ++ /* Polling the RX_CMD_RECEIVE_INT to ensure rx_tasklet can find the data */ ++ regmap_read(priv->map, ASPEED_MCTP_INT_STS, &mctp_int_sts); ++ if (mctp_int_sts & RX_CMD_RECEIVE_INT) ++ regmap_write(priv->map, ASPEED_MCTP_INT_STS, ++ mctp_int_sts); ++ } ++ ++ tasklet_hi_schedule(&priv->rx.tasklet); ++ poll_wait(file, &client->wait_queue, pt); ++ ++ if (!ptr_ring_full_bh(&client->tx_queue)) ++ ret |= EPOLLOUT; ++ ++ if (__ptr_ring_peek(&client->rx_queue)) ++ ret |= EPOLLIN; ++ ++ return ret; ++} ++ ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++static int aspeed_mctp_pcie_vdm_op_send_pkt(struct device *dev, ++ u8 *data, size_t size) ++{ ++ struct mctp_pcie_packet *packet; ++ struct platform_device *pdev; ++ struct aspeed_mctp *priv; ++ int rc; ++ ++ pdev = to_platform_device(dev); ++ priv = platform_get_drvdata(pdev); ++ // freed at aspeed-mctp tx tasklet or send failure ++ packet = aspeed_mctp_packet_alloc(GFP_KERNEL); ++ ++ if (!packet) { ++ dev_err(priv->dev, "failed to alloc packet\n"); ++ return -ENOMEM; ++ } ++ ++ memcpy((u8 *)&packet->data.hdr, data, PCIE_VDM_HDR_SIZE); ++ memcpy((u8 *)&packet->data.payload, data + PCIE_VDM_HDR_SIZE, size); ++ packet->size = (size + PCIE_VDM_HDR_SIZE); ++ ++ rc = aspeed_mctp_send_packet(priv->default_client, packet); ++ if (rc) { ++ dev_err(priv->dev, "failed to send packet\n"); ++ aspeed_mctp_packet_free(packet); ++ return rc; ++ } ++ return 0; ++} ++ ++static u8 *aspeed_mctp_pcie_vdm_op_recv_pkt(struct device *dev) ++{ ++ struct platform_device *pdev; ++ struct aspeed_mctp *priv; ++ struct mctp_pcie_packet *rx_packet; ++ ++ pdev = to_platform_device(dev); ++ priv = platform_get_drvdata(pdev); ++ rx_packet = aspeed_mctp_receive_packet(priv->default_client, 0); ++ ++ if (IS_ERR(rx_packet)) { ++ if (PTR_ERR(rx_packet) == -ETIME) { ++ dev_dbg(priv->dev, "all packet received\n"); ++ } else { ++ dev_err(priv->dev, "failed to receive packet: %ld\n", ++ PTR_ERR(rx_packet)); ++ } ++ return (u8 *)rx_packet; ++ } ++ return (u8 *)&rx_packet->data; ++} ++ ++static void aspeed_mctp_pcie_vdm_op_uninit(struct device *dev) ++{ ++ struct platform_device *pdev; ++ struct aspeed_mctp *priv; ++ ++ pdev = to_platform_device(dev); ++ priv = platform_get_drvdata(pdev); ++ ++ aspeed_mctp_flush_all_tx_queues(priv); ++ aspeed_mctp_flush_rx_queue(priv->default_client); ++ aspeed_mctp_delete_client(priv->default_client); ++} ++ ++static const struct mctp_pcie_vdm_ops aspeed_mctp_pcie_vdm_ops = { ++ .send_packet = aspeed_mctp_pcie_vdm_op_send_pkt, ++ .recv_packet = aspeed_mctp_pcie_vdm_op_recv_pkt, ++ .free_packet = aspeed_mctp_packet_free, ++ .uninit = aspeed_mctp_pcie_vdm_op_uninit, ++}; ++ ++static void aspeed_mctp_pcie_vdm_register(struct aspeed_mctp *priv) ++{ ++ struct net_device *ndev; ++ struct mctp_client *client; ++ char ifname[IFNAMSIZ]; ++ ++ /** use priv's default client to send/receive mctp packets */ ++ client = aspeed_mctp_create_client(priv); ++ aspeed_mctp_register_default_handler(client); ++ ++ snprintf(ifname, IFNAMSIZ, "mctppci%d", priv->dev_id); ++ ndev = mctp_pcie_vdm_add_dev(priv->dev, &aspeed_mctp_pcie_vdm_ops, ifname); ++ if (IS_ERR(ndev)) ++ dev_err(priv->dev, "Failed to add mctp pcie vdm device Err %ld\n", PTR_ERR(ndev)); ++ priv->ndev = ndev; ++} ++ ++#endif ++ ++static const struct file_operations aspeed_mctp_fops = { ++ .owner = THIS_MODULE, ++ .open = aspeed_mctp_open, ++ .release = aspeed_mctp_release, ++ .read = aspeed_mctp_read, ++ .write = aspeed_mctp_write, ++ .unlocked_ioctl = aspeed_mctp_ioctl, ++ .poll = aspeed_mctp_poll, ++}; ++ ++static const struct regmap_config aspeed_mctp_regmap_cfg = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = ASPEED_G7_MCTP_PCIE_BDF, ++}; ++ ++struct device_type aspeed_mctp_type = { ++ .name = "aspeed-mctp", ++}; ++ ++static void aspeed_mctp_send_pcie_uevent(struct kobject *kobj, bool ready) ++{ ++ char buf[32]; ++ char *envp[2]; ++ ++ snprintf(buf, sizeof(buf), ASPEED_MCTP_READY "=%d", ready ? 1 : 0); ++ envp[0] = buf; ++ envp[1] = NULL; ++ ++ kobject_uevent_env(kobj, KOBJ_CHANGE, envp); ++} ++ ++static void aspeed_mctp_irq_enable(struct aspeed_mctp *priv) ++{ ++ u32 enable = TX_CMD_SENT_INT | TX_CMD_WRONG_INT | ++ RX_CMD_RECEIVE_INT | RX_CMD_NO_MORE_INT; ++ ++ regmap_write(priv->map, ASPEED_MCTP_INT_EN, enable); ++} ++ ++static void aspeed_mctp_irq_disable(struct aspeed_mctp *priv) ++{ ++ regmap_write(priv->map, ASPEED_MCTP_INT_EN, 0); ++} ++ ++static void aspeed_mctp_pcie_setup(struct aspeed_mctp *priv) ++{ ++ int ret; ++ u8 tx_max_payload_size; ++ u8 rx_max_payload_size; ++ struct kobject *kobj = &priv->mctp_miscdev.this_device->kobj; ++ ++ ret = _get_bdf(priv); ++ ++ if (ret >= 0) { ++ cancel_delayed_work(&priv->pcie.rst_dwork); ++ if (priv->match_data->need_address_mapping) ++ regmap_update_bits(priv->map, ASPEED_MCTP_EID, ++ MEMORY_SPACE_MAPPING, BIT(31)); ++ ++ /* Only set TX MPS since HW will parse RX packet to decide how many bytes to receive ++ * based on the length field in PCIe VDM header. ++ */ ++ if (priv->match_data->dma_need_64bits_width) { ++ tx_max_payload_size = ++ FIELD_GET(TX_MAX_PAYLOAD_SIZE_MASK, ++ ilog2(ASPEED_MCTP_MTU >> 6)); ++ } else { ++ /* ++ * In ast2600, tx som and eom will not match expected result. ++ * e.g. When Maximum Transmit Unit (MTU) set to 64 byte, and then transfer ++ * size set between 61 ~ 124 (MTU-3 ~ 2*MTU-4), the engine will set all ++ * packet vdm header eom to 1, no matter what it setted. To fix that ++ * issue, the driver set MTU to next level(e.g. 64 to 128). ++ */ ++ tx_max_payload_size = ++ FIELD_GET(TX_MAX_PAYLOAD_SIZE_MASK, ++ fls(ASPEED_MCTP_MTU >> 6)); ++ } ++ ++ regmap_update_bits(priv->map, ASPEED_MCTP_ENGINE_CTRL, ++ TX_MAX_PAYLOAD_SIZE_MASK | RX_MAX_PAYLOAD_SIZE_MASK, ++ (rx_max_payload_size << RX_MAX_PAYLOAD_SIZE_SHIFT) | tx_max_payload_size); ++ ++ aspeed_mctp_flush_all_tx_queues(priv); ++ if (!priv->miss_mctp_int) { ++ aspeed_mctp_irq_enable(priv); ++ } else { ++ if (priv->rx_det_period_us) ++ schedule_delayed_work(&priv->rx_det_dwork, ++ usecs_to_jiffies(priv->rx_det_period_us)); ++ } ++ aspeed_mctp_rx_trigger(&priv->rx); ++ aspeed_mctp_send_pcie_uevent(kobj, true); ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++ mctp_pcie_vdm_set_carrier(priv->ndev, true); ++#endif ++ } else { ++ schedule_delayed_work(&priv->pcie.rst_dwork, ++ msecs_to_jiffies(1000)); ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++ mctp_pcie_vdm_set_carrier(priv->ndev, false); ++#endif ++ } ++} ++ ++static void aspeed_mctp_reset_work(struct work_struct *work) ++{ ++ struct aspeed_mctp *priv = container_of(work, typeof(*priv), ++ pcie.rst_dwork.work); ++ struct kobject *kobj = &priv->mctp_miscdev.this_device->kobj; ++ ++ if (priv->pcie.need_uevent) { ++ aspeed_mctp_send_pcie_uevent(kobj, false); ++ priv->pcie.need_uevent = false; ++ } ++ ++ aspeed_mctp_pcie_setup(priv); ++} ++ ++static void aspeed_mctp_rx_detect_work(struct work_struct *work) ++{ ++ struct aspeed_mctp *priv = ++ container_of(work, typeof(*priv), rx_det_dwork.work); ++ ++ tasklet_hi_schedule(&priv->rx.tasklet); ++ schedule_delayed_work(&priv->rx_det_dwork, ++ usecs_to_jiffies(priv->rx_det_period_us)); ++} ++ ++static void aspeed_mctp_channels_init(struct aspeed_mctp *priv) ++{ ++ aspeed_mctp_rx_chan_init(&priv->rx); ++ aspeed_mctp_tx_chan_init(&priv->tx); ++} ++ ++static irqreturn_t aspeed_mctp_irq_handler(int irq, void *arg) ++{ ++ struct aspeed_mctp *priv = arg; ++ u32 handled = 0; ++ u32 status; ++ ++ regmap_read(priv->map, ASPEED_MCTP_INT_STS, &status); ++ regmap_write(priv->map, ASPEED_MCTP_INT_STS, status); ++ ++ if (status & TX_CMD_SENT_INT) { ++ tasklet_hi_schedule(&priv->tx.tasklet); ++ if (!priv->match_data->fifo_auto_surround) ++ priv->tx.rd_ptr = (priv->tx.rd_ptr + 1) % TX_PACKET_COUNT; ++ handled |= TX_CMD_SENT_INT; ++ } ++ ++ if (status & TX_CMD_WRONG_INT) { ++ /* TODO: print the actual command */ ++ dev_warn(priv->dev, "TX wrong"); ++ ++ handled |= TX_CMD_WRONG_INT; ++ } ++ ++ if (status & RX_CMD_RECEIVE_INT) { ++ tasklet_hi_schedule(&priv->rx.tasklet); ++ ++ handled |= RX_CMD_RECEIVE_INT; ++ } ++ ++ if (status & RX_CMD_NO_MORE_INT) { ++ dev_dbg(priv->dev, "RX full"); ++ priv->rx.stopped = true; ++ tasklet_hi_schedule(&priv->rx.tasklet); ++ ++ handled |= RX_CMD_NO_MORE_INT; ++ } ++ ++ if (!handled) ++ return IRQ_NONE; ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t aspeed_mctp_pcie_rst_irq_handler(int irq, void *arg) ++{ ++ struct aspeed_mctp *priv = arg; ++ ++ aspeed_mctp_channels_init(priv); ++ ++ priv->pcie.need_uevent = true; ++ priv->eid = 0; ++ ++ schedule_delayed_work(&priv->pcie.rst_dwork, 0); ++ ++ return IRQ_HANDLED; ++} ++ ++static void aspeed_mctp_drv_init(struct aspeed_mctp *priv) ++{ ++ INIT_LIST_HEAD(&priv->clients); ++ INIT_LIST_HEAD(&priv->mctp_type_handlers); ++ INIT_LIST_HEAD(&priv->endpoints); ++ ++ spin_lock_init(&priv->clients_lock); ++ mutex_init(&priv->endpoints_lock); ++ ++ INIT_DELAYED_WORK(&priv->pcie.rst_dwork, aspeed_mctp_reset_work); ++ ++ tasklet_init(&priv->tx.tasklet, aspeed_mctp_tx_tasklet, ++ (unsigned long)&priv->tx); ++ tasklet_init(&priv->rx.tasklet, aspeed_mctp_rx_tasklet, ++ (unsigned long)&priv->rx); ++} ++ ++static void aspeed_mctp_drv_fini(struct aspeed_mctp *priv) ++{ ++ aspeed_mctp_eid_info_list_remove(&priv->endpoints); ++ tasklet_disable(&priv->tx.tasklet); ++ tasklet_kill(&priv->tx.tasklet); ++ tasklet_disable(&priv->rx.tasklet); ++ tasklet_kill(&priv->rx.tasklet); ++ ++ cancel_delayed_work_sync(&priv->pcie.rst_dwork); ++ if (priv->miss_mctp_int) ++ cancel_delayed_work_sync(&priv->rx_det_dwork); ++} ++ ++static int aspeed_mctp_resources_init(struct aspeed_mctp *priv) ++{ ++ struct platform_device *pdev = to_platform_device(priv->dev); ++ void __iomem *regs; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) { ++ dev_err(priv->dev, "Failed to get regmap!\n"); ++ return PTR_ERR(regs); ++ } ++ ++ priv->map = devm_regmap_init_mmio(priv->dev, regs, ++ &aspeed_mctp_regmap_cfg); ++ if (IS_ERR(priv->map)) ++ return PTR_ERR(priv->map); ++ ++ priv->reset = ++ priv->rc_f ? ++ devm_reset_control_get_by_index(priv->dev, 0) : ++ devm_reset_control_get_shared_by_index(priv->dev, 0); ++ if (IS_ERR(priv->reset)) { ++ dev_err(priv->dev, "Failed to get reset!\n"); ++ return PTR_ERR(priv->reset); ++ } ++ ++ if (priv->match_data->dma_need_64bits_width) { ++ priv->reset_dma = NULL; ++ } else { ++ if (priv->rc_f) { ++ priv->reset_dma = devm_reset_control_get_shared_by_index(priv->dev, 1); ++ if (IS_ERR(priv->reset_dma)) { ++ dev_err(priv->dev, "Failed to get ep reset!\n"); ++ return PTR_ERR(priv->reset_dma); ++ } ++ } ++ } ++ priv->pcie.map = ++ syscon_regmap_lookup_by_phandle(priv->dev->of_node, ++ "aspeed,pcieh"); ++ if (IS_ERR(priv->pcie.map)) { ++ dev_err(priv->dev, "Failed to find PCIe Host regmap!\n"); ++ return PTR_ERR(priv->pcie.map); ++ } ++ ++ platform_set_drvdata(pdev, priv); ++ ++ return 0; ++} ++ ++static void aspeed_release_rmem(void *d) ++{ ++ of_reserved_mem_device_release(d); ++} ++ ++static int aspeed_mctp_dma_init(struct aspeed_mctp *priv) ++{ ++ struct mctp_channel *tx = &priv->tx; ++ struct mctp_channel *rx = &priv->rx; ++ size_t alloc_size; ++ int ret = -ENOMEM; ++ ++ BUILD_BUG_ON(TX_PACKET_COUNT >= TX_MAX_PACKET_COUNT); ++ BUILD_BUG_ON(RX_PACKET_COUNT >= RX_MAX_PACKET_COUNT); ++ ++ ret = dma_set_mask_and_coherent(priv->dev, DMA_BIT_MASK(64)); ++ if (ret) { ++ dev_err(priv->dev, "cannot set 64-bits DMA mask\n"); ++ return ret; ++ } ++ ++ ret = of_reserved_mem_device_init(priv->dev); ++ if (ret) { ++ dev_err(priv->dev, "device does not have specific DMA pool: %d\n", ++ ret); ++ return ret; ++ } ++ ++ ret = devm_add_action_or_reset(priv->dev, aspeed_release_rmem, ++ priv->dev); ++ if (ret) ++ return ret; ++ ++ alloc_size = PAGE_ALIGN(priv->rx_packet_count * priv->match_data->packet_unit_size); ++ rx->data.vaddr = ++ dma_alloc_coherent(priv->dev, alloc_size, &rx->data.dma_handle, GFP_KERNEL); ++ ++ if (!rx->data.vaddr) ++ return -ENOMEM; ++ ++ alloc_size = PAGE_ALIGN(priv->rx_packet_count * priv->match_data->rx_cmd_size); ++ rx->cmd.vaddr = dma_alloc_coherent(priv->dev, alloc_size, &rx->cmd.dma_handle, GFP_KERNEL); ++ ++ if (!rx->cmd.vaddr) ++ goto out_rx_cmd; ++ ++ alloc_size = PAGE_ALIGN(TX_PACKET_COUNT * priv->match_data->packet_unit_size); ++ tx->data.vaddr = ++ dma_alloc_coherent(priv->dev, alloc_size, &tx->data.dma_handle, GFP_KERNEL); ++ ++ if (!tx->data.vaddr) ++ goto out_tx_data; ++ alloc_size = PAGE_ALIGN(TX_PACKET_COUNT * priv->match_data->tx_cmd_size); ++ tx->cmd.vaddr = dma_alloc_coherent(priv->dev, alloc_size, &tx->cmd.dma_handle, GFP_KERNEL); ++ ++ if (!tx->cmd.vaddr) ++ goto out_tx_cmd; ++ ++ return 0; ++out_tx_cmd: ++ alloc_size = PAGE_ALIGN(TX_PACKET_COUNT * ++ priv->match_data->packet_unit_size); ++ dma_free_coherent(priv->dev, alloc_size, tx->data.vaddr, ++ tx->data.dma_handle); ++ ++out_tx_data: ++ alloc_size = PAGE_ALIGN(priv->rx_packet_count * ++ priv->match_data->rx_cmd_size); ++ dma_free_coherent(priv->dev, alloc_size, rx->cmd.vaddr, ++ rx->cmd.dma_handle); ++ ++out_rx_cmd: ++ alloc_size = PAGE_ALIGN(priv->rx_packet_count * ++ priv->match_data->packet_unit_size); ++ dma_free_coherent(priv->dev, alloc_size, rx->data.vaddr, ++ rx->data.dma_handle); ++ ++ return -ENOMEM; ++} ++ ++static void aspeed_mctp_dma_fini(struct aspeed_mctp *priv) ++{ ++ struct mctp_channel *tx = &priv->tx; ++ struct mctp_channel *rx = &priv->rx; ++ size_t free_size; ++ ++ free_size = PAGE_ALIGN(TX_PACKET_COUNT * priv->match_data->tx_cmd_size); ++ dma_free_coherent(priv->dev, free_size, tx->cmd.vaddr, ++ tx->cmd.dma_handle); ++ ++ free_size = PAGE_ALIGN(priv->rx_packet_count * ++ priv->match_data->rx_cmd_size); ++ dma_free_coherent(priv->dev, free_size, rx->cmd.vaddr, ++ rx->cmd.dma_handle); ++ ++ free_size = PAGE_ALIGN(TX_PACKET_COUNT * ++ priv->match_data->packet_unit_size); ++ dma_free_coherent(priv->dev, free_size, tx->data.vaddr, ++ tx->data.dma_handle); ++ ++ free_size = PAGE_ALIGN(priv->rx_packet_count * ++ priv->match_data->packet_unit_size); ++ dma_free_coherent(priv->dev, free_size, rx->data.vaddr, ++ rx->data.dma_handle); ++} ++ ++static int aspeed_mctp_irq_init(struct aspeed_mctp *priv) ++{ ++ struct platform_device *pdev = to_platform_device(priv->dev); ++ int irq, ret; ++ ++ irq = platform_get_irq_byname_optional(pdev, "mctp"); ++ if (irq < 0) { ++ /* mctp irq is option */ ++ priv->miss_mctp_int = 1; ++ INIT_DELAYED_WORK(&priv->rx_det_dwork, aspeed_mctp_rx_detect_work); ++ } else { ++ ret = devm_request_irq(priv->dev, irq, aspeed_mctp_irq_handler, ++ IRQF_SHARED, dev_name(&pdev->dev), priv); ++ if (ret) ++ return ret; ++ aspeed_mctp_irq_enable(priv); ++ } ++ irq = platform_get_irq_byname(pdev, "pcie"); ++ if (irq < 0) ++ return irq; ++ ++ ret = devm_request_irq(priv->dev, irq, aspeed_mctp_pcie_rst_irq_handler, ++ IRQF_SHARED, dev_name(&pdev->dev), priv); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int aspeed_mctp_hw_reset(struct aspeed_mctp *priv) ++{ ++ int ret = 0; ++ ++ ret = reset_control_deassert(priv->reset); ++ if (ret) { ++ dev_warn(priv->dev, "Failed to deassert reset\n"); ++ return ret; ++ } ++ ++ if (priv->rc_f && priv->reset_dma) { ++ ret = reset_control_deassert(priv->reset_dma); ++ if (ret) { ++ dev_warn(priv->dev, "Failed to deassert ep reset\n"); ++ return ret; ++ } ++ } ++ ++ if (priv->match_data->dma_need_64bits_width) ++ ret = pcie_vdm_enable(priv->dev); ++ ++ return ret; ++} ++ ++static int aspeed_mctp_probe(struct platform_device *pdev) ++{ ++ struct aspeed_mctp *priv; ++ int ret, id; ++ const char *name; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ priv->dev = &pdev->dev; ++ priv->rc_f = ++ of_find_property(priv->dev->of_node, "pcie_rc", NULL) ? 1 : 0; ++ priv->match_data = of_device_get_match_data(priv->dev); ++ ++ ret = device_property_read_u32(priv->dev, "aspeed,rx-packet-count", ++ &priv->rx_packet_count); ++ if (ret) { ++ priv->rx_packet_count = RX_PACKET_COUNT; ++ } else if (priv->rx_packet_count % 4 || ++ priv->rx_packet_count >= RX_MAX_PACKET_COUNT) { ++ dev_err(priv->dev, ++ "The aspeed,rx-packet-count:%d should be 4-aligned and less than %ld", ++ priv->rx_packet_count, RX_MAX_PACKET_COUNT); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ ret = device_property_read_u32(priv->dev, "aspeed,rx-ring-count", ++ &priv->rx_ring_count); ++ if (ret) ++ priv->rx_ring_count = RX_RING_COUNT; ++ ++ ret = device_property_read_u32(priv->dev, "aspeed,tx-ring-count", ++ &priv->tx_ring_count); ++ if (ret) ++ priv->tx_ring_count = TX_RING_COUNT; ++ ++ ret = device_property_read_u32(priv->dev, "aspeed,rx-det-period-us", ++ &priv->rx_det_period_us); ++ if (ret) ++ priv->rx_det_period_us = 1000; ++ ++ aspeed_mctp_drv_init(priv); ++ ++ ret = aspeed_mctp_resources_init(priv); ++ if (ret) { ++ dev_err(priv->dev, "Failed to init resources\n"); ++ goto out_drv; ++ } ++ ++ ret = aspeed_mctp_dma_init(priv); ++ if (ret) { ++ dev_err(priv->dev, "Failed to init DMA\n"); ++ goto out_drv; ++ } ++ ++ ret = aspeed_mctp_hw_reset(priv); ++ if (ret) ++ goto out_dma; ++ ++ aspeed_mctp_channels_init(priv); ++ ++ id = of_alias_get_id(priv->dev->of_node, "mctp"); ++ if (id < 0) { ++ ret = id; ++ goto out_dma; ++ } ++ ++ priv->dev_id = id; ++ priv->mctp_miscdev.parent = priv->dev; ++ priv->mctp_miscdev.minor = MISC_DYNAMIC_MINOR; ++ priv->mctp_miscdev.name = devm_kasprintf(priv->dev, GFP_KERNEL, "aspeed-mctp%d", id); ++ priv->mctp_miscdev.fops = &aspeed_mctp_fops; ++ ret = misc_register(&priv->mctp_miscdev); ++ if (ret) { ++ dev_err(priv->dev, "Failed to register miscdev\n"); ++ goto out_dma; ++ } ++ priv->mctp_miscdev.this_device->type = &aspeed_mctp_type; ++ ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++ if (!priv->pcie_vdm_enabled) { ++ aspeed_mctp_pcie_vdm_register(priv); ++ priv->pcie_vdm_enabled = true; ++ } ++#endif ++ ++ ret = aspeed_mctp_irq_init(priv); ++ if (ret) { ++ dev_err(priv->dev, "Failed to init IRQ!\n"); ++ goto out_irq; ++ } ++ aspeed_mctp_pcie_setup(priv); ++ ++ name = devm_kasprintf(priv->dev, GFP_KERNEL, "peci-mctp%d", id); ++ priv->peci_mctp = ++ platform_device_register_data(priv->dev, name, PLATFORM_DEVID_NONE, NULL, 0); ++ if (IS_ERR(priv->peci_mctp)) ++ dev_err(priv->dev, "Failed to register peci-mctp device\n"); ++ ++ return 0; ++out_irq: ++ misc_deregister(&priv->mctp_miscdev); ++out_dma: ++ aspeed_mctp_dma_fini(priv); ++out_drv: ++ aspeed_mctp_drv_fini(priv); ++out: ++ dev_err(&pdev->dev, "Failed to probe Aspeed MCTP: %d\n", ret); ++ return ret; ++} ++ ++static void aspeed_mctp_remove(struct platform_device *pdev) ++{ ++ struct aspeed_mctp *priv = platform_get_drvdata(pdev); ++ ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++ if (priv->pcie_vdm_enabled) { ++ mctp_pcie_vdm_remove_dev(priv->ndev); ++ priv->pcie_vdm_enabled = false; ++ } ++#endif ++ ++ platform_device_unregister(priv->peci_mctp); ++ ++ misc_deregister(&priv->mctp_miscdev); ++ ++ aspeed_mctp_irq_disable(priv); ++ ++ aspeed_mctp_dma_fini(priv); ++ ++ aspeed_mctp_drv_fini(priv); ++} ++ ++static const struct aspeed_mctp_match_data ast2500_mctp_match_data = { ++ .rx_cmd_size = sizeof(struct aspeed_mctp_rx_cmd), ++ .tx_cmd_size = sizeof(struct aspeed_mctp_tx_cmd), ++ .packet_unit_size = 128, ++ .need_address_mapping = true, ++ .vdm_hdr_direct_xfer = false, ++ .fifo_auto_surround = false, ++}; ++ ++static const struct aspeed_mctp_match_data ast2600_mctp_match_data = { ++ .rx_cmd_size = sizeof(u32), ++ .tx_cmd_size = sizeof(struct aspeed_mctp_tx_cmd), ++ .packet_unit_size = sizeof(struct mctp_pcie_packet_data), ++ .need_address_mapping = false, ++ .vdm_hdr_direct_xfer = true, ++ .fifo_auto_surround = true, ++}; ++ ++static const struct aspeed_mctp_match_data ast2700_mctp0_match_data = { ++ .rx_cmd_size = sizeof(struct aspeed_mctp_rx_cmd), ++ .tx_cmd_size = sizeof(struct aspeed_g7_mctp_tx_cmd), ++ .packet_unit_size = sizeof(struct mctp_pcie_packet_data), ++ .need_address_mapping = false, ++ .vdm_hdr_direct_xfer = true, ++ .fifo_auto_surround = true, ++ .dma_need_64bits_width = true, ++ .scu_pcie_ctrl_offset = ASPEED_G7_SCU_PCIE0_CTRL_OFFSET, ++}; ++ ++static const struct aspeed_mctp_match_data ast2700_mctp1_match_data = { ++ .rx_cmd_size = sizeof(struct aspeed_mctp_rx_cmd), ++ .tx_cmd_size = sizeof(struct aspeed_g7_mctp_tx_cmd), ++ .packet_unit_size = sizeof(struct mctp_pcie_packet_data), ++ .need_address_mapping = false, ++ .vdm_hdr_direct_xfer = true, ++ .fifo_auto_surround = true, ++ .dma_need_64bits_width = true, ++ .scu_pcie_ctrl_offset = ASPEED_G7_SCU_PCIE1_CTRL_OFFSET, ++}; ++ ++static const struct of_device_id aspeed_mctp_match_table[] = { ++ { .compatible = "aspeed,ast2500-mctp", .data = &ast2500_mctp_match_data}, ++ { .compatible = "aspeed,ast2600-mctp", .data = &ast2600_mctp_match_data}, ++ { .compatible = "aspeed,ast2700-mctp0", .data = &ast2700_mctp0_match_data}, ++ { .compatible = "aspeed,ast2700-mctp1", .data = &ast2700_mctp1_match_data}, ++ { } ++}; ++ ++static struct platform_driver aspeed_mctp_driver = { ++ .driver = { ++ .name = "aspeed-mctp", ++ .of_match_table = of_match_ptr(aspeed_mctp_match_table), ++ }, ++ .probe = aspeed_mctp_probe, ++ .remove = aspeed_mctp_remove, ++}; ++ ++static int __init aspeed_mctp_init(void) ++{ ++ packet_cache = ++ kmem_cache_create_usercopy("mctp-packet", ++ sizeof(struct mctp_pcie_packet), ++ 0, 0, 0, ++ sizeof(struct mctp_pcie_packet), ++ NULL); ++ if (!packet_cache) ++ return -ENOMEM; ++ ++ return platform_driver_register(&aspeed_mctp_driver); ++} ++ ++static void __exit aspeed_mctp_exit(void) ++{ ++ platform_driver_unregister(&aspeed_mctp_driver); ++ kmem_cache_destroy(packet_cache); ++} ++ ++module_init(aspeed_mctp_init) ++module_exit(aspeed_mctp_exit) ++ ++MODULE_DEVICE_TABLE(of, aspeed_mctp_match_table); ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Iwona Winiarska "); ++MODULE_DESCRIPTION("Aspeed MCTP driver"); +diff --git a/drivers/soc/aspeed/aspeed-socinfo.c b/drivers/soc/aspeed/aspeed-socinfo.c +--- a/drivers/soc/aspeed/aspeed-socinfo.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-socinfo.c 2026-09-09 15:51:44.718238072 +0000 +@@ -39,7 +39,7 @@ + unsigned int i; + + for (i = 0 ; i < ARRAY_SIZE(rev_table) ; ++i) { +- if ((rev_table[i].id & 0xff00ffff) == id) ++ if (rev_table[i].id == id) + return rev_table[i].name; + } + +diff --git a/drivers/soc/aspeed/aspeed-ssp.c b/drivers/soc/aspeed/aspeed-ssp.c +--- a/drivers/soc/aspeed/aspeed-ssp.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-ssp.c 2026-09-09 15:51:44.718238072 +0000 +@@ -0,0 +1,277 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright (C) ASPEED Technology Inc. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SSP_FILE_NAME "ast2600_ssp.bin" ++#define AST2600_CVIC_TRIGGER 0x28 ++#define AST2600_CVIC_PENDING_STATUS 0x18 ++#define AST2600_CVIC_PENDING_CLEAR 0x1C ++ ++#define SSP_CTRL_REG 0xa00 ++#define SSP_CTRL_RESET_ASSERT BIT(1) ++#define SSP_CTRL_EN BIT(0) ++ ++#define SSP_MEM_BASE_REG 0xa04 ++#define SSP_IMEM_LIMIT_REG 0xa08 ++#define SSP_DMEM_LIMIT_REG 0xa0c ++#define SSP_CACHE_RANGE_REG 0xa40 ++#define SSP_CACHE_INVALID_REG 0xa44 ++#define SSP_CACHE_CTRL_REG 0xa48 ++#define SSP_CACHE_CLEAR_ICACHE BIT(2) ++#define SSP_CACHE_CLEAR_DCACHE BIT(1) ++#define SSP_CACHE_EN BIT(0) ++ ++#define SSP_TOTAL_MEM_SZ (32 * 1024 * 1024) ++#define SSP_CACHED_MEM_SZ (16 * 1024 * 1024) ++#define SSP_UNCACHED_MEM_SZ (SSP_TOTAL_MEM_SZ - SSP_CACHED_MEM_SZ) ++#define SSP_CACHE_1ST_16MB_ENABLE BIT(0) ++ ++struct ast2600_ssp { ++ struct device *dev; ++ struct regmap *scu; ++ dma_addr_t ssp_mem_phy_addr; ++ void __iomem *ssp_mem_vir_addr; ++ dma_addr_t ssp_shared_mem_phy_addr; ++ void __iomem *ssp_shared_mem_vir_addr; ++ int ssp_shared_mem_size; ++ void __iomem *cvic; ++ int irq[16]; ++ int n_irq; ++}; ++ ++static int ast_ssp_open(struct inode *inode, struct file *file) ++{ ++ return 0; ++} ++ ++static int ast_ssp_release(struct inode *inode, struct file *file) ++{ ++ return 0; ++} ++ ++static const struct file_operations ast_ssp_fops = { ++ .owner = THIS_MODULE, ++ .open = ast_ssp_open, ++ .release = ast_ssp_release, ++ .llseek = no_llseek, ++}; ++ ++struct miscdevice ast_ssp_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "ast-ssp", ++ .fops = &ast_ssp_fops, ++}; ++ ++static irqreturn_t ast2600_ssp_interrupt(int irq, void *dev_id) ++{ ++ u32 i; ++ struct ast2600_ssp *priv = dev_id; ++ u32 isr = readl(priv->cvic + AST2600_CVIC_PENDING_STATUS); ++ u32 ssp_shared_rx_tx_size = priv->ssp_shared_mem_size / 2; ++ u32 *ssp_shared_mem_tx = priv->ssp_shared_mem_vir_addr; ++ u32 *ssp_shared_mem_rx = priv->ssp_shared_mem_vir_addr + ssp_shared_rx_tx_size; ++ ++ dev_info(priv->dev, "isr %x\n", isr); ++ writel(isr, priv->cvic + AST2600_CVIC_PENDING_CLEAR); ++ ++ dev_info(priv->dev, "[CA7] rx addr:%08x, tx addr:%08x\n", ++ (u32)ssp_shared_mem_rx, (u32)ssp_shared_mem_tx); ++ ++ /* Check the CA7 RX data from CM3 TX data. */ ++ dev_info(priv->dev, "CA7 RX data from CM3 TX data: "); ++ for (i = 0; i < ssp_shared_rx_tx_size / 4; i++) { ++ if (readl(ssp_shared_mem_rx + i) != 0) { ++ dev_info(priv->dev, "[%08x] %08x ", ++ (u32)(ssp_shared_mem_rx + i), readl(ssp_shared_mem_rx + i)); ++ } else { ++ break; ++ } ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int ast_ssp_probe(struct platform_device *pdev) ++{ ++ struct device_node *np, *mnode = dev_of_node(&pdev->dev); ++ const struct firmware *firmware; ++ struct ast2600_ssp *priv; ++ struct reserved_mem *rmem; ++ int i, ret; ++ ++ priv = kzalloc(sizeof(*priv), GFP_KERNEL); ++ if (!priv) { ++ ret = -ENOMEM; ++ goto finish; ++ } ++ ++ priv->dev = &pdev->dev; ++ priv->scu = syscon_regmap_lookup_by_phandle(priv->dev->of_node, "aspeed,scu"); ++ if (IS_ERR(priv->scu)) { ++ dev_err(priv->dev, "failed to find SCU regmap\n"); ++ ret = -EINVAL; ++ goto finish; ++ } ++ platform_set_drvdata(pdev, priv); ++ ++ ret = misc_register(&ast_ssp_misc); ++ if (ret) { ++ pr_err("can't misc_register :(\n"); ++ ret = -EIO; ++ goto finish; ++ } ++ dev_set_drvdata(ast_ssp_misc.this_device, pdev); ++ ++ ret = of_reserved_mem_device_init(&pdev->dev); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to initialize reserved mem: %d\n", ret); ++ ret = -ENOMEM; ++ goto finish; ++ } ++ ++ np = of_parse_phandle(priv->dev->of_node, "memory-region", 0); ++ if (!np) { ++ dev_err(priv->dev, "can't find memory-region node\n"); ++ ret = -ENOMEM; ++ goto finish; ++ } ++ ++ rmem = of_reserved_mem_lookup(np); ++ of_node_put(np); ++ if (!rmem) { ++ dev_err(priv->dev, "can't find reserved memory.\n"); ++ ret = -ENOMEM; ++ goto finish; ++ } else { ++ priv->ssp_mem_phy_addr = rmem->base; ++ priv->ssp_mem_vir_addr = devm_ioremap(priv->dev, priv->ssp_mem_phy_addr, SSP_TOTAL_MEM_SZ); ++ if (!priv->ssp_mem_vir_addr) { ++ dev_err(priv->dev, "can't create reserved memory.\n"); ++ ret = -ENOMEM; ++ goto finish; ++ } else { ++ dev_info(priv->dev, "\nSSP memory: virt(0x%08x), phys(0x%08x)\n", ++ (uint32_t)priv->ssp_mem_vir_addr, priv->ssp_mem_phy_addr); ++ } ++ } ++ ++ if (of_property_read_u32(np, "shm-size", &priv->ssp_shared_mem_size)) { ++ dev_err(priv->dev, "can't find shm-size property\n"); ++ ret = -ENOMEM; ++ goto finish; ++ } ++ ++ priv->ssp_shared_mem_vir_addr = priv->ssp_mem_vir_addr + SSP_TOTAL_MEM_SZ ++ - priv->ssp_shared_mem_size; ++ priv->ssp_shared_mem_phy_addr = priv->ssp_mem_phy_addr + SSP_TOTAL_MEM_SZ ++ - priv->ssp_shared_mem_size; ++ dev_info(priv->dev, "\nSSP shared memory: virt(0x%08x), phys(0x%08x), size(0x%08x)\n", ++ (uint32_t)priv->ssp_shared_mem_vir_addr, priv->ssp_shared_mem_phy_addr, ++ priv->ssp_shared_mem_size); ++ ++ if (request_firmware(&firmware, SSP_FILE_NAME, priv->dev) < 0) { ++ dev_err(priv->dev, "don't have %s\n", SSP_FILE_NAME); ++ release_firmware(firmware); ++ ret = -EINVAL; ++ goto finish; ++ } ++ ++ memcpy(priv->ssp_mem_vir_addr, (void *)firmware->data, firmware->size); ++ release_firmware(firmware); ++ ++ np = of_parse_phandle(mnode, "aspeed,cvic", 0); ++ if (!np) { ++ dev_err(priv->dev, "can't find CVIC\n"); ++ ret = -EINVAL; ++ goto finish; ++ } ++ ++ priv->cvic = devm_of_iomap(priv->dev, np, 0, NULL); ++ if (IS_ERR(priv->cvic)) { ++ dev_err(priv->dev, "can't map CVIC\n"); ++ ret = -EINVAL; ++ goto finish; ++ } ++ ++ i = 0; ++ while (0 != (priv->irq[i] = irq_of_parse_and_map(mnode, i))) { ++ ret = request_irq(priv->irq[i], ast2600_ssp_interrupt, 0, ++ "ssp-sw-irq", priv); ++ i++; ++ } ++ priv->n_irq = i; ++ dev_info(priv->dev, "%d ISRs registered\n", priv->n_irq); ++ ++ regmap_write(priv->scu, SSP_CTRL_REG, 0); ++ mdelay(1); ++ regmap_write(priv->scu, SSP_MEM_BASE_REG, priv->ssp_mem_phy_addr); ++ regmap_write(priv->scu, SSP_IMEM_LIMIT_REG, priv->ssp_mem_phy_addr + SSP_CACHED_MEM_SZ); ++ regmap_write(priv->scu, SSP_DMEM_LIMIT_REG, priv->ssp_mem_phy_addr + SSP_TOTAL_MEM_SZ); ++ ++ regmap_write(priv->scu, SSP_CACHE_RANGE_REG, SSP_CACHE_1ST_16MB_ENABLE); ++ ++ regmap_write(priv->scu, SSP_CTRL_REG, SSP_CTRL_RESET_ASSERT); ++ mdelay(1); ++ regmap_write(priv->scu, SSP_CTRL_REG, 0); ++ mdelay(1); ++ regmap_write(priv->scu, SSP_CTRL_REG, SSP_CTRL_EN); ++ ++ dev_info(priv->dev, "Init successful\n"); ++ ret = 0; ++finish: ++ return ret; ++} ++ ++static int ast_ssp_remove(struct platform_device *pdev) ++{ ++ struct ast2600_ssp *priv = platform_get_drvdata(pdev); ++ int i; ++ ++ dev_info(priv->dev, "SSP module removed\n"); ++ regmap_write(priv->scu, SSP_CTRL_REG, 0); ++ for (i = 0; i < priv->n_irq; i++) ++ free_irq(priv->irq[i], priv); ++ ++ kfree(priv); ++ ++ misc_deregister((struct miscdevice *)&ast_ssp_misc); ++ ++ return 0; ++} ++ ++static const struct of_device_id of_ast_ssp_match_table[] = { ++ { .compatible = "aspeed,ast2600-ssp", }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, of_ast_ssp_match_table); ++ ++static struct platform_driver ast_ssp_driver = { ++ .probe = ast_ssp_probe, ++ .remove = ast_ssp_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = of_ast_ssp_match_table, ++ }, ++}; ++ ++module_platform_driver(ast_ssp_driver); ++ ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/soc/aspeed/aspeed-uart-routing.c b/drivers/soc/aspeed/aspeed-uart-routing.c +--- a/drivers/soc/aspeed/aspeed-uart-routing.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-uart-routing.c 2026-09-09 15:51:44.719238056 +0000 +@@ -15,20 +15,30 @@ + #define HICRA 0x9c + + /* attributes options */ ++#define UART_ROUTING_IO0 "io0" + #define UART_ROUTING_IO1 "io1" + #define UART_ROUTING_IO2 "io2" + #define UART_ROUTING_IO3 "io3" + #define UART_ROUTING_IO4 "io4" + #define UART_ROUTING_IO5 "io5" + #define UART_ROUTING_IO6 "io6" ++#define UART_ROUTING_IO7 "io7" ++#define UART_ROUTING_IO8 "io8" ++#define UART_ROUTING_IO9 "io9" + #define UART_ROUTING_IO10 "io10" ++#define UART_ROUTING_IO12 "io12" ++#define UART_ROUTING_UART0 "uart0" + #define UART_ROUTING_UART1 "uart1" + #define UART_ROUTING_UART2 "uart2" + #define UART_ROUTING_UART3 "uart3" + #define UART_ROUTING_UART4 "uart4" + #define UART_ROUTING_UART5 "uart5" + #define UART_ROUTING_UART6 "uart6" ++#define UART_ROUTING_UART7 "uart7" ++#define UART_ROUTING_UART8 "uart8" ++#define UART_ROUTING_UART9 "uart9" + #define UART_ROUTING_UART10 "uart10" ++#define UART_ROUTING_UART12 "uart12" + #define UART_ROUTING_RES "reserved" + + struct aspeed_uart_routing { +@@ -488,6 +498,416 @@ + .attrs = ast2600_uart_routing_attrs, + }; + ++/* routing selector for AST27xx node 0 */ ++static struct aspeed_uart_routing_selector ast2700n0_uart9_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART9), ++ .reg = HICR9, ++ .shift = 12, ++ .mask = 0xf, ++ .options = { ++ UART_ROUTING_IO9, ++ UART_ROUTING_IO0, ++ UART_ROUTING_IO1, ++ UART_ROUTING_IO2, ++ UART_ROUTING_IO3, ++ UART_ROUTING_RES, ++ UART_ROUTING_UART0, ++ UART_ROUTING_UART1, ++ UART_ROUTING_UART2, ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART12, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_io9_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO9), ++ .reg = HICR9, ++ .shift = 8, ++ .mask = 0xf, ++ .options = { ++ UART_ROUTING_UART0, ++ UART_ROUTING_UART1, ++ UART_ROUTING_UART2, ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART12, ++ UART_ROUTING_IO0, ++ UART_ROUTING_IO1, ++ UART_ROUTING_IO2, ++ UART_ROUTING_IO3, ++ UART_ROUTING_RES, ++ UART_ROUTING_UART9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_uart3_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART3), ++ .reg = HICRA, ++ .shift = 25, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO3, ++ UART_ROUTING_IO0, ++ UART_ROUTING_IO1, ++ UART_ROUTING_IO2, ++ UART_ROUTING_UART0, ++ UART_ROUTING_UART1, ++ UART_ROUTING_UART2, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_uart2_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART2), ++ .reg = HICRA, ++ .shift = 22, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO2, ++ UART_ROUTING_IO3, ++ UART_ROUTING_IO0, ++ UART_ROUTING_IO1, ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART0, ++ UART_ROUTING_UART1, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_uart1_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART1), ++ .reg = HICRA, ++ .shift = 19, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO1, ++ UART_ROUTING_IO2, ++ UART_ROUTING_IO3, ++ UART_ROUTING_IO0, ++ UART_ROUTING_UART2, ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART0, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_uart0_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART0), ++ .reg = HICRA, ++ .shift = 16, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO0, ++ UART_ROUTING_IO1, ++ UART_ROUTING_IO2, ++ UART_ROUTING_IO3, ++ UART_ROUTING_UART1, ++ UART_ROUTING_UART2, ++ UART_ROUTING_UART3, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_io3_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO3), ++ .reg = HICRA, ++ .shift = 9, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART9, ++ UART_ROUTING_UART0, ++ UART_ROUTING_UART1, ++ UART_ROUTING_UART2, ++ UART_ROUTING_IO0, ++ UART_ROUTING_IO1, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_io2_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO2), ++ .reg = HICRA, ++ .shift = 6, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART2, ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART9, ++ UART_ROUTING_UART0, ++ UART_ROUTING_UART1, ++ UART_ROUTING_IO0, ++ UART_ROUTING_IO1, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_io1_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO1), ++ .reg = HICRA, ++ .shift = 3, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART1, ++ UART_ROUTING_UART2, ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART9, ++ UART_ROUTING_UART0, ++ UART_ROUTING_IO2, ++ UART_ROUTING_IO3, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n0_io0_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO0), ++ .reg = HICRA, ++ .shift = 0, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART0, ++ UART_ROUTING_UART1, ++ UART_ROUTING_UART2, ++ UART_ROUTING_UART3, ++ UART_ROUTING_UART9, ++ UART_ROUTING_IO2, ++ UART_ROUTING_IO3, ++ UART_ROUTING_IO9, ++ NULL, ++ }, ++}; ++ ++static struct attribute *ast2700n0_uart_routing_attrs[] = { ++ &ast2700n0_uart9_sel.dev_attr.attr, ++ &ast2700n0_io9_sel.dev_attr.attr, ++ &ast2700n0_uart3_sel.dev_attr.attr, ++ &ast2700n0_uart2_sel.dev_attr.attr, ++ &ast2700n0_uart1_sel.dev_attr.attr, ++ &ast2700n0_uart0_sel.dev_attr.attr, ++ &ast2700n0_io3_sel.dev_attr.attr, ++ &ast2700n0_io2_sel.dev_attr.attr, ++ &ast2700n0_io1_sel.dev_attr.attr, ++ &ast2700n0_io0_sel.dev_attr.attr, ++ NULL, ++}; ++ ++static const struct attribute_group ast2700n0_uart_routing_attr_group = { ++ .attrs = ast2700n0_uart_routing_attrs, ++}; ++ ++/* routing selector for AST27xx node 1 */ ++static struct aspeed_uart_routing_selector ast2700n1_uart10_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART10), ++ .reg = HICR9, ++ .shift = 12, ++ .mask = 0xf, ++ .options = { ++ UART_ROUTING_IO10, ++ UART_ROUTING_IO5, ++ UART_ROUTING_IO6, ++ UART_ROUTING_IO7, ++ UART_ROUTING_IO8, ++ UART_ROUTING_RES, ++ UART_ROUTING_UART5, ++ UART_ROUTING_UART6, ++ UART_ROUTING_UART7, ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART12, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_io10_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO10), ++ .reg = HICR9, ++ .shift = 8, ++ .mask = 0xf, ++ .options = { ++ UART_ROUTING_UART5, ++ UART_ROUTING_UART6, ++ UART_ROUTING_UART7, ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART12, ++ UART_ROUTING_IO5, ++ UART_ROUTING_IO6, ++ UART_ROUTING_IO7, ++ UART_ROUTING_IO8, ++ UART_ROUTING_RES, ++ UART_ROUTING_UART10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_uart8_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART8), ++ .reg = HICRA, ++ .shift = 25, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO8, ++ UART_ROUTING_IO5, ++ UART_ROUTING_IO6, ++ UART_ROUTING_IO7, ++ UART_ROUTING_UART5, ++ UART_ROUTING_UART6, ++ UART_ROUTING_UART7, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_uart7_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART7), ++ .reg = HICRA, ++ .shift = 22, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO7, ++ UART_ROUTING_IO8, ++ UART_ROUTING_IO5, ++ UART_ROUTING_IO6, ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART5, ++ UART_ROUTING_UART6, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_uart6_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART6), ++ .reg = HICRA, ++ .shift = 19, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO6, ++ UART_ROUTING_IO7, ++ UART_ROUTING_IO8, ++ UART_ROUTING_IO5, ++ UART_ROUTING_UART7, ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART5, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_uart5_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_UART5), ++ .reg = HICRA, ++ .shift = 16, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_IO5, ++ UART_ROUTING_IO6, ++ UART_ROUTING_IO7, ++ UART_ROUTING_IO8, ++ UART_ROUTING_UART6, ++ UART_ROUTING_UART7, ++ UART_ROUTING_UART8, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_io8_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO8), ++ .reg = HICRA, ++ .shift = 9, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART10, ++ UART_ROUTING_UART5, ++ UART_ROUTING_UART6, ++ UART_ROUTING_UART7, ++ UART_ROUTING_IO5, ++ UART_ROUTING_IO6, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_io7_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO7), ++ .reg = HICRA, ++ .shift = 6, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART7, ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART10, ++ UART_ROUTING_UART5, ++ UART_ROUTING_UART6, ++ UART_ROUTING_IO5, ++ UART_ROUTING_IO6, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_io6_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO6), ++ .reg = HICRA, ++ .shift = 3, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART6, ++ UART_ROUTING_UART7, ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART10, ++ UART_ROUTING_UART5, ++ UART_ROUTING_IO7, ++ UART_ROUTING_IO8, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector ast2700n1_io5_sel = { ++ .dev_attr = ROUTING_ATTR(UART_ROUTING_IO5), ++ .reg = HICRA, ++ .shift = 0, ++ .mask = 0x7, ++ .options = { ++ UART_ROUTING_UART5, ++ UART_ROUTING_UART6, ++ UART_ROUTING_UART7, ++ UART_ROUTING_UART8, ++ UART_ROUTING_UART10, ++ UART_ROUTING_IO7, ++ UART_ROUTING_IO8, ++ UART_ROUTING_IO10, ++ NULL, ++ }, ++}; ++ ++static struct attribute *ast2700n1_uart_routing_attrs[] = { ++ &ast2700n1_uart10_sel.dev_attr.attr, ++ &ast2700n1_io10_sel.dev_attr.attr, ++ &ast2700n1_uart8_sel.dev_attr.attr, ++ &ast2700n1_uart7_sel.dev_attr.attr, ++ &ast2700n1_uart6_sel.dev_attr.attr, ++ &ast2700n1_uart5_sel.dev_attr.attr, ++ &ast2700n1_io8_sel.dev_attr.attr, ++ &ast2700n1_io7_sel.dev_attr.attr, ++ &ast2700n1_io6_sel.dev_attr.attr, ++ &ast2700n1_io5_sel.dev_attr.attr, ++ NULL, ++}; ++ ++static const struct attribute_group ast2700n1_uart_routing_attr_group = { ++ .attrs = ast2700n1_uart_routing_attrs, ++}; ++ + static ssize_t aspeed_uart_routing_show(struct device *dev, + struct device_attribute *attr, + char *buf) +@@ -580,6 +1000,10 @@ + .data = &ast2500_uart_routing_attr_group }, + { .compatible = "aspeed,ast2600-uart-routing", + .data = &ast2600_uart_routing_attr_group }, ++ { .compatible = "aspeed,ast2700n0-uart-routing", ++ .data = &ast2700n0_uart_routing_attr_group }, ++ { .compatible = "aspeed,ast2700n1-uart-routing", ++ .data = &ast2700n1_uart_routing_attr_group }, + { }, + }; + +diff --git a/drivers/soc/aspeed/aspeed-udma.c b/drivers/soc/aspeed/aspeed-udma.c +--- a/drivers/soc/aspeed/aspeed-udma.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-udma.c 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,424 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright 2020 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVICE_NAME "aspeed-udma" ++ ++/* UART DMA registers offset */ ++#define UDMA_TX_DMA_EN 0x000 ++#define UDMA_RX_DMA_EN 0x004 ++#define UDMA_MISC 0x008 ++#define UDMA_MISC_RX_BUFSZ GENMASK(3, 2) ++#define UDMA_MISC_TX_BUFSZ GENMASK(1, 0) ++#define UDMA_TMOUT_TIMER 0x00c ++#define UDMA_TX_DMA_RST 0x020 ++#define UDMA_RX_DMA_RST 0x024 ++#define UDMA_TX_DMA_INT_EN 0x030 ++#define UDMA_TX_DMA_INT_STS 0x034 ++#define UDMA_RX_DMA_INT_EN 0x038 ++#define UDMA_RX_DMA_INT_STS 0x03c ++ ++#define UDMA_CHX_OFF(x) ((x) * 0x20) ++#define UDMA_CHX_TX_RD_PTR(x) (0x040 + UDMA_CHX_OFF(x)) ++#define UDMA_CHX_TX_WR_PTR(x) (0x044 + UDMA_CHX_OFF(x)) ++#define UDMA_CHX_TX_BUF_ADDR(x) (0x048 + UDMA_CHX_OFF(x)) ++#define UDMA_CHX_TX_CTRL(x) (0x04c + UDMA_CHX_OFF(x)) ++#define UDMA_TX_CTRL_BUF_ADDRH GENMASK(10, 8) ++#define UDMA_TX_CTRL_TMOUT_DIS BIT(4) ++#define UDMA_TX_CTRL_BUFSZ GENMASK(3, 0) ++#define UDMA_CHX_RX_RD_PTR(x) (0x050 + UDMA_CHX_OFF(x)) ++#define UDMA_CHX_RX_WR_PTR(x) (0x054 + UDMA_CHX_OFF(x)) ++#define UDMA_CHX_RX_BUF_ADDR(x) (0x058 + UDMA_CHX_OFF(x)) ++#define UDMA_CHX_RX_CTRL(x) (0x05c + UDMA_CHX_OFF(x)) ++#define UDMA_RX_CTRL_BUF_ADDRH GENMASK(10, 8) ++#define UDMA_RX_CTRL_TMOUT_DIS BIT(4) ++#define UDMA_RX_CTRL_BUFSZ GENMASK(1, 0) ++ ++#define UDMA_MAX_CHANNEL 16 ++#define UDMA_TMOUT 0x200 ++ ++enum aspeed_udma_bufsz_code { ++ UDMA_BUFSZ_CODE_1KB, ++ UDMA_BUFSZ_CODE_4KB, ++ UDMA_BUFSZ_CODE_16KB, ++ UDMA_BUFSZ_CODE_64KB, ++}; ++ ++struct aspeed_udma_chan { ++ dma_addr_t dma_addr; ++ ++ u32 rb_sz; ++ ++ aspeed_udma_cb_t cb; ++ void *cb_arg; ++ ++ bool dis_tmout; ++}; ++ ++struct aspeed_udma { ++ struct device *dev; ++ u8 __iomem *regs; ++ int irq; ++ struct aspeed_udma_chan tx_chs[UDMA_MAX_CHANNEL]; ++ struct aspeed_udma_chan rx_chs[UDMA_MAX_CHANNEL]; ++ spinlock_t lock; ++}; ++ ++struct aspeed_udma udma[1]; ++ ++static int aspeed_udma_get_bufsz_code(u32 buf_sz) ++{ ++ switch (buf_sz) { ++ case SZ_1K: ++ return UDMA_BUFSZ_CODE_1KB; ++ case SZ_4K: ++ return UDMA_BUFSZ_CODE_4KB; ++ case SZ_16K: ++ return UDMA_BUFSZ_CODE_16KB; ++ case SZ_64K: ++ return UDMA_BUFSZ_CODE_64KB; ++ default: ++ break; ++ } ++ ++ return -1; ++} ++ ++static u32 aspeed_udma_get_tx_rptr(u32 ch_no) ++{ ++ return readl(udma->regs + UDMA_CHX_TX_RD_PTR(ch_no)); ++} ++ ++static u32 aspeed_udma_get_rx_wptr(u32 ch_no) ++{ ++ return readl(udma->regs + UDMA_CHX_RX_WR_PTR(ch_no)); ++} ++ ++static void aspeed_udma_set_ptr(u32 ch_no, u32 ptr, bool is_tx) ++{ ++ writel(ptr, udma->regs + ++ ((is_tx) ? UDMA_CHX_TX_WR_PTR(ch_no) : UDMA_CHX_RX_RD_PTR(ch_no))); ++} ++ ++void aspeed_udma_set_tx_wptr(u32 ch_no, u32 wptr) ++{ ++ aspeed_udma_set_ptr(ch_no, wptr, true); ++} ++EXPORT_SYMBOL(aspeed_udma_set_tx_wptr); ++ ++void aspeed_udma_set_rx_rptr(u32 ch_no, u32 rptr) ++{ ++ aspeed_udma_set_ptr(ch_no, rptr, false); ++} ++EXPORT_SYMBOL(aspeed_udma_set_rx_rptr); ++ ++static int aspeed_udma_free_chan(u32 ch_no, bool is_tx) ++{ ++ u32 reg; ++ unsigned long flags; ++ ++ if (ch_no > UDMA_MAX_CHANNEL) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&udma->lock, flags); ++ ++ reg = readl(udma->regs + ++ ((is_tx) ? UDMA_TX_DMA_INT_EN : UDMA_RX_DMA_INT_EN)); ++ reg &= ~(0x1 << ch_no); ++ ++ writel(reg, udma->regs + ++ ((is_tx) ? UDMA_TX_DMA_INT_EN : UDMA_RX_DMA_INT_EN)); ++ ++ spin_unlock_irqrestore(&udma->lock, flags); ++ ++ return 0; ++} ++ ++int aspeed_udma_free_tx_chan(u32 ch_no) ++{ ++ return aspeed_udma_free_chan(ch_no, true); ++} ++EXPORT_SYMBOL(aspeed_udma_free_tx_chan); ++ ++int aspeed_udma_free_rx_chan(u32 ch_no) ++{ ++ return aspeed_udma_free_chan(ch_no, false); ++} ++EXPORT_SYMBOL(aspeed_udma_free_rx_chan); ++ ++static int aspeed_udma_request_chan(u32 ch_no, dma_addr_t addr, u32 rb_sz, ++ aspeed_udma_cb_t cb, void *id, ++ bool dis_tmout, bool is_tx) ++{ ++ int retval = 0; ++ int rbsz_code; ++ ++ u32 reg; ++ unsigned long flags; ++ struct aspeed_udma_chan *ch; ++ ++ if (ch_no > UDMA_MAX_CHANNEL) { ++ retval = -EINVAL; ++ goto out; ++ } ++ ++ rbsz_code = aspeed_udma_get_bufsz_code(rb_sz); ++ if (rbsz_code < 0) { ++ retval = -EINVAL; ++ goto out; ++ } ++ ++ spin_lock_irqsave(&udma->lock, flags); ++ ++ if (is_tx) { ++ reg = readl(udma->regs + UDMA_TX_DMA_INT_EN); ++ if (reg & (0x1 << ch_no)) { ++ retval = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ reg |= (0x1 << ch_no); ++ writel(reg, udma->regs + UDMA_TX_DMA_INT_EN); ++ ++ reg = FIELD_PREP(UDMA_TX_CTRL_BUF_ADDRH, (u64)addr >> 32) | ++ ((dis_tmout) ? UDMA_TX_CTRL_TMOUT_DIS : 0) | ++ FIELD_PREP(UDMA_TX_CTRL_BUFSZ, rbsz_code); ++ writel(reg, udma->regs + UDMA_CHX_TX_CTRL(ch_no)); ++ ++ writel(addr, udma->regs + UDMA_CHX_TX_BUF_ADDR(ch_no)); ++ } else { ++ reg = readl(udma->regs + UDMA_RX_DMA_INT_EN); ++ if (reg & (0x1 << ch_no)) { ++ retval = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ reg |= (0x1 << ch_no); ++ writel(reg, udma->regs + UDMA_RX_DMA_INT_EN); ++ ++ reg = FIELD_PREP(UDMA_RX_CTRL_BUF_ADDRH, (u64)addr >> 32) | ++ ((dis_tmout) ? UDMA_RX_CTRL_TMOUT_DIS : 0) | ++ FIELD_PREP(UDMA_RX_CTRL_BUFSZ, rbsz_code); ++ writel(reg, udma->regs + UDMA_CHX_RX_CTRL(ch_no)); ++ ++ writel(addr, udma->regs + UDMA_CHX_RX_BUF_ADDR(ch_no)); ++ } ++ ++ ch = (is_tx) ? &udma->tx_chs[ch_no] : &udma->rx_chs[ch_no]; ++ ch->rb_sz = rb_sz; ++ ch->cb = cb; ++ ch->cb_arg = id; ++ ch->dma_addr = addr; ++ ch->dis_tmout = dis_tmout; ++ ++unlock_n_out: ++ spin_unlock_irqrestore(&udma->lock, flags); ++out: ++ return 0; ++} ++ ++int aspeed_udma_request_tx_chan(u32 ch_no, dma_addr_t addr, u32 rb_sz, ++ aspeed_udma_cb_t cb, void *id, bool dis_tmout) ++{ ++ return aspeed_udma_request_chan(ch_no, addr, rb_sz, cb, id, ++ dis_tmout, true); ++} ++EXPORT_SYMBOL(aspeed_udma_request_tx_chan); ++ ++int aspeed_udma_request_rx_chan(u32 ch_no, dma_addr_t addr, u32 rb_sz, ++ aspeed_udma_cb_t cb, void *id, bool dis_tmout) ++{ ++ return aspeed_udma_request_chan(ch_no, addr, rb_sz, cb, id, ++ dis_tmout, false); ++} ++EXPORT_SYMBOL(aspeed_udma_request_rx_chan); ++ ++static void aspeed_udma_chan_ctrl(u32 ch_no, u32 op, bool is_tx) ++{ ++ unsigned long flags; ++ u32 reg_en, reg_rst; ++ u32 reg_en_off = (is_tx) ? UDMA_TX_DMA_EN : UDMA_RX_DMA_EN; ++ u32 reg_rst_off = (is_tx) ? UDMA_TX_DMA_RST : UDMA_TX_DMA_RST; ++ ++ if (ch_no > UDMA_MAX_CHANNEL) ++ return; ++ ++ spin_lock_irqsave(&udma->lock, flags); ++ ++ reg_en = readl(udma->regs + reg_en_off); ++ reg_rst = readl(udma->regs + reg_rst_off); ++ ++ switch (op) { ++ case ASPEED_UDMA_OP_ENABLE: ++ reg_en |= (0x1 << ch_no); ++ writel(reg_en, udma->regs + reg_en_off); ++ break; ++ case ASPEED_UDMA_OP_DISABLE: ++ reg_en &= ~(0x1 << ch_no); ++ writel(reg_en, udma->regs + reg_en_off); ++ break; ++ case ASPEED_UDMA_OP_RESET: ++ reg_en &= ~(0x1 << ch_no); ++ writel(reg_en, udma->regs + reg_en_off); ++ ++ reg_rst |= (0x1 << ch_no); ++ writel(reg_rst, udma->regs + reg_rst_off); ++ ++ udelay(100); ++ ++ reg_rst &= ~(0x1 << ch_no); ++ writel(reg_rst, udma->regs + reg_rst_off); ++ break; ++ default: ++ break; ++ } ++ ++ spin_unlock_irqrestore(&udma->lock, flags); ++} ++ ++void aspeed_udma_tx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op) ++{ ++ aspeed_udma_chan_ctrl(ch_no, op, true); ++} ++EXPORT_SYMBOL(aspeed_udma_tx_chan_ctrl); ++ ++void aspeed_udma_rx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op) ++{ ++ aspeed_udma_chan_ctrl(ch_no, op, false); ++} ++EXPORT_SYMBOL(aspeed_udma_rx_chan_ctrl); ++ ++static irqreturn_t aspeed_udma_isr(int irq, void *arg) ++{ ++ u32 bit; ++ unsigned long tx_sts = readl(udma->regs + UDMA_TX_DMA_INT_STS); ++ unsigned long rx_sts = readl(udma->regs + UDMA_RX_DMA_INT_STS); ++ ++ if (udma != (struct aspeed_udma *)arg) ++ return IRQ_NONE; ++ ++ if (tx_sts == 0 && rx_sts == 0) ++ return IRQ_NONE; ++ ++ for_each_set_bit(bit, &tx_sts, UDMA_MAX_CHANNEL) { ++ writel((0x1 << bit), udma->regs + UDMA_TX_DMA_INT_STS); ++ if (udma->tx_chs[bit].cb) ++ udma->tx_chs[bit].cb(aspeed_udma_get_tx_rptr(bit), ++ udma->tx_chs[bit].cb_arg); ++ } ++ ++ for_each_set_bit(bit, &rx_sts, UDMA_MAX_CHANNEL) { ++ writel((0x1 << bit), udma->regs + UDMA_RX_DMA_INT_STS); ++ if (udma->rx_chs[bit].cb) ++ udma->rx_chs[bit].cb(aspeed_udma_get_rx_wptr(bit), ++ udma->rx_chs[bit].cb_arg); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_udma_probe(struct platform_device *pdev) ++{ ++ int i, rc; ++ uint32_t reg; ++ struct resource *res; ++ struct device *dev = &pdev->dev; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (IS_ERR_OR_NULL(res)) { ++ dev_err(dev, "failed to get register base\n"); ++ return -ENODEV; ++ } ++ ++ udma->regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR_OR_NULL(udma->regs)) { ++ dev_err(dev, "failed to map registers\n"); ++ return PTR_ERR(udma->regs); ++ } ++ ++ /* disable for safety */ ++ writel(0x0, udma->regs + UDMA_TX_DMA_EN); ++ writel(0x0, udma->regs + UDMA_RX_DMA_EN); ++ ++ udma->irq = platform_get_irq(pdev, 0); ++ if (udma->irq < 0) { ++ dev_err(dev, "failed to get IRQ number\n"); ++ return -ENODEV; ++ } ++ ++ rc = devm_request_irq(dev, udma->irq, aspeed_udma_isr, ++ IRQF_SHARED, DEVICE_NAME, udma); ++ if (rc) { ++ dev_err(dev, "failed to request IRQ handler\n"); ++ return rc; ++ } ++ ++ /* ++ * For legacy design. ++ * - TX ringbuffer size: 4KB ++ * - RX ringbuffer size: 64KB ++ * - Timeout timer disabled ++ */ ++ reg = FIELD_PREP(UDMA_MISC_TX_BUFSZ, UDMA_BUFSZ_CODE_4KB) | ++ FIELD_PREP(UDMA_MISC_RX_BUFSZ, UDMA_BUFSZ_CODE_64KB); ++ writel(reg, udma->regs + UDMA_MISC); ++ ++ for (i = 0; i < UDMA_MAX_CHANNEL; ++i) { ++ writel(0, udma->regs + UDMA_CHX_TX_WR_PTR(i)); ++ writel(0, udma->regs + UDMA_CHX_RX_RD_PTR(i)); ++ } ++ ++ writel(0xffffffff, udma->regs + UDMA_TX_DMA_RST); ++ writel(0x0, udma->regs + UDMA_TX_DMA_RST); ++ ++ writel(0xffffffff, udma->regs + UDMA_RX_DMA_RST); ++ writel(0x0, udma->regs + UDMA_RX_DMA_RST); ++ ++ writel(0x0, udma->regs + UDMA_TX_DMA_INT_EN); ++ writel(0xffffffff, udma->regs + UDMA_TX_DMA_INT_STS); ++ writel(0x0, udma->regs + UDMA_RX_DMA_INT_EN); ++ writel(0xffffffff, udma->regs + UDMA_RX_DMA_INT_STS); ++ ++ writel(UDMA_TMOUT, udma->regs + UDMA_TMOUT_TIMER); ++ ++ spin_lock_init(&udma->lock); ++ ++ dev_set_drvdata(dev, udma); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_udma_match[] = { ++ { .compatible = "aspeed,ast2500-udma" }, ++ { .compatible = "aspeed,ast2600-udma" }, ++ { .compatible = "aspeed,ast2700-udma" }, ++ { }, ++}; ++ ++static struct platform_driver aspeed_udma_driver = { ++ .driver = { ++ .name = DEVICE_NAME, ++ .of_match_table = aspeed_udma_match, ++ ++ }, ++ .probe = aspeed_udma_probe, ++}; ++ ++module_platform_driver(aspeed_udma_driver); ++ ++MODULE_AUTHOR("Chia-Wei Wang "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Aspeed UDMA Engine Driver"); +diff --git a/drivers/soc/aspeed/aspeed-usb-hp.c b/drivers/soc/aspeed/aspeed-usb-hp.c +--- a/drivers/soc/aspeed/aspeed-usb-hp.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-usb-hp.c 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,157 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2021 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define USB_HP_BEHCI84 0x84 /* Controller Fine-tune Register */ ++#define USB_HP_BEHCI88 0x88 /* Frame Timing Adjustment */ ++ ++#define BEHCI_64BIT BIT(11) /* Enable support 64 bit address mode */ ++#define BEHCI_PRE_EOF1(x) ((x) << 12) ++#define BEHCI_PRE_EOF2(x) ((x) << 22) ++/* ++ * Set preEOF1 to 0x100 and preEOF2 to 0x140 ++ * (i.e. preEOF1 + MPS 0x40) as the workaround. ++ */ ++#define BEHCI_EOF1_EOF2_TIMING \ ++ (BEHCI_PRE_EOF1(0x100) | BEHCI_PRE_EOF2(0x140)) ++ ++static const struct of_device_id aspeed_usb_hp_dt_ids[] = { ++ { ++ .compatible = "aspeed,ast2600-usb2ahp", ++ }, ++ { ++ .compatible = "aspeed,ast2700-usb3hp", ++ }, ++ { ++ .compatible = "aspeed,ast2700-usb2hp", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aspeed_usb_hp_dt_ids); ++ ++static int aspeed_usb_hp_probe(struct platform_device *pdev) ++{ ++ void __iomem *regs; ++ u32 val; ++ struct clk *clk; ++ struct reset_control *rst; ++ struct regmap *device; ++ struct phy *usb3_phy; ++ bool is_pcie_xhci; ++ int rc = 0; ++ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2600-usb2ahp")) { ++ dev_info(&pdev->dev, "Initialized AST2600 USB2AHP\n"); ++ return 0; ++ } ++ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2700-usb3hp")) ++ is_pcie_xhci = true; ++ else if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2700-usb2hp")) ++ is_pcie_xhci = false; ++ ++ clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ ++ rc = clk_prepare_enable(clk); ++ if (rc) { ++ dev_err(&pdev->dev, "Unable to enable clock (%d)\n", rc); ++ return rc; ++ } ++ ++ rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL); ++ if (IS_ERR(rst)) { ++ rc = PTR_ERR(rst); ++ goto err; ++ } ++ rc = reset_control_deassert(rst); ++ if (rc) ++ goto err; ++ ++ device = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "aspeed,device"); ++ if (IS_ERR(device)) { ++ dev_err(&pdev->dev, "failed to find device regmap\n"); ++ goto err; ++ } ++ ++ if (is_pcie_xhci) { ++ usb3_phy = devm_phy_get(&pdev->dev, "usb3-phy"); ++ if (IS_ERR(usb3_phy)) { ++ rc = dev_err_probe(&pdev->dev, PTR_ERR(usb3_phy), ++ "failed to get usb3 phy\n"); ++ goto err; ++ } ++ rc = phy_init(usb3_phy); ++ if (rc < 0) { ++ dev_err(&pdev->dev, "failed to init usb3 phy\n"); ++ goto err; ++ } ++ //EnPCIaMSI_EnPCIaIntA_EnPCIaMst_EnPCIaDev ++ /* Turn on PCIe xHCI without MSI */ ++ regmap_update_bits(device, 0x70, ++ BIT(19) | BIT(11) | BIT(3), ++ BIT(19) | BIT(11) | BIT(3)); ++ } else { ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) { ++ dev_err(&pdev->dev, "Failed to map resources\n"); ++ rc = PTR_ERR(regs); ++ goto err; ++ } ++ ++ if (device_property_read_bool(&pdev->dev, "aspeed,ehci_32bits_cap")) { ++ val = readl(regs + USB_HP_BEHCI84) & ~BEHCI_64BIT; ++ writel(val, regs + USB_HP_BEHCI84); ++ } ++ ++ /* Adjust PCIe-EHCI EOF1/EOF2 timing to workaround the DMA termination bug. */ ++ writel(BEHCI_EOF1_EOF2_TIMING, regs + USB_HP_BEHCI88); ++ ++ //EnPCIaMSI_EnPCIaIntA_EnPCIaMst_EnPCIaDev ++ /* Turn on PCIe EHCI without MSI */ ++ regmap_update_bits(device, 0x70, ++ BIT(18) | BIT(10) | BIT(2), ++ BIT(18) | BIT(10) | BIT(2)); ++ } ++ dev_info(&pdev->dev, "Initialized AST2700 USB Host PCIe %s\n", is_pcie_xhci ? "xHCI" : "EHCI"); ++ return 0; ++err: ++ if (clk) ++ clk_disable_unprepare(clk); ++ return rc; ++} ++ ++static void aspeed_usb_hp_remove(struct platform_device *pdev) ++{ ++ dev_info(&pdev->dev, "Remove USB Host PCIe\n"); ++} ++ ++static struct platform_driver aspeed_usb_hp_driver = { ++ .probe = aspeed_usb_hp_probe, ++ .remove = aspeed_usb_hp_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_usb_hp_dt_ids, ++ }, ++}; ++module_platform_driver(aspeed_usb_hp_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Neal Liu "); +diff --git a/drivers/soc/aspeed/aspeed-usb-phy.c b/drivers/soc/aspeed/aspeed-usb-phy.c +--- a/drivers/soc/aspeed/aspeed-usb-phy.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-usb-phy.c 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,112 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2021 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct usb_phy_ctrl { ++ u32 offset; ++ u32 value; ++}; ++ ++static const struct of_device_id aspeed_usb_phy_dt_ids[] = { ++ { ++ .compatible = "aspeed,ast2600-uphyb", ++ }, ++ { ++ .compatible = "aspeed,ast2700-uphy2a", ++ }, ++ { ++ .compatible = "aspeed,ast2700-uphy2b", ++ }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_usb_phy_dt_ids); ++ ++static int aspeed_usb_phy_probe(struct platform_device *pdev) ++{ ++ struct device_node *node = pdev->dev.of_node; ++ struct usb_phy_ctrl *ctrl_data; ++ void __iomem *base; ++ struct regmap *scu; ++ int ctrl_num = 1; ++ int ret, i; ++ u32 val; ++ ++ scu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "aspeed,scu"); ++ if (IS_ERR(scu)) { ++ dev_err(&pdev->dev, "cannot to find SCU regmap\n"); ++ return -ENODEV; ++ } ++ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2600-uphyb")) { ++ /* Check SCU040[3] USB port B controller reset is deassert */ ++ regmap_read(scu, 0x40, &val); ++ if ((val & BIT(3))) ++ return -EPROBE_DEFER; ++ } ++ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2700-uphy2a")) { ++ /* Check SCU220[0] USB vHubA1 controller reset is deassert */ ++ regmap_read(scu, 0x220, &val); ++ if ((val & BIT(0))) ++ return -EPROBE_DEFER; ++ } ++ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2700-uphy2b")) { ++ /* Check SCU220[3] USB vHubB1 controller reset is deassert */ ++ regmap_read(scu, 0x220, &val); ++ if ((val & BIT(3))) ++ return -EPROBE_DEFER; ++ } ++ ++ ctrl_data = devm_kzalloc(&pdev->dev, ++ sizeof(struct usb_phy_ctrl) * ctrl_num, ++ GFP_KERNEL); ++ if (!ctrl_data) ++ return -ENOMEM; ++ ++ base = of_iomap(node, 0); ++ ++ ret = of_property_read_u32_array(node, "ctrl", (u32 *)ctrl_data, ++ ctrl_num * 2); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "Could not read ctrl property\n"); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < ctrl_num; i++) ++ writel(ctrl_data[i].value, base + ctrl_data[i].offset); ++ ++ dev_info(&pdev->dev, "Initialized USB PHY\n"); ++ ++ return 0; ++} ++ ++static void aspeed_usb_phy_remove(struct platform_device *pdev) ++{ ++} ++ ++static struct platform_driver aspeed_usb_phy_driver = { ++ .probe = aspeed_usb_phy_probe, ++ .remove = aspeed_usb_phy_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_usb_phy_dt_ids, ++ }, ++}; ++module_platform_driver(aspeed_usb_phy_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Neal Liu "); +diff --git a/drivers/soc/aspeed/aspeed-xdma.c b/drivers/soc/aspeed/aspeed-xdma.c +--- a/drivers/soc/aspeed/aspeed-xdma.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/aspeed-xdma.c 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,1379 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright IBM Corp 2019 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVICE_NAME "aspeed-xdma" ++ ++#define SCU_AST2600_MISC_CTRL 0x0c0 ++#define SCU_AST2600_MISC_CTRL_XDMA_BMC BIT(8) ++#define SCU_AST2700_MISC_CTRL_XDMA_CLIENT BIT(4) ++ ++#define SCU_AST2600_DEBUG_CTRL 0x0c8 ++#define DEBUG_CTRL_AST2600_XDMA_DISABLE BIT(2) ++#define DEBUG_CTRL_AST2700_XDMA_DISABLE BIT(8) ++ ++#define SCU_AST2500_PCIE_CONF 0x180 ++#define SCU_AST2600_PCIE_CONF 0xc20 ++#define SCU_AST2700_PCIE0_CONF 0x970 ++#define SCU_AST2700_PCIE1_CONF 0x9B0 ++#define SCU_PCIE_CONF_VGA_EN BIT(0) ++#define SCU_PCIE_CONF_VGA_EN_MMIO BIT(1) ++#define SCU_PCIE_CONF_VGA_EN_LPC BIT(2) ++#define SCU_PCIE_CONF_VGA_EN_MSI BIT(3) ++#define SCU_PCIE_CONF_VGA_EN_MCTP BIT(4) ++#define SCU_PCIE_CONF_VGA_EN_IRQ BIT(5) ++#define SCU_PCIE_CONF_VGA_EN_DMA BIT(6) ++#define SCU_PCIE_CONF_BMC_EN BIT(8) ++#define SCU_PCIE_CONF_BMC_EN_MMIO BIT(9) ++#define SCU_PCIE_CONF_BMC_EN_MSI BIT(11) ++#define SCU_PCIE_CONF_BMC_EN_MCTP BIT(12) ++#define SCU_PCIE_CONF_BMC_EN_IRQ BIT(13) ++#define SCU_PCIE_CONF_BMC_EN_DMA BIT(14) ++ ++#define SCU_AST2700_PCIE0_CTRL 0xa60 ++#define SCU_AST2700_PCIE1_CTRL 0xae0 ++#define SCU_AST2700_PCIE_CTRL_DMA_EN BIT(2) ++ ++#define SCU_AST2500_BMC_CLASS_REV 0x19c ++#define SCU_AST2600_BMC_CLASS_REV 0xc68 ++#define SCU_AST2700_PCIE0_BMC_CLASS_REV 0xa18 ++#define SCU_AST2700_PCIE1_BMC_CLASS_REV 0xa98 ++#define SCU_BMC_CLASS_REV_XDMA 0xff000001 ++#define SCU_BMC_CLASS_REV_MASK 0xffffff00 ++ ++#define XDMA_CMDQ_SIZE PAGE_SIZE ++#define XDMA_NUM_CMDS \ ++ (XDMA_CMDQ_SIZE / sizeof(struct aspeed_xdma_cmd)) ++ ++/* Aspeed specification requires 100us after disabling the reset */ ++#define XDMA_ENGINE_SETUP_TIME_MAX_US 1000 ++#define XDMA_ENGINE_SETUP_TIME_MIN_US 100 ++ ++#define XDMA_CMD_AST2500_PITCH_SHIFT 3 ++#define XDMA_CMD_AST2500_PITCH_BMC GENMASK_ULL(62, 51) ++#define XDMA_CMD_AST2500_PITCH_HOST GENMASK_ULL(46, 35) ++#define XDMA_CMD_AST2500_PITCH_UPSTREAM BIT_ULL(31) ++#define XDMA_CMD_AST2500_PITCH_ADDR GENMASK_ULL(29, 4) ++#define XDMA_CMD_AST2500_PITCH_ID BIT_ULL(0) ++#define XDMA_CMD_AST2500_CMD_IRQ_EN BIT_ULL(31) ++#define XDMA_CMD_AST2500_CMD_LINE_NO GENMASK_ULL(27, 16) ++#define XDMA_CMD_AST2500_CMD_IRQ_BMC BIT_ULL(15) ++#define XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT 4 ++#define XDMA_CMD_AST2500_CMD_LINE_SIZE \ ++ GENMASK_ULL(14, XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT) ++#define XDMA_CMD_AST2500_CMD_ID BIT_ULL(1) ++ ++#define XDMA_CMD_AST2600_PITCH_BMC GENMASK_ULL(62, 48) ++#define XDMA_CMD_AST2600_PITCH_HOST GENMASK_ULL(46, 32) ++#define XDMA_CMD_AST2600_PITCH_ADDR GENMASK_ULL(30, 0) ++#define XDMA_CMD_AST2600_CMD_64_EN BIT_ULL(40) ++#define XDMA_CMD_AST2600_CMD_IRQ_BMC BIT_ULL(37) ++#define XDMA_CMD_AST2600_CMD_IRQ_HOST BIT_ULL(36) ++#define XDMA_CMD_AST2600_CMD_UPSTREAM BIT_ULL(32) ++#define XDMA_CMD_AST2600_CMD_LINE_NO GENMASK_ULL(27, 16) ++#define XDMA_CMD_AST2600_CMD_LINE_SIZE GENMASK_ULL(14, 0) ++#define XDMA_CMD_AST2600_CMD_MULTILINE_SIZE GENMASK_ULL(14, 12) ++ ++#define XDMA_CMD_AST2700_PITCH_BMC GENMASK_ULL(62, 48) ++#define XDMA_CMD_AST2700_PITCH_HOST GENMASK_ULL(46, 32) ++#define XDMA_CMD_AST2700_CMD_64_EN BIT_ULL(40) ++#define XDMA_CMD_AST2700_CMD_IRQ_BMC BIT_ULL(37) ++#define XDMA_CMD_AST2700_CMD_UPSTREAM BIT_ULL(32) ++#define XDMA_CMD_AST2700_CMD_LINE_NO GENMASK_ULL(27, 16) ++#define XDMA_CMD_AST2700_CMD_LINE_SIZE GENMASK_ULL(14, 0) ++#define XDMA_CMD_AST2700_CMD_MULTILINE_SIZE GENMASK_ULL(14, 12) ++#define XDMA_CMD_AST2700_BMC_ADDR GENMASK_ULL(34, 0) ++ ++#define XDMA_AST2500_QUEUE_ENTRY_SIZE 4 ++#define XDMA_AST2500_HOST_CMDQ_ADDR0 0x00 ++#define XDMA_AST2500_HOST_CMDQ_ENDP 0x04 ++#define XDMA_AST2500_HOST_CMDQ_WRITEP 0x08 ++#define XDMA_AST2500_HOST_CMDQ_READP 0x0c ++#define XDMA_AST2500_BMC_CMDQ_ADDR 0x10 ++#define XDMA_AST2500_BMC_CMDQ_ENDP 0x14 ++#define XDMA_AST2500_BMC_CMDQ_WRITEP 0x18 ++#define XDMA_AST2500_BMC_CMDQ_READP 0x1c ++#define XDMA_BMC_CMDQ_READP_RESET 0xee882266 ++#define XDMA_AST2500_CTRL 0x20 ++#define XDMA_AST2500_CTRL_US_COMP BIT(4) ++#define XDMA_AST2500_CTRL_DS_COMP BIT(5) ++#define XDMA_AST2500_CTRL_DS_DIRTY BIT(6) ++#define XDMA_AST2500_CTRL_DS_SIZE_256 BIT(17) ++#define XDMA_AST2500_CTRL_DS_TIMEOUT BIT(28) ++#define XDMA_AST2500_CTRL_DS_CHECK_ID BIT(29) ++#define XDMA_AST2500_STATUS 0x24 ++#define XDMA_AST2500_STATUS_US_COMP BIT(4) ++#define XDMA_AST2500_STATUS_DS_COMP BIT(5) ++#define XDMA_AST2500_STATUS_DS_DIRTY BIT(6) ++#define XDMA_AST2500_INPRG_DS_CMD1 0x38 ++#define XDMA_AST2500_INPRG_DS_CMD2 0x3c ++#define XDMA_AST2500_INPRG_US_CMD00 0x40 ++#define XDMA_AST2500_INPRG_US_CMD01 0x44 ++#define XDMA_AST2500_INPRG_US_CMD10 0x48 ++#define XDMA_AST2500_INPRG_US_CMD11 0x4c ++#define XDMA_AST2500_INPRG_US_CMD20 0x50 ++#define XDMA_AST2500_INPRG_US_CMD21 0x54 ++#define XDMA_AST2500_HOST_CMDQ_ADDR1 0x60 ++#define XDMA_AST2500_VGA_CMDQ_ADDR0 0x64 ++#define XDMA_AST2500_VGA_CMDQ_ENDP 0x68 ++#define XDMA_AST2500_VGA_CMDQ_WRITEP 0x6c ++#define XDMA_AST2500_VGA_CMDQ_READP 0x70 ++#define XDMA_AST2500_VGA_CMD_STATUS 0x74 ++#define XDMA_AST2500_VGA_CMDQ_ADDR1 0x78 ++ ++#define XDMA_AST2600_QUEUE_ENTRY_SIZE 2 ++#define XDMA_AST2600_HOST_CMDQ_ADDR0 0x00 ++#define XDMA_AST2600_HOST_CMDQ_ADDR1 0x04 ++#define XDMA_AST2600_HOST_CMDQ_ENDP 0x08 ++#define XDMA_AST2600_HOST_CMDQ_WRITEP 0x0c ++#define XDMA_AST2600_HOST_CMDQ_READP 0x10 ++#define XDMA_AST2600_BMC_CMDQ_ADDR 0x14 ++#define XDMA_AST2600_BMC_CMDQ_ENDP 0x18 ++#define XDMA_AST2600_BMC_CMDQ_WRITEP 0x1c ++#define XDMA_AST2600_BMC_CMDQ_READP 0x20 ++#define XDMA_AST2600_VGA_CMDQ_ADDR0 0x24 ++#define XDMA_AST2600_VGA_CMDQ_ADDR1 0x28 ++#define XDMA_AST2600_VGA_CMDQ_ENDP 0x2c ++#define XDMA_AST2600_VGA_CMDQ_WRITEP 0x30 ++#define XDMA_AST2600_VGA_CMDQ_READP 0x34 ++#define XDMA_AST2600_CTRL 0x38 ++#define XDMA_AST2600_CTRL_US_COMP BIT(16) ++#define XDMA_AST2600_CTRL_DS_COMP BIT(17) ++#define XDMA_AST2600_CTRL_DS_DIRTY BIT(18) ++#define XDMA_AST2600_CTRL_DS_SIZE_256 BIT(20) ++#define XDMA_AST2600_STATUS 0x3c ++#define XDMA_AST2600_STATUS_US_COMP BIT(16) ++#define XDMA_AST2600_STATUS_DS_COMP BIT(17) ++#define XDMA_AST2600_STATUS_DS_DIRTY BIT(18) ++#define XDMA_AST2600_INPRG_DS_CMD00 0x40 ++#define XDMA_AST2600_INPRG_DS_CMD01 0x44 ++#define XDMA_AST2600_INPRG_DS_CMD10 0x48 ++#define XDMA_AST2600_INPRG_DS_CMD11 0x4c ++#define XDMA_AST2600_INPRG_DS_CMD20 0x50 ++#define XDMA_AST2600_INPRG_DS_CMD21 0x54 ++#define XDMA_AST2600_INPRG_US_CMD00 0x60 ++#define XDMA_AST2600_INPRG_US_CMD01 0x64 ++#define XDMA_AST2600_INPRG_US_CMD10 0x68 ++#define XDMA_AST2600_INPRG_US_CMD11 0x6c ++#define XDMA_AST2600_INPRG_US_CMD20 0x70 ++#define XDMA_AST2600_INPRG_US_CMD21 0x74 ++ ++#define XDMA_AST2700_QUEUE_ENTRY_SIZE 2 ++#define XDMA_AST2700_BMC_CMDQ_ADDR0 0x10 ++#define XDMA_AST2700_BMC_CMDQ_ADDR1 0x14 ++#define XDMA_AST2700_BMC_CMDQ_ENDP 0x18 ++#define XDMA_AST2700_BMC_CMDQ_WRITEP 0x1c ++#define XDMA_AST2700_BMC_CMDQ_READP 0x20 ++#define XDMA_AST2700_CTRL 0x38 ++#define XDMA_AST2700_CTRL_US_COMP BIT(16) ++#define XDMA_AST2700_CTRL_DS_COMP BIT(17) ++#define XDMA_AST2700_CTRL_DS_DIRTY BIT(18) ++#define XDMA_AST2700_CTRL_IDLE BIT(19) ++#define XDMA_AST2700_CTRL_DS_SIZE_256 BIT(20) ++#define XDMA_AST2700_STATUS 0x3c ++#define XDMA_AST2700_STATUS_US_COMP BIT(16) ++#define XDMA_AST2700_STATUS_DS_COMP BIT(17) ++#define XDMA_AST2700_STATUS_DS_DIRTY BIT(18) ++#define XDMA_AST2700_STATUS_IDLE BIT(19) ++#define XDMA_AST2700_INPRG_DS_CMD00 0x40 ++#define XDMA_AST2700_INPRG_DS_CMD01 0x44 ++#define XDMA_AST2700_INPRG_DS_CMD10 0x48 ++#define XDMA_AST2700_INPRG_DS_CMD11 0x4c ++#define XDMA_AST2700_INPRG_DS_CMD20 0x50 ++#define XDMA_AST2700_INPRG_DS_CMD21 0x54 ++#define XDMA_AST2700_INPRG_US_CMD00 0x60 ++#define XDMA_AST2700_INPRG_US_CMD01 0x64 ++#define XDMA_AST2700_INPRG_US_CMD10 0x68 ++#define XDMA_AST2700_INPRG_US_CMD11 0x6c ++#define XDMA_AST2700_INPRG_US_CMD20 0x70 ++#define XDMA_AST2700_INPRG_US_CMD21 0x74 ++ ++struct aspeed_xdma_cmd { ++ u64 host_addr; ++ u64 pitch; ++ u64 cmd; ++ u64 reserved; ++}; ++ ++struct aspeed_xdma_regs { ++ u8 bmc_cmdq_addr; ++ u8 bmc_cmdq_addr_ext; ++ u8 bmc_cmdq_endp; ++ u8 bmc_cmdq_writep; ++ u8 bmc_cmdq_readp; ++ u8 control; ++ u8 status; ++}; ++ ++struct aspeed_xdma_status_bits { ++ u32 us_comp; ++ u32 ds_comp; ++ u32 ds_dirty; ++}; ++ ++struct aspeed_xdma; ++ ++struct aspeed_xdma_chip { ++ u32 control; ++ u32 scu_bmc_class; ++ u32 scu_misc_ctrl; ++ u32 scu_misc_mask; ++ u32 scu_disable_mask; ++ u32 scu_pcie_conf; ++ u32 scu_pcie_ctrl; ++ unsigned int queue_entry_size; ++ struct aspeed_xdma_regs regs; ++ struct aspeed_xdma_status_bits status_bits; ++ unsigned int (*set_cmd)(struct aspeed_xdma *ctx, ++ struct aspeed_xdma_cmd cmds[2], ++ struct aspeed_xdma_op *op, u64 bmc_addr); ++}; ++ ++struct aspeed_xdma_client; ++ ++struct aspeed_xdma { ++ struct kobject kobj; ++ const struct aspeed_xdma_chip *chip; ++ ++ int irq; ++ int pcie_irq; ++ struct clk *clock; ++ struct device *dev; ++ void __iomem *base; ++ struct reset_control *reset; ++ struct reset_control *reset_rc; ++ ++ /* Protects current_client */ ++ spinlock_t client_lock; ++ struct aspeed_xdma_client *current_client; ++ ++ /* Protects engine configuration */ ++ spinlock_t engine_lock; ++ struct aspeed_xdma_cmd *cmdq; ++ unsigned int cmd_idx; ++ bool in_reset; ++ bool upstream; ++ ++ /* Queue waiters for idle engine */ ++ wait_queue_head_t wait; ++ ++ struct work_struct reset_work; ++ ++ phys_addr_t mem_phys; ++ phys_addr_t mem_size; ++ void *mem_virt; ++ dma_addr_t mem_coherent; ++ dma_addr_t cmdq_phys; ++ struct gen_pool *pool; ++ ++ struct miscdevice misc; ++}; ++ ++struct aspeed_xdma_client { ++ struct aspeed_xdma *ctx; ++ ++ bool error; ++ bool in_progress; ++ void *virt; ++ dma_addr_t phys; ++ u32 size; ++}; ++ ++#define CREATE_TRACE_POINTS ++#include ++ ++static u32 aspeed_xdma_readl(struct aspeed_xdma *ctx, u8 reg) ++{ ++ u32 v = readl(ctx->base + reg); ++ ++ dev_dbg(ctx->dev, "read %02x[%08x]\n", reg, v); ++ return v; ++} ++ ++static void aspeed_xdma_writel(struct aspeed_xdma *ctx, u8 reg, u32 val) ++{ ++ writel(val, ctx->base + reg); ++ dev_dbg(ctx->dev, "write %02x[%08x]\n", reg, val); ++} ++ ++static void aspeed_xdma_init_eng(struct aspeed_xdma *ctx) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ctx->engine_lock, flags); ++ aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_endp, ++ ctx->chip->queue_entry_size * XDMA_NUM_CMDS); ++ aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_readp, ++ XDMA_BMC_CMDQ_READP_RESET); ++ aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_writep, 0); ++ aspeed_xdma_writel(ctx, ctx->chip->regs.control, ctx->chip->control); ++ aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_addr, ctx->cmdq_phys); ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ if (ctx->chip->regs.bmc_cmdq_addr_ext) ++ aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_addr_ext, ctx->cmdq_phys >> 32); ++#endif ++ ++ ctx->cmd_idx = 0; ++ spin_unlock_irqrestore(&ctx->engine_lock, flags); ++} ++ ++static unsigned int aspeed_xdma_ast2500_set_cmd(struct aspeed_xdma *ctx, ++ struct aspeed_xdma_cmd cmds[2], ++ struct aspeed_xdma_op *op, ++ u64 bmc_addr) ++{ ++ unsigned int rc = 1; ++ unsigned int pitch = 1; ++ unsigned int line_no = 1; ++ unsigned int line_size = op->len >> ++ XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT; ++ u64 cmd = XDMA_CMD_AST2500_CMD_IRQ_EN | XDMA_CMD_AST2500_CMD_IRQ_BMC | ++ XDMA_CMD_AST2500_CMD_ID; ++ u64 cmd_pitch = (op->direction ? XDMA_CMD_AST2500_PITCH_UPSTREAM : 0) | ++ XDMA_CMD_AST2500_PITCH_ID; ++ ++ dev_dbg(ctx->dev, "xdma %s ast2500: bmc[%08llx] len[%08x] host[%08x]\n", ++ op->direction ? "upstream" : "downstream", bmc_addr, op->len, ++ (u32)op->host_addr); ++ ++ if (op->len > XDMA_CMD_AST2500_CMD_LINE_SIZE) { ++ unsigned int rem; ++ unsigned int total; ++ ++ line_no = op->len / XDMA_CMD_AST2500_CMD_LINE_SIZE; ++ total = XDMA_CMD_AST2500_CMD_LINE_SIZE * line_no; ++ rem = (op->len - total) >> ++ XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT; ++ line_size = XDMA_CMD_AST2500_CMD_LINE_SIZE; ++ pitch = line_size >> XDMA_CMD_AST2500_PITCH_SHIFT; ++ line_size >>= XDMA_CMD_AST2500_CMD_LINE_SIZE_SHIFT; ++ ++ if (rem) { ++ u32 rbmc = bmc_addr + total; ++ ++ cmds[1].host_addr = op->host_addr + (u64)total; ++ cmds[1].pitch = cmd_pitch | ++ ((u64)rbmc & XDMA_CMD_AST2500_PITCH_ADDR) | ++ FIELD_PREP(XDMA_CMD_AST2500_PITCH_HOST, 1) | ++ FIELD_PREP(XDMA_CMD_AST2500_PITCH_BMC, 1); ++ cmds[1].cmd = cmd | ++ FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_NO, 1) | ++ FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_SIZE, ++ rem); ++ cmds[1].reserved = 0ULL; ++ ++ print_hex_dump_debug("xdma rem ", DUMP_PREFIX_OFFSET, ++ 16, 1, &cmds[1], sizeof(*cmds), ++ true); ++ ++ cmd &= ~(XDMA_CMD_AST2500_CMD_IRQ_EN | ++ XDMA_CMD_AST2500_CMD_IRQ_BMC); ++ ++ rc++; ++ } ++ } ++ ++ cmds[0].host_addr = op->host_addr; ++ cmds[0].pitch = cmd_pitch | ++ (bmc_addr & XDMA_CMD_AST2500_PITCH_ADDR) | ++ FIELD_PREP(XDMA_CMD_AST2500_PITCH_HOST, pitch) | ++ FIELD_PREP(XDMA_CMD_AST2500_PITCH_BMC, pitch); ++ cmds[0].cmd = cmd | FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_NO, line_no) | ++ FIELD_PREP(XDMA_CMD_AST2500_CMD_LINE_SIZE, line_size); ++ cmds[0].reserved = 0ULL; ++ ++ print_hex_dump_debug("xdma cmd ", DUMP_PREFIX_OFFSET, 16, 1, cmds, ++ sizeof(*cmds), true); ++ ++ return rc; ++} ++ ++static unsigned int aspeed_xdma_ast2600_set_cmd(struct aspeed_xdma *ctx, ++ struct aspeed_xdma_cmd cmds[2], ++ struct aspeed_xdma_op *op, ++ u64 bmc_addr) ++{ ++ unsigned int rc = 1; ++ unsigned int pitch = 1; ++ unsigned int line_no = 1; ++ unsigned int line_size = op->len; ++ u64 cmd = XDMA_CMD_AST2600_CMD_IRQ_BMC | ++ (op->direction ? XDMA_CMD_AST2600_CMD_UPSTREAM : 0); ++ ++ if (op->host_addr & 0xffffffff00000000ULL || ++ (op->host_addr + (u64)op->len) & 0xffffffff00000000ULL) ++ cmd |= XDMA_CMD_AST2600_CMD_64_EN; ++ ++ dev_dbg(ctx->dev, "xdma %s ast2600: bmc[%08llx] len[%08x] host[%016llx]\n", ++ op->direction ? "upstream" : "downstream", ++ bmc_addr, op->len, op->host_addr); ++ ++ if ((op->host_addr & 0xff) + op->len > XDMA_CMD_AST2600_CMD_LINE_SIZE) { ++ unsigned int rem; ++ unsigned int total; ++ ++ line_no = op->len / XDMA_CMD_AST2600_CMD_MULTILINE_SIZE; ++ total = XDMA_CMD_AST2600_CMD_MULTILINE_SIZE * line_no; ++ rem = op->len - total; ++ line_size = XDMA_CMD_AST2600_CMD_MULTILINE_SIZE; ++ pitch = line_size; ++ ++ if (rem) { ++ u32 rbmc = bmc_addr + total; ++ ++ cmds[1].host_addr = op->host_addr + (u64)total; ++ cmds[1].pitch = ++ ((u64)rbmc & XDMA_CMD_AST2600_PITCH_ADDR) | ++ FIELD_PREP(XDMA_CMD_AST2600_PITCH_HOST, 1) | ++ FIELD_PREP(XDMA_CMD_AST2600_PITCH_BMC, 1); ++ cmds[1].cmd = cmd | ++ FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_NO, 1) | ++ FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_SIZE, ++ rem); ++ cmds[1].reserved = 0ULL; ++ ++ print_hex_dump_debug("xdma rem ", DUMP_PREFIX_OFFSET, ++ 16, 1, &cmds[1], sizeof(*cmds), ++ true); ++ ++ cmd &= ~XDMA_CMD_AST2600_CMD_IRQ_BMC; ++ ++ rc++; ++ } ++ } ++ ++ cmds[0].host_addr = op->host_addr; ++ cmds[0].pitch = (bmc_addr & XDMA_CMD_AST2600_PITCH_ADDR) | ++ FIELD_PREP(XDMA_CMD_AST2600_PITCH_HOST, pitch) | ++ FIELD_PREP(XDMA_CMD_AST2600_PITCH_BMC, pitch); ++ cmds[0].cmd = cmd | FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_NO, line_no) | ++ FIELD_PREP(XDMA_CMD_AST2600_CMD_LINE_SIZE, line_size); ++ cmds[0].reserved = 0ULL; ++ ++ print_hex_dump_debug("xdma cmd ", DUMP_PREFIX_OFFSET, 16, 1, cmds, ++ sizeof(*cmds), true); ++ ++ return rc; ++} ++ ++static unsigned int aspeed_xdma_ast2700_set_cmd(struct aspeed_xdma *ctx, ++ struct aspeed_xdma_cmd cmds[2], ++ struct aspeed_xdma_op *op, ++ u64 bmc_addr) ++{ ++ unsigned int rc = 1; ++ unsigned int pitch = 1; ++ unsigned int line_no = 1; ++ unsigned int line_size = op->len; ++ u64 cmd = XDMA_CMD_AST2700_CMD_IRQ_BMC | ++ (op->direction ? XDMA_CMD_AST2700_CMD_UPSTREAM : 0); ++ ++ if (op->host_addr & 0xffffffff00000000ULL || ++ (op->host_addr + (u64)op->len) & 0xffffffff00000000ULL) ++ cmd |= XDMA_CMD_AST2700_CMD_64_EN; ++ ++ dev_dbg(ctx->dev, "xdma %s ast2700: bmc[%016llx] len[%08x] host[%016llx]\n", ++ op->direction ? "upstream" : "downstream", ++ bmc_addr, op->len, op->host_addr); ++ ++ if (op->len > XDMA_CMD_AST2700_CMD_LINE_SIZE) { ++ unsigned int rem; ++ unsigned int total; ++ ++ line_no = op->len / XDMA_CMD_AST2700_CMD_MULTILINE_SIZE; ++ total = XDMA_CMD_AST2700_CMD_MULTILINE_SIZE * line_no; ++ rem = op->len - total; ++ line_size = XDMA_CMD_AST2700_CMD_MULTILINE_SIZE; ++ pitch = line_size; ++ ++ if (rem) { ++ u64 rbmc = bmc_addr + total; ++ ++ cmds[1].host_addr = op->host_addr + (u64)total; ++ cmds[1].pitch = ++ FIELD_PREP(XDMA_CMD_AST2700_PITCH_HOST, 1) | ++ FIELD_PREP(XDMA_CMD_AST2700_PITCH_BMC, 1); ++ cmds[1].cmd = cmd | ++ FIELD_PREP(XDMA_CMD_AST2700_CMD_LINE_NO, 1) | ++ FIELD_PREP(XDMA_CMD_AST2700_CMD_LINE_SIZE, ++ rem); ++ cmds[1].reserved = rbmc & XDMA_CMD_AST2700_BMC_ADDR; ++ ++ print_hex_dump_debug("xdma rem ", DUMP_PREFIX_OFFSET, ++ 16, 1, &cmds[1], sizeof(*cmds), ++ true); ++ ++ cmd &= ~XDMA_CMD_AST2700_CMD_IRQ_BMC; ++ ++ rc++; ++ } ++ } ++ cmds[0].host_addr = op->host_addr; ++ cmds[0].pitch = ++ FIELD_PREP(XDMA_CMD_AST2700_PITCH_HOST, pitch) | ++ FIELD_PREP(XDMA_CMD_AST2700_PITCH_BMC, pitch); ++ cmds[0].cmd = cmd | FIELD_PREP(XDMA_CMD_AST2700_CMD_LINE_NO, line_no) | ++ FIELD_PREP(XDMA_CMD_AST2700_CMD_LINE_SIZE, line_size); ++ cmds[0].reserved = bmc_addr & XDMA_CMD_AST2700_BMC_ADDR; ++ ++ print_hex_dump_debug("xdma cmd ", DUMP_PREFIX_OFFSET, 16, 1, cmds, ++ sizeof(*cmds), true); ++ ++ return rc; ++} ++ ++static int aspeed_xdma_start(struct aspeed_xdma *ctx, unsigned int num_cmds, ++ struct aspeed_xdma_cmd cmds[2], bool upstream, ++ struct aspeed_xdma_client *client) ++{ ++ unsigned int i; ++ int rc = -EBUSY; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ctx->engine_lock, flags); ++ if (ctx->in_reset) ++ goto unlock; ++ ++ spin_lock(&ctx->client_lock); ++ if (ctx->current_client) { ++ spin_unlock(&ctx->client_lock); ++ goto unlock; ++ } ++ ++ client->error = false; ++ client->in_progress = true; ++ ctx->current_client = client; ++ spin_unlock(&ctx->client_lock); ++ ++ ctx->upstream = upstream; ++ for (i = 0; i < num_cmds; ++i) { ++ trace_xdma_start(ctx, &cmds[i]); ++ /* ++ * Use memcpy_toio here to get some barriers before starting ++ * the operation. The command(s) need to be in physical memory ++ * before the XDMA engine starts. ++ */ ++ memcpy_toio(&ctx->cmdq[ctx->cmd_idx], &cmds[i], ++ sizeof(struct aspeed_xdma_cmd)); ++ ctx->cmd_idx = (ctx->cmd_idx + 1) % XDMA_NUM_CMDS; ++ } ++ ++ aspeed_xdma_writel(ctx, ctx->chip->regs.bmc_cmdq_writep, ++ ctx->cmd_idx * ctx->chip->queue_entry_size); ++ rc = 0; ++ ++unlock: ++ spin_unlock_irqrestore(&ctx->engine_lock, flags); ++ return rc; ++} ++ ++static void aspeed_xdma_done(struct aspeed_xdma *ctx, bool error) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ctx->client_lock, flags); ++ if (ctx->current_client) { ++ ctx->current_client->error = error; ++ ctx->current_client->in_progress = false; ++ ctx->current_client = NULL; ++ } ++ spin_unlock_irqrestore(&ctx->client_lock, flags); ++ ++ wake_up_interruptible_all(&ctx->wait); ++} ++ ++static irqreturn_t aspeed_xdma_irq(int irq, void *arg) ++{ ++ struct aspeed_xdma *ctx = arg; ++ u32 status; ++ ++ spin_lock(&ctx->engine_lock); ++ status = aspeed_xdma_readl(ctx, ctx->chip->regs.status); ++ ++ trace_xdma_irq(status); ++ ++ if (status & ctx->chip->status_bits.ds_dirty) { ++ aspeed_xdma_done(ctx, true); ++ } else { ++ if (status & ctx->chip->status_bits.us_comp) { ++ if (ctx->upstream) ++ aspeed_xdma_done(ctx, false); ++ } ++ ++ if (status & ctx->chip->status_bits.ds_comp) { ++ if (!ctx->upstream) ++ aspeed_xdma_done(ctx, false); ++ } ++ } ++ ++ aspeed_xdma_writel(ctx, ctx->chip->regs.status, status); ++ spin_unlock(&ctx->engine_lock); ++ ++ return IRQ_HANDLED; ++} ++ ++static void aspeed_xdma_reset(struct aspeed_xdma *ctx) ++{ ++ unsigned long flags; ++ ++ trace_xdma_reset(ctx); ++ ++ reset_control_assert(ctx->reset); ++ usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, ++ XDMA_ENGINE_SETUP_TIME_MAX_US); ++ reset_control_deassert(ctx->reset); ++ usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, ++ XDMA_ENGINE_SETUP_TIME_MAX_US); ++ ++ aspeed_xdma_init_eng(ctx); ++ ++ aspeed_xdma_done(ctx, true); ++ ++ spin_lock_irqsave(&ctx->engine_lock, flags); ++ ctx->in_reset = false; ++ spin_unlock_irqrestore(&ctx->engine_lock, flags); ++ ++ wake_up_interruptible(&ctx->wait); ++} ++ ++static void aspeed_xdma_reset_work(struct work_struct *work) ++{ ++ struct aspeed_xdma *ctx = container_of(work, struct aspeed_xdma, ++ reset_work); ++ ++ aspeed_xdma_reset(ctx); ++} ++ ++static irqreturn_t aspeed_xdma_pcie_irq(int irq, void *arg) ++{ ++ struct aspeed_xdma *ctx = arg; ++ ++ trace_xdma_perst(ctx); ++ ++ spin_lock(&ctx->engine_lock); ++ if (ctx->in_reset) { ++ spin_unlock(&ctx->engine_lock); ++ return IRQ_HANDLED; ++ } ++ ++ ctx->in_reset = true; ++ spin_unlock(&ctx->engine_lock); ++ ++ schedule_work(&ctx->reset_work); ++ return IRQ_HANDLED; ++} ++ ++static ssize_t aspeed_xdma_write(struct file *file, const char __user *buf, ++ size_t len, loff_t *offset) ++{ ++ int rc; ++ unsigned int num_cmds; ++ struct aspeed_xdma_op op; ++ struct aspeed_xdma_cmd cmds[2]; ++ struct aspeed_xdma_client *client = file->private_data; ++ struct aspeed_xdma *ctx = client->ctx; ++ ++ if (len != sizeof(op)) ++ return -EINVAL; ++ ++ if (READ_ONCE(client->in_progress)) ++ return -EBUSY; ++ ++ if (copy_from_user(&op, buf, len)) ++ return -EFAULT; ++ ++ if (!op.len || op.len > client->size || ++ op.direction > ASPEED_XDMA_DIRECTION_UPSTREAM) ++ return -EINVAL; ++ ++ num_cmds = ctx->chip->set_cmd(ctx, cmds, &op, client->phys); ++ do { ++ rc = aspeed_xdma_start(ctx, num_cmds, cmds, !!op.direction, ++ client); ++ if (!rc) ++ break; ++ ++ if ((file->f_flags & O_NONBLOCK) || rc != -EBUSY) ++ return rc; ++ ++ rc = wait_event_interruptible(ctx->wait, ++ !(ctx->current_client || ++ ctx->in_reset)); ++ } while (!rc); ++ ++ if (rc) ++ return -EINTR; ++ ++ if (!(file->f_flags & O_NONBLOCK)) { ++ rc = wait_event_interruptible(ctx->wait, !client->in_progress); ++ if (rc) ++ return -EINTR; ++ ++ if (client->error) ++ return -EIO; ++ } ++ ++ return len; ++} ++ ++static __poll_t aspeed_xdma_poll(struct file *file, ++ struct poll_table_struct *wait) ++{ ++ __poll_t mask = 0; ++ __poll_t req = poll_requested_events(wait); ++ struct aspeed_xdma_client *client = file->private_data; ++ struct aspeed_xdma *ctx = client->ctx; ++ ++ if (req & (EPOLLIN | EPOLLRDNORM)) { ++ if (READ_ONCE(client->in_progress)) ++ poll_wait(file, &ctx->wait, wait); ++ ++ if (!READ_ONCE(client->in_progress)) { ++ if (READ_ONCE(client->error)) ++ mask |= EPOLLERR; ++ else ++ mask |= EPOLLIN | EPOLLRDNORM; ++ } ++ } ++ ++ if (req & (EPOLLOUT | EPOLLWRNORM)) { ++ if (READ_ONCE(ctx->current_client)) ++ poll_wait(file, &ctx->wait, wait); ++ ++ if (!READ_ONCE(ctx->current_client)) ++ mask |= EPOLLOUT | EPOLLWRNORM; ++ } ++ ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ // 2600 and older IC do reset here will trigger unexpected irq. ++ // Only do it for 2700. ++ if (mask) ++ aspeed_xdma_reset(ctx); ++#endif ++ ++ return mask; ++} ++ ++static long aspeed_xdma_ioctl(struct file *file, unsigned int cmd, ++ unsigned long param) ++{ ++ unsigned long flags; ++ struct aspeed_xdma_client *client = file->private_data; ++ struct aspeed_xdma *ctx = client->ctx; ++ ++ switch (cmd) { ++ case ASPEED_XDMA_IOCTL_RESET: ++ spin_lock_irqsave(&ctx->engine_lock, flags); ++ if (ctx->in_reset) { ++ spin_unlock_irqrestore(&ctx->engine_lock, flags); ++ return 0; ++ } ++ ++ ctx->in_reset = true; ++ spin_unlock_irqrestore(&ctx->engine_lock, flags); ++ ++ if (READ_ONCE(ctx->current_client)) ++ dev_warn(ctx->dev, ++ "User reset with transfer in progress.\n"); ++ ++ aspeed_xdma_reset(ctx); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void aspeed_xdma_vma_close(struct vm_area_struct *vma) ++{ ++ int rc; ++ struct aspeed_xdma_client *client = vma->vm_private_data; ++ ++ rc = wait_event_interruptible(client->ctx->wait, !client->in_progress); ++ if (rc) ++ return; ++ ++ gen_pool_free(client->ctx->pool, (unsigned long)client->virt, ++ client->size); ++ trace_xdma_unmap(client); ++ ++ client->virt = NULL; ++ client->phys = 0; ++ client->size = 0; ++} ++ ++static const struct vm_operations_struct aspeed_xdma_vm_ops = { ++ .close = aspeed_xdma_vma_close, ++}; ++ ++static int aspeed_xdma_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ int rc; ++ struct aspeed_xdma_client *client = file->private_data; ++ struct aspeed_xdma *ctx = client->ctx; ++ ++ /* restrict file to one mapping */ ++ if (client->size) ++ return -EBUSY; ++ ++ client->size = vma->vm_end - vma->vm_start; ++ client->virt = gen_pool_dma_alloc(ctx->pool, client->size, ++ &client->phys); ++ if (!client->virt) { ++ trace_xdma_mmap_error(client, 0UL); ++ client->phys = 0; ++ client->size = 0; ++ return -ENOMEM; ++ } ++ ++ vma->vm_pgoff = (client->phys - ctx->mem_phys) >> PAGE_SHIFT; ++ vma->vm_ops = &aspeed_xdma_vm_ops; ++ vma->vm_private_data = client; ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ ++ rc = io_remap_pfn_range(vma, vma->vm_start, client->phys >> PAGE_SHIFT, ++ client->size, vma->vm_page_prot); ++ if (rc) { ++ dev_warn(ctx->dev, "mmap err: v[%08lx] to p[%pa], s[%08x]\n", ++ vma->vm_start, &client->phys, client->size); ++ ++ gen_pool_free(ctx->pool, (unsigned long)client->virt, ++ client->size); ++ ++ trace_xdma_mmap_error(client, vma->vm_start); ++ client->virt = NULL; ++ client->phys = 0; ++ client->size = 0; ++ return rc; ++ } ++ ++ trace_xdma_mmap(client); ++ dev_dbg(ctx->dev, "mmap: v[%08lx] to p[%pa], s[%08x]\n", ++ vma->vm_start, &client->phys, client->size); ++ ++ return 0; ++} ++ ++static int aspeed_xdma_open(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *misc = file->private_data; ++ struct aspeed_xdma *ctx = container_of(misc, struct aspeed_xdma, misc); ++ struct aspeed_xdma_client *client = kzalloc(sizeof(*client), ++ GFP_KERNEL); ++ ++ if (!client) ++ return -ENOMEM; ++ ++ kobject_get(&ctx->kobj); ++ client->ctx = ctx; ++ file->private_data = client; ++ return 0; ++} ++ ++static int aspeed_xdma_release(struct inode *inode, struct file *file) ++{ ++ bool reset = false; ++ unsigned long flags; ++ struct aspeed_xdma_client *client = file->private_data; ++ struct aspeed_xdma *ctx = client->ctx; ++ ++ spin_lock_irqsave(&ctx->client_lock, flags); ++ if (client == ctx->current_client) { ++ spin_lock(&ctx->engine_lock); ++ if (ctx->in_reset) { ++ ctx->current_client = NULL; ++ } else { ++ ctx->in_reset = true; ++ reset = true; ++ } ++ spin_unlock(&ctx->engine_lock); ++ } ++ spin_unlock_irqrestore(&ctx->client_lock, flags); ++ ++ if (reset) ++ aspeed_xdma_reset(ctx); ++ ++ if (client->virt) { ++ gen_pool_free(ctx->pool, (unsigned long)client->virt, ++ client->size); ++ trace_xdma_unmap(client); ++ } ++ ++ kfree(client); ++ kobject_put(&ctx->kobj); ++ return 0; ++} ++ ++static const struct file_operations aspeed_xdma_fops = { ++ .owner = THIS_MODULE, ++ .write = aspeed_xdma_write, ++ .poll = aspeed_xdma_poll, ++ .unlocked_ioctl = aspeed_xdma_ioctl, ++ .mmap = aspeed_xdma_mmap, ++ .open = aspeed_xdma_open, ++ .release = aspeed_xdma_release, ++}; ++ ++static int aspeed_xdma_init_scu(struct aspeed_xdma *ctx, struct device *dev) ++{ ++ struct regmap *scu = syscon_regmap_lookup_by_phandle(dev->of_node, ++ "aspeed,scu"); ++ ++ if (!IS_ERR(scu)) { ++ u32 selection; ++ bool pcie_device_bmc = true; ++ const u32 bmc = SCU_PCIE_CONF_BMC_EN | ++ SCU_PCIE_CONF_BMC_EN_MSI | SCU_PCIE_CONF_BMC_EN_IRQ | ++ SCU_PCIE_CONF_BMC_EN_DMA; ++ const u32 vga = SCU_PCIE_CONF_VGA_EN | ++ SCU_PCIE_CONF_VGA_EN_MSI | SCU_PCIE_CONF_VGA_EN_IRQ | ++ SCU_PCIE_CONF_VGA_EN_DMA; ++ const char *pcie = NULL; ++ ++ if (!of_property_read_string(dev->of_node, ++ "aspeed,pcie-device", &pcie)) { ++ if (!strcmp(pcie, "vga")) { ++ pcie_device_bmc = false; ++ } else if (strcmp(pcie, "bmc")) { ++ dev_err(dev, ++ "Invalid pcie-device property %s.\n", ++ pcie); ++ return -EINVAL; ++ } ++ } ++ ++ if (pcie_device_bmc) { ++ selection = bmc; ++ regmap_update_bits(scu, ctx->chip->scu_bmc_class, ++ SCU_BMC_CLASS_REV_MASK, ++ SCU_BMC_CLASS_REV_XDMA); ++ } else { ++ selection = vga; ++ } ++ ++ regmap_update_bits(scu, ctx->chip->scu_pcie_conf, bmc | vga, ++ selection); ++ ++ if (ctx->chip->scu_misc_ctrl) { ++ regmap_update_bits(scu, ctx->chip->scu_misc_ctrl, ++ ctx->chip->scu_misc_mask, ++ ctx->chip->scu_misc_mask); ++ ++ regmap_update_bits(scu, SCU_AST2600_DEBUG_CTRL, ++ ctx->chip->scu_disable_mask, 0); ++ } ++ ++ if (ctx->chip->scu_pcie_ctrl) { ++ regmap_update_bits(scu, ctx->chip->scu_pcie_ctrl, ++ SCU_AST2700_PCIE_CTRL_DMA_EN, ++ SCU_AST2700_PCIE_CTRL_DMA_EN); ++ } ++ } else { ++ dev_warn(dev, "Unable to configure PCIe: %ld; continuing.\n", ++ PTR_ERR(scu)); ++ } ++ ++ return 0; ++} ++ ++static void aspeed_xdma_kobject_release(struct kobject *kobj) ++{ ++ struct aspeed_xdma *ctx = container_of(kobj, struct aspeed_xdma, kobj); ++ ++ gen_pool_free(ctx->pool, (unsigned long)ctx->cmdq, XDMA_CMDQ_SIZE); ++ ++ gen_pool_destroy(ctx->pool); ++ ++ dma_free_coherent(ctx->dev, ctx->mem_size, ctx->mem_virt, ++ ctx->mem_coherent); ++} ++ ++static const struct kobj_type aspeed_xdma_kobject_type = { ++ .release = aspeed_xdma_kobject_release, ++}; ++ ++static int aspeed_xdma_probe(struct platform_device *pdev) ++{ ++ int rc, id; ++ struct aspeed_xdma *ctx; ++ struct reserved_mem *mem; ++ struct device *dev = &pdev->dev; ++ struct device_node *memory_region; ++ const void *md = of_device_get_match_data(dev); ++ bool rc_f; ++ ++ if (!md) ++ return -ENODEV; ++ ++ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return -ENOMEM; ++ ++ ctx->chip = md; ++ ctx->dev = dev; ++ platform_set_drvdata(pdev, ctx); ++ spin_lock_init(&ctx->client_lock); ++ spin_lock_init(&ctx->engine_lock); ++ INIT_WORK(&ctx->reset_work, aspeed_xdma_reset_work); ++ init_waitqueue_head(&ctx->wait); ++ ++ rc_f = of_find_property(dev->of_node, "pcie_rc", NULL) ? 1 : 0; ++ ++ ctx->base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(ctx->base)) ++ return PTR_ERR(ctx->base); ++ ++ ctx->irq = platform_get_irq(pdev, 0); ++ if (ctx->irq < 0) { ++ dev_err(dev, "Failed to find IRQ.\n"); ++ return ctx->irq; ++ } ++ ++ ctx->pcie_irq = platform_get_irq(pdev, 1); ++ if (ctx->pcie_irq < 0) ++ return ctx->pcie_irq; ++ ++ rc = devm_request_irq(dev, ctx->irq, aspeed_xdma_irq, 0, ++ dev_name(dev), ctx); ++ if (rc < 0) { ++ dev_err(dev, "Failed to request IRQ %d.\n", ctx->irq); ++ return rc; ++ } ++ ++ ctx->clock = devm_clk_get(dev, NULL); ++ if (IS_ERR(ctx->clock)) { ++ dev_err(dev, "Failed to request clock.\n"); ++ return PTR_ERR(ctx->clock); ++ } ++ ++ ctx->reset = devm_reset_control_get_exclusive(dev, NULL); ++ if (IS_ERR(ctx->reset)) { ++ dev_err(dev, "Failed to request reset control.\n"); ++ return PTR_ERR(ctx->reset); ++ } ++ ++ if (rc_f) { ++ ctx->reset_rc = devm_reset_control_get_exclusive(dev, "root-complex"); ++ if (IS_ERR(ctx->reset_rc)) { ++ dev_dbg(dev, "Failed to request reset RC control.\n"); ++ ctx->reset_rc = NULL; ++ } ++ } ++ ++ memory_region = of_parse_phandle(dev->of_node, "memory-region", 0); ++ if (!memory_region) { ++ dev_err(dev, "Failed to find memory-region.\n"); ++ rc = -ENOMEM; ++ return rc; ++ } ++ ++ mem = of_reserved_mem_lookup(memory_region); ++ of_node_put(memory_region); ++ if (!mem) { ++ dev_err(dev, "Failed to find reserved memory.\n"); ++ rc = -ENOMEM; ++ return rc; ++ } ++ ++ ctx->mem_phys = mem->base; ++ ctx->mem_size = mem->size; ++ ++ rc = of_reserved_mem_device_init(dev); ++ if (rc) { ++ dev_err(dev, "Failed to init reserved memory.\n"); ++ return rc; ++ } ++ ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); ++ if (rc) { ++ dev_err(dev, "Failed to mask DMA.\n"); ++ return rc; ++ } ++ ++ ctx->mem_virt = dma_alloc_coherent(dev, ctx->mem_size, ++ &ctx->mem_coherent, __GFP_NOWARN); ++ if (!ctx->mem_virt) { ++ dev_err(dev, "Failed to allocate reserved memory.\n"); ++ rc = -ENOMEM; ++ return rc; ++ } ++ ++ ctx->pool = gen_pool_create(ilog2(PAGE_SIZE), -1); ++ if (!ctx->pool) { ++ dev_err(dev, "Failed to setup genalloc pool.\n"); ++ rc = -ENOMEM; ++ goto err_nopool; ++ } ++ ++ rc = gen_pool_add_virt(ctx->pool, (unsigned long)ctx->mem_virt, ++ ctx->mem_phys, ctx->mem_size, -1); ++ if (rc) { ++ dev_err(ctx->dev, "Failed to add memory to genalloc pool.\n"); ++ goto err_pool_scu_clk; ++ } ++ ++ rc = aspeed_xdma_init_scu(ctx, dev); ++ if (rc) ++ goto err_pool_scu_clk; ++ ++ rc = clk_prepare_enable(ctx->clock); ++ if (rc) { ++ dev_err(dev, "Failed to enable the clock.\n"); ++ goto err_pool_scu_clk; ++ } ++ ++ if (ctx->reset_rc) { ++ rc = reset_control_deassert(ctx->reset_rc); ++ if (rc) { ++ dev_err(dev, "Failed to clear the RC reset.\n"); ++ goto err_reset_rc; ++ } ++ usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, ++ XDMA_ENGINE_SETUP_TIME_MAX_US); ++ } ++ ++ rc = reset_control_deassert(ctx->reset); ++ if (rc) { ++ dev_err(dev, "Failed to clear the reset.\n"); ++ goto err_reset; ++ } ++ usleep_range(XDMA_ENGINE_SETUP_TIME_MIN_US, ++ XDMA_ENGINE_SETUP_TIME_MAX_US); ++ ++ ctx->cmdq = gen_pool_dma_alloc(ctx->pool, XDMA_CMDQ_SIZE, ++ &ctx->cmdq_phys); ++ if (!ctx->cmdq) { ++ dev_err(ctx->dev, "Failed to genalloc cmdq.\n"); ++ rc = -ENOMEM; ++ goto err_pool; ++ } ++ ++ aspeed_xdma_init_eng(ctx); ++ ++ id = of_alias_get_id(dev->of_node, "xdma"); ++ if (id < 0) ++ id = 0; ++ ++ ctx->misc.minor = MISC_DYNAMIC_MINOR; ++ ctx->misc.fops = &aspeed_xdma_fops; ++ ctx->misc.name = kasprintf(GFP_KERNEL, "%s%d", DEVICE_NAME, id); ++ ctx->misc.parent = dev; ++ rc = misc_register(&ctx->misc); ++ if (rc) { ++ dev_err(dev, "Failed to register xdma miscdevice.\n"); ++ goto err_misc; ++ } ++ ++ /* ++ * This interrupt could fire immediately so only request it once the ++ * engine and driver are initialized. ++ */ ++ rc = devm_request_irq(dev, ctx->pcie_irq, aspeed_xdma_pcie_irq, ++ IRQF_SHARED, dev_name(dev), ctx); ++ if (rc) ++ return rc; ++ ++ kobject_init(&ctx->kobj, &aspeed_xdma_kobject_type); ++ return 0; ++ ++err_misc: ++ gen_pool_free(ctx->pool, (unsigned long)ctx->cmdq, XDMA_CMDQ_SIZE); ++err_pool: ++ reset_control_assert(ctx->reset); ++err_reset: ++ if (ctx->reset_rc) ++ reset_control_assert(ctx->reset_rc); ++err_reset_rc: ++ clk_disable_unprepare(ctx->clock); ++err_pool_scu_clk: ++ gen_pool_destroy(ctx->pool); ++err_nopool: ++ dma_free_coherent(ctx->dev, ctx->mem_size, ctx->mem_virt, ++ ctx->mem_coherent); ++ return rc; ++} ++ ++static void aspeed_xdma_remove(struct platform_device *pdev) ++{ ++ struct aspeed_xdma *ctx = platform_get_drvdata(pdev); ++ ++ reset_control_assert(ctx->reset); ++ if (ctx->reset_rc) ++ reset_control_assert(ctx->reset_rc); ++ clk_disable_unprepare(ctx->clock); ++ ++ aspeed_xdma_done(ctx, true); ++ ++ misc_deregister(&ctx->misc); ++ kobject_put(&ctx->kobj); ++} ++ ++static const struct aspeed_xdma_chip aspeed_ast2500_xdma_chip = { ++ .control = XDMA_AST2500_CTRL_US_COMP | XDMA_AST2500_CTRL_DS_COMP | ++ XDMA_AST2500_CTRL_DS_DIRTY | XDMA_AST2500_CTRL_DS_SIZE_256 | ++ XDMA_AST2500_CTRL_DS_TIMEOUT | XDMA_AST2500_CTRL_DS_CHECK_ID, ++ .scu_bmc_class = SCU_AST2500_BMC_CLASS_REV, ++ .scu_misc_ctrl = 0, ++ .scu_pcie_conf = SCU_AST2500_PCIE_CONF, ++ .scu_pcie_ctrl = 0, ++ .queue_entry_size = XDMA_AST2500_QUEUE_ENTRY_SIZE, ++ .regs = { ++ .bmc_cmdq_addr = XDMA_AST2500_BMC_CMDQ_ADDR, ++ .bmc_cmdq_addr_ext = 0, ++ .bmc_cmdq_endp = XDMA_AST2500_BMC_CMDQ_ENDP, ++ .bmc_cmdq_writep = XDMA_AST2500_BMC_CMDQ_WRITEP, ++ .bmc_cmdq_readp = XDMA_AST2500_BMC_CMDQ_READP, ++ .control = XDMA_AST2500_CTRL, ++ .status = XDMA_AST2500_STATUS, ++ }, ++ .status_bits = { ++ .us_comp = XDMA_AST2500_STATUS_US_COMP, ++ .ds_comp = XDMA_AST2500_STATUS_DS_COMP, ++ .ds_dirty = XDMA_AST2500_STATUS_DS_DIRTY, ++ }, ++ .set_cmd = aspeed_xdma_ast2500_set_cmd, ++}; ++ ++static const struct aspeed_xdma_chip aspeed_ast2600_xdma_chip = { ++ .control = XDMA_AST2600_CTRL_US_COMP | XDMA_AST2600_CTRL_DS_COMP | ++ XDMA_AST2600_CTRL_DS_DIRTY | XDMA_AST2600_CTRL_DS_SIZE_256, ++ .scu_bmc_class = SCU_AST2600_BMC_CLASS_REV, ++ .scu_misc_ctrl = SCU_AST2600_MISC_CTRL, ++ .scu_misc_mask = SCU_AST2600_MISC_CTRL_XDMA_BMC, ++ .scu_disable_mask = DEBUG_CTRL_AST2600_XDMA_DISABLE, ++ .scu_pcie_conf = SCU_AST2600_PCIE_CONF, ++ .scu_pcie_ctrl = 0, ++ .queue_entry_size = XDMA_AST2600_QUEUE_ENTRY_SIZE, ++ .regs = { ++ .bmc_cmdq_addr = XDMA_AST2600_BMC_CMDQ_ADDR, ++ .bmc_cmdq_addr_ext = 0, ++ .bmc_cmdq_endp = XDMA_AST2600_BMC_CMDQ_ENDP, ++ .bmc_cmdq_writep = XDMA_AST2600_BMC_CMDQ_WRITEP, ++ .bmc_cmdq_readp = XDMA_AST2600_BMC_CMDQ_READP, ++ .control = XDMA_AST2600_CTRL, ++ .status = XDMA_AST2600_STATUS, ++ }, ++ .status_bits = { ++ .us_comp = XDMA_AST2600_STATUS_US_COMP, ++ .ds_comp = XDMA_AST2600_STATUS_DS_COMP, ++ .ds_dirty = XDMA_AST2600_STATUS_DS_DIRTY, ++ }, ++ .set_cmd = aspeed_xdma_ast2600_set_cmd, ++}; ++ ++static const struct aspeed_xdma_chip aspeed_ast2700_xdma0_chip = { ++ .control = XDMA_AST2700_CTRL_US_COMP | XDMA_AST2700_CTRL_DS_COMP | ++ XDMA_AST2700_CTRL_DS_DIRTY, ++ .scu_bmc_class = SCU_AST2700_PCIE0_BMC_CLASS_REV, ++ .scu_misc_ctrl = SCU_AST2600_MISC_CTRL, ++ .scu_misc_mask = SCU_AST2700_MISC_CTRL_XDMA_CLIENT, ++ .scu_disable_mask = DEBUG_CTRL_AST2600_XDMA_DISABLE | DEBUG_CTRL_AST2700_XDMA_DISABLE, ++ .scu_pcie_conf = SCU_AST2700_PCIE0_CONF, ++ .scu_pcie_ctrl = SCU_AST2700_PCIE0_CTRL, ++ .queue_entry_size = XDMA_AST2700_QUEUE_ENTRY_SIZE, ++ .regs = { ++ .bmc_cmdq_addr = XDMA_AST2700_BMC_CMDQ_ADDR0, ++ .bmc_cmdq_addr_ext = XDMA_AST2700_BMC_CMDQ_ADDR1, ++ .bmc_cmdq_endp = XDMA_AST2700_BMC_CMDQ_ENDP, ++ .bmc_cmdq_writep = XDMA_AST2700_BMC_CMDQ_WRITEP, ++ .bmc_cmdq_readp = XDMA_AST2700_BMC_CMDQ_READP, ++ .control = XDMA_AST2700_CTRL, ++ .status = XDMA_AST2700_STATUS, ++ }, ++ .status_bits = { ++ .us_comp = XDMA_AST2700_STATUS_US_COMP, ++ .ds_comp = XDMA_AST2700_STATUS_DS_COMP, ++ .ds_dirty = XDMA_AST2700_STATUS_DS_DIRTY, ++ }, ++ .set_cmd = aspeed_xdma_ast2700_set_cmd, ++}; ++ ++static const struct aspeed_xdma_chip aspeed_ast2700_xdma1_chip = { ++ .control = XDMA_AST2700_CTRL_US_COMP | XDMA_AST2700_CTRL_DS_COMP | ++ XDMA_AST2700_CTRL_DS_DIRTY, ++ .scu_bmc_class = SCU_AST2700_PCIE1_BMC_CLASS_REV, ++ .scu_misc_ctrl = SCU_AST2600_MISC_CTRL, ++ .scu_misc_mask = SCU_AST2700_MISC_CTRL_XDMA_CLIENT, ++ .scu_disable_mask = DEBUG_CTRL_AST2600_XDMA_DISABLE | DEBUG_CTRL_AST2700_XDMA_DISABLE, ++ .scu_pcie_conf = SCU_AST2700_PCIE1_CONF, ++ .scu_pcie_ctrl = SCU_AST2700_PCIE1_CTRL, ++ .queue_entry_size = XDMA_AST2700_QUEUE_ENTRY_SIZE, ++ .regs = { ++ .bmc_cmdq_addr = XDMA_AST2700_BMC_CMDQ_ADDR0, ++ .bmc_cmdq_addr_ext = XDMA_AST2700_BMC_CMDQ_ADDR1, ++ .bmc_cmdq_endp = XDMA_AST2700_BMC_CMDQ_ENDP, ++ .bmc_cmdq_writep = XDMA_AST2700_BMC_CMDQ_WRITEP, ++ .bmc_cmdq_readp = XDMA_AST2700_BMC_CMDQ_READP, ++ .control = XDMA_AST2700_CTRL, ++ .status = XDMA_AST2700_STATUS, ++ }, ++ .status_bits = { ++ .us_comp = XDMA_AST2700_STATUS_US_COMP, ++ .ds_comp = XDMA_AST2700_STATUS_DS_COMP, ++ .ds_dirty = XDMA_AST2700_STATUS_DS_DIRTY, ++ }, ++ .set_cmd = aspeed_xdma_ast2700_set_cmd, ++}; ++ ++static const struct of_device_id aspeed_xdma_match[] = { ++ { ++ .compatible = "aspeed,ast2500-xdma", ++ .data = &aspeed_ast2500_xdma_chip, ++ }, ++ { ++ .compatible = "aspeed,ast2600-xdma", ++ .data = &aspeed_ast2600_xdma_chip, ++ }, ++ { ++ .compatible = "aspeed,ast2700-xdma0", ++ .data = &aspeed_ast2700_xdma0_chip, ++ }, ++ { ++ .compatible = "aspeed,ast2700-xdma1", ++ .data = &aspeed_ast2700_xdma1_chip, ++ }, ++ { }, ++}; ++ ++static struct platform_driver aspeed_xdma_driver = { ++ .probe = aspeed_xdma_probe, ++ .remove = aspeed_xdma_remove, ++ .driver = { ++ .name = DEVICE_NAME, ++ .of_match_table = aspeed_xdma_match, ++ }, ++}; ++ ++module_platform_driver(aspeed_xdma_driver); ++ ++MODULE_AUTHOR("Eddie James"); ++MODULE_DESCRIPTION("ASPEED XDMA Engine Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/ast2600-otp.c b/drivers/soc/aspeed/ast2600-otp.c +--- a/drivers/soc/aspeed/ast2600-otp.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/ast2600-otp.c 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,638 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) ASPEED Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ASPEED_REVISION_ID0 0x04 ++#define ASPEED_REVISION_ID1 0x14 ++#define ID0_AST2600A0 0x05000303 ++#define ID1_AST2600A0 0x05000303 ++#define ID0_AST2600A1 0x05010303 ++#define ID1_AST2600A1 0x05010303 ++#define ID0_AST2600A2 0x05010303 ++#define ID1_AST2600A2 0x05020303 ++#define ID0_AST2600A3 0x05030303 ++#define ID1_AST2600A3 0x05030303 ++#define ID0_AST2620A1 0x05010203 ++#define ID1_AST2620A1 0x05010203 ++#define ID0_AST2620A2 0x05010203 ++#define ID1_AST2620A2 0x05020203 ++#define ID0_AST2620A3 0x05030203 ++#define ID1_AST2620A3 0x05030203 ++#define ID0_AST2605A2 0x05010103 ++#define ID1_AST2605A2 0x05020103 ++#define ID0_AST2605A3 0x05030103 ++#define ID1_AST2605A3 0x05030103 ++#define ID0_AST2625A3 0x05030403 ++#define ID1_AST2625A3 0x05030403 ++ ++#define OTP_PROTECT_KEY 0x0 ++#define OTP_PASSWD 0x349fe38a ++#define OTP_COMMAND 0x4 ++#define OTP_TIMING 0x8 ++#define OTP_ADDR 0x10 ++#define OTP_STATUS 0x14 ++#define OTP_COMPARE_1 0x20 ++#define OTP_COMPARE_2 0x24 ++#define OTP_COMPARE_3 0x28 ++#define OTP_COMPARE_4 0x2c ++#define SW_REV_ID0 0x68 ++#define SW_REV_ID1 0x6c ++#define SEC_KEY_NUM 0x78 ++#define RETRY 20 ++ ++struct aspeed_otp { ++ struct miscdevice miscdev; ++ void __iomem *reg_base; ++ bool is_open; ++ u32 otp_ver; ++ u32 *data; ++}; ++ ++static DEFINE_SPINLOCK(otp_state_lock); ++ ++static inline u32 aspeed_otp_read(struct aspeed_otp *ctx, u32 reg) ++{ ++ int val; ++ ++ val = readl(ctx->reg_base + reg); ++ // printk("read:reg = 0x%08x, val = 0x%08x\n", reg, val); ++ return val; ++} ++ ++static inline void aspeed_otp_write(struct aspeed_otp *ctx, u32 val, u32 reg) ++{ ++ // printk("write:reg = 0x%08x, val = 0x%08x\n", reg, val); ++ writel(val, ctx->reg_base + reg); ++} ++ ++static uint32_t chip_version(u32 revid0, u32 revid1) ++{ ++ if (revid0 == ID0_AST2600A0 && revid1 == ID1_AST2600A0) { ++ /* AST2600-A0 */ ++ return OTP_A0; ++ } else if (revid0 == ID0_AST2600A1 && revid1 == ID1_AST2600A1) { ++ /* AST2600-A1 */ ++ return OTP_A1; ++ } else if (revid0 == ID0_AST2600A2 && revid1 == ID1_AST2600A2) { ++ /* AST2600-A2 */ ++ return OTP_A2; ++ } else if (revid0 == ID0_AST2600A3 && revid1 == ID1_AST2600A3) { ++ /* AST2600-A3 */ ++ return OTP_A3; ++ } else if (revid0 == ID0_AST2620A1 && revid1 == ID1_AST2620A1) { ++ /* AST2620-A1 */ ++ return OTP_A1; ++ } else if (revid0 == ID0_AST2620A2 && revid1 == ID1_AST2620A2) { ++ /* AST2620-A2 */ ++ return OTP_A2; ++ } else if (revid0 == ID0_AST2620A3 && revid1 == ID1_AST2620A3) { ++ /* AST2620-A3 */ ++ return OTP_A3; ++ } else if (revid0 == ID0_AST2605A2 && revid1 == ID1_AST2605A2) { ++ /* AST2605-A2 */ ++ return OTP_A2; ++ } else if (revid0 == ID0_AST2605A3 && revid1 == ID1_AST2605A3) { ++ /* AST2605-A3 */ ++ return OTP_A3; ++ } else if (revid0 == ID0_AST2625A3 && revid1 == ID1_AST2625A3) { ++ /* AST2605-A3 */ ++ return OTP_A3; ++ } ++ return -1; ++} ++ ++static void wait_complete(struct aspeed_otp *ctx) ++{ ++ int reg; ++ int i = 0; ++ ++ do { ++ reg = aspeed_otp_read(ctx, OTP_STATUS); ++ if ((reg & 0x6) == 0x6) ++ i++; ++ } while (i != 2); ++} ++ ++static void otp_write(struct aspeed_otp *ctx, u32 otp_addr, u32 val) ++{ ++ aspeed_otp_write(ctx, otp_addr, OTP_ADDR); //write address ++ aspeed_otp_write(ctx, val, OTP_COMPARE_1); //write val ++ aspeed_otp_write(ctx, 0x23b1e362, OTP_COMMAND); //write command ++ wait_complete(ctx); ++} ++ ++static void otp_soak(struct aspeed_otp *ctx, int soak) ++{ ++ if (ctx->otp_ver == OTP_A2 || ctx->otp_ver == OTP_A3) { ++ switch (soak) { ++ case 0: //default ++ otp_write(ctx, 0x3000, 0x0); // Write MRA ++ otp_write(ctx, 0x5000, 0x0); // Write MRB ++ otp_write(ctx, 0x1000, 0x0); // Write MR ++ break; ++ case 1: //normal program ++ otp_write(ctx, 0x3000, 0x1320); // Write MRA ++ otp_write(ctx, 0x5000, 0x1008); // Write MRB ++ otp_write(ctx, 0x1000, 0x0024); // Write MR ++ aspeed_otp_write(ctx, 0x04191388, OTP_TIMING); // 200us ++ break; ++ case 2: //soak program ++ otp_write(ctx, 0x3000, 0x1320); // Write MRA ++ otp_write(ctx, 0x5000, 0x0007); // Write MRB ++ otp_write(ctx, 0x1000, 0x0100); // Write MR ++ aspeed_otp_write(ctx, 0x04193a98, OTP_TIMING); // 600us ++ break; ++ } ++ } else { ++ switch (soak) { ++ case 0: //default ++ otp_write(ctx, 0x3000, 0x0); // Write MRA ++ otp_write(ctx, 0x5000, 0x0); // Write MRB ++ otp_write(ctx, 0x1000, 0x0); // Write MR ++ break; ++ case 1: //normal program ++ otp_write(ctx, 0x3000, 0x4021); // Write MRA ++ otp_write(ctx, 0x5000, 0x302f); // Write MRB ++ otp_write(ctx, 0x1000, 0x4020); // Write MR ++ aspeed_otp_write(ctx, 0x04190760, OTP_TIMING); // 75us ++ break; ++ case 2: //soak program ++ otp_write(ctx, 0x3000, 0x4021); // Write MRA ++ otp_write(ctx, 0x5000, 0x1027); // Write MRB ++ otp_write(ctx, 0x1000, 0x4820); // Write MR ++ aspeed_otp_write(ctx, 0x041930d4, OTP_TIMING); // 500us ++ break; ++ } ++ } ++ ++ wait_complete(ctx); ++} ++ ++static int verify_bit(struct aspeed_otp *ctx, u32 otp_addr, int bit_offset, int value) ++{ ++ u32 ret[2]; ++ ++ if (otp_addr % 2 == 0) ++ aspeed_otp_write(ctx, otp_addr, OTP_ADDR); //Read address ++ else ++ aspeed_otp_write(ctx, otp_addr - 1, OTP_ADDR); //Read address ++ ++ aspeed_otp_write(ctx, 0x23b1e361, OTP_COMMAND); //trigger read ++ wait_complete(ctx); ++ ret[0] = aspeed_otp_read(ctx, OTP_COMPARE_1); ++ ret[1] = aspeed_otp_read(ctx, OTP_COMPARE_2); ++ ++ if (otp_addr % 2 == 0) { ++ if (((ret[0] >> bit_offset) & 1) == value) ++ return 0; ++ else ++ return -1; ++ } else { ++ if (((ret[1] >> bit_offset) & 1) == value) ++ return 0; ++ else ++ return -1; ++ } ++} ++ ++static void otp_prog(struct aspeed_otp *ctx, u32 otp_addr, u32 prog_bit) ++{ ++ otp_write(ctx, 0x0, prog_bit); ++ aspeed_otp_write(ctx, otp_addr, OTP_ADDR); //write address ++ aspeed_otp_write(ctx, prog_bit, OTP_COMPARE_1); //write data ++ aspeed_otp_write(ctx, 0x23b1e364, OTP_COMMAND); //write command ++ wait_complete(ctx); ++} ++ ++static void _otp_prog_bit(struct aspeed_otp *ctx, u32 value, u32 prog_address, u32 bit_offset) ++{ ++ int prog_bit; ++ ++ if (prog_address % 2 == 0) { ++ if (value) ++ prog_bit = ~(0x1 << bit_offset); ++ else ++ return; ++ } else { ++ if (ctx->otp_ver != OTP_A3) ++ prog_address |= 1 << 15; ++ if (!value) ++ prog_bit = 0x1 << bit_offset; ++ else ++ return; ++ } ++ otp_prog(ctx, prog_address, prog_bit); ++} ++ ++static int otp_prog_bit(struct aspeed_otp *ctx, u32 value, u32 prog_address, u32 bit_offset) ++{ ++ int pass; ++ int i; ++ ++ otp_soak(ctx, 1); ++ _otp_prog_bit(ctx, value, prog_address, bit_offset); ++ pass = 0; ++ ++ for (i = 0; i < RETRY; i++) { ++ if (verify_bit(ctx, prog_address, bit_offset, value) != 0) { ++ otp_soak(ctx, 2); ++ _otp_prog_bit(ctx, value, prog_address, bit_offset); ++ if (verify_bit(ctx, prog_address, bit_offset, value) != 0) { ++ otp_soak(ctx, 1); ++ } else { ++ pass = 1; ++ break; ++ } ++ } else { ++ pass = 1; ++ break; ++ } ++ } ++ otp_soak(ctx, 0); ++ return pass; ++} ++ ++static void otp_read_conf_dw(struct aspeed_otp *ctx, u32 offset, u32 *buf) ++{ ++ u32 config_offset; ++ ++ config_offset = 0x800; ++ config_offset |= (offset / 8) * 0x200; ++ config_offset |= (offset % 8) * 0x2; ++ ++ aspeed_otp_write(ctx, config_offset, OTP_ADDR); //Read address ++ aspeed_otp_write(ctx, 0x23b1e361, OTP_COMMAND); //trigger read ++ wait_complete(ctx); ++ buf[0] = aspeed_otp_read(ctx, OTP_COMPARE_1); ++} ++ ++static void otp_read_conf(struct aspeed_otp *ctx, u32 offset, u32 len) ++{ ++ int i, j; ++ ++ otp_soak(ctx, 0); ++ for (i = offset, j = 0; j < len; i++, j++) ++ otp_read_conf_dw(ctx, i, &ctx->data[j]); ++} ++ ++static void otp_read_data_2dw(struct aspeed_otp *ctx, u32 offset, u32 *buf) ++{ ++ aspeed_otp_write(ctx, offset, OTP_ADDR); //Read address ++ aspeed_otp_write(ctx, 0x23b1e361, OTP_COMMAND); //trigger read ++ wait_complete(ctx); ++ buf[0] = aspeed_otp_read(ctx, OTP_COMPARE_1); ++ buf[1] = aspeed_otp_read(ctx, OTP_COMPARE_2); ++} ++ ++static void otp_read_data(struct aspeed_otp *ctx, u32 offset, u32 len) ++{ ++ int i, j; ++ u32 ret[2]; ++ ++ otp_soak(ctx, 0); ++ ++ i = offset; ++ j = 0; ++ if (offset % 2) { ++ otp_read_data_2dw(ctx, i - 1, ret); ++ ctx->data[0] = ret[1]; ++ i++; ++ j++; ++ } ++ for (; j < len; i += 2, j += 2) ++ otp_read_data_2dw(ctx, i, &ctx->data[j]); ++} ++ ++static int otp_prog_data(struct aspeed_otp *ctx, u32 value, u32 dw_offset, u32 bit_offset) ++{ ++ u32 read[2]; ++ int otp_bit; ++ ++ if (dw_offset % 2 == 0) { ++ otp_read_data_2dw(ctx, dw_offset, read); ++ otp_bit = (read[0] >> bit_offset) & 0x1; ++ ++ if (otp_bit == 1 && value == 0) { ++ pr_err("OTPDATA%X[%X] = 1\n", dw_offset, bit_offset); ++ pr_err("OTP is programed, which can't be cleaned\n"); ++ return -EINVAL; ++ } ++ } else { ++ otp_read_data_2dw(ctx, dw_offset - 1, read); ++ otp_bit = (read[1] >> bit_offset) & 0x1; ++ ++ if (otp_bit == 0 && value == 1) { ++ pr_err("OTPDATA%X[%X] = 1\n", dw_offset, bit_offset); ++ pr_err("OTP is programed, which can't be writen\n"); ++ return -EINVAL; ++ } ++ } ++ if (otp_bit == value) { ++ pr_err("OTPDATA%X[%X] = %d\n", dw_offset, bit_offset, value); ++ pr_err("No need to program\n"); ++ return 0; ++ } ++ ++ return otp_prog_bit(ctx, value, dw_offset, bit_offset); ++} ++ ++static int otp_prog_conf(struct aspeed_otp *ctx, u32 value, u32 dw_offset, u32 bit_offset) ++{ ++ u32 read; ++ u32 prog_address = 0; ++ int otp_bit; ++ ++ otp_read_conf_dw(ctx, dw_offset, &read); ++ ++ prog_address = 0x800; ++ prog_address |= (dw_offset / 8) * 0x200; ++ prog_address |= (dw_offset % 8) * 0x2; ++ otp_bit = (read >> bit_offset) & 0x1; ++ if (otp_bit == value) { ++ pr_err("OTPCFG%X[%X] = %d\n", dw_offset, bit_offset, value); ++ pr_err("No need to program\n"); ++ return 0; ++ } ++ if (otp_bit == 1 && value == 0) { ++ pr_err("OTPCFG%X[%X] = 1\n", dw_offset, bit_offset); ++ pr_err("OTP is programed, which can't be clean\n"); ++ return -EINVAL; ++ } ++ ++ return otp_prog_bit(ctx, value, prog_address, bit_offset); ++} ++ ++struct aspeed_otp *glob_ctx; ++ ++void otp_read_data_buf(u32 offset, u32 *buf, u32 len) ++{ ++ int i, j; ++ u32 ret[2]; ++ ++ aspeed_otp_write(glob_ctx, OTP_PASSWD, OTP_PROTECT_KEY); ++ ++ otp_soak(glob_ctx, 0); ++ ++ i = offset; ++ j = 0; ++ if (offset % 2) { ++ otp_read_data_2dw(glob_ctx, i - 1, ret); ++ buf[0] = ret[1]; ++ i++; ++ j++; ++ } ++ for (; j < len; i += 2, j += 2) ++ otp_read_data_2dw(glob_ctx, i, &buf[j]); ++ aspeed_otp_write(glob_ctx, 0, OTP_PROTECT_KEY); ++} ++EXPORT_SYMBOL(otp_read_data_buf); ++ ++void otp_read_conf_buf(u32 offset, u32 *buf, u32 len) ++{ ++ int i, j; ++ ++ aspeed_otp_write(glob_ctx, OTP_PASSWD, OTP_PROTECT_KEY); ++ otp_soak(glob_ctx, 0); ++ for (i = offset, j = 0; j < len; i++, j++) ++ otp_read_conf_dw(glob_ctx, i, &buf[j]); ++ aspeed_otp_write(glob_ctx, 0, OTP_PROTECT_KEY); ++} ++EXPORT_SYMBOL(otp_read_conf_buf); ++ ++static long otp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct miscdevice *c = file->private_data; ++ struct aspeed_otp *ctx = container_of(c, struct aspeed_otp, miscdev); ++ void __user *argp = (void __user *)arg; ++ struct otp_read xfer; ++ struct otp_prog prog; ++ u32 reg_read[2]; ++ int ret = 0; ++ ++ switch (cmd) { ++ case ASPEED_OTP_READ_DATA: ++ if (copy_from_user(&xfer, argp, sizeof(struct otp_read))) ++ return -EFAULT; ++ if ((xfer.offset + xfer.len) > 0x800) { ++ pr_err("out of range"); ++ return -EINVAL; ++ } ++ ++ aspeed_otp_write(ctx, OTP_PASSWD, OTP_PROTECT_KEY); ++ otp_read_data(ctx, xfer.offset, xfer.len); ++ aspeed_otp_write(ctx, 0, OTP_PROTECT_KEY); ++ ++ if (copy_to_user(xfer.data, ctx->data, xfer.len * 4)) ++ return -EFAULT; ++ if (copy_to_user(argp, &xfer, sizeof(struct otp_read))) ++ return -EFAULT; ++ break; ++ case ASPEED_OTP_READ_CONF: ++ if (copy_from_user(&xfer, argp, sizeof(struct otp_read))) ++ return -EFAULT; ++ if ((xfer.offset + xfer.len) > 0x800) { ++ pr_err("out of range"); ++ return -EINVAL; ++ } ++ ++ aspeed_otp_write(ctx, OTP_PASSWD, OTP_PROTECT_KEY); ++ otp_read_conf(ctx, xfer.offset, xfer.len); ++ aspeed_otp_write(ctx, 0, OTP_PROTECT_KEY); ++ ++ if (copy_to_user(xfer.data, ctx->data, xfer.len * 4)) ++ return -EFAULT; ++ if (copy_to_user(argp, &xfer, sizeof(struct otp_read))) ++ return -EFAULT; ++ break; ++ case ASPEED_OTP_PROG_DATA: ++ if (copy_from_user(&prog, argp, sizeof(struct otp_prog))) ++ return -EFAULT; ++ if (prog.bit_offset >= 32 || (prog.value != 0 && prog.value != 1)) { ++ pr_err("out of range"); ++ return -EINVAL; ++ } ++ if (prog.dw_offset >= 0x800) { ++ pr_err("out of range"); ++ return -EINVAL; ++ } ++ aspeed_otp_write(ctx, OTP_PASSWD, OTP_PROTECT_KEY); ++ ret = otp_prog_data(ctx, prog.value, prog.dw_offset, prog.bit_offset); ++ break; ++ case ASPEED_OTP_PROG_CONF: ++ if (copy_from_user(&prog, argp, sizeof(struct otp_prog))) ++ return -EFAULT; ++ if (prog.bit_offset >= 32 || (prog.value != 0 && prog.value != 1)) { ++ pr_err("out of range"); ++ return -EINVAL; ++ } ++ if (prog.dw_offset >= 0x20) { ++ pr_err("out of range"); ++ return -EINVAL; ++ } ++ aspeed_otp_write(ctx, OTP_PASSWD, OTP_PROTECT_KEY); ++ ret = otp_prog_conf(ctx, prog.value, prog.dw_offset, prog.bit_offset); ++ break; ++ case ASPEED_OTP_VER: ++ if (copy_to_user(argp, &ctx->otp_ver, sizeof(u32))) ++ return -EFAULT; ++ break; ++ case ASPEED_OTP_SW_RID: ++ reg_read[0] = aspeed_otp_read(ctx, SW_REV_ID0); ++ reg_read[1] = aspeed_otp_read(ctx, SW_REV_ID1); ++ if (copy_to_user(argp, reg_read, sizeof(u32) * 2)) ++ return -EFAULT; ++ break; ++ case ASPEED_SEC_KEY_NUM: ++ reg_read[0] = aspeed_otp_read(ctx, SEC_KEY_NUM) & 7; ++ if (copy_to_user(argp, reg_read, sizeof(u32))) ++ return -EFAULT; ++ break; ++ } ++ return ret; ++} ++ ++static int otp_open(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *c = file->private_data; ++ struct aspeed_otp *ctx = container_of(c, struct aspeed_otp, miscdev); ++ ++ spin_lock(&otp_state_lock); ++ ++ if (ctx->is_open) { ++ spin_unlock(&otp_state_lock); ++ return -EBUSY; ++ } ++ ++ ctx->is_open = true; ++ ++ spin_unlock(&otp_state_lock); ++ ++ return 0; ++} ++ ++static int otp_release(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *c = file->private_data; ++ struct aspeed_otp *ctx = container_of(c, struct aspeed_otp, miscdev); ++ ++ spin_lock(&otp_state_lock); ++ ++ ctx->is_open = false; ++ ++ spin_unlock(&otp_state_lock); ++ ++ return 0; ++} ++ ++static const struct file_operations otp_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = otp_ioctl, ++ .open = otp_open, ++ .release = otp_release, ++}; ++ ++static const struct of_device_id aspeed_otp_of_matches[] = { ++ { .compatible = "aspeed,ast2600-sbc" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_otp_of_matches); ++ ++static int aspeed_otp_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct regmap *scu; ++ struct aspeed_otp *priv; ++ struct resource *res; ++ u32 revid0, revid1; ++ int rc; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ glob_ctx = priv; ++ if (!priv) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n"); ++ return -ENOENT; ++ } ++ ++ priv->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (!priv->reg_base) ++ return -EIO; ++ ++ scu = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,scu"); ++ if (IS_ERR(scu)) { ++ dev_err(dev, "failed to find 2600 SCU regmap\n"); ++ return PTR_ERR(scu); ++ } ++ ++ regmap_read(scu, ASPEED_REVISION_ID0, &revid0); ++ regmap_read(scu, ASPEED_REVISION_ID1, &revid1); ++ ++ priv->otp_ver = chip_version(revid0, revid1); ++ ++ if (priv->otp_ver == -1) { ++ dev_err(dev, "invalid SCU\n"); ++ return -EINVAL; ++ } ++ ++ priv->data = kmalloc(8192, GFP_KERNEL); ++ if (!priv->data) ++ return -ENOMEM; ++ ++ dev_set_drvdata(dev, priv); ++ ++ /* Set up the miscdevice */ ++ priv->miscdev.minor = MISC_DYNAMIC_MINOR; ++ priv->miscdev.name = "aspeed-otp"; ++ priv->miscdev.fops = &otp_fops; ++ ++ /* Register the device */ ++ rc = misc_register(&priv->miscdev); ++ if (rc) { ++ dev_err(dev, "Unable to register device\n"); ++ return rc; ++ } ++ ++ return 0; ++} ++ ++static void aspeed_otp_remove(struct platform_device *pdev) ++{ ++ struct aspeed_otp *ctx = dev_get_drvdata(&pdev->dev); ++ ++ kfree(ctx->data); ++ misc_deregister(&ctx->miscdev); ++} ++ ++static struct platform_driver aspeed_otp_driver = { ++ .probe = aspeed_otp_probe, ++ .remove = aspeed_otp_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_otp_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_otp_driver); ++ ++MODULE_AUTHOR("Neal Liu "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("ASPEED OTP Driver"); +diff --git a/drivers/soc/aspeed/ast2700-otp.c b/drivers/soc/aspeed/ast2700-otp.c +--- a/drivers/soc/aspeed/ast2700-otp.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/ast2700-otp.c 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,565 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2024 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static DEFINE_SPINLOCK(otp_state_lock); ++ ++/*********************** ++ * * ++ * OTP regs definition * ++ * * ++ ***********************/ ++#define OTP_REG_SIZE 0x200 ++ ++#define OTP_PASSWD 0x349fe38a ++#define OTP_CMD_READ 0x23b1e361 ++#define OTP_CMD_PROG 0x23b1e364 ++#define OTP_CMD_PROG_MULTI 0x23b1e365 ++#define OTP_CMD_CMP 0x23b1e363 ++#define OTP_CMD_BIST 0x23b1e368 ++ ++#define OTP_CMD_OFFSET 0x20 ++#define OTP_MASTER OTP_M0 ++ ++#define OTP_KEY 0x0 ++#define OTP_CMD (OTP_MASTER * OTP_CMD_OFFSET + 0x4) ++#define OTP_WDATA_0 (OTP_MASTER * OTP_CMD_OFFSET + 0x8) ++#define OTP_WDATA_1 (OTP_MASTER * OTP_CMD_OFFSET + 0xc) ++#define OTP_WDATA_2 (OTP_MASTER * OTP_CMD_OFFSET + 0x10) ++#define OTP_WDATA_3 (OTP_MASTER * OTP_CMD_OFFSET + 0x14) ++#define OTP_STATUS (OTP_MASTER * OTP_CMD_OFFSET + 0x18) ++#define OTP_ADDR (OTP_MASTER * OTP_CMD_OFFSET + 0x1c) ++#define OTP_RDATA (OTP_MASTER * OTP_CMD_OFFSET + 0x20) ++ ++#define OTP_DBG00 0x0C4 ++#define OTP_DBG01 0x0C8 ++#define OTP_MASTER_PID 0x0D0 ++#define OTP_ECC_EN 0x0D4 ++#define OTP_CMD_LOCK 0x0D8 ++#define OTP_SW_RST 0x0DC ++#define OTP_SLV_ID 0x0E0 ++#define OTP_PMC_CQ 0x0E4 ++#define OTP_FPGA 0x0EC ++#define OTP_CLR_FPGA 0x0F0 ++#define OTP_REGION_ROM_PATCH 0x100 ++#define OTP_REGION_OTPCFG 0x104 ++#define OTP_REGION_OTPSTRAP 0x108 ++#define OTP_REGION_OTPSTRAP_EXT 0x10C ++#define OTP_REGION_SECURE0 0x120 ++#define OTP_REGION_SECURE0_RANGE 0x124 ++#define OTP_REGION_SECURE1 0x128 ++#define OTP_REGION_SECURE1_RANGE 0x12C ++#define OTP_REGION_SECURE2 0x130 ++#define OTP_REGION_SECURE2_RANGE 0x134 ++#define OTP_REGION_SECURE3 0x138 ++#define OTP_REGION_SECURE3_RANGE 0x13C ++#define OTP_REGION_USR0 0x140 ++#define OTP_REGION_USR0_RANGE 0x144 ++#define OTP_REGION_USR1 0x148 ++#define OTP_REGION_USR1_RANGE 0x14C ++#define OTP_REGION_USR2 0x150 ++#define OTP_REGION_USR2_RANGE 0x154 ++#define OTP_REGION_USR3 0x158 ++#define OTP_REGION_USR3_RANGE 0x15C ++#define OTP_REGION_CALIPTRA_0 0x160 ++#define OTP_REGION_CALIPTRA_0_RANGE 0x164 ++#define OTP_REGION_CALIPTRA_1 0x168 ++#define OTP_REGION_CALIPTRA_1_RANGE 0x16C ++#define OTP_REGION_CALIPTRA_2 0x170 ++#define OTP_REGION_CALIPTRA_2_RANGE 0x174 ++#define OTP_REGION_CALIPTRA_3 0x178 ++#define OTP_REGION_CALIPTRA_3_RANGE 0x17C ++#define OTP_RBP_SOC_SVN 0x180 ++#define OTP_RBP_SOC_KEYRETIRE 0x184 ++#define OTP_RBP_CALIP_SVN 0x188 ++#define OTP_RBP_CALIP_KEYRETIRE 0x18C ++#define OTP_PUF 0x1A0 ++#define OTP_MASTER_ID 0x1B0 ++#define OTP_MASTER_ID_EXT 0x1B4 ++#define OTP_R_MASTER_ID 0x1B8 ++#define OTP_R_MASTER_ID_EXT 0x1BC ++#define OTP_SOC_ECCKEY 0x1C0 ++#define OTP_SEC_BOOT_EN 0x1C4 ++#define OTP_SOC_KEY 0x1C8 ++#define OTP_CALPITRA_MANU_KEY 0x1CC ++#define OTP_CALPITRA_OWNER_KEY 0x1D0 ++#define OTP_FW_ID_LSB 0x1D4 ++#define OTP_FW_ID_MSB 0x1D8 ++#define OTP_CALIP_FMC_SVN 0x1DC ++#define OTP_CALIP_RUNTIME_SVN0 0x1E0 ++#define OTP_CALIP_RUNTIME_SVN1 0x1E4 ++#define OTP_CALIP_RUNTIME_SVN2 0x1E8 ++#define OTP_CALIP_RUNTIME_SVN3 0x1EC ++#define OTP_SVN_WLOCK 0x1F0 ++#define OTP_INTR_EN 0x200 ++#define OTP_INTR_STS 0x204 ++#define OTP_INTR_MID 0x208 ++#define OTP_INTR_FUNC_INFO 0x20C ++#define OTP_INTR_M_INFO 0x210 ++#define OTP_INTR_R_INFO 0x214 ++ ++#define OTP_PMC 0x400 ++#define OTP_DAP 0x500 ++ ++/* OTP status: [0] */ ++#define OTP_STS_IDLE 0x0 ++#define OTP_STS_BUSY 0x1 ++ ++/* OTP cmd status: [7:4] */ ++#define OTP_GET_CMD_STS(x) (((x) & 0xF0) >> 4) ++#define OTP_STS_PASS 0x0 ++#define OTP_STS_FAIL 0x1 ++#define OTP_STS_CMP_FAIL 0x2 ++#define OTP_STS_REGION_FAIL 0x3 ++#define OTP_STS_MASTER_FAIL 0x4 ++ ++/* OTP ECC EN */ ++#define ECC_ENABLE 0x1 ++#define ECC_DISABLE 0x0 ++#define ECCBRP_EN BIT(0) ++ ++#define ROM_REGION_START_ADDR 0x0 ++#define ROM_REGION_END_ADDR 0x3e0 ++#define RBP_REGION_START_ADDR ROM_REGION_END_ADDR ++#define RBP_REGION_END_ADDR 0x400 ++#define CONF_REGION_START_ADDR RBP_REGION_END_ADDR ++#define CONF_REGION_END_ADDR 0x420 ++#define STRAP_REGION_START_ADDR CONF_REGION_END_ADDR ++#define STRAP_REGION_END_ADDR 0x430 ++#define STRAPEXT_REGION_START_ADDR STRAP_REGION_END_ADDR ++#define STRAPEXT_REGION_END_ADDR 0x440 ++#define USER_REGION_START_ADDR STRAPEXT_REGION_END_ADDR ++#define USER_REGION_END_ADDR 0x1000 ++#define SEC_REGION_START_ADDR USER_REGION_END_ADDR ++#define SEC_REGION_END_ADDR 0x1c00 ++#define CAL_REGION_START_ADDR SEC_REGION_END_ADDR ++#define CAL_REGION_END_ADDR 0x1f80 ++#define SW_PUF_REGION_START_ADDR CAL_REGION_END_ADDR ++#define SW_PUF_REGION_END_ADDR 0x1fc0 ++#define HW_PUF_REGION_START_ADDR SW_PUF_REGION_END_ADDR ++#define HW_PUF_REGION_END_ADDR 0x2000 ++ ++#define OTP_MEMORY_SIZE (HW_PUF_REGION_END_ADDR * 2) ++ ++#define OTP_TIMEOUT_US 10000 ++ ++/* OTPSTRAP */ ++#define OTPSTRAP0_ADDR STRAP_REGION_START_ADDR ++#define OTPSTRAP14_ADDR (OTPSTRAP0_ADDR + 0xe) ++ ++#define OTPTOOL_VERSION(a, b, c) (((a) << 24) + ((b) << 12) + (c)) ++#define OTPTOOL_VERSION_MAJOR(x) (((x) >> 24) & 0xff) ++#define OTPTOOL_VERSION_PATCHLEVEL(x) (((x) >> 12) & 0xfff) ++#define OTPTOOL_VERSION_SUBLEVEL(x) ((x) & 0xfff) ++#define OTPTOOL_COMPT_VERSION 2 ++ ++enum otp_error_code { ++ OTP_SUCCESS, ++ OTP_READ_FAIL, ++ OTP_PROG_FAIL, ++ OTP_CMP_FAIL, ++}; ++ ++enum aspeed_otp_master_id { ++ OTP_M0 = 0, ++ OTP_M1, ++ OTP_M2, ++ OTP_M3, ++ OTP_M4, ++ OTP_M5, ++ OTP_MID_MAX, ++}; ++ ++struct aspeed_otp { ++ struct miscdevice miscdev; ++ struct device *dev; ++ void __iomem *base; ++ u32 chip_revid0; ++ u32 chip_revid1; ++ bool is_open; ++ int gbl_ecc_en; ++ u8 *data; ++}; ++ ++enum otp_ioctl_cmds { ++ GET_ECC_STATUS = 1, ++ SET_ECC_ENABLE, ++}; ++ ++enum otp_ecc_codes { ++ OTP_ECC_MISMATCH = -1, ++ OTP_ECC_DISABLE = 0, ++ OTP_ECC_ENABLE = 1, ++}; ++ ++static void otp_unlock(struct device *dev) ++{ ++ struct aspeed_otp *ctx = dev_get_drvdata(dev); ++ ++ writel(OTP_PASSWD, ctx->base + OTP_KEY); ++} ++ ++static void otp_lock(struct device *dev) ++{ ++ struct aspeed_otp *ctx = dev_get_drvdata(dev); ++ ++ writel(0x1, ctx->base + OTP_KEY); ++} ++ ++static int wait_complete(struct device *dev) ++{ ++ struct aspeed_otp *ctx = dev_get_drvdata(dev); ++ int ret; ++ u32 val; ++ ++ ret = readl_poll_timeout(ctx->base + OTP_STATUS, val, (val == 0x0), ++ 1, OTP_TIMEOUT_US); ++ if (ret) ++ dev_warn(dev, "timeout. sts:0x%x\n", val); ++ ++ return ret; ++} ++ ++static int otp_read_data(struct aspeed_otp *ctx, u32 offset, u16 *data) ++{ ++ struct device *dev = ctx->dev; ++ int ret; ++ ++ writel(ctx->gbl_ecc_en, ctx->base + OTP_ECC_EN); ++ writel(offset, ctx->base + OTP_ADDR); ++ writel(OTP_CMD_READ, ctx->base + OTP_CMD); ++ ret = wait_complete(dev); ++ if (ret) ++ return OTP_READ_FAIL; ++ ++ data[0] = readl(ctx->base + OTP_RDATA); ++ ++ return 0; ++} ++ ++static int otp_prog_data(struct aspeed_otp *ctx, u32 offset, u16 data) ++{ ++ struct device *dev = ctx->dev; ++ int ret; ++ ++ writel(ctx->gbl_ecc_en, ctx->base + OTP_ECC_EN); ++ writel(offset, ctx->base + OTP_ADDR); ++ writel(data, ctx->base + OTP_WDATA_0); ++ writel(OTP_CMD_PROG, ctx->base + OTP_CMD); ++ ret = wait_complete(dev); ++ if (ret) ++ return OTP_PROG_FAIL; ++ ++ return 0; ++} ++ ++static int otp_prog_multi_data(struct aspeed_otp *ctx, u32 offset, u32 *data, int count) ++{ ++ struct device *dev = ctx->dev; ++ int ret; ++ ++ writel(ctx->gbl_ecc_en, ctx->base + OTP_ECC_EN); ++ writel(offset, ctx->base + OTP_ADDR); ++ for (int i = 0; i < count; i++) ++ writel(data[i], ctx->base + OTP_WDATA_0 + 4 * i); ++ ++ writel(OTP_CMD_PROG_MULTI, ctx->base + OTP_CMD); ++ ret = wait_complete(dev); ++ if (ret) ++ return OTP_PROG_FAIL; ++ ++ return 0; ++} ++ ++static int aspeed_otp_read(struct aspeed_otp *ctx, int offset, ++ void *buf, int size) ++{ ++ struct device *dev = ctx->dev; ++ u16 *data = buf; ++ int ret; ++ ++ otp_unlock(dev); ++ for (int i = 0; i < size; i++) { ++ ret = otp_read_data(ctx, offset + i, data + i); ++ if (ret) { ++ dev_warn(ctx->dev, "read failed\n"); ++ break; ++ } ++ } ++ ++ otp_lock(dev); ++ return ret; ++} ++ ++static int aspeed_otp_write(struct aspeed_otp *ctx, int offset, ++ const void *buf, int size) ++{ ++ struct device *dev = ctx->dev; ++ u32 *data32 = (u32 *)buf; ++ u16 *data = (u16 *)buf; ++ int ret; ++ ++ otp_unlock(dev); ++ ++ if (size == 1) ++ ret = otp_prog_data(ctx, offset, data[0]); ++ else ++ ret = otp_prog_multi_data(ctx, offset, data32, size / 2); ++ ++ if (ret) ++ dev_warn(ctx->dev, "prog failed\n"); ++ ++ otp_lock(dev); ++ return ret; ++} ++ ++static int aspeed_otp_ecc_en(struct aspeed_otp *ctx) ++{ ++ struct device *dev = ctx->dev; ++ int ret = 0; ++ ++ /* Check ecc is already enabled */ ++ if (ctx->gbl_ecc_en == 1) ++ return 0; ++ ++ otp_unlock(dev); ++ ++ /* enable cfg ecc */ ++ ret = otp_prog_data(ctx, OTPSTRAP14_ADDR, 0x1); ++ if (ret) { ++ dev_warn(dev, "%s: prog failed\n", __func__); ++ goto end; ++ } ++ ++ ctx->gbl_ecc_en = 1; ++end: ++ otp_lock(dev); ++ ++ return ret; ++} ++ ++static long aspeed_otp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct miscdevice *c = file->private_data; ++ struct aspeed_otp *ctx = container_of(c, struct aspeed_otp, miscdev); ++ void __user *argp = (void __user *)arg; ++ struct otp_revid revid; ++ struct otp_read rdata; ++ struct otp_prog pdata; ++ int ret = 0; ++ ++ switch (cmd) { ++ case ASPEED_OTP_READ_DATA: ++ if (copy_from_user(&rdata, argp, sizeof(struct otp_read))) ++ return -EFAULT; ++ ++ ret = aspeed_otp_read(ctx, rdata.offset, ctx->data, rdata.len); ++ if (ret) ++ return -EFAULT; ++ ++ if (copy_to_user(rdata.data, ctx->data, rdata.len * 2)) ++ return -EFAULT; ++ ++ break; ++ ++ case ASPEED_OTP_PROG_DATA: ++ if (copy_from_user(&pdata, argp, sizeof(struct otp_prog))) ++ return -EFAULT; ++ ++ ret = aspeed_otp_write(ctx, pdata.w_offset, pdata.data, pdata.len); ++ break; ++ ++ case ASPEED_OTP_GET_ECC: ++ if (copy_to_user(argp, &ctx->gbl_ecc_en, sizeof(ctx->gbl_ecc_en))) ++ return -EFAULT; ++ break; ++ ++ case ASPEED_OTP_SET_ECC: ++ ret = aspeed_otp_ecc_en(ctx); ++ break; ++ ++ case ASPEED_OTP_GET_REVID: ++ revid.revid0 = ctx->chip_revid0; ++ revid.revid1 = ctx->chip_revid1; ++ if (copy_to_user(argp, &revid, sizeof(struct otp_revid))) ++ return -EFAULT; ++ break; ++ default: ++ dev_warn(ctx->dev, "cmd 0x%x is not supported\n", cmd); ++ break; ++ } ++ ++ return ret; ++} ++ ++static int aspeed_otp_ecc_init(struct device *dev) ++{ ++ struct aspeed_otp *ctx = dev_get_drvdata(dev); ++ int ret; ++ u32 val; ++ ++ otp_unlock(dev); ++ ++ /* Check cfg_ecc_en */ ++ writel(0, ctx->base + OTP_ECC_EN); ++ writel(OTPSTRAP14_ADDR, ctx->base + OTP_ADDR); ++ writel(OTP_CMD_READ, ctx->base + OTP_CMD); ++ ret = wait_complete(dev); ++ if (ret) ++ return OTP_READ_FAIL; ++ ++ val = readl(ctx->base + OTP_RDATA); ++ if (val & 0x1) ++ ctx->gbl_ecc_en = 0x1; ++ else ++ ctx->gbl_ecc_en = 0x0; ++ ++ otp_lock(dev); ++ ++ return 0; ++} ++ ++static int aspeed_otp_open(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *c = file->private_data; ++ struct aspeed_otp *ctx = container_of(c, struct aspeed_otp, miscdev); ++ ++ spin_lock(&otp_state_lock); ++ ++ if (ctx->is_open) { ++ spin_unlock(&otp_state_lock); ++ return -EBUSY; ++ } ++ ++ ctx->is_open = true; ++ ++ spin_unlock(&otp_state_lock); ++ ++ return 0; ++} ++ ++static int aspeed_otp_release(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *c = file->private_data; ++ struct aspeed_otp *ctx = container_of(c, struct aspeed_otp, miscdev); ++ ++ spin_lock(&otp_state_lock); ++ ++ ctx->is_open = false; ++ ++ spin_unlock(&otp_state_lock); ++ ++ return 0; ++} ++ ++static const struct file_operations otp_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = aspeed_otp_ioctl, ++ .open = aspeed_otp_open, ++ .release = aspeed_otp_release, ++}; ++ ++static const struct of_device_id aspeed_otp_of_matches[] = { ++ { .compatible = "aspeed,ast2700-otp" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_otp_of_matches); ++ ++static int aspeed_otp_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct regmap *scu0, *scu1; ++ struct aspeed_otp *priv; ++ struct resource *res; ++ int rc; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n"); ++ return -ENOENT; ++ } ++ ++ priv->base = devm_ioremap_resource(&pdev->dev, res); ++ if (!priv->base) ++ return -EIO; ++ ++ scu0 = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,scu0"); ++ scu1 = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,scu1"); ++ if (IS_ERR(scu0) || IS_ERR(scu1)) { ++ dev_err(dev, "failed to find SCU regmap\n"); ++ return PTR_ERR(scu0) || PTR_ERR(scu1); ++ } ++ ++ regmap_read(scu0, 0x0, &priv->chip_revid0); ++ regmap_read(scu1, 0x0, &priv->chip_revid1); ++ ++ priv->dev = dev; ++ dev_set_drvdata(dev, priv); ++ ++ /* OTP ECC init */ ++ rc = aspeed_otp_ecc_init(dev); ++ if (rc) ++ return -EIO; ++ ++ priv->data = kmalloc(OTP_MEMORY_SIZE, GFP_KERNEL); ++ if (!priv->data) ++ return -ENOMEM; ++ ++ /* Set up the miscdevice */ ++ priv->miscdev.minor = MISC_DYNAMIC_MINOR; ++ priv->miscdev.name = "aspeed-otp"; ++ priv->miscdev.fops = &otp_fops; ++ ++ /* Register the device */ ++ rc = misc_register(&priv->miscdev); ++ if (rc) { ++ dev_err(dev, "Unable to register device\n"); ++ return rc; ++ } ++ ++ dev_info(dev, "Aspeed OTP driver successfully registered\n"); ++ ++ return 0; ++} ++ ++static void aspeed_otp_remove(struct platform_device *pdev) ++{ ++ struct aspeed_otp *ctx = dev_get_drvdata(&pdev->dev); ++ ++ kfree(ctx->data); ++ misc_deregister(&ctx->miscdev); ++} ++ ++static struct platform_driver aspeed_otp_driver = { ++ .probe = aspeed_otp_probe, ++ .remove = aspeed_otp_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_otp_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_otp_driver); ++ ++MODULE_AUTHOR("Neal Liu "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("ASPEED OTP Driver"); +diff --git a/drivers/soc/aspeed/espi/Kconfig b/drivers/soc/aspeed/espi/Kconfig +--- a/drivers/soc/aspeed/espi/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/Kconfig 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,22 @@ ++menu "ASPEED eSPI drivers" ++ ++config ASPEED_ESPI ++ tristate "ASPEED eSPI slave driver" ++ help ++ Enable driver support for Aspeed AST2700 eSPI engine. The eSPI engine ++ plays as a slave device in BMC to communicate with the Host over ++ the eSPI interface. The four eSPI channels, namely peripheral, ++ virtual wire, out-of-band, and flash are supported. ++ ++config AST2700_RTC_OVER_ESPI ++ tristate "ASPEED AST2700 RTC over eSPI drvier" ++ depends on HAS_IOMEM ++ help ++ Enable driver support for Aspeed AST2700 RTC over eSPI function. ++ Periodically copies RAM content from a RTC tm to a memory-mapped eSPI region. ++ ++config AST2700_INTEL_ESPI_MMBI ++ bool "Enable support for AST2700 ESPI MMBI controller with Intel PCH host" ++ depends on ASPEED_ESPI ++ ++endmenu +diff --git a/drivers/soc/aspeed/espi/Makefile b/drivers/soc/aspeed/espi/Makefile +--- a/drivers/soc/aspeed/espi/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/Makefile 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,7 @@ ++obj-$(CONFIG_ASPEED_ESPI) += \ ++ ast2700-espi.o \ ++ ast2600-espi.o \ ++ ast2500-espi.o \ ++ aspeed-espi.o ++obj-$(CONFIG_AST2700_RTC_OVER_ESPI) += ast2700-rtc-over-espi.o ++obj-$(CONFIG_AST2700_INTEL_ESPI_MMBI) += ast2700-intel-espi-mmbi.o +\ No newline at end of file +diff --git a/drivers/soc/aspeed/espi/aspeed-espi-comm.h b/drivers/soc/aspeed/espi/aspeed-espi-comm.h +--- a/drivers/soc/aspeed/espi/aspeed-espi-comm.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/aspeed-espi-comm.h 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,215 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Aspeed eSPI protocol definitions and IOCTL methods ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#ifndef __ASPEED_ESPI_COMM_H__ ++#define __ASPEED_ESPI_COMM_H__ ++ ++#include ++#include ++ ++/* ++ * eSPI cycle type encoding ++ * ++ * Section 5.1 Cycle Types and Packet Format, ++ * Intel eSPI Interface Base Specification, Rev 1.0, Jan. 2016. ++ */ ++#define ESPI_PERIF_MEMRD32 0x00 ++#define ESPI_PERIF_MEMRD64 0x02 ++#define ESPI_PERIF_MEMWR32 0x01 ++#define ESPI_PERIF_MEMWR64 0x03 ++#define ESPI_PERIF_MSG 0x10 ++#define ESPI_PERIF_MSG_D 0x11 ++#define ESPI_PERIF_SUC_CMPLT 0x06 ++#define ESPI_PERIF_SUC_CMPLT_D_MIDDLE 0x09 ++#define ESPI_PERIF_SUC_CMPLT_D_FIRST 0x0b ++#define ESPI_PERIF_SUC_CMPLT_D_LAST 0x0d ++#define ESPI_PERIF_SUC_CMPLT_D_ONLY 0x0f ++#define ESPI_PERIF_UNSUC_CMPLT 0x0c ++#define ESPI_OOB_MSG 0x21 ++#define ESPI_FLASH_READ 0x00 ++#define ESPI_FLASH_WRITE 0x01 ++#define ESPI_FLASH_ERASE 0x02 ++#define ESPI_FLASH_SUC_CMPLT 0x06 ++#define ESPI_FLASH_SUC_CMPLT_D_MIDDLE 0x09 ++#define ESPI_FLASH_SUC_CMPLT_D_FIRST 0x0b ++#define ESPI_FLASH_SUC_CMPLT_D_LAST 0x0d ++#define ESPI_FLASH_SUC_CMPLT_D_ONLY 0x0f ++#define ESPI_FLASH_UNSUC_CMPLT 0x0c ++ ++/* ++ * eSPI packet format structure ++ * ++ * Section 5.1 Cycle Types and Packet Format, ++ * Intel eSPI Interface Base Specification, Rev 1.0, Jan. 2016. ++ */ ++struct espi_comm_hdr { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++}; ++ ++struct espi_perif_mem32 { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++ u32 addr_be; ++ u8 data[]; ++} __packed; ++ ++struct espi_perif_mem64 { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++ u32 addr_be; ++ u8 data[]; ++} __packed; ++ ++struct espi_perif_msg { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++ u8 msg_code; ++ u8 msg_byte[4]; ++ u8 data[]; ++} __packed; ++ ++struct espi_perif_cmplt { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++ u8 data[]; ++} __packed; ++ ++struct espi_oob_msg { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++ u8 data[]; ++}; ++ ++struct espi_flash_rwe { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++ u32 addr_be; ++ u8 data[]; ++} __packed; ++ ++struct espi_flash_cmplt { ++ u8 cyc; ++ u8 len_h : 4; ++ u8 tag : 4; ++ u8 len_l; ++ u8 data[]; ++} __packed; ++ ++#define ESPI_MAX_PLD_LEN BIT(12) ++ ++/* ++ * Aspeed IOCTL for eSPI raw packet send/receive ++ * ++ * This IOCTL interface works in the eSPI packet in/out paradigm. ++ * ++ * Only the virtual wire IOCTL is a special case which does not send ++ * or receive an eSPI packet. However, to keep a more consisten use from ++ * userspace, we make all of the four channel drivers serve through the ++ * IOCTL interface. ++ * ++ * For the eSPI packet format, refer to ++ * Section 5.1 Cycle Types and Packet Format, ++ * Intel eSPI Interface Base Specification, Rev 1.0, Jan. 2016. ++ * ++ * For the example user apps using these IOCTL, refer to ++ * https://github.com/AspeedTech-BMC/aspeed_app/tree/master/espi_test ++ */ ++#define __ASPEED_ESPI_IOCTL_MAGIC 0xb8 ++ ++/* ++ * we choose the longest header and the max payload size ++ * based on the Intel specification to define the maximum ++ * eSPI packet length ++ */ ++#define ESPI_MAX_PKT_LEN (sizeof(struct espi_perif_msg) + ESPI_MAX_PLD_LEN) ++ ++struct aspeed_espi_ioc { ++ u32 pkt_len; ++ u8 *pkt; ++}; ++ ++/* ++ * Peripheral Channel (CH0) ++ * - ASPEED_ESPI_PERIF_PC_GET_RX ++ * Receive an eSPI Posted/Completion packet ++ * - ASPEED_ESPI_PERIF_PC_PUT_TX ++ * Transmit an eSPI Posted/Completion packet ++ * - ASPEED_ESPI_PERIF_NP_PUT_TX ++ * Transmit an eSPI Non-Posted packet ++ */ ++#define ASPEED_ESPI_PERIF_PC_GET_RX _IOR(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x00, struct aspeed_espi_ioc) ++#define ASPEED_ESPI_PERIF_PC_PUT_TX _IOW(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x01, struct aspeed_espi_ioc) ++#define ASPEED_ESPI_PERIF_NP_PUT_TX _IOW(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x02, struct aspeed_espi_ioc) ++/* ++ * Virtual Wire Channel (CH1) ++ * - ASPEED_ESPI_VW_GET_GPIO_VAL ++ * Read the input value of GPIO over the VW channel ++ * - ASPEED_ESPI_VW_PUT_GPIO_VAL ++ * Write the output value of GPIO over the VW channel ++ * - ASPEED_ESPI_VW_GET_GPIO_VAL1 (new feature in AST2700) ++ * Read the input value1 of GPIO over the VW channel ++ * - ASPEED_ESPI_VW_PUT_GPIO_VAL1 (new feature in AST2700) ++ * Write the output value1 of GPIO over the VW channel ++ * - ASPEED_ESPI_VW_GET_PCH_GENERIC ++ * Read the input value of PCH Generic Indexes over the VW channel ++ * - ASPEED_ESPI_VW_PUT_PCH_GENERIC ++ * Write the output value of PCH Generic Indexes over the VW channel ++ */ ++#define ASPEED_ESPI_VW_GET_GPIO_VAL _IOR(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x10, u32) ++#define ASPEED_ESPI_VW_PUT_GPIO_VAL _IOW(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x11, u32) ++#ifdef CONFIG_ARM64 ++#define ASPEED_ESPI_VW_GET_GPIO_VAL1 _IOR(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x12, u32) ++#define ASPEED_ESPI_VW_PUT_GPIO_VAL1 _IOW(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x13, u32) ++#endif ++#define ASPEED_ESPI_VW_GET_PCH_GENERIC _IOR(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x14, u32) ++#define ASPEED_ESPI_VW_PUT_PCH_GENERIC _IOW(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x15, u32) ++/* ++ * Out-of-band Channel (CH2) ++ * - ASPEED_ESPI_OOB_GET_RX ++ * Receive an eSPI OOB packet ++ * - ASPEED_ESPI_OOB_PUT_TX ++ * Transmit an eSPI OOB packet ++ */ ++#define ASPEED_ESPI_OOB_GET_RX _IOR(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x20, struct aspeed_espi_ioc) ++#define ASPEED_ESPI_OOB_PUT_TX _IOW(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x21, struct aspeed_espi_ioc) ++/* ++ * Flash Channel (CH3) ++ * - ASPEED_ESPI_FLASH_GET_RX ++ * Receive an eSPI flash packet ++ * - ASPEED_ESPI_FLASH_PUT_TX ++ * Transmit an eSPI flash packet ++ */ ++#define ASPEED_ESPI_FLASH_GET_RX _IOR(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x30, struct aspeed_espi_ioc) ++#define ASPEED_ESPI_FLASH_PUT_TX _IOW(__ASPEED_ESPI_IOCTL_MAGIC, \ ++ 0x31, struct aspeed_espi_ioc) ++ ++#endif +diff --git a/drivers/soc/aspeed/espi/aspeed-espi.c b/drivers/soc/aspeed/espi/aspeed-espi.c +--- a/drivers/soc/aspeed/espi/aspeed-espi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/aspeed-espi.c 2026-09-09 15:51:44.719238056 +0000 +@@ -0,0 +1,256 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Unified Aspeed eSPI driver (AST2500/AST2600/AST2700) ++ * ++ * This wraps the existing SoC-specific implementations behind a ++ * single driver name and compatible strings. For now we keep the ++ * full implementations in their original files and dispatch through ++ * small per-SoC glue ops. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "aspeed-espi.h" ++#include "ast2500-espi.h" ++#include "ast2600-espi.h" ++#include "ast2700-espi.h" ++ ++struct aspeed_espi_ops { ++ enum aspeed_espi_platform_id platform_id; ++ void (*espi_pre_init)(struct aspeed_espi *espi); ++ void (*espi_post_init)(struct aspeed_espi *espi); ++ void (*espi_deinit)(struct aspeed_espi *espi); ++ int (*espi_perif_probe)(struct aspeed_espi *espi); ++ int (*espi_perif_remove)(struct aspeed_espi *espi); ++ int (*espi_vw_probe)(struct aspeed_espi *espi); ++ int (*espi_vw_remove)(struct aspeed_espi *espi); ++ int (*espi_oob_probe)(struct aspeed_espi *espi); ++ int (*espi_oob_remove)(struct aspeed_espi *espi); ++ int (*espi_flash_probe)(struct aspeed_espi *espi); ++ int (*espi_flash_remove)(struct aspeed_espi *espi); ++ irqreturn_t (*espi_isr)(int irq, void *espi); ++}; ++ ++static const struct aspeed_espi_ops aspeed_espi_ast2500_ops = { ++ .platform_id = ASPEED_ESPI_PLATFORM_ID_AST2500, ++ .espi_pre_init = ast2500_espi_pre_init, ++ .espi_post_init = ast2500_espi_post_init, ++ .espi_deinit = ast2500_espi_deinit, ++ .espi_perif_probe = ast2500_espi_perif_probe, ++ .espi_perif_remove = ast2500_espi_perif_remove, ++ .espi_vw_probe = ast2500_espi_vw_probe, ++ .espi_vw_remove = ast2500_espi_vw_remove, ++ .espi_oob_probe = ast2500_espi_oob_probe, ++ .espi_oob_remove = ast2500_espi_oob_remove, ++ .espi_flash_probe = ast2500_espi_flash_probe, ++ .espi_flash_remove = ast2500_espi_flash_remove, ++ .espi_isr = ast2500_espi_isr, ++}; ++ ++static const struct aspeed_espi_ops aspeed_espi_ast2600_ops = { ++ .platform_id = ASPEED_ESPI_PLATFORM_ID_AST2600, ++ .espi_pre_init = ast2600_espi_pre_init, ++ .espi_post_init = ast2600_espi_post_init, ++ .espi_deinit = ast2600_espi_deinit, ++ .espi_perif_probe = ast2600_espi_perif_probe, ++ .espi_perif_remove = ast2600_espi_perif_remove, ++ .espi_vw_probe = ast2600_espi_vw_probe, ++ .espi_vw_remove = ast2600_espi_vw_remove, ++ .espi_oob_probe = ast2600_espi_oob_probe, ++ .espi_oob_remove = ast2600_espi_oob_remove, ++ .espi_flash_probe = ast2600_espi_flash_probe, ++ .espi_flash_remove = ast2600_espi_flash_remove, ++ .espi_isr = ast2600_espi_isr, ++}; ++ ++static const struct aspeed_espi_ops aspeed_espi_ast2700_ops = { ++ .platform_id = ASPEED_ESPI_PLATFORM_ID_AST2700, ++ .espi_pre_init = ast2700_espi_pre_init, ++ .espi_post_init = ast2700_espi_post_init, ++ .espi_deinit = ast2700_espi_deinit, ++ .espi_perif_probe = ast2700_espi_perif_probe, ++ .espi_perif_remove = ast2700_espi_perif_remove, ++ .espi_vw_probe = ast2700_espi_vw_probe, ++ .espi_vw_remove = ast2700_espi_vw_remove, ++ .espi_oob_probe = ast2700_espi_oob_probe, ++ .espi_oob_remove = ast2700_espi_oob_remove, ++ .espi_flash_probe = ast2700_espi_flash_probe, ++ .espi_flash_remove = ast2700_espi_flash_remove, ++ .espi_isr = ast2700_espi_isr, ++}; ++ ++static const struct of_device_id aspeed_espi_of_matches[] = { ++ { .compatible = "aspeed,ast2500-espi", .data = &aspeed_espi_ast2500_ops }, ++ { .compatible = "aspeed,ast2600-espi", .data = &aspeed_espi_ast2600_ops }, ++ { .compatible = "aspeed,ast2700-espi", .data = &aspeed_espi_ast2700_ops }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_espi_of_matches); ++ ++static int aspeed_espi_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *match; ++ struct device *dev = &pdev->dev; ++ struct aspeed_espi *espi; ++ struct resource *res; ++ int rc; ++ ++ espi = devm_kzalloc(dev, sizeof(*espi), GFP_KERNEL); ++ if (!espi) ++ return -ENOMEM; ++ ++ espi->dev = dev; ++ match = of_match_device(aspeed_espi_of_matches, dev); ++ if (!match) ++ return -ENODEV; ++ espi->ops = match->data; ++ ++ espi->pdev = pdev; ++ ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_err(dev, "cannot set 64-bits DMA mask\n"); ++ return rc; ++ } ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(dev, "cannot get resource\n"); ++ return -ENODEV; ++ } ++ ++ espi->regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR(espi->regs)) { ++ dev_err(dev, "cannot map registers\n"); ++ return PTR_ERR(espi->regs); ++ } ++ ++ espi->irq = platform_get_irq(pdev, 0); ++ if (espi->irq < 0) { ++ dev_err(dev, "cannot get IRQ number\n"); ++ return -ENODEV; ++ } ++ ++ espi->rst = devm_reset_control_get_optional(&pdev->dev, NULL); ++ if (IS_ERR(espi->rst)) { ++ dev_err(dev, "cannot get reset control\n"); ++ return PTR_ERR(espi->rst); ++ } ++ ++ espi->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(espi->clk)) { ++ dev_err(dev, "cannot get clock control\n"); ++ return PTR_ERR(espi->clk); ++ } ++ ++ rc = clk_prepare_enable(espi->clk); ++ if (rc) { ++ dev_err(dev, "cannot enable clocks\n"); ++ return rc; ++ } ++ ++ espi->ops->espi_pre_init(espi); ++ ++ rc = espi->ops->espi_perif_probe(espi); ++ if (rc) { ++ dev_err(dev, "cannot init peripheral channel, rc=%d\n", rc); ++ return rc; ++ } ++ ++ rc = espi->ops->espi_vw_probe(espi); ++ if (rc) { ++ dev_err(dev, "cannot init vw channel, rc=%d\n", rc); ++ goto err_remove_perif; ++ } ++ ++ rc = espi->ops->espi_oob_probe(espi); ++ if (rc) { ++ dev_err(dev, "cannot init oob channel, rc=%d\n", rc); ++ goto err_remove_vw; ++ } ++ ++ rc = espi->ops->espi_flash_probe(espi); ++ if (rc) { ++ dev_err(dev, "cannot init flash channel, rc=%d\n", rc); ++ goto err_remove_oob; ++ } ++ ++ rc = devm_request_irq(dev, espi->irq, espi->ops->espi_isr, 0, ++ dev_name(dev), espi); ++ if (rc) { ++ dev_err(dev, "cannot request IRQ\n"); ++ goto err_remove_flash; ++ } ++ ++ if (espi->perif.mmbi.enable) { ++ rc = devm_request_irq(dev, espi->perif.mmbi.irq, ++ espi->perif.mmbi.mmbi_isr, 0, dev_name(dev), ++ espi); ++ if (rc) { ++ dev_err(dev, "cannot request MMBI IRQ\n"); ++ goto err_remove_flash; ++ } ++ } ++ ++ espi->ops->espi_post_init(espi); ++ ++ platform_set_drvdata(pdev, espi); ++ ++ dev_info(dev, "module loaded\n"); ++ ++ return 0; ++ ++err_remove_flash: ++ espi->ops->espi_flash_remove(espi); ++err_remove_oob: ++ espi->ops->espi_oob_remove(espi); ++err_remove_vw: ++ espi->ops->espi_vw_remove(espi); ++err_remove_perif: ++ espi->ops->espi_perif_remove(espi); ++ ++ return rc; ++} ++ ++static void aspeed_espi_remove(struct platform_device *pdev) ++{ ++ struct aspeed_espi *espi; ++ struct device *dev; ++ ++ dev = &pdev->dev; ++ ++ espi = dev_get_drvdata(dev); ++ ++ espi->ops->espi_deinit(espi); ++ ++ if (espi->ops->espi_perif_remove(espi)) ++ dev_warn(dev, "cannot remove peripheral channel\n"); ++ if (espi->ops->espi_vw_remove(espi)) ++ dev_warn(dev, "cannot remove vw channel\n"); ++ if (espi->ops->espi_oob_remove(espi)) ++ dev_warn(dev, "cannot remove oob channel\n"); ++ if (espi->ops->espi_flash_remove(espi)) ++ dev_warn(dev, "cannot remove flash channel\n"); ++} ++ ++static struct platform_driver aspeed_espi_driver = { ++ .driver = { ++ .name = "aspeed-espi", ++ .of_match_table = aspeed_espi_of_matches, ++ }, ++ .probe = aspeed_espi_probe, ++ .remove = aspeed_espi_remove, ++}; ++ ++module_platform_driver(aspeed_espi_driver); ++ ++MODULE_AUTHOR("Aspeed Technology Inc."); ++MODULE_DESCRIPTION("Aspeed eSPI controller (AST2500/2600/2700)"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/espi/aspeed-espi.h b/drivers/soc/aspeed/espi/aspeed-espi.h +--- a/drivers/soc/aspeed/espi/aspeed-espi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/aspeed-espi.h 2026-09-09 15:51:44.720238041 +0000 +@@ -0,0 +1,231 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Unified eSPI driver header file and data structures ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++#ifndef __ASPEED_ESPI_H__ ++#define __ASPEED_ESPI_H__ ++ ++#include "linux/platform_device.h" ++#include ++#include ++#include ++ ++#define PERIF_MMBI_INST_NUM 8 ++#define DEVICE_NAME "aspeed-espi" ++ ++enum aspeed_espi_platform_id { ++ ASPEED_ESPI_PLATFORM_ID_AST2500 = 0x2500, ++ ASPEED_ESPI_PLATFORM_ID_AST2600 = 0x2600, ++ ASPEED_ESPI_PLATFORM_ID_AST2700 = 0x2700, ++}; ++ ++/* consistent with DTS property "flash-safs-mode" */ ++enum aspeed_edaf_mode { ++ EDAF_MODE_MIX = 0x0, ++ EDAF_MODE_SW, ++ EDAF_MODE_HW, ++ EDAF_MODES, ++}; ++ ++struct aspeed_espi_perif; ++ ++struct aspeed_espi_perif_mmbi { ++ void *b2h_virt; ++ void *h2b_virt; ++ dma_addr_t b2h_addr; ++ dma_addr_t h2b_addr; ++ struct miscdevice b2h_mdev; ++ struct miscdevice h2b_mdev; ++ bool host_rwp_update; ++ wait_queue_head_t wq; ++ struct aspeed_espi_perif *perif; ++}; ++ ++struct aspeed_espi_perif { ++ struct { ++ bool enable; ++ int irq; ++ void *virt; ++ dma_addr_t taddr; ++ dma_addr_t saddr; ++ resource_size_t size; ++ u32 inst_num; ++ u32 inst_size; ++ irqreturn_t (*mmbi_isr)(int irq, void *espi); ++ struct aspeed_espi_perif_mmbi inst[PERIF_MMBI_INST_NUM]; ++ } mmbi; ++ ++ struct { ++ bool enable; ++ void *virt; ++ dma_addr_t taddr; ++ dma_addr_t saddr; ++ resource_size_t size; ++ } mcyc; ++ ++ struct { ++ bool enable; ++ void *np_tx_virt; ++ dma_addr_t np_tx_addr; ++ void *pc_tx_virt; ++ dma_addr_t pc_tx_addr; ++ void *pc_rx_virt; ++ dma_addr_t pc_rx_addr; ++ } dma; ++ ++ bool rtc_enable; ++ bool rx_ready; ++ wait_queue_head_t wq; ++ ++ spinlock_t lock; // protects rx_ready ++ struct mutex np_tx_mtx; // protects np_tx_virt/addr ++ struct mutex pc_tx_mtx; // protects pc_tx_virt/addr ++ struct mutex pc_rx_mtx; // protects pc_rx_virt/addr ++ ++ struct miscdevice mdev; ++}; ++ ++struct aspeed_espi_vw { ++ struct { ++ bool hw_mode; ++ u32 grp; ++ u32 dir0; ++ u32 dir1; ++ u32 val0; ++ u32 val1; ++ } gpio; ++ ++ struct { ++ u8 pch_generic; ++ } platform; ++ ++ struct { ++ bool enabled; ++ spinlock_t pltrst_lock; // protects pltrst_status ++ wait_queue_head_t pltrst_wq; ++ char pltrst_status; ++ bool pltrst_avail; ++ } pltrst; ++ ++ struct miscdevice pltrst_mdev; ++ struct miscdevice mdev; ++}; ++ ++struct ast2700_espi_oob_dma_tx_desc { ++ u32 data_addrl; ++ u32 data_addrh; ++ u8 cyc; ++ u16 tag : 4; ++ u16 len : 12; ++ u8 msg_type : 3; ++ u8 raz0 : 1; ++ u8 pec : 1; ++ u8 int_en : 1; ++ u8 pause : 1; ++ u8 raz1 : 1; ++ u32 raz2; ++ u32 raz3; ++ u32 pad[3]; ++} __packed; ++ ++struct ast2700_espi_oob_dma_rx_desc { ++ u32 data_addrl; ++ u32 data_addrh; ++ u8 cyc; ++ u16 tag : 4; ++ u16 len : 12; ++ u8 raz : 7; ++ u8 dirty : 1; ++ u32 pad; ++} __packed; ++ ++struct ast2600_espi_oob_dma_tx_desc { ++ u32 data_addr; ++ u8 cyc; ++ u16 tag : 4; ++ u16 len : 12; ++ u8 msg_type : 3; ++ u8 raz0 : 1; ++ u8 pec : 1; ++ u8 int_en : 1; ++ u8 pause : 1; ++ u8 raz1 : 1; ++ u32 raz2; ++ u32 raz3; ++} __packed; ++ ++struct ast2600_espi_oob_dma_rx_desc { ++ u32 data_addr; ++ u8 cyc; ++ u16 tag : 4; ++ u16 len : 12; ++ u8 raz : 7; ++ u8 dirty : 1; ++} __packed; ++ ++struct aspeed_espi_oob { ++ struct { ++ bool enable; ++ void *txd_virt; ++ dma_addr_t txd_addr; ++ void *rxd_virt; ++ dma_addr_t rxd_addr; ++ void *tx_virt; ++ dma_addr_t tx_addr; ++ void *rx_virt; ++ dma_addr_t rx_addr; ++ } dma; ++ ++ bool rx_ready; ++ wait_queue_head_t wq; ++ ++ spinlock_t lock; // protects rx_ready ++ struct mutex tx_mtx; // protects tx_virt/addr ++ struct mutex rx_mtx; // protects rx_virt/addr ++ ++ struct miscdevice mdev; ++}; ++ ++struct aspeed_espi_flash { ++ struct { ++ u32 mode; ++ phys_addr_t taddr; ++ resource_size_t size; ++ } edaf; ++ ++ struct { ++ bool enable; ++ void *tx_virt; ++ dma_addr_t tx_addr; ++ void *rx_virt; ++ dma_addr_t rx_addr; ++ } dma; ++ ++ bool rx_ready; ++ wait_queue_head_t wq; ++ ++ spinlock_t lock; // protects rx_ready ++ struct mutex rx_mtx; // protects rx_virt/addr ++ struct mutex tx_mtx; // protects tx_virt/addr ++ ++ struct miscdevice mdev; ++}; ++ ++struct aspeed_espi { ++ struct platform_device *pdev; ++ struct device *dev; ++ void __iomem *regs; ++ struct reset_control *rst; ++ struct clk *clk; ++ int dev_id; ++ int irq; ++ ++ struct aspeed_espi_perif perif; ++ struct aspeed_espi_vw vw; ++ struct aspeed_espi_oob oob; ++ struct aspeed_espi_flash flash; ++ const struct aspeed_espi_ops *ops; ++}; ++ ++#endif // __ASPEED_ESPI_H__ +diff --git a/drivers/soc/aspeed/espi/ast2500-espi.c b/drivers/soc/aspeed/espi/ast2500-espi.c +--- a/drivers/soc/aspeed/espi/ast2500-espi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2500-espi.c 2026-09-09 15:51:44.720238041 +0000 +@@ -0,0 +1,1510 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ast2500-espi.h" ++#include "aspeed-espi-comm.h" ++#include "aspeed-espi.h" ++ ++#define PERIF_MCYC_UNLOCK 0xfedc756e ++#define PERIF_MCYC_ALIGN SZ_64K ++ ++#define FLASH_SAFS_ALIGN SZ_16M ++ ++/* peripheral channel (CH0) */ ++static long ast2500_espi_perif_pc_get_rx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&perif->pc_rx_mtx)) ++ return -EAGAIN; ++ ++ if (!perif->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&perif->pc_rx_mtx); ++ ++ if (!perif->rx_ready) { ++ rc = wait_event_interruptible(perif->wq, perif->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_PERIF_PC_RX_CTRL); ++ cyc = FIELD_GET(ESPI_PERIF_PC_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_PERIF_PC_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_PERIF_PC_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ switch (cyc) { ++ case ESPI_PERIF_MSG: ++ pkt_len = sizeof(struct espi_perif_msg); ++ break; ++ case ESPI_PERIF_MSG_D: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_perif_msg); ++ break; ++ case ESPI_PERIF_SUC_CMPLT_D_MIDDLE: ++ case ESPI_PERIF_SUC_CMPLT_D_FIRST: ++ case ESPI_PERIF_SUC_CMPLT_D_LAST: ++ case ESPI_PERIF_SUC_CMPLT_D_ONLY: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_perif_cmplt); ++ break; ++ case ESPI_PERIF_SUC_CMPLT: ++ case ESPI_PERIF_UNSUC_CMPLT: ++ pkt_len = sizeof(struct espi_perif_cmplt); ++ break; ++ default: ++ rc = -EFAULT; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ if (perif->dma.enable) { ++ memcpy(hdr + 1, perif->dma.pc_rx_virt, pkt_len - sizeof(*hdr)); ++ } else { ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ reg = readl(espi->regs + ESPI_PERIF_PC_RX_DATA) & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&perif->lock, flags); ++ ++ writel(ESPI_PERIF_PC_RX_CTRL_SERV_PEND, espi->regs + ESPI_PERIF_PC_RX_CTRL); ++ perif->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&perif->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&perif->pc_rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2500_espi_perif_pc_put_tx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (!mutex_trylock(&perif->pc_tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_PERIF_PC_TX_CTRL); ++ if (reg & ESPI_PERIF_PC_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (perif->dma.enable) { ++ memcpy(perif->dma.pc_tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_PERIF_PC_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_PERIF_PC_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_PERIF_PC_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_PERIF_PC_TX_CTRL_LEN, len) ++ | ESPI_PERIF_PC_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_PERIF_PC_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_n_out: ++ mutex_unlock(&perif->pc_tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2500_espi_perif_np_put_tx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (!mutex_trylock(&perif->np_tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_PERIF_NP_TX_CTRL); ++ if (reg & ESPI_PERIF_NP_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (perif->dma.enable) { ++ memcpy(perif->dma.np_tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_PERIF_NP_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_PERIF_NP_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_PERIF_NP_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_PERIF_NP_TX_CTRL_LEN, len) ++ | ESPI_PERIF_NP_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_PERIF_NP_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_n_out: ++ mutex_unlock(&perif->np_tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2500_espi_perif_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_perif *perif; ++ struct aspeed_espi_ioc ioc; ++ ++ perif = container_of(fp->private_data, struct aspeed_espi_perif, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_PERIF_PC_GET_RX: ++ return ast2500_espi_perif_pc_get_rx(fp, perif, &ioc); ++ case ASPEED_ESPI_PERIF_PC_PUT_TX: ++ return ast2500_espi_perif_pc_put_tx(fp, perif, &ioc); ++ case ASPEED_ESPI_PERIF_NP_PUT_TX: ++ return ast2500_espi_perif_np_put_tx(fp, perif, &ioc); ++ default: ++ break; ++ }; ++ ++ return -EINVAL; ++} ++ ++static int ast2500_espi_perif_mmap(struct file *fp, struct vm_area_struct *vma) ++{ ++ struct aspeed_espi_perif *perif; ++ unsigned long vm_size; ++ pgprot_t vm_prot; ++ ++ perif = container_of(fp->private_data, struct aspeed_espi_perif, mdev); ++ if (!perif->mcyc.enable) ++ return -EPERM; ++ ++ vm_size = vma->vm_end - vma->vm_start; ++ vm_prot = vma->vm_page_prot; ++ ++ if (((vma->vm_pgoff << PAGE_SHIFT) + vm_size) > perif->mcyc.size) ++ return -EINVAL; ++ ++ vm_prot = pgprot_noncached(vm_prot); ++ ++ if (remap_pfn_range(vma, vma->vm_start, ++ (perif->mcyc.taddr >> PAGE_SHIFT) + vma->vm_pgoff, ++ vm_size, vm_prot)) ++ return -EAGAIN; ++ ++ return 0; ++} ++ ++static const struct file_operations ast2500_espi_perif_fops = { ++ .owner = THIS_MODULE, ++ .mmap = ast2500_espi_perif_mmap, ++ .unlocked_ioctl = ast2500_espi_perif_ioctl, ++}; ++ ++static void ast2500_espi_perif_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ unsigned long flags; ++ u32 sts; ++ ++ perif = &espi->perif; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_PERIF_PC_RX_CMPLT) { ++ writel(ESPI_INT_STS_PERIF_PC_RX_CMPLT, espi->regs + ESPI_INT_STS); ++ ++ spin_lock_irqsave(&perif->lock, flags); ++ perif->rx_ready = true; ++ spin_unlock_irqrestore(&perif->lock, flags); ++ ++ wake_up_interruptible(&perif->wq); ++ } ++} ++ ++static void ast2500_espi_perif_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ struct device *dev; ++ u32 reg, mask; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_PERIF); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ writel(ESPI_INT_STS_PERIF, espi->regs + ESPI_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_PERIF_NP_TX_SW_RST ++ | ESPI_CTRL_PERIF_NP_RX_SW_RST ++ | ESPI_CTRL_PERIF_PC_TX_SW_RST ++ | ESPI_CTRL_PERIF_PC_RX_SW_RST ++ | ESPI_CTRL_PERIF_NP_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_RX_DMA_EN ++ | ESPI_CTRL_PERIF_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CTRL_PERIF_NP_TX_SW_RST ++ | ESPI_CTRL_PERIF_NP_RX_SW_RST ++ | ESPI_CTRL_PERIF_PC_TX_SW_RST ++ | ESPI_CTRL_PERIF_PC_RX_SW_RST); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (perif->mcyc.enable) { ++ mask = ~(perif->mcyc.size - 1); ++ writel(PERIF_MCYC_UNLOCK, espi->regs + ESPI_PERIF_MCYC_MASK); ++ writel(mask, espi->regs + ESPI_PERIF_MCYC_MASK); ++ ++ writel(perif->mcyc.saddr, espi->regs + ESPI_PERIF_MCYC_SADDR); ++ writel(perif->mcyc.taddr, espi->regs + ESPI_PERIF_MCYC_TADDR); ++ } ++ ++ if (perif->dma.enable) { ++ writel(perif->dma.np_tx_addr, espi->regs + ESPI_PERIF_NP_TX_DMA); ++ writel(perif->dma.pc_tx_addr, espi->regs + ESPI_PERIF_PC_TX_DMA); ++ writel(perif->dma.pc_rx_addr, espi->regs + ESPI_PERIF_PC_RX_DMA); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ESPI_CTRL_PERIF_NP_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CTRL); ++ } ++ ++ reg = readl(espi->regs + ESPI_INT_EN) | ESPI_INT_EN_PERIF_PC_RX_CMPLT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL) | ESPI_CTRL_PERIF_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++int ast2500_espi_perif_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ struct device *dev; ++ int rc; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ init_waitqueue_head(&perif->wq); ++ ++ spin_lock_init(&perif->lock); ++ ++ mutex_init(&perif->np_tx_mtx); ++ mutex_init(&perif->pc_tx_mtx); ++ mutex_init(&perif->pc_rx_mtx); ++ ++ perif->mcyc.enable = of_property_read_bool(dev->of_node, "perif-mcyc-enable"); ++ if (perif->mcyc.enable) { ++ rc = of_property_read_u32(dev->of_node, "perif-mcyc-src-addr", (u32 *)&perif->mcyc.saddr); ++ if (rc || !IS_ALIGNED(perif->mcyc.saddr, PERIF_MCYC_ALIGN)) { ++ dev_err(dev, "cannot get 64KB-aligned memory cycle host address\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "perif-mcyc-size", (u32 *)&perif->mcyc.size); ++ if (rc || !IS_ALIGNED(perif->mcyc.size, PERIF_MCYC_ALIGN)) { ++ dev_err(dev, "cannot get 64KB-aligned memory cycle size\n"); ++ return -EINVAL; ++ } ++ ++ perif->mcyc.virt = dmam_alloc_coherent(dev, perif->mcyc.size, ++ &perif->mcyc.taddr, GFP_KERNEL); ++ if (!perif->mcyc.virt) { ++ dev_err(dev, "cannot allocate memory cycle\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ perif->dma.enable = of_property_read_bool(dev->of_node, "perif-dma-mode"); ++ if (perif->dma.enable) { ++ perif->dma.pc_tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.pc_tx_addr, GFP_KERNEL); ++ if (!perif->dma.pc_tx_virt) { ++ dev_err(dev, "cannot allocate posted TX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ perif->dma.pc_rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.pc_rx_addr, GFP_KERNEL); ++ if (!perif->dma.pc_rx_virt) { ++ dev_err(dev, "cannot allocate posted RX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ perif->dma.np_tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.np_tx_addr, GFP_KERNEL); ++ if (!perif->dma.np_tx_virt) { ++ dev_err(dev, "cannot allocate non-posted TX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ perif->mdev.parent = dev; ++ perif->mdev.minor = MISC_DYNAMIC_MINOR; ++ perif->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-peripheral", DEVICE_NAME); ++ perif->mdev.fops = &ast2500_espi_perif_fops; ++ rc = misc_register(&perif->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", perif->mdev.name); ++ return rc; ++ } ++ ++ ast2500_espi_perif_reset(espi); ++ ++ return 0; ++} ++ ++int ast2500_espi_perif_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_PERIF); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_PERIF_NP_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_RX_DMA_EN ++ | ESPI_CTRL_PERIF_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (perif->mcyc.enable) ++ dmam_free_coherent(dev, perif->mcyc.size, perif->mcyc.virt, ++ perif->mcyc.taddr); ++ ++ if (perif->dma.enable) { ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.np_tx_virt, ++ perif->dma.np_tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.pc_tx_virt, ++ perif->dma.pc_tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.pc_rx_virt, ++ perif->dma.pc_rx_addr); ++ } ++ ++ mutex_destroy(&perif->np_tx_mtx); ++ mutex_destroy(&perif->pc_tx_mtx); ++ mutex_destroy(&perif->pc_rx_mtx); ++ ++ misc_deregister(&perif->mdev); ++ ++ return 0; ++} ++ ++/* virtual wire channel (CH1) */ ++static long ast2500_espi_vw_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_vw *vw; ++ struct aspeed_espi *espi; ++ u32 gpio; ++ ++ vw = container_of(fp->private_data, struct aspeed_espi_vw, mdev); ++ espi = container_of(vw, struct aspeed_espi, vw); ++ gpio = vw->gpio.val0; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_VW_GET_GPIO_VAL: ++ if (put_user(gpio, (u32 __user *)arg)) ++ return -EFAULT; ++ break; ++ case ASPEED_ESPI_VW_PUT_GPIO_VAL: ++ if (get_user(gpio, (u32 __user *)arg)) ++ return -EFAULT; ++ ++ writel(gpio, espi->regs + ESPI_VW_GPIO_VAL); ++ break; ++ default: ++ return -EINVAL; ++ }; ++ ++ return 0; ++} ++ ++static const struct file_operations ast2500_espi_vw_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2500_espi_vw_ioctl, ++}; ++ ++static void ast2500_espi_vw_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_vw *vw; ++ u32 reg, sts, sts_sysevt; ++ ++ vw = &espi->vw; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_VW_SYSEVT) { ++ sts_sysevt = readl(espi->regs + ESPI_VW_SYSEVT_INT_STS); ++ ++ if (sts_sysevt & ESPI_VW_SYSEVT_INT_STS_HOST_RST_WARN) { ++ reg = readl(espi->regs + ESPI_VW_SYSEVT) | ESPI_VW_SYSEVT_HOST_RST_ACK; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT); ++ writel(ESPI_VW_SYSEVT_INT_STS_HOST_RST_WARN, espi->regs + ESPI_VW_SYSEVT_INT_STS); ++ } ++ ++ if (sts_sysevt & ESPI_VW_SYSEVT_INT_STS_OOB_RST_WARN) { ++ reg = readl(espi->regs + ESPI_VW_SYSEVT) | ESPI_VW_SYSEVT_OOB_RST_ACK; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT); ++ writel(ESPI_VW_SYSEVT_INT_STS_OOB_RST_WARN, espi->regs + ESPI_VW_SYSEVT_INT_STS); ++ } ++ ++ writel(ESPI_INT_STS_VW_SYSEVT, espi->regs + ESPI_INT_STS); ++ } ++ ++ if (sts & ESPI_INT_STS_VW_SYSEVT1) { ++ sts_sysevt = readl(espi->regs + ESPI_VW_SYSEVT1_INT_STS); ++ ++ if (sts_sysevt & ESPI_VW_SYSEVT1_INT_STS_SUSPEND_WARN) { ++ reg = readl(espi->regs + ESPI_VW_SYSEVT1) | ESPI_VW_SYSEVT1_SUSPEND_ACK; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT1); ++ writel(ESPI_VW_SYSEVT1_INT_STS_SUSPEND_WARN, espi->regs + ESPI_VW_SYSEVT1_INT_STS); ++ } ++ ++ writel(ESPI_INT_STS_VW_SYSEVT1, espi->regs + ESPI_INT_STS); ++ } ++ ++ if (sts & ESPI_INT_STS_VW_GPIO) { ++ vw->gpio.val0 = readl(espi->regs + ESPI_VW_GPIO_VAL); ++ writel(ESPI_INT_STS_VW_GPIO, espi->regs + ESPI_INT_STS); ++ } ++} ++ ++static void ast2500_espi_vw_reset(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ struct aspeed_espi_vw *vw = &espi->vw; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_VW); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ writel(ESPI_INT_STS_VW, espi->regs + ESPI_INT_STS); ++ ++ vw->gpio.val0 = readl(espi->regs + ESPI_VW_GPIO_VAL); ++ ++ /* Host Reset Warn and OOB Reset Warn system events */ ++ reg = readl(espi->regs + ESPI_VW_SYSEVT_INT_T2) ++ | ESPI_VW_SYSEVT_INT_T2_HOST_RST_WARN ++ | ESPI_VW_SYSEVT_INT_T2_OOB_RST_WARN; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT_INT_T2); ++ ++ reg = readl(espi->regs + ESPI_VW_SYSEVT_INT_EN) ++ | ESPI_VW_SYSEVT_INT_EN_HOST_RST_WARN ++ | ESPI_VW_SYSEVT_INT_EN_OOB_RST_WARN; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT_INT_EN); ++ ++ /* Suspend Warn system event */ ++ reg = readl(espi->regs + ESPI_VW_SYSEVT1_INT_T0) | ESPI_VW_SYSEVT1_INT_T0_SUSPEND_WARN; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT1_INT_T0); ++ ++ reg = readl(espi->regs + ESPI_VW_SYSEVT1_INT_EN) | ESPI_VW_SYSEVT1_INT_EN_SUSPEND_WARN; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT1_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_INT_EN) ++ | ESPI_INT_EN_VW_GPIO ++ | ESPI_INT_EN_VW_SYSEVT ++ | ESPI_INT_EN_VW_SYSEVT1; ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_VW_SYSEVT) ++ | ESPI_VW_SYSEVT_SLV_BOOT_STS ++ | ESPI_VW_SYSEVT_SLV_BOOT_DONE; ++ writel(reg, espi->regs + ESPI_VW_SYSEVT); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ((vw->gpio.hw_mode) ? 0 : ESPI_CTRL_VW_GPIO_SW) ++ | ESPI_CTRL_VW_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++int ast2500_espi_vw_probe(struct aspeed_espi *espi) ++{ ++ int rc; ++ struct device *dev = espi->dev; ++ struct aspeed_espi_vw *vw = &espi->vw; ++ ++ writel(0x0, espi->regs + ESPI_VW_SYSEVT_INT_EN); ++ writel(0xffffffff, espi->regs + ESPI_VW_SYSEVT_INT_STS); ++ ++ writel(0x0, espi->regs + ESPI_VW_SYSEVT1_INT_EN); ++ writel(0xffffffff, espi->regs + ESPI_VW_SYSEVT1_INT_STS); ++ ++ vw->gpio.hw_mode = of_property_read_bool(dev->of_node, "vw-gpio-hw-mode"); ++ ++ vw->mdev.parent = dev; ++ vw->mdev.minor = MISC_DYNAMIC_MINOR; ++ vw->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-vw", DEVICE_NAME); ++ vw->mdev.fops = &ast2500_espi_vw_fops; ++ rc = misc_register(&vw->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", vw->mdev.name); ++ return rc; ++ } ++ ++ ast2500_espi_vw_reset(espi); ++ ++ return 0; ++} ++ ++int ast2500_espi_vw_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_vw *vw; ++ u32 reg; ++ ++ vw = &espi->vw; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_VW); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ misc_deregister(&vw->mdev); ++ ++ return 0; ++} ++ ++/* out-of-band channel (CH2) */ ++static long ast2500_espi_oob_get_rx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&oob->rx_mtx)) ++ return -EAGAIN; ++ ++ if (!oob->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&oob->rx_mtx); ++ ++ if (!oob->rx_ready) { ++ rc = wait_event_interruptible(oob->wq, oob->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_OOB_RX_CTRL); ++ cyc = FIELD_GET(ESPI_OOB_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_OOB_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_OOB_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + sizeof(struct espi_comm_hdr); ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ if (oob->dma.enable) { ++ memcpy(hdr + 1, oob->dma.rx_virt, pkt_len - sizeof(*hdr)); ++ } else { ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ pkt[i] = readl(espi->regs + ESPI_OOB_RX_DATA) & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ ++ writel(ESPI_OOB_RX_CTRL_SERV_PEND, espi->regs + ESPI_OOB_RX_CTRL); ++ oob->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&oob->rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2500_espi_oob_put_tx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ if (!mutex_trylock(&oob->tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_OOB_TX_CTRL); ++ if (reg & ESPI_OOB_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len > ESPI_MAX_PKT_LEN) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (oob->dma.enable) { ++ memcpy(oob->dma.tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_OOB_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_OOB_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_OOB_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_OOB_TX_CTRL_LEN, len) ++ | ESPI_OOB_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_OOB_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&oob->tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2500_espi_oob_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_oob *oob; ++ struct aspeed_espi_ioc ioc; ++ ++ oob = container_of(fp->private_data, struct aspeed_espi_oob, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_OOB_GET_RX: ++ return ast2500_espi_oob_get_rx(fp, oob, &ioc); ++ case ASPEED_ESPI_OOB_PUT_TX: ++ return ast2500_espi_oob_put_tx(fp, oob, &ioc); ++ default: ++ break; ++ }; ++ ++ return -EINVAL; ++} ++ ++static const struct file_operations ast2500_espi_oob_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2500_espi_oob_ioctl, ++}; ++ ++static void ast2500_espi_oob_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ unsigned long flags; ++ u32 sts; ++ ++ oob = &espi->oob; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_OOB_RX_CMPLT) { ++ writel(ESPI_INT_STS_OOB_RX_CMPLT, espi->regs + ESPI_INT_STS); ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ oob->rx_ready = true; ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ wake_up_interruptible(&oob->wq); ++ } ++} ++ ++static void ast2500_espi_oob_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ u32 reg; ++ ++ oob = &espi->oob; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_OOB); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ writel(ESPI_INT_STS_OOB, espi->regs + ESPI_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_OOB_TX_SW_RST ++ | ESPI_CTRL_OOB_RX_SW_RST ++ | ESPI_CTRL_OOB_TX_DMA_EN ++ | ESPI_CTRL_OOB_RX_DMA_EN ++ | ESPI_CTRL_OOB_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CTRL_OOB_TX_SW_RST | ESPI_CTRL_OOB_RX_SW_RST); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (oob->dma.enable) { ++ writel(oob->dma.tx_addr, espi->regs + ESPI_OOB_TX_DMA); ++ writel(oob->dma.rx_addr, espi->regs + ESPI_OOB_RX_DMA); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ESPI_CTRL_OOB_TX_DMA_EN ++ | ESPI_CTRL_OOB_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CTRL); ++ } ++ ++ reg = readl(espi->regs + ESPI_INT_EN) | ESPI_INT_EN_OOB_RX_CMPLT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL) | ESPI_CTRL_OOB_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++int ast2500_espi_oob_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct device *dev; ++ int rc; ++ ++ dev = espi->dev; ++ ++ oob = &espi->oob; ++ ++ init_waitqueue_head(&oob->wq); ++ ++ spin_lock_init(&oob->lock); ++ ++ mutex_init(&oob->tx_mtx); ++ mutex_init(&oob->rx_mtx); ++ ++ oob->dma.enable = of_property_read_bool(dev->of_node, "oob-dma-mode"); ++ if (oob->dma.enable) { ++ oob->dma.tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &oob->dma.tx_addr, GFP_KERNEL); ++ if (!oob->dma.tx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ ++ oob->dma.rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &oob->dma.rx_addr, GFP_KERNEL); ++ if (!oob->dma.rx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ oob->mdev.parent = dev; ++ oob->mdev.minor = MISC_DYNAMIC_MINOR; ++ oob->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-oob", DEVICE_NAME); ++ oob->mdev.fops = &ast2500_espi_oob_fops; ++ rc = misc_register(&oob->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", oob->mdev.name); ++ return rc; ++ } ++ ++ ast2500_espi_oob_reset(espi); ++ ++ return 0; ++} ++ ++int ast2500_espi_oob_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ oob = &espi->oob; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_OOB); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_OOB_TX_DMA_EN ++ | ESPI_CTRL_OOB_RX_DMA_EN ++ | ESPI_CTRL_OOB_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (oob->dma.enable) { ++ dmam_free_coherent(dev, PAGE_SIZE, oob->dma.tx_virt, oob->dma.tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, oob->dma.rx_virt, oob->dma.rx_addr); ++ } ++ ++ mutex_destroy(&oob->tx_mtx); ++ mutex_destroy(&oob->rx_mtx); ++ ++ misc_deregister(&oob->mdev); ++ ++ return 0; ++} ++ ++/* flash channel (CH3) */ ++static long ast2500_espi_flash_get_rx(struct file *fp, ++ struct aspeed_espi_flash *flash, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ rc = 0; ++ ++ espi = container_of(flash, struct aspeed_espi, flash); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&flash->rx_mtx)) ++ return -EAGAIN; ++ ++ if (!flash->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&flash->rx_mtx); ++ ++ if (!flash->rx_ready) { ++ rc = wait_event_interruptible(flash->wq, flash->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_FLASH_RX_CTRL); ++ cyc = FIELD_GET(ESPI_FLASH_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_FLASH_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_FLASH_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ switch (cyc) { ++ case ESPI_FLASH_WRITE: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_flash_rwe); ++ break; ++ case ESPI_FLASH_READ: ++ case ESPI_FLASH_ERASE: ++ pkt_len = sizeof(struct espi_flash_rwe); ++ break; ++ case ESPI_FLASH_SUC_CMPLT_D_MIDDLE: ++ case ESPI_FLASH_SUC_CMPLT_D_FIRST: ++ case ESPI_FLASH_SUC_CMPLT_D_LAST: ++ case ESPI_FLASH_SUC_CMPLT_D_ONLY: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_flash_cmplt); ++ break; ++ case ESPI_FLASH_SUC_CMPLT: ++ case ESPI_FLASH_UNSUC_CMPLT: ++ pkt_len = sizeof(struct espi_flash_cmplt); ++ break; ++ default: ++ rc = -EFAULT; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ if (flash->dma.enable) { ++ memcpy(hdr + 1, flash->dma.rx_virt, pkt_len - sizeof(*hdr)); ++ } else { ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ pkt[i] = readl(espi->regs + ESPI_FLASH_RX_DATA) & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&flash->lock, flags); ++ ++ writel(ESPI_FLASH_RX_CTRL_SERV_PEND, espi->regs + ESPI_FLASH_RX_CTRL); ++ flash->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&flash->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&flash->rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2500_espi_flash_put_tx(struct file *fp, ++ struct aspeed_espi_flash *flash, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(flash, struct aspeed_espi, flash); ++ ++ if (!mutex_trylock(&flash->tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_FLASH_TX_CTRL); ++ if (reg & ESPI_FLASH_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (flash->dma.enable) { ++ memcpy(flash->dma.tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_FLASH_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_FLASH_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_FLASH_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_FLASH_TX_CTRL_LEN, len) ++ | ESPI_FLASH_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_FLASH_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&flash->tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2500_espi_flash_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_flash *flash; ++ struct aspeed_espi_ioc ioc; ++ ++ flash = container_of(fp->private_data, struct aspeed_espi_flash, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_FLASH_GET_RX: ++ return ast2500_espi_flash_get_rx(fp, flash, &ioc); ++ case ASPEED_ESPI_FLASH_PUT_TX: ++ return ast2500_espi_flash_put_tx(fp, flash, &ioc); ++ default: ++ break; ++ }; ++ ++ return -EINVAL; ++} ++ ++static const struct file_operations ast2500_espi_flash_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2500_espi_flash_ioctl, ++}; ++ ++static void ast2500_espi_flash_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ unsigned long flags; ++ u32 sts; ++ ++ flash = &espi->flash; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_FLASH_RX_CMPLT) { ++ spin_lock_irqsave(&flash->lock, flags); ++ flash->rx_ready = true; ++ spin_unlock_irqrestore(&flash->lock, flags); ++ ++ wake_up_interruptible(&flash->wq); ++ ++ writel(ESPI_INT_STS_FLASH_RX_CMPLT, espi->regs + ESPI_INT_STS); ++ } ++} ++ ++static void ast2500_espi_flash_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash = &espi->flash; ++ u32 reg; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_FLASH); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ writel(ESPI_INT_STS_FLASH, espi->regs + ESPI_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_FLASH_TX_SW_RST ++ | ESPI_CTRL_FLASH_RX_SW_RST ++ | ESPI_CTRL_FLASH_TX_DMA_EN ++ | ESPI_CTRL_FLASH_RX_DMA_EN ++ | ESPI_CTRL_FLASH_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CTRL_FLASH_TX_SW_RST | ESPI_CTRL_FLASH_RX_SW_RST); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (flash->edaf.mode == EDAF_MODE_MIX) { ++ reg = FIELD_PREP(ESPI_FLASH_SAFS_TADDR_BASE, flash->edaf.taddr >> 24) ++ | FIELD_PREP(ESPI_FLASH_SAFS_TADDR_MASK, (~(flash->edaf.size - 1)) >> 24); ++ writel(reg, espi->regs + ESPI_FLASH_SAFS_TADDR); ++ } else { ++ reg = readl(espi->regs + ESPI_CTRL) | ESPI_CTRL_FLASH_SAFS_SW_MODE; ++ writel(reg, espi->regs + ESPI_CTRL); ++ } ++ ++ if (flash->dma.enable) { ++ writel(flash->dma.tx_addr, espi->regs + ESPI_FLASH_TX_DMA); ++ writel(flash->dma.rx_addr, espi->regs + ESPI_FLASH_RX_DMA); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ESPI_CTRL_FLASH_TX_DMA_EN ++ | ESPI_CTRL_FLASH_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CTRL); ++ } ++ ++ reg = readl(espi->regs + ESPI_INT_EN) | ESPI_INT_EN_FLASH_RX_CMPLT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL) | ESPI_CTRL_FLASH_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++int ast2500_espi_flash_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ struct device *dev; ++ int rc; ++ ++ dev = espi->dev; ++ ++ flash = &espi->flash; ++ ++ init_waitqueue_head(&flash->wq); ++ ++ spin_lock_init(&flash->lock); ++ ++ mutex_init(&flash->tx_mtx); ++ mutex_init(&flash->rx_mtx); ++ ++ flash->edaf.mode = EDAF_MODE_MIX; ++ ++ of_property_read_u32(dev->of_node, "flash-safs-mode", &flash->edaf.mode); ++ if (flash->edaf.mode == EDAF_MODE_MIX) { ++ rc = of_property_read_u32(dev->of_node, "flash-safs-tgt-addr", (u32 *)&flash->edaf.taddr); ++ if (rc || !IS_ALIGNED(flash->edaf.taddr, FLASH_SAFS_ALIGN)) { ++ dev_err(dev, "cannot get 16MB-aligned SAFS target address\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "flash-safs-size", (u32 *)&flash->edaf.size); ++ if (rc || !IS_ALIGNED(flash->edaf.size, FLASH_SAFS_ALIGN)) { ++ dev_err(dev, "cannot get 16MB-aligned SAFS size\n"); ++ return -ENODEV; ++ } ++ } ++ ++ flash->dma.enable = of_property_read_bool(dev->of_node, "flash-dma-mode"); ++ if (flash->dma.enable) { ++ flash->dma.tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &flash->dma.tx_addr, GFP_KERNEL); ++ if (!flash->dma.tx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ ++ flash->dma.rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &flash->dma.rx_addr, GFP_KERNEL); ++ if (!flash->dma.rx_virt) { ++ dev_err(dev, "cannot allocate DMA RX buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ flash->mdev.parent = dev; ++ flash->mdev.minor = MISC_DYNAMIC_MINOR; ++ flash->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-flash", DEVICE_NAME); ++ flash->mdev.fops = &ast2500_espi_flash_fops; ++ rc = misc_register(&flash->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", flash->mdev.name); ++ return rc; ++ } ++ ++ ast2500_espi_flash_reset(espi); ++ ++ return 0; ++} ++ ++int ast2500_espi_flash_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ flash = &espi->flash; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_FLASH); ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_FLASH_TX_DMA_EN ++ | ESPI_CTRL_FLASH_RX_DMA_EN ++ | ESPI_CTRL_FLASH_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (flash->dma.enable) { ++ dmam_free_coherent(dev, PAGE_SIZE, flash->dma.tx_virt, flash->dma.tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, flash->dma.rx_virt, flash->dma.rx_addr); ++ } ++ ++ mutex_destroy(&flash->tx_mtx); ++ mutex_destroy(&flash->rx_mtx); ++ ++ misc_deregister(&flash->mdev); ++ ++ return 0; ++} ++ ++/* global control */ ++irqreturn_t ast2500_espi_isr(int irq, void *arg) ++{ ++ struct aspeed_espi *espi; ++ u32 sts; ++ ++ espi = (struct aspeed_espi *)arg; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ if (!sts) ++ return IRQ_NONE; ++ ++ if (sts & ESPI_INT_STS_PERIF) ++ ast2500_espi_perif_isr(espi); ++ ++ if (sts & ESPI_INT_STS_VW) ++ ast2500_espi_vw_isr(espi); ++ ++ if (sts & ESPI_INT_STS_OOB) ++ ast2500_espi_oob_isr(espi); ++ ++ if (sts & ESPI_INT_STS_FLASH) ++ ast2500_espi_flash_isr(espi); ++ ++ if (sts & ESPI_INT_STS_RST_DEASSERT) { ++ ast2500_espi_perif_reset(espi); ++ ast2500_espi_vw_reset(espi); ++ ast2500_espi_oob_reset(espi); ++ ast2500_espi_flash_reset(espi); ++ writel(ESPI_INT_STS_RST_DEASSERT, espi->regs + ESPI_INT_STS); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++void ast2500_espi_pre_init(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~ESPI_INT_EN_RST_DEASSERT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++} ++ ++void ast2500_espi_post_init(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg |= ESPI_INT_EN_RST_DEASSERT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++} ++ ++void ast2500_espi_deinit(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~(ESPI_INT_EN_RST_DEASSERT); ++ writel(reg, espi->regs + ESPI_INT_EN); ++} +diff --git a/drivers/soc/aspeed/espi/ast2500-espi.h b/drivers/soc/aspeed/espi/ast2500-espi.h +--- a/drivers/soc/aspeed/espi/ast2500-espi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2500-espi.h 2026-09-09 15:51:44.720238041 +0000 +@@ -0,0 +1,258 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Register definitions for Aspeed AST2500 eSPI controller ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#ifndef _AST2500_ESPI_H_ ++#define _AST2500_ESPI_H_ ++ ++#include ++#include ++#include "aspeed-espi.h" ++ ++/* registers */ ++#define ESPI_CTRL 0x000 ++#define ESPI_CTRL_FLASH_TX_SW_RST BIT(31) ++#define ESPI_CTRL_FLASH_RX_SW_RST BIT(30) ++#define ESPI_CTRL_OOB_TX_SW_RST BIT(29) ++#define ESPI_CTRL_OOB_RX_SW_RST BIT(28) ++#define ESPI_CTRL_PERIF_NP_TX_SW_RST BIT(27) ++#define ESPI_CTRL_PERIF_NP_RX_SW_RST BIT(26) ++#define ESPI_CTRL_PERIF_PC_TX_SW_RST BIT(25) ++#define ESPI_CTRL_PERIF_PC_RX_SW_RST BIT(24) ++#define ESPI_CTRL_FLASH_TX_DMA_EN BIT(23) ++#define ESPI_CTRL_FLASH_RX_DMA_EN BIT(22) ++#define ESPI_CTRL_OOB_TX_DMA_EN BIT(21) ++#define ESPI_CTRL_OOB_RX_DMA_EN BIT(20) ++#define ESPI_CTRL_PERIF_NP_TX_DMA_EN BIT(19) ++#define ESPI_CTRL_PERIF_PC_TX_DMA_EN BIT(17) ++#define ESPI_CTRL_PERIF_PC_RX_DMA_EN BIT(16) ++#define ESPI_CTRL_FLASH_SAFS_SW_MODE BIT(10) ++#define ESPI_CTRL_VW_GPIO_SW BIT(9) ++#define ESPI_CTRL_FLASH_SW_RDY BIT(7) ++#define ESPI_CTRL_OOB_SW_RDY BIT(4) ++#define ESPI_CTRL_VW_SW_RDY BIT(3) ++#define ESPI_CTRL_PERIF_SW_RDY BIT(1) ++#define ESPI_STS 0x004 ++#define ESPI_INT_STS 0x008 ++#define ESPI_INT_STS_RST_DEASSERT BIT(31) ++#define ESPI_INT_STS_OOB_RX_TMOUT BIT(23) ++#define ESPI_INT_STS_VW_SYSEVT1 BIT(22) ++#define ESPI_INT_STS_FLASH_TX_ERR BIT(21) ++#define ESPI_INT_STS_OOB_TX_ERR BIT(20) ++#define ESPI_INT_STS_FLASH_TX_ABT BIT(19) ++#define ESPI_INT_STS_OOB_TX_ABT BIT(18) ++#define ESPI_INT_STS_PERIF_NP_TX_ABT BIT(17) ++#define ESPI_INT_STS_PERIF_PC_TX_ABT BIT(16) ++#define ESPI_INT_STS_FLASH_RX_ABT BIT(15) ++#define ESPI_INT_STS_OOB_RX_ABT BIT(14) ++#define ESPI_INT_STS_PERIF_NP_RX_ABT BIT(13) ++#define ESPI_INT_STS_PERIF_PC_RX_ABT BIT(12) ++#define ESPI_INT_STS_PERIF_NP_TX_ERR BIT(11) ++#define ESPI_INT_STS_PERIF_PC_TX_ERR BIT(10) ++#define ESPI_INT_STS_VW_GPIO BIT(9) ++#define ESPI_INT_STS_VW_SYSEVT BIT(8) ++#define ESPI_INT_STS_FLASH_TX_CMPLT BIT(7) ++#define ESPI_INT_STS_FLASH_RX_CMPLT BIT(6) ++#define ESPI_INT_STS_OOB_TX_CMPLT BIT(5) ++#define ESPI_INT_STS_OOB_RX_CMPLT BIT(4) ++#define ESPI_INT_STS_PERIF_NP_TX_CMPLT BIT(3) ++#define ESPI_INT_STS_PERIF_PC_TX_CMPLT BIT(1) ++#define ESPI_INT_STS_PERIF_PC_RX_CMPLT BIT(0) ++#define ESPI_INT_EN 0x00c ++#define ESPI_INT_EN_RST_DEASSERT BIT(31) ++#define ESPI_INT_EN_OOB_RX_TMOUT BIT(23) ++#define ESPI_INT_EN_VW_SYSEVT1 BIT(22) ++#define ESPI_INT_EN_FLASH_TX_ERR BIT(21) ++#define ESPI_INT_EN_OOB_TX_ERR BIT(20) ++#define ESPI_INT_EN_FLASH_TX_ABT BIT(19) ++#define ESPI_INT_EN_OOB_TX_ABT BIT(18) ++#define ESPI_INT_EN_PERIF_NP_TX_ABT BIT(17) ++#define ESPI_INT_EN_PERIF_PC_TX_ABT BIT(16) ++#define ESPI_INT_EN_FLASH_RX_ABT BIT(15) ++#define ESPI_INT_EN_OOB_RX_ABT BIT(14) ++#define ESPI_INT_EN_PERIF_NP_RX_ABT BIT(13) ++#define ESPI_INT_EN_PERIF_PC_RX_ABT BIT(12) ++#define ESPI_INT_EN_PERIF_NP_TX_ERR BIT(11) ++#define ESPI_INT_EN_PERIF_PC_TX_ERR BIT(10) ++#define ESPI_INT_EN_VW_GPIO BIT(9) ++#define ESPI_INT_EN_VW_SYSEVT BIT(8) ++#define ESPI_INT_EN_FLASH_TX_CMPLT BIT(7) ++#define ESPI_INT_EN_FLASH_RX_CMPLT BIT(6) ++#define ESPI_INT_EN_OOB_TX_CMPLT BIT(5) ++#define ESPI_INT_EN_OOB_RX_CMPLT BIT(4) ++#define ESPI_INT_EN_PERIF_NP_TX_CMPLT BIT(3) ++#define ESPI_INT_EN_PERIF_PC_TX_CMPLT BIT(1) ++#define ESPI_INT_EN_PERIF_PC_RX_CMPLT BIT(0) ++#define ESPI_PERIF_PC_RX_DMA 0x010 ++#define ESPI_PERIF_PC_RX_CTRL 0x014 ++#define ESPI_PERIF_PC_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_PERIF_PC_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_PERIF_PC_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_PERIF_PC_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_PERIF_PC_RX_DATA 0x018 ++#define ESPI_PERIF_PC_TX_DMA 0x020 ++#define ESPI_PERIF_PC_TX_CTRL 0x024 ++#define ESPI_PERIF_PC_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_PERIF_PC_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_PERIF_PC_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_PERIF_PC_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_PERIF_PC_TX_DATA 0x028 ++#define ESPI_PERIF_NP_TX_DMA 0x030 ++#define ESPI_PERIF_NP_TX_CTRL 0x034 ++#define ESPI_PERIF_NP_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_PERIF_NP_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_PERIF_NP_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_PERIF_NP_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_PERIF_NP_TX_DATA 0x038 ++#define ESPI_OOB_RX_DMA 0x040 ++#define ESPI_OOB_RX_CTRL 0x044 ++#define ESPI_OOB_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_OOB_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_OOB_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_OOB_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_OOB_RX_DATA 0x048 ++#define ESPI_OOB_TX_DMA 0x050 ++#define ESPI_OOB_TX_CTRL 0x054 ++#define ESPI_OOB_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_OOB_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_OOB_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_OOB_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_OOB_TX_DATA 0x058 ++#define ESPI_FLASH_RX_DMA 0x060 ++#define ESPI_FLASH_RX_CTRL 0x064 ++#define ESPI_FLASH_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_FLASH_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_FLASH_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_FLASH_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_FLASH_RX_DATA 0x068 ++#define ESPI_FLASH_TX_DMA 0x070 ++#define ESPI_FLASH_TX_CTRL 0x074 ++#define ESPI_FLASH_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_FLASH_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_FLASH_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_FLASH_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_FLASH_TX_DATA 0x078 ++#define ESPI_PERIF_MCYC_SADDR 0x084 ++#define ESPI_PERIF_MCYC_TADDR 0x088 ++#define ESPI_PERIF_MCYC_MASK 0x08c ++#define ESPI_FLASH_SAFS_TADDR 0x090 ++#define ESPI_FLASH_SAFS_TADDR_BASE GENMASK(31, 24) ++#define ESPI_FLASH_SAFS_TADDR_MASK GENMASK(15, 8) ++#define ESPI_VW_SYSEVT_INT_EN 0x094 ++#define ESPI_VW_SYSEVT_INT_EN_HOST_RST_WARN BIT(8) ++#define ESPI_VW_SYSEVT_INT_EN_OOB_RST_WARN BIT(6) ++#define ESPI_VW_SYSEVT 0x098 ++#define ESPI_VW_SYSEVT_HOST_RST_ACK BIT(27) ++#define ESPI_VW_SYSEVT_SLV_BOOT_STS BIT(23) ++#define ESPI_VW_SYSEVT_SLV_BOOT_DONE BIT(20) ++#define ESPI_VW_SYSEVT_OOB_RST_ACK BIT(16) ++#define ESPI_VW_SYSEVT_HOST_RST_WARN BIT(8) ++#define ESPI_VW_SYSEVT_OOB_RST_WARN BIT(6) ++#define ESPI_VW_GPIO_VAL 0x09c ++#define ESPI_GEN_CAP_N_CONF 0x0a0 ++#define ESPI_CH0_CAP_N_CONF 0x0a4 ++#define ESPI_CH1_CAP_N_CONF 0x0a8 ++#define ESPI_CH2_CAP_N_CONF 0x0ac ++#define ESPI_CH3_CAP_N_CONF 0x0b0 ++#define ESPI_CH3_CAP_N_CONF2 0x0b4 ++#define ESPI_VW_GPIO_DIR 0x0c0 ++#define ESPI_VW_GPIO_GRP 0x0c4 ++#define ESPI_VW_SYSEVT1_INT_EN 0x100 ++#define ESPI_VW_SYSEVT1_INT_EN_SUSPEND_WARN BIT(0) ++#define ESPI_VW_SYSEVT1 0x104 ++#define ESPI_VW_SYSEVT1_SUSPEND_ACK BIT(20) ++#define ESPI_VW_SYSEVT1_SUSPEND_WARN BIT(0) ++#define ESPI_VW_SYSEVT_INT_T0 0x110 ++#define ESPI_VW_SYSEVT_INT_T1 0x114 ++#define ESPI_VW_SYSEVT_INT_T2 0x118 ++#define ESPI_VW_SYSEVT_INT_T2_HOST_RST_WARN BIT(8) ++#define ESPI_VW_SYSEVT_INT_T2_OOB_RST_WARN BIT(6) ++#define ESPI_VW_SYSEVT_INT_STS 0x11c ++#define ESPI_VW_SYSEVT_INT_STS_HOST_RST_WARN BIT(8) ++#define ESPI_VW_SYSEVT_INT_STS_OOB_RST_WARN BIT(6) ++#define ESPI_VW_SYSEVT1_INT_T0 0x120 ++#define ESPI_VW_SYSEVT1_INT_T0_SUSPEND_WARN BIT(0) ++#define ESPI_VW_SYSEVT1_INT_T1 0x124 ++#define ESPI_VW_SYSEVT1_INT_T2 0x128 ++#define ESPI_VW_SYSEVT1_INT_STS 0x12c ++#define ESPI_VW_SYSEVT1_INT_STS_SUSPEND_WARN BIT(0) ++ ++/* collect ESPI_INT_EN bits for convenience */ ++#define ESPI_INT_EN_PERIF \ ++ (ESPI_INT_EN_PERIF_NP_TX_ABT | \ ++ ESPI_INT_EN_PERIF_PC_TX_ABT | \ ++ ESPI_INT_EN_PERIF_NP_RX_ABT | \ ++ ESPI_INT_EN_PERIF_PC_RX_ABT | \ ++ ESPI_INT_EN_PERIF_NP_TX_ERR | \ ++ ESPI_INT_EN_PERIF_PC_TX_ERR | \ ++ ESPI_INT_EN_PERIF_NP_TX_CMPLT | \ ++ ESPI_INT_EN_PERIF_PC_TX_CMPLT | \ ++ ESPI_INT_EN_PERIF_PC_RX_CMPLT) ++ ++#define ESPI_INT_EN_VW \ ++ (ESPI_INT_EN_VW_SYSEVT1 | \ ++ ESPI_INT_EN_VW_GPIO | \ ++ ESPI_INT_EN_VW_SYSEVT) ++ ++#define ESPI_INT_EN_OOB \ ++ (ESPI_INT_EN_OOB_RX_TMOUT | \ ++ ESPI_INT_EN_OOB_TX_ERR | \ ++ ESPI_INT_EN_OOB_TX_ABT | \ ++ ESPI_INT_EN_OOB_RX_ABT | \ ++ ESPI_INT_EN_OOB_TX_CMPLT | \ ++ ESPI_INT_EN_OOB_RX_CMPLT) ++ ++#define ESPI_INT_EN_FLASH \ ++ (ESPI_INT_EN_FLASH_TX_ERR | \ ++ ESPI_INT_EN_FLASH_TX_ABT | \ ++ ESPI_INT_EN_FLASH_RX_ABT | \ ++ ESPI_INT_EN_FLASH_TX_CMPLT | \ ++ ESPI_INT_EN_FLASH_RX_CMPLT) ++ ++/* collect ESPI_INT_STS bits for convenience */ ++#define ESPI_INT_STS_PERIF \ ++ (ESPI_INT_STS_PERIF_NP_TX_ABT | \ ++ ESPI_INT_STS_PERIF_PC_TX_ABT | \ ++ ESPI_INT_STS_PERIF_NP_RX_ABT | \ ++ ESPI_INT_STS_PERIF_PC_RX_ABT | \ ++ ESPI_INT_STS_PERIF_NP_TX_ERR | \ ++ ESPI_INT_STS_PERIF_PC_TX_ERR | \ ++ ESPI_INT_STS_PERIF_NP_TX_CMPLT | \ ++ ESPI_INT_STS_PERIF_PC_TX_CMPLT | \ ++ ESPI_INT_STS_PERIF_PC_RX_CMPLT) ++ ++#define ESPI_INT_STS_VW \ ++ (ESPI_INT_STS_VW_SYSEVT1 | \ ++ ESPI_INT_STS_VW_GPIO | \ ++ ESPI_INT_STS_VW_SYSEVT) ++ ++#define ESPI_INT_STS_OOB \ ++ (ESPI_INT_STS_OOB_RX_TMOUT | \ ++ ESPI_INT_STS_OOB_TX_ERR | \ ++ ESPI_INT_STS_OOB_TX_ABT | \ ++ ESPI_INT_STS_OOB_RX_ABT | \ ++ ESPI_INT_STS_OOB_TX_CMPLT | \ ++ ESPI_INT_STS_OOB_RX_CMPLT) ++ ++#define ESPI_INT_STS_FLASH \ ++ (ESPI_INT_STS_FLASH_TX_ERR | \ ++ ESPI_INT_STS_FLASH_TX_ABT | \ ++ ESPI_INT_STS_FLASH_RX_ABT | \ ++ ESPI_INT_STS_FLASH_TX_CMPLT | \ ++ ESPI_INT_STS_FLASH_RX_CMPLT) ++ ++/* operators for framework initialization */ ++void ast2500_espi_pre_init(struct aspeed_espi *espi); ++void ast2500_espi_post_init(struct aspeed_espi *espi); ++void ast2500_espi_deinit(struct aspeed_espi *espi); ++int ast2500_espi_perif_probe(struct aspeed_espi *espi); ++int ast2500_espi_perif_remove(struct aspeed_espi *espi); ++int ast2500_espi_vw_probe(struct aspeed_espi *espi); ++int ast2500_espi_vw_remove(struct aspeed_espi *espi); ++int ast2500_espi_oob_probe(struct aspeed_espi *espi); ++int ast2500_espi_oob_remove(struct aspeed_espi *espi); ++int ast2500_espi_flash_probe(struct aspeed_espi *espi); ++int ast2500_espi_flash_remove(struct aspeed_espi *espi); ++irqreturn_t ast2500_espi_isr(int irq, void *arg); ++#endif +diff --git a/drivers/soc/aspeed/espi/ast2600-espi.c b/drivers/soc/aspeed/espi/ast2600-espi.c +--- a/drivers/soc/aspeed/espi/ast2600-espi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2600-espi.c 2026-09-09 15:51:44.720238041 +0000 +@@ -0,0 +1,1910 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ast2600-espi.h" ++#include "aspeed-espi-comm.h" ++#include "aspeed-espi.h" ++ ++#define PERIF_MCYC_ALIGN SZ_64K ++#define PERIF_MMBI_ALIGN SZ_64K ++#define PERIF_MMBI_INST_NUM 8 ++ ++#define OOB_DMA_RPTR_KEY 0x45538073 ++#define OOB_DMA_DESC_NUM 8 ++#define OOB_DMA_DESC_CUSTOM 0x4 ++ ++#define FLASH_EDAF_ALIGN SZ_16M ++ ++/* peripheral channel (CH0) */ ++static int ast2600_espi_mmbi_b2h_mmap(struct file *fp, struct vm_area_struct *vma) ++{ ++ struct aspeed_espi_perif_mmbi *mmbi; ++ struct aspeed_espi_perif *perif; ++ struct aspeed_espi *espi; ++ unsigned long vm_size; ++ pgprot_t prot; ++ ++ mmbi = container_of(fp->private_data, struct aspeed_espi_perif_mmbi, b2h_mdev); ++ ++ perif = mmbi->perif; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ vm_size = vma->vm_end - vma->vm_start; ++ prot = vma->vm_page_prot; ++ ++ if (((vma->vm_pgoff << PAGE_SHIFT) + vm_size) > (SZ_4K << perif->mmbi.inst_size)) ++ return -EINVAL; ++ ++ prot = pgprot_noncached(prot); ++ ++ if (remap_pfn_range(vma, vma->vm_start, ++ (mmbi->b2h_addr >> PAGE_SHIFT) + vma->vm_pgoff, ++ vm_size, prot)) ++ return -EAGAIN; ++ ++ return 0; ++} ++ ++static int ast2600_espi_mmbi_h2b_mmap(struct file *fp, struct vm_area_struct *vma) ++{ ++ struct aspeed_espi_perif_mmbi *mmbi; ++ struct aspeed_espi_perif *perif; ++ struct aspeed_espi *espi; ++ unsigned long vm_size; ++ pgprot_t prot; ++ ++ mmbi = container_of(fp->private_data, struct aspeed_espi_perif_mmbi, h2b_mdev); ++ ++ perif = mmbi->perif; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ vm_size = vma->vm_end - vma->vm_start; ++ prot = vma->vm_page_prot; ++ ++ if (((vma->vm_pgoff << PAGE_SHIFT) + vm_size) > (SZ_4K << perif->mmbi.inst_size)) ++ return -EINVAL; ++ ++ prot = pgprot_noncached(prot); ++ ++ if (remap_pfn_range(vma, vma->vm_start, ++ (mmbi->h2b_addr >> PAGE_SHIFT) + vma->vm_pgoff, ++ vm_size, prot)) ++ return -EAGAIN; ++ ++ return 0; ++} ++ ++static __poll_t ast2600_espi_mmbi_h2b_poll(struct file *fp, struct poll_table_struct *pt) ++{ ++ struct aspeed_espi_perif_mmbi *mmbi; ++ ++ mmbi = container_of(fp->private_data, struct aspeed_espi_perif_mmbi, h2b_mdev); ++ ++ poll_wait(fp, &mmbi->wq, pt); ++ ++ if (!mmbi->host_rwp_update) ++ return 0; ++ ++ mmbi->host_rwp_update = false; ++ ++ return EPOLLIN; ++} ++ ++static long ast2600_espi_perif_pc_get_rx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&perif->pc_rx_mtx)) ++ return -EAGAIN; ++ ++ if (!perif->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&perif->pc_rx_mtx); ++ ++ if (!perif->rx_ready) { ++ rc = wait_event_interruptible(perif->wq, perif->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_PERIF_PC_RX_CTRL); ++ cyc = FIELD_GET(ESPI_PERIF_PC_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_PERIF_PC_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_PERIF_PC_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ switch (cyc) { ++ case ESPI_PERIF_MSG: ++ pkt_len = sizeof(struct espi_perif_msg); ++ break; ++ case ESPI_PERIF_MSG_D: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_perif_msg); ++ break; ++ case ESPI_PERIF_SUC_CMPLT_D_MIDDLE: ++ case ESPI_PERIF_SUC_CMPLT_D_FIRST: ++ case ESPI_PERIF_SUC_CMPLT_D_LAST: ++ case ESPI_PERIF_SUC_CMPLT_D_ONLY: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_perif_cmplt); ++ break; ++ case ESPI_PERIF_SUC_CMPLT: ++ case ESPI_PERIF_UNSUC_CMPLT: ++ pkt_len = sizeof(struct espi_perif_cmplt); ++ break; ++ default: ++ rc = -EFAULT; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ if (perif->dma.enable) { ++ memcpy(hdr + 1, perif->dma.pc_rx_virt, pkt_len - sizeof(*hdr)); ++ } else { ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ reg = readl(espi->regs + ESPI_PERIF_PC_RX_DATA) & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&perif->lock, flags); ++ ++ writel(ESPI_PERIF_PC_RX_CTRL_SERV_PEND, espi->regs + ESPI_PERIF_PC_RX_CTRL); ++ perif->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&perif->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&perif->pc_rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2600_espi_perif_pc_put_tx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (!mutex_trylock(&perif->pc_tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_PERIF_PC_TX_CTRL); ++ if (reg & ESPI_PERIF_PC_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (perif->dma.enable) { ++ memcpy(perif->dma.pc_tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_PERIF_PC_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_PERIF_PC_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_PERIF_PC_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_PERIF_PC_TX_CTRL_LEN, len) ++ | ESPI_PERIF_PC_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_PERIF_PC_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_n_out: ++ mutex_unlock(&perif->pc_tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2600_espi_perif_np_put_tx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (!mutex_trylock(&perif->np_tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_PERIF_NP_TX_CTRL); ++ if (reg & ESPI_PERIF_NP_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (perif->dma.enable) { ++ memcpy(perif->dma.np_tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_PERIF_NP_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_PERIF_NP_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_PERIF_NP_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_PERIF_NP_TX_CTRL_LEN, len) ++ | ESPI_PERIF_NP_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_PERIF_NP_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_n_out: ++ mutex_unlock(&perif->np_tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2600_espi_perif_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_perif *perif; ++ struct aspeed_espi_ioc ioc; ++ ++ perif = container_of(fp->private_data, struct aspeed_espi_perif, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_PERIF_PC_GET_RX: ++ return ast2600_espi_perif_pc_get_rx(fp, perif, &ioc); ++ case ASPEED_ESPI_PERIF_PC_PUT_TX: ++ return ast2600_espi_perif_pc_put_tx(fp, perif, &ioc); ++ case ASPEED_ESPI_PERIF_NP_PUT_TX: ++ return ast2600_espi_perif_np_put_tx(fp, perif, &ioc); ++ default: ++ break; ++ }; ++ ++ return -EINVAL; ++} ++ ++static int ast2600_espi_perif_mmap(struct file *fp, struct vm_area_struct *vma) ++{ ++ struct aspeed_espi_perif *perif; ++ unsigned long vm_size; ++ pgprot_t vm_prot; ++ ++ perif = container_of(fp->private_data, struct aspeed_espi_perif, mdev); ++ if (!perif->mcyc.enable) ++ return -EPERM; ++ ++ vm_size = vma->vm_end - vma->vm_start; ++ vm_prot = vma->vm_page_prot; ++ ++ if (((vma->vm_pgoff << PAGE_SHIFT) + vm_size) > perif->mcyc.size) ++ return -EINVAL; ++ ++ vm_prot = pgprot_noncached(vm_prot); ++ ++ if (remap_pfn_range(vma, vma->vm_start, ++ (perif->mcyc.taddr >> PAGE_SHIFT) + vma->vm_pgoff, ++ vm_size, vm_prot)) ++ return -EAGAIN; ++ ++ return 0; ++} ++ ++static const struct file_operations ast2600_espi_mmbi_b2h_fops = { ++ .owner = THIS_MODULE, ++ .mmap = ast2600_espi_mmbi_b2h_mmap, ++}; ++ ++static const struct file_operations ast2600_espi_mmbi_h2b_fops = { ++ .owner = THIS_MODULE, ++ .mmap = ast2600_espi_mmbi_h2b_mmap, ++ .poll = ast2600_espi_mmbi_h2b_poll, ++}; ++ ++static const struct file_operations ast2600_espi_perif_fops = { ++ .owner = THIS_MODULE, ++ .mmap = ast2600_espi_perif_mmap, ++ .unlocked_ioctl = ast2600_espi_perif_ioctl, ++}; ++ ++static irqreturn_t ast2600_espi_perif_mmbi_isr(int irq, void *arg) ++{ ++ struct aspeed_espi_perif_mmbi *mmbi; ++ struct aspeed_espi_perif *perif; ++ struct aspeed_espi *espi; ++ u32 sts, tmp; ++ u32 *p; ++ int i; ++ ++ espi = (struct aspeed_espi *)arg; ++ ++ perif = &espi->perif; ++ ++ sts = readl(espi->regs + ESPI_MMBI_INT_STS); ++ if (!sts) ++ return IRQ_NONE; ++ ++ for (i = 0, tmp = sts; i < PERIF_MMBI_INST_NUM; ++i, tmp >>= 2) { ++ if (!(tmp & 0x3)) ++ continue; ++ ++ mmbi = &perif->mmbi.inst[i]; ++ ++ p = (u32 *)mmbi->h2b_virt; ++ p[0] = readl(espi->regs + ESPI_MMBI_HOST_RWP(i)); ++ p[1] = readl(espi->regs + ESPI_MMBI_HOST_RWP(i) + 4); ++ ++ mmbi->host_rwp_update = true; ++ ++ wake_up_interruptible(&mmbi->wq); ++ } ++ ++ writel(sts, espi->regs + ESPI_MMBI_INT_STS); ++ ++ return IRQ_HANDLED; ++} ++ ++static void ast2600_espi_perif_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ unsigned long flags; ++ u32 sts; ++ ++ perif = &espi->perif; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_PERIF_PC_RX_CMPLT) { ++ writel(ESPI_INT_STS_PERIF_PC_RX_CMPLT, espi->regs + ESPI_INT_STS); ++ ++ spin_lock_irqsave(&perif->lock, flags); ++ perif->rx_ready = true; ++ spin_unlock_irqrestore(&perif->lock, flags); ++ ++ wake_up_interruptible(&perif->wq); ++ } ++} ++ ++static void ast2600_espi_perif_sw_reset(struct aspeed_espi *espi) ++{ ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_PERIF_NP_TX_SW_RST ++ | ESPI_CTRL_PERIF_NP_RX_SW_RST ++ | ESPI_CTRL_PERIF_PC_TX_SW_RST ++ | ESPI_CTRL_PERIF_PC_RX_SW_RST ++ | ESPI_CTRL_PERIF_NP_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_RX_DMA_EN ++ | ESPI_CTRL_PERIF_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CTRL_PERIF_NP_TX_SW_RST ++ | ESPI_CTRL_PERIF_NP_RX_SW_RST ++ | ESPI_CTRL_PERIF_PC_TX_SW_RST ++ | ESPI_CTRL_PERIF_PC_RX_SW_RST); ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++static void ast2600_espi_perif_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ struct device *dev; ++ u32 reg, mask; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ writel(ESPI_INT_EN_PERIF, espi->regs + ESPI_INT_EN_CLR); ++ writel(ESPI_INT_STS_PERIF, espi->regs + ESPI_INT_STS); ++ ++ writel(0x0, espi->regs + ESPI_MMBI_INT_EN); ++ writel(0xffffffff, espi->regs + ESPI_MMBI_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CTRL2); ++ reg &= ~(ESPI_CTRL2_MCYC_RD_DIS_WDT | ESPI_CTRL2_MCYC_WR_DIS_WDT); ++ writel(reg, espi->regs + ESPI_CTRL2); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_PERIF_NP_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_RX_DMA_EN ++ | ESPI_CTRL_PERIF_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (perif->mmbi.enable) { ++ reg = readl(espi->regs + ESPI_MMBI_CTRL); ++ reg &= ~(ESPI_MMBI_CTRL_EN); ++ writel(reg, espi->regs + ESPI_MMBI_CTRL); ++ ++ mask = ~(perif->mmbi.size - 1); ++ writel(mask, espi->regs + ESPI_PERIF_MMBI_MASK); ++ writel(perif->mmbi.saddr, espi->regs + ESPI_PERIF_MMBI_SADDR); ++ writel(perif->mmbi.taddr, espi->regs + ESPI_PERIF_MMBI_TADDR); ++ ++ writel(0xffffffff, espi->regs + ESPI_MMBI_INT_EN); ++ ++ reg = FIELD_PREP(ESPI_MMBI_CTRL_INST_SZ, perif->mmbi.inst_size) ++ | FIELD_PREP(ESPI_MMBI_CTRL_TOTAL_SZ, perif->mmbi.inst_size) ++ | ESPI_MMBI_CTRL_EN; ++ writel(reg, espi->regs + ESPI_MMBI_CTRL); ++ ++ reg = readl(espi->regs + ESPI_CTRL2) & ~(ESPI_CTRL2_MMBI_RD_DIS | ESPI_CTRL2_MMBI_WR_DIS); ++ writel(reg, espi->regs + ESPI_CTRL2); ++ } ++ ++ if (perif->mcyc.enable) { ++ mask = ~(perif->mcyc.size - 1); ++ writel(mask, espi->regs + ESPI_PERIF_MCYC_MASK); ++ writel(perif->mcyc.saddr, espi->regs + ESPI_PERIF_MCYC_SADDR); ++ writel(perif->mcyc.taddr, espi->regs + ESPI_PERIF_MCYC_TADDR); ++ ++ reg = readl(espi->regs + ESPI_CTRL2) & ~(ESPI_CTRL2_MCYC_RD_DIS | ESPI_CTRL2_MCYC_WR_DIS); ++ writel(reg, espi->regs + ESPI_CTRL2); ++ } ++ ++ if (perif->dma.enable) { ++ writel(perif->dma.np_tx_addr, espi->regs + ESPI_PERIF_NP_TX_DMA); ++ writel(perif->dma.pc_tx_addr, espi->regs + ESPI_PERIF_PC_TX_DMA); ++ writel(perif->dma.pc_rx_addr, espi->regs + ESPI_PERIF_PC_RX_DMA); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ESPI_CTRL_PERIF_NP_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CTRL); ++ } ++ ++ writel(ESPI_INT_EN_PERIF_PC_RX_CMPLT, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL) | ESPI_CTRL_PERIF_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++int ast2600_espi_perif_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif_mmbi *mmbi; ++ struct aspeed_espi_perif *perif; ++ struct platform_device *pdev; ++ struct device *dev; ++ int i, rc; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ init_waitqueue_head(&perif->wq); ++ ++ spin_lock_init(&perif->lock); ++ ++ mutex_init(&perif->np_tx_mtx); ++ mutex_init(&perif->pc_tx_mtx); ++ mutex_init(&perif->pc_rx_mtx); ++ ++ perif->mmbi.enable = of_property_read_bool(dev->of_node, "perif-mmbi-enable"); ++ if (perif->mmbi.enable) { ++ pdev = espi->pdev; ++ ++ perif->mmbi.irq = platform_get_irq(pdev, 1); ++ if (perif->mmbi.irq < 0) { ++ dev_err(dev, "cannot get MMBI IRQ number\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "perif-mmbi-src-addr", (u32 *)&perif->mmbi.saddr); ++ if (rc || !IS_ALIGNED(perif->mmbi.saddr, PERIF_MMBI_ALIGN)) { ++ dev_err(dev, "cannot get 64KB-aligned MMBI host address\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "perif-mmbi-instance-size", (u32 *)&perif->mmbi.inst_size); ++ if (rc || perif->mmbi.inst_size >= MMBI_INST_SIZE_TYPES) { ++ dev_err(dev, "cannot get valid MMBI instance size\n"); ++ return -EINVAL; ++ } ++ ++ perif->mmbi.size = (SZ_8K << perif->mmbi.inst_size) * PERIF_MMBI_INST_NUM; ++ perif->mmbi.virt = dmam_alloc_coherent(dev, perif->mmbi.size, ++ &perif->mmbi.taddr, GFP_KERNEL); ++ if (!perif->mmbi.virt) { ++ dev_err(dev, "cannot allocate MMBI\n"); ++ return -ENOMEM; ++ } ++ ++ perif->mmbi.mmbi_isr = ast2600_espi_perif_mmbi_isr; ++ ++ for (i = 0; i < PERIF_MMBI_INST_NUM; ++i) { ++ mmbi = &perif->mmbi.inst[i]; ++ ++ init_waitqueue_head(&mmbi->wq); ++ ++ mmbi->perif = perif; ++ mmbi->host_rwp_update = false; ++ ++ mmbi->b2h_virt = perif->mmbi.virt + ((SZ_4K << perif->mmbi.inst_size) * i); ++ mmbi->b2h_addr = perif->mmbi.taddr + ((SZ_4K << perif->mmbi.inst_size) * i); ++ mmbi->b2h_mdev.parent = dev; ++ mmbi->b2h_mdev.minor = MISC_DYNAMIC_MINOR; ++ mmbi->b2h_mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-mmbi-b2h%d", DEVICE_NAME, i); ++ mmbi->b2h_mdev.fops = &ast2600_espi_mmbi_b2h_fops; ++ rc = misc_register(&mmbi->b2h_mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", mmbi->b2h_mdev.name); ++ return rc; ++ } ++ ++ mmbi->h2b_virt = perif->mmbi.virt + ((SZ_4K << perif->mmbi.inst_size) * (i + PERIF_MMBI_INST_NUM)); ++ mmbi->h2b_addr = perif->mmbi.taddr + ((SZ_4K << perif->mmbi.inst_size) * (i + PERIF_MMBI_INST_NUM)); ++ mmbi->h2b_mdev.parent = dev; ++ mmbi->h2b_mdev.minor = MISC_DYNAMIC_MINOR; ++ mmbi->h2b_mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-mmbi-h2b%d", DEVICE_NAME, i); ++ mmbi->h2b_mdev.fops = &ast2600_espi_mmbi_h2b_fops; ++ rc = misc_register(&mmbi->h2b_mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", mmbi->h2b_mdev.name); ++ return rc; ++ } ++ } ++ } ++ ++ perif->mcyc.enable = of_property_read_bool(dev->of_node, "perif-mcyc-enable"); ++ if (perif->mcyc.enable) { ++ if (perif->mmbi.enable) { ++ dev_err(dev, "cannot enable memory cycle, occupied by MMBI\n"); ++ return -EPERM; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "perif-mcyc-src-addr", (u32 *)&perif->mcyc.saddr); ++ if (rc || !IS_ALIGNED(perif->mcyc.saddr, PERIF_MCYC_ALIGN)) { ++ dev_err(dev, "cannot get 64KB-aligned memory cycle host address\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "perif-mcyc-size", (u32 *)&perif->mcyc.size); ++ if (rc || !IS_ALIGNED(perif->mcyc.size, PERIF_MCYC_ALIGN)) { ++ dev_err(dev, "cannot get 64KB-aligned memory cycle size\n"); ++ return -EINVAL; ++ } ++ ++ perif->mcyc.virt = dmam_alloc_coherent(dev, perif->mcyc.size, ++ &perif->mcyc.taddr, GFP_KERNEL); ++ if (!perif->mcyc.virt) { ++ dev_err(dev, "cannot allocate memory cycle\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ perif->dma.enable = of_property_read_bool(dev->of_node, "perif-dma-mode"); ++ if (perif->dma.enable) { ++ perif->dma.pc_tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.pc_tx_addr, GFP_KERNEL); ++ if (!perif->dma.pc_tx_virt) { ++ dev_err(dev, "cannot allocate posted TX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ perif->dma.pc_rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.pc_rx_addr, GFP_KERNEL); ++ if (!perif->dma.pc_rx_virt) { ++ dev_err(dev, "cannot allocate posted RX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ perif->dma.np_tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.np_tx_addr, GFP_KERNEL); ++ if (!perif->dma.np_tx_virt) { ++ dev_err(dev, "cannot allocate non-posted TX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ perif->mdev.parent = dev; ++ perif->mdev.minor = MISC_DYNAMIC_MINOR; ++ perif->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-peripheral", DEVICE_NAME); ++ perif->mdev.fops = &ast2600_espi_perif_fops; ++ rc = misc_register(&perif->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", perif->mdev.name); ++ return rc; ++ } ++ ++ ast2600_espi_perif_reset(espi); ++ ++ return 0; ++} ++ ++int ast2600_espi_perif_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif_mmbi *mmbi; ++ struct aspeed_espi_perif *perif; ++ struct device *dev; ++ u32 reg; ++ int i; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ writel(ESPI_INT_EN_PERIF, espi->regs + ESPI_INT_EN_CLR); ++ ++ reg = readl(espi->regs + ESPI_CTRL2); ++ reg |= (ESPI_CTRL2_MCYC_RD_DIS | ESPI_CTRL2_MCYC_WR_DIS); ++ writel(reg, espi->regs + ESPI_CTRL2); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_PERIF_NP_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_TX_DMA_EN ++ | ESPI_CTRL_PERIF_PC_RX_DMA_EN ++ | ESPI_CTRL_PERIF_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (perif->mmbi.enable) { ++ reg = readl(espi->regs + ESPI_MMBI_CTRL); ++ reg &= ~ESPI_MMBI_CTRL_EN; ++ writel(reg, espi->regs + ESPI_MMBI_CTRL); ++ ++ for (i = 0; i < PERIF_MMBI_INST_NUM; ++i) { ++ mmbi = &perif->mmbi.inst[i]; ++ misc_deregister(&mmbi->b2h_mdev); ++ misc_deregister(&mmbi->h2b_mdev); ++ } ++ ++ dmam_free_coherent(dev, perif->mmbi.size, perif->mmbi.virt, ++ perif->mmbi.taddr); ++ } ++ ++ if (perif->mcyc.enable) ++ dmam_free_coherent(dev, perif->mcyc.size, perif->mcyc.virt, ++ perif->mcyc.taddr); ++ ++ if (perif->dma.enable) { ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.np_tx_virt, ++ perif->dma.np_tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.pc_tx_virt, ++ perif->dma.pc_tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.pc_rx_virt, ++ perif->dma.pc_rx_addr); ++ } ++ ++ mutex_destroy(&perif->np_tx_mtx); ++ mutex_destroy(&perif->pc_tx_mtx); ++ mutex_destroy(&perif->pc_rx_mtx); ++ ++ misc_deregister(&perif->mdev); ++ ++ return 0; ++} ++ ++/* virtual wire channel (CH1) */ ++static long ast2600_espi_vw_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_vw *vw; ++ struct aspeed_espi *espi; ++ u32 gpio, hw_mode; ++ ++ vw = container_of(fp->private_data, struct aspeed_espi_vw, mdev); ++ espi = container_of(vw, struct aspeed_espi, vw); ++ gpio = vw->gpio.val0; ++ hw_mode = vw->gpio.hw_mode; ++ ++ if (hw_mode) { ++ dev_err(espi->dev, "HW mode: vGPIO reflect on physical GPIO. Get state from GPIO driver.\n"); ++ return -EFAULT; ++ } ++ ++ switch (cmd) { ++ case ASPEED_ESPI_VW_GET_GPIO_VAL: ++ if (put_user(gpio, (u32 __user *)arg)) { ++ dev_err(espi->dev, "failed to get vGPIO value\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Get vGPIO value: 0x%x\n", gpio); ++ break; ++ ++ case ASPEED_ESPI_VW_PUT_GPIO_VAL: ++ if (get_user(gpio, (u32 __user *)arg)) { ++ dev_err(espi->dev, "failed to put vGPIO value\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Put vGPIO value: 0x%x\n", gpio); ++ writel(gpio, espi->regs + ESPI_VW_GPIO_VAL); ++ break; ++ ++ default: ++ return -EINVAL; ++ }; ++ ++ return 0; ++} ++ ++static const struct file_operations ast2600_espi_vw_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2600_espi_vw_ioctl, ++}; ++ ++static void ast2600_espi_vw_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_vw *vw; ++ u32 sts; ++ ++ vw = &espi->vw; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_VW_GPIO) { ++ vw->gpio.val0 = readl(espi->regs + ESPI_VW_GPIO_VAL); ++ writel(ESPI_INT_STS_VW_GPIO, espi->regs + ESPI_INT_STS); ++ } else if (sts & ESPI_INT_STS_VW_SYSEVT) { ++ /* Handle system event */ ++ writel(ESPI_INT_STS_VW_SYSEVT, espi->regs + ESPI_INT_STS); ++ } else if (sts & (ESPI_INT_STS_VW_SYSEVT1)) { ++ /* Handle system event1 */ ++ writel(ESPI_INT_STS_VW_SYSEVT1, espi->regs + ESPI_INT_STS); ++ } ++} ++ ++static void ast2600_espi_vw_reset(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ struct aspeed_espi_vw *vw = &espi->vw; ++ ++ writel(ESPI_INT_EN_VW, espi->regs + ESPI_INT_EN_CLR); ++ writel(ESPI_INT_STS_VW, espi->regs + ESPI_INT_STS); ++ ++ writel(vw->gpio.dir0, espi->regs + ESPI_VW_GPIO_DIR); ++ ++ vw->gpio.val0 = readl(espi->regs + ESPI_VW_GPIO_VAL); ++ ++ reg = readl(espi->regs + ESPI_CTRL2) & ~(ESPI_CTRL2_VW_TX_SORT); ++ writel(reg, espi->regs + ESPI_CTRL2); ++ ++ writel(ESPI_INT_EN_VW_GPIO, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ((vw->gpio.hw_mode) ? 0 : ESPI_CTRL_VW_GPIO_SW) ++ | ESPI_CTRL_VW_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ writel(0x0, espi->regs + ESPI_VW_SYSEVT_INT_T0); ++ writel(0x0, espi->regs + ESPI_VW_SYSEVT_INT_T1); ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg |= ESPI_INT_EN_RST_DEASSERT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++ ++ writel(0xffffffff, espi->regs + ESPI_VW_SYSEVT_INT_EN); ++ writel(0x1, espi->regs + ESPI_VW_SYSEVT1_INT_EN); ++ writel(0x1, espi->regs + ESPI_VW_SYSEVT1_INT_T0); ++} ++ ++int ast2600_espi_vw_probe(struct aspeed_espi *espi) ++{ ++ int rc; ++ struct device *dev = espi->dev; ++ struct aspeed_espi_vw *vw = &espi->vw; ++ ++ vw->gpio.hw_mode = of_property_read_bool(dev->of_node, "vw-gpio-hw-mode"); ++ of_property_read_u32(dev->of_node, "vw-gpio-direction", &vw->gpio.dir0); ++ ++ vw->mdev.parent = dev; ++ vw->mdev.minor = MISC_DYNAMIC_MINOR; ++ vw->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-vw", DEVICE_NAME); ++ vw->mdev.fops = &ast2600_espi_vw_fops; ++ rc = misc_register(&vw->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", vw->mdev.name); ++ return rc; ++ } ++ ++ ast2600_espi_vw_reset(espi); ++ ++ return 0; ++} ++ ++int ast2600_espi_vw_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_vw *vw; ++ ++ vw = &espi->vw; ++ ++ writel(ESPI_INT_EN_VW, espi->regs + ESPI_INT_EN_CLR); ++ ++ misc_deregister(&vw->mdev); ++ ++ return 0; ++} ++ ++/* out-of-band channel (CH2) */ ++static long ast2600_espi_oob_dma_get_rx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ struct ast2600_espi_oob_dma_rx_desc *d; ++ struct ast2600_espi_oob_dma_rx_desc *rx_descs; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u32 wptr, pkt_len; ++ unsigned long flags; ++ u8 *pkt; ++ int rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ wptr = FIELD_PREP(ESPI_OOB_RX_DESC_WPTR_WP, readl(espi->regs + ESPI_OOB_RX_DESC_WPTR)); ++ ++ rx_descs = (struct ast2600_espi_oob_dma_rx_desc *)oob->dma.rxd_virt; ++ d = &rx_descs[wptr]; ++ ++ if (!d->dirty) ++ return -EFAULT; ++ ++ pkt_len = ((d->len) ? d->len : ESPI_MAX_PLD_LEN) + sizeof(struct espi_comm_hdr); ++ ++ if (ioc->pkt_len < pkt_len) ++ return -EINVAL; ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) ++ return -ENOMEM; ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = d->cyc; ++ hdr->tag = d->tag; ++ hdr->len_h = d->len >> 8; ++ hdr->len_l = d->len & 0xff; ++ memcpy(hdr + 1, oob->dma.rx_virt + (PAGE_SIZE * wptr), pkt_len - sizeof(*hdr)); ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ ++ /* make current descriptor available again */ ++ d->dirty = 0; ++ ++ wptr = (wptr + 1) % OOB_DMA_DESC_NUM; ++ writel(wptr | ESPI_OOB_RX_DESC_WPTR_RECV_EN, espi->regs + ESPI_OOB_RX_DESC_WPTR); ++ ++ /* set ready flag base on the next RX descriptor */ ++ oob->rx_ready = rx_descs[wptr].dirty; ++ ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++ return rc; ++} ++ ++static long ast2600_espi_oob_get_rx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&oob->rx_mtx)) ++ return -EAGAIN; ++ ++ if (!oob->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&oob->rx_mtx); ++ ++ if (!oob->rx_ready) { ++ rc = wait_event_interruptible(oob->wq, oob->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ if (oob->dma.enable) { ++ rc = ast2600_espi_oob_dma_get_rx(fp, oob, ioc); ++ goto unlock_mtx_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_OOB_RX_CTRL); ++ cyc = FIELD_GET(ESPI_OOB_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_OOB_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_OOB_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + sizeof(struct espi_comm_hdr); ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ pkt[i] = readl(espi->regs + ESPI_OOB_RX_DATA) & 0xff; ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ ++ writel(ESPI_OOB_RX_CTRL_SERV_PEND, espi->regs + ESPI_OOB_RX_CTRL); ++ oob->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&oob->rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2600_espi_oob_dma_put_tx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ struct ast2600_espi_oob_dma_tx_desc *d; ++ struct ast2600_espi_oob_dma_tx_desc *tx_descs; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u32 rptr, wptr; ++ u8 *pkt; ++ int rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ pkt = vzalloc(ioc->pkt_len); ++ if (!pkt) ++ return -ENOMEM; ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* kick HW to update descriptor read/write pointer */ ++ writel(ESPI_OOB_TX_DESC_RPTR_UPDATE, espi->regs + ESPI_OOB_TX_DESC_RPTR); ++ ++ rptr = readl(espi->regs + ESPI_OOB_TX_DESC_RPTR); ++ wptr = readl(espi->regs + ESPI_OOB_TX_DESC_WPTR); ++ ++ if (((wptr + 1) % OOB_DMA_DESC_NUM) == rptr) { ++ rc = -EBUSY; ++ goto free_n_out; ++ } ++ ++ tx_descs = (struct ast2600_espi_oob_dma_tx_desc *)oob->dma.txd_virt; ++ d = &tx_descs[wptr]; ++ d->cyc = hdr->cyc; ++ d->tag = hdr->tag; ++ d->len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ d->msg_type = OOB_DMA_DESC_CUSTOM; ++ ++ memcpy(oob->dma.tx_virt + (PAGE_SIZE * wptr), hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ ++ dma_wmb(); ++ ++ wptr = (wptr + 1) % OOB_DMA_DESC_NUM; ++ writel(wptr | ESPI_OOB_TX_DESC_WPTR_SEND_EN, espi->regs + ESPI_OOB_TX_DESC_WPTR); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++ return rc; ++} ++ ++static long ast2600_espi_oob_put_tx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ if (!mutex_trylock(&oob->tx_mtx)) ++ return -EAGAIN; ++ ++ if (oob->dma.enable) { ++ rc = ast2600_espi_oob_dma_put_tx(fp, oob, ioc); ++ goto unlock_mtx_n_out; ++ } ++ ++ reg = readl(espi->regs + ESPI_OOB_TX_CTRL); ++ if (reg & ESPI_OOB_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len > ESPI_MAX_PKT_LEN) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_OOB_TX_DATA); ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_OOB_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_OOB_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_OOB_TX_CTRL_LEN, len) ++ | ESPI_OOB_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_OOB_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&oob->tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2600_espi_oob_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_oob *oob; ++ struct aspeed_espi_ioc ioc; ++ ++ oob = container_of(fp->private_data, struct aspeed_espi_oob, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_OOB_GET_RX: ++ return ast2600_espi_oob_get_rx(fp, oob, &ioc); ++ case ASPEED_ESPI_OOB_PUT_TX: ++ return ast2600_espi_oob_put_tx(fp, oob, &ioc); ++ }; ++ ++ return -EINVAL; ++} ++ ++static const struct file_operations ast2600_espi_oob_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2600_espi_oob_ioctl, ++}; ++ ++static void ast2600_espi_oob_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ unsigned long flags; ++ u32 sts; ++ ++ oob = &espi->oob; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_OOB_RX_CMPLT) { ++ writel(ESPI_INT_STS_OOB_RX_CMPLT, espi->regs + ESPI_INT_STS); ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ oob->rx_ready = true; ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ wake_up_interruptible(&oob->wq); ++ } ++} ++ ++static void ast2600_espi_oob_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct ast2600_espi_oob_dma_tx_desc *tx_desc; ++ struct ast2600_espi_oob_dma_rx_desc *rx_desc; ++ dma_addr_t tx_addr, rx_addr; ++ u32 reg; ++ int i; ++ ++ writel(ESPI_INT_EN_OOB, espi->regs + ESPI_INT_EN_CLR); ++ writel(ESPI_INT_STS_OOB, espi->regs + ESPI_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_OOB_TX_SW_RST ++ | ESPI_CTRL_OOB_RX_SW_RST ++ | ESPI_CTRL_OOB_TX_DMA_EN ++ | ESPI_CTRL_OOB_RX_DMA_EN ++ | ESPI_CTRL_OOB_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CTRL_OOB_TX_SW_RST | ESPI_CTRL_OOB_RX_SW_RST); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ oob = &espi->oob; ++ ++ if (oob->dma.enable) { ++ tx_addr = oob->dma.tx_addr; ++ rx_addr = oob->dma.rx_addr; ++ ++ tx_desc = (struct ast2600_espi_oob_dma_tx_desc *)oob->dma.txd_virt; ++ rx_desc = (struct ast2600_espi_oob_dma_rx_desc *)oob->dma.rxd_virt; ++ ++ for (i = 0; i < OOB_DMA_DESC_NUM; ++i) { ++ tx_desc[i].data_addr = tx_addr; ++ tx_addr += PAGE_SIZE; ++ ++ rx_desc[i].data_addr = rx_addr; ++ rx_desc[i].dirty = 0; ++ rx_addr += PAGE_SIZE; ++ } ++ ++ writel(oob->dma.txd_addr, espi->regs + ESPI_OOB_TX_DMA); ++ writel(OOB_DMA_RPTR_KEY, espi->regs + ESPI_OOB_TX_DESC_RPTR); ++ writel(0x0, espi->regs + ESPI_OOB_TX_DESC_WPTR); ++ writel(OOB_DMA_DESC_NUM, espi->regs + ESPI_OOB_TX_DESC_NUM); ++ ++ writel(oob->dma.rxd_addr, espi->regs + ESPI_OOB_RX_DMA); ++ writel(OOB_DMA_RPTR_KEY, espi->regs + ESPI_OOB_RX_DESC_RPTR); ++ writel(0x0, espi->regs + ESPI_OOB_RX_DESC_WPTR); ++ writel(OOB_DMA_DESC_NUM, espi->regs + ESPI_OOB_RX_DESC_NUM); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ESPI_CTRL_OOB_TX_DMA_EN ++ | ESPI_CTRL_OOB_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ /* activate RX DMA to make OOB_FREE */ ++ writel(ESPI_OOB_RX_DESC_WPTR_RECV_EN, espi->regs + ESPI_OOB_RX_DESC_WPTR); ++ } ++ ++ writel(ESPI_INT_EN_OOB_RX_CMPLT, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL) | ESPI_CTRL_OOB_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++int ast2600_espi_oob_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct device *dev; ++ int rc; ++ ++ dev = espi->dev; ++ ++ oob = &espi->oob; ++ ++ init_waitqueue_head(&oob->wq); ++ ++ spin_lock_init(&oob->lock); ++ ++ mutex_init(&oob->tx_mtx); ++ mutex_init(&oob->rx_mtx); ++ ++ oob->dma.enable = of_property_read_bool(dev->of_node, "oob-dma-mode"); ++ if (oob->dma.enable) { ++ oob->dma.txd_virt = dmam_alloc_coherent(dev, ++ sizeof(struct ast2600_espi_oob_dma_tx_desc) * ++ OOB_DMA_DESC_NUM, ++ &oob->dma.txd_addr, GFP_KERNEL); ++ if (!oob->dma.txd_virt) { ++ dev_err(dev, "cannot allocate DMA TX descriptor\n"); ++ return -ENOMEM; ++ } ++ oob->dma.tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, &oob->dma.tx_addr, GFP_KERNEL); ++ if (!oob->dma.tx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ ++ oob->dma.rxd_virt = dmam_alloc_coherent(dev, ++ sizeof(struct ast2600_espi_oob_dma_rx_desc) * ++ OOB_DMA_DESC_NUM, ++ &oob->dma.rxd_addr, GFP_KERNEL); ++ if (!oob->dma.rxd_virt) { ++ dev_err(dev, "cannot allocate DMA RX descriptor\n"); ++ return -ENOMEM; ++ } ++ ++ oob->dma.rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, &oob->dma.rx_addr, GFP_KERNEL); ++ if (!oob->dma.rx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ oob->mdev.parent = dev; ++ oob->mdev.minor = MISC_DYNAMIC_MINOR; ++ oob->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-oob", DEVICE_NAME); ++ oob->mdev.fops = &ast2600_espi_oob_fops; ++ rc = misc_register(&oob->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", oob->mdev.name); ++ return rc; ++ } ++ ++ ast2600_espi_oob_reset(espi); ++ ++ return 0; ++} ++ ++int ast2600_espi_oob_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ oob = &espi->oob; ++ ++ writel(ESPI_INT_EN_OOB, espi->regs + ESPI_INT_EN_CLR); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_OOB_TX_DMA_EN ++ | ESPI_CTRL_OOB_RX_DMA_EN ++ | ESPI_CTRL_OOB_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (oob->dma.enable) { ++ dmam_free_coherent(dev, ++ sizeof(struct ast2600_espi_oob_dma_tx_desc) * ++ OOB_DMA_DESC_NUM, ++ oob->dma.txd_virt, oob->dma.txd_addr); ++ dmam_free_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, ++ oob->dma.tx_virt, oob->dma.tx_addr); ++ ++ dmam_free_coherent(dev, ++ sizeof(struct ast2600_espi_oob_dma_rx_desc) * ++ OOB_DMA_DESC_NUM, ++ oob->dma.rxd_virt, oob->dma.rxd_addr); ++ dmam_free_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, ++ oob->dma.rx_virt, oob->dma.rx_addr); ++ } ++ ++ mutex_destroy(&oob->tx_mtx); ++ mutex_destroy(&oob->rx_mtx); ++ ++ misc_deregister(&oob->mdev); ++ ++ return 0; ++} ++ ++/* flash channel (CH3) */ ++static long ast2600_espi_flash_get_rx(struct file *fp, ++ struct aspeed_espi_flash *flash, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ rc = 0; ++ ++ espi = container_of(flash, struct aspeed_espi, flash); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&flash->rx_mtx)) ++ return -EAGAIN; ++ ++ if (!flash->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&flash->rx_mtx); ++ ++ if (!flash->rx_ready) { ++ rc = wait_event_interruptible(flash->wq, flash->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_FLASH_RX_CTRL); ++ cyc = FIELD_GET(ESPI_FLASH_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_FLASH_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_FLASH_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ switch (cyc) { ++ case ESPI_FLASH_WRITE: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_flash_rwe); ++ break; ++ case ESPI_FLASH_READ: ++ case ESPI_FLASH_ERASE: ++ pkt_len = sizeof(struct espi_flash_rwe); ++ break; ++ case ESPI_FLASH_SUC_CMPLT_D_MIDDLE: ++ case ESPI_FLASH_SUC_CMPLT_D_FIRST: ++ case ESPI_FLASH_SUC_CMPLT_D_LAST: ++ case ESPI_FLASH_SUC_CMPLT_D_ONLY: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_flash_cmplt); ++ break; ++ case ESPI_FLASH_SUC_CMPLT: ++ case ESPI_FLASH_UNSUC_CMPLT: ++ pkt_len = sizeof(struct espi_flash_cmplt); ++ break; ++ default: ++ rc = -EFAULT; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ if (flash->dma.enable) { ++ memcpy(hdr + 1, flash->dma.rx_virt, pkt_len - sizeof(*hdr)); ++ } else { ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ pkt[i] = readl(espi->regs + ESPI_FLASH_RX_DATA) & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&flash->lock, flags); ++ ++ writel(ESPI_FLASH_RX_CTRL_SERV_PEND, espi->regs + ESPI_FLASH_RX_CTRL); ++ flash->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&flash->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&flash->rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2600_espi_flash_put_tx(struct file *fp, ++ struct aspeed_espi_flash *flash, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(flash, struct aspeed_espi, flash); ++ ++ if (!mutex_trylock(&flash->tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_FLASH_TX_CTRL); ++ if (reg & ESPI_FLASH_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (flash->dma.enable) { ++ memcpy(flash->dma.tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_FLASH_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_FLASH_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_FLASH_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_FLASH_TX_CTRL_LEN, len) ++ | ESPI_FLASH_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_FLASH_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&flash->tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2600_espi_flash_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_flash *flash; ++ struct aspeed_espi_ioc ioc; ++ ++ flash = container_of(fp->private_data, struct aspeed_espi_flash, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_FLASH_GET_RX: ++ return ast2600_espi_flash_get_rx(fp, flash, &ioc); ++ case ASPEED_ESPI_FLASH_PUT_TX: ++ return ast2600_espi_flash_put_tx(fp, flash, &ioc); ++ default: ++ break; ++ }; ++ ++ return -EINVAL; ++} ++ ++static const struct file_operations ast2600_espi_flash_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2600_espi_flash_ioctl, ++}; ++ ++static void ast2600_espi_flash_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ unsigned long flags; ++ u32 sts; ++ ++ flash = &espi->flash; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ ++ if (sts & ESPI_INT_STS_FLASH_RX_CMPLT) { ++ writel(ESPI_INT_STS_FLASH_RX_CMPLT, espi->regs + ESPI_INT_STS); ++ ++ spin_lock_irqsave(&flash->lock, flags); ++ flash->rx_ready = true; ++ spin_unlock_irqrestore(&flash->lock, flags); ++ ++ wake_up_interruptible(&flash->wq); ++ } ++} ++ ++static void ast2600_espi_flash_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ u32 reg; ++ ++ flash = &espi->flash; ++ ++ writel(ESPI_INT_EN_FLASH, espi->regs + ESPI_INT_EN_CLR); ++ writel(ESPI_INT_STS_FLASH, espi->regs + ESPI_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_FLASH_TX_SW_RST ++ | ESPI_CTRL_FLASH_RX_SW_RST ++ | ESPI_CTRL_FLASH_TX_DMA_EN ++ | ESPI_CTRL_FLASH_RX_DMA_EN ++ | ESPI_CTRL_FLASH_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CTRL_FLASH_TX_SW_RST | ESPI_CTRL_FLASH_RX_SW_RST); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ reg = readl(espi->regs + ESPI_CTRL) & ~ESPI_CTRL_FLASH_EDAF_MODE; ++ reg |= FIELD_PREP(ESPI_CTRL_FLASH_EDAF_MODE, flash->edaf.mode); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (flash->edaf.mode == EDAF_MODE_MIX) { ++ reg = FIELD_PREP(ESPI_FLASH_EDAF_TADDR_BASE, flash->edaf.taddr >> 24) ++ | FIELD_PREP(ESPI_FLASH_EDAF_TADDR_MASK, (~(flash->edaf.size - 1)) >> 24); ++ writel(reg, espi->regs + ESPI_FLASH_EDAF_TADDR); ++ } ++ ++ if (flash->dma.enable) { ++ writel(flash->dma.tx_addr, espi->regs + ESPI_FLASH_TX_DMA); ++ writel(flash->dma.rx_addr, espi->regs + ESPI_FLASH_RX_DMA); ++ ++ reg = readl(espi->regs + ESPI_CTRL) ++ | ESPI_CTRL_FLASH_TX_DMA_EN ++ | ESPI_CTRL_FLASH_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CTRL); ++ } ++ ++ writel(ESPI_INT_EN_FLASH_RX_CMPLT, espi->regs + ESPI_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CTRL) | ESPI_CTRL_FLASH_SW_RDY; ++ writel(reg, espi->regs + ESPI_CTRL); ++} ++ ++int ast2600_espi_flash_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ struct device *dev; ++ int rc; ++ ++ dev = espi->dev; ++ ++ flash = &espi->flash; ++ ++ init_waitqueue_head(&flash->wq); ++ ++ spin_lock_init(&flash->lock); ++ ++ mutex_init(&flash->tx_mtx); ++ mutex_init(&flash->rx_mtx); ++ ++ flash->edaf.mode = EDAF_MODE_HW; ++ ++ of_property_read_u32(dev->of_node, "flash-safs-mode", &flash->edaf.mode); ++ if (flash->edaf.mode == EDAF_MODE_MIX) { ++ rc = of_property_read_u32(dev->of_node, "flash-safs-tgt-addr", (u32 *)&flash->edaf.taddr); ++ if (rc || !IS_ALIGNED(flash->edaf.taddr, FLASH_EDAF_ALIGN)) { ++ dev_err(dev, "cannot get 16MB-aligned SAFS target address\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "flash-safs-size", (u32 *)&flash->edaf.size); ++ if (rc || !IS_ALIGNED(flash->edaf.size, FLASH_EDAF_ALIGN)) { ++ dev_err(dev, "cannot get 16MB-aligned SAFS size\n"); ++ return -ENODEV; ++ } ++ } ++ ++ flash->dma.enable = of_property_read_bool(dev->of_node, "flash-dma-mode"); ++ if (flash->dma.enable) { ++ flash->dma.tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &flash->dma.tx_addr, GFP_KERNEL); ++ if (!flash->dma.tx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ ++ flash->dma.rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &flash->dma.rx_addr, GFP_KERNEL); ++ if (!flash->dma.rx_virt) { ++ dev_err(dev, "cannot allocate DMA RX buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ flash->mdev.parent = dev; ++ flash->mdev.minor = MISC_DYNAMIC_MINOR; ++ flash->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-flash", DEVICE_NAME); ++ flash->mdev.fops = &ast2600_espi_flash_fops; ++ rc = misc_register(&flash->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", flash->mdev.name); ++ return rc; ++ } ++ ++ ast2600_espi_flash_reset(espi); ++ ++ return 0; ++} ++ ++int ast2600_espi_flash_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ flash = &espi->flash; ++ ++ writel(ESPI_INT_EN_FLASH, espi->regs + ESPI_INT_EN_CLR); ++ ++ reg = readl(espi->regs + ESPI_CTRL); ++ reg &= ~(ESPI_CTRL_FLASH_TX_DMA_EN ++ | ESPI_CTRL_FLASH_RX_DMA_EN ++ | ESPI_CTRL_FLASH_SW_RDY); ++ writel(reg, espi->regs + ESPI_CTRL); ++ ++ if (flash->dma.enable) { ++ dmam_free_coherent(dev, PAGE_SIZE, flash->dma.tx_virt, flash->dma.tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, flash->dma.rx_virt, flash->dma.rx_addr); ++ } ++ ++ mutex_destroy(&flash->tx_mtx); ++ mutex_destroy(&flash->rx_mtx); ++ ++ misc_deregister(&flash->mdev); ++ ++ return 0; ++} ++ ++/* global control */ ++irqreturn_t ast2600_espi_isr(int irq, void *arg) ++{ ++ struct aspeed_espi *espi; ++ u32 sts; ++ ++ espi = (struct aspeed_espi *)arg; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ if (!sts) ++ return IRQ_NONE; ++ ++ if (sts & ESPI_INT_STS_PERIF) ++ ast2600_espi_perif_isr(espi); ++ ++ if (sts & ESPI_INT_STS_VW) ++ ast2600_espi_vw_isr(espi); ++ ++ if (sts & ESPI_INT_STS_OOB) ++ ast2600_espi_oob_isr(espi); ++ ++ if (sts & ESPI_INT_STS_FLASH) ++ ast2600_espi_flash_isr(espi); ++ ++ if (sts & ESPI_INT_STS_RST_DEASSERT) { ++ /* this will clear all interrupt enable and status */ ++ reset_control_assert(espi->rst); ++ reset_control_deassert(espi->rst); ++ ++ ast2600_espi_perif_sw_reset(espi); ++ ast2600_espi_perif_reset(espi); ++ ast2600_espi_vw_reset(espi); ++ ast2600_espi_oob_reset(espi); ++ ast2600_espi_flash_reset(espi); ++ ++ /* re-enable eSPI_RESET# interrupt */ ++ writel(ESPI_INT_EN_RST_DEASSERT, espi->regs + ESPI_INT_EN); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++void ast2600_espi_pre_init(struct aspeed_espi *espi) ++{ ++ writel(ESPI_INT_EN_RST_DEASSERT, espi->regs + ESPI_INT_EN_CLR); ++} ++ ++void ast2600_espi_post_init(struct aspeed_espi *espi) ++{ ++ writel(ESPI_INT_EN_RST_DEASSERT, espi->regs + ESPI_INT_EN); ++} ++ ++void ast2600_espi_deinit(struct aspeed_espi *espi) ++{ ++ writel(ESPI_INT_EN_RST_DEASSERT, espi->regs + ESPI_INT_EN_CLR); ++} +diff --git a/drivers/soc/aspeed/espi/ast2600-espi.h b/drivers/soc/aspeed/espi/ast2600-espi.h +--- a/drivers/soc/aspeed/espi/ast2600-espi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2600-espi.h 2026-09-09 15:51:44.720238041 +0000 +@@ -0,0 +1,306 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Register definitions for Aspeed AST2600 eSPI controller ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#ifndef _AST2600_ESPI_H_ ++#define _AST2600_ESPI_H_ ++ ++#include ++#include ++ ++#include "aspeed-espi.h" ++ ++/* registers */ ++#define ESPI_CTRL 0x000 ++#define ESPI_CTRL_FLASH_TX_SW_RST BIT(31) ++#define ESPI_CTRL_FLASH_RX_SW_RST BIT(30) ++#define ESPI_CTRL_OOB_TX_SW_RST BIT(29) ++#define ESPI_CTRL_OOB_RX_SW_RST BIT(28) ++#define ESPI_CTRL_PERIF_NP_TX_SW_RST BIT(27) ++#define ESPI_CTRL_PERIF_NP_RX_SW_RST BIT(26) ++#define ESPI_CTRL_PERIF_PC_TX_SW_RST BIT(25) ++#define ESPI_CTRL_PERIF_PC_RX_SW_RST BIT(24) ++#define ESPI_CTRL_FLASH_TX_DMA_EN BIT(23) ++#define ESPI_CTRL_FLASH_RX_DMA_EN BIT(22) ++#define ESPI_CTRL_OOB_TX_DMA_EN BIT(21) ++#define ESPI_CTRL_OOB_RX_DMA_EN BIT(20) ++#define ESPI_CTRL_PERIF_NP_TX_DMA_EN BIT(19) ++#define ESPI_CTRL_PERIF_PC_TX_DMA_EN BIT(17) ++#define ESPI_CTRL_PERIF_PC_RX_DMA_EN BIT(16) ++#define ESPI_CTRL_FLASH_EDAF_MODE GENMASK(11, 10) ++#define ESPI_CTRL_VW_GPIO_SW BIT(9) ++#define ESPI_CTRL_FLASH_SW_RDY BIT(7) ++#define ESPI_CTRL_OOB_SW_RDY BIT(4) ++#define ESPI_CTRL_VW_SW_RDY BIT(3) ++#define ESPI_CTRL_PERIF_SW_RDY BIT(1) ++#define ESPI_STS 0x004 ++#define ESPI_INT_STS 0x008 ++#define ESPI_INT_STS_RST_DEASSERT BIT(31) ++#define ESPI_INT_STS_OOB_RX_TMOUT BIT(23) ++#define ESPI_INT_STS_VW_SYSEVT1 BIT(22) ++#define ESPI_INT_STS_FLASH_TX_ERR BIT(21) ++#define ESPI_INT_STS_OOB_TX_ERR BIT(20) ++#define ESPI_INT_STS_FLASH_TX_ABT BIT(19) ++#define ESPI_INT_STS_OOB_TX_ABT BIT(18) ++#define ESPI_INT_STS_PERIF_NP_TX_ABT BIT(17) ++#define ESPI_INT_STS_PERIF_PC_TX_ABT BIT(16) ++#define ESPI_INT_STS_FLASH_RX_ABT BIT(15) ++#define ESPI_INT_STS_OOB_RX_ABT BIT(14) ++#define ESPI_INT_STS_PERIF_NP_RX_ABT BIT(13) ++#define ESPI_INT_STS_PERIF_PC_RX_ABT BIT(12) ++#define ESPI_INT_STS_PERIF_NP_TX_ERR BIT(11) ++#define ESPI_INT_STS_PERIF_PC_TX_ERR BIT(10) ++#define ESPI_INT_STS_VW_GPIO BIT(9) ++#define ESPI_INT_STS_VW_SYSEVT BIT(8) ++#define ESPI_INT_STS_FLASH_TX_CMPLT BIT(7) ++#define ESPI_INT_STS_FLASH_RX_CMPLT BIT(6) ++#define ESPI_INT_STS_OOB_TX_CMPLT BIT(5) ++#define ESPI_INT_STS_OOB_RX_CMPLT BIT(4) ++#define ESPI_INT_STS_PERIF_NP_TX_CMPLT BIT(3) ++#define ESPI_INT_STS_PERIF_PC_TX_CMPLT BIT(1) ++#define ESPI_INT_STS_PERIF_PC_RX_CMPLT BIT(0) ++#define ESPI_INT_EN 0x00c ++#define ESPI_INT_EN_RST_DEASSERT BIT(31) ++#define ESPI_INT_EN_OOB_RX_TMOUT BIT(23) ++#define ESPI_INT_EN_VW_SYSEVT1 BIT(22) ++#define ESPI_INT_EN_FLASH_TX_ERR BIT(21) ++#define ESPI_INT_EN_OOB_TX_ERR BIT(20) ++#define ESPI_INT_EN_FLASH_TX_ABT BIT(19) ++#define ESPI_INT_EN_OOB_TX_ABT BIT(18) ++#define ESPI_INT_EN_PERIF_NP_TX_ABT BIT(17) ++#define ESPI_INT_EN_PERIF_PC_TX_ABT BIT(16) ++#define ESPI_INT_EN_FLASH_RX_ABT BIT(15) ++#define ESPI_INT_EN_OOB_RX_ABT BIT(14) ++#define ESPI_INT_EN_PERIF_NP_RX_ABT BIT(13) ++#define ESPI_INT_EN_PERIF_PC_RX_ABT BIT(12) ++#define ESPI_INT_EN_PERIF_NP_TX_ERR BIT(11) ++#define ESPI_INT_EN_PERIF_PC_TX_ERR BIT(10) ++#define ESPI_INT_EN_VW_GPIO BIT(9) ++#define ESPI_INT_EN_VW_SYSEVT BIT(8) ++#define ESPI_INT_EN_FLASH_TX_CMPLT BIT(7) ++#define ESPI_INT_EN_FLASH_RX_CMPLT BIT(6) ++#define ESPI_INT_EN_OOB_TX_CMPLT BIT(5) ++#define ESPI_INT_EN_OOB_RX_CMPLT BIT(4) ++#define ESPI_INT_EN_PERIF_NP_TX_CMPLT BIT(3) ++#define ESPI_INT_EN_PERIF_PC_TX_CMPLT BIT(1) ++#define ESPI_INT_EN_PERIF_PC_RX_CMPLT BIT(0) ++#define ESPI_PERIF_PC_RX_DMA 0x010 ++#define ESPI_PERIF_PC_RX_CTRL 0x014 ++#define ESPI_PERIF_PC_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_PERIF_PC_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_PERIF_PC_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_PERIF_PC_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_PERIF_PC_RX_DATA 0x018 ++#define ESPI_PERIF_PC_TX_DMA 0x020 ++#define ESPI_PERIF_PC_TX_CTRL 0x024 ++#define ESPI_PERIF_PC_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_PERIF_PC_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_PERIF_PC_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_PERIF_PC_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_PERIF_PC_TX_DATA 0x028 ++#define ESPI_PERIF_NP_TX_DMA 0x030 ++#define ESPI_PERIF_NP_TX_CTRL 0x034 ++#define ESPI_PERIF_NP_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_PERIF_NP_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_PERIF_NP_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_PERIF_NP_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_PERIF_NP_TX_DATA 0x038 ++#define ESPI_OOB_RX_DMA 0x040 ++#define ESPI_OOB_RX_CTRL 0x044 ++#define ESPI_OOB_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_OOB_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_OOB_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_OOB_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_OOB_RX_DATA 0x048 ++#define ESPI_OOB_TX_DMA 0x050 ++#define ESPI_OOB_TX_CTRL 0x054 ++#define ESPI_OOB_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_OOB_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_OOB_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_OOB_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_OOB_TX_DATA 0x058 ++#define ESPI_FLASH_RX_DMA 0x060 ++#define ESPI_FLASH_RX_CTRL 0x064 ++#define ESPI_FLASH_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_FLASH_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_FLASH_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_FLASH_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_FLASH_RX_DATA 0x068 ++#define ESPI_FLASH_TX_DMA 0x070 ++#define ESPI_FLASH_TX_CTRL 0x074 ++#define ESPI_FLASH_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_FLASH_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_FLASH_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_FLASH_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_FLASH_TX_DATA 0x078 ++#define ESPI_CTRL2 0x080 ++#define ESPI_CTRL2_VW_TX_SORT BIT(30) ++#define ESPI_CTRL2_MCYC_RD_DIS_WDT BIT(11) ++#define ESPI_CTRL2_MCYC_WR_DIS_WDT BIT(10) ++#define ESPI_CTRL2_MCYC_RD_DIS BIT(6) ++#define ESPI_CTRL2_MMBI_RD_DIS ESPI_CTRL2_MCYC_RD_DIS ++#define ESPI_CTRL2_MCYC_WR_DIS BIT(4) ++#define ESPI_CTRL2_MMBI_WR_DIS ESPI_CTRL2_MCYC_WR_DIS ++#define ESPI_PERIF_MCYC_SADDR 0x084 ++#define ESPI_PERIF_MMBI_SADDR ESPI_PERIF_MCYC_SADDR ++#define ESPI_PERIF_MCYC_TADDR 0x088 ++#define ESPI_PERIF_MMBI_TADDR ESPI_PERIF_MCYC_TADDR ++#define ESPI_PERIF_MCYC_MASK 0x08c ++#define ESPI_PERIF_MMBI_MASK ESPI_PERIF_MCYC_MASK ++#define ESPI_FLASH_EDAF_TADDR 0x090 ++#define ESPI_FLASH_EDAF_TADDR_BASE GENMASK(31, 24) ++#define ESPI_FLASH_EDAF_TADDR_MASK GENMASK(15, 8) ++#define ESPI_VW_SYSEVT_INT_EN 0x094 ++#define ESPI_VW_SYSEVT 0x098 ++#define ESPI_VW_SYSEVT_HOST_RST_ACK BIT(27) ++#define ESPI_VW_SYSEVT_RST_CPU_INIT BIT(26) ++#define ESPI_VW_SYSEVT_SLV_BOOT_STS BIT(23) ++#define ESPI_VW_SYSEVT_NON_FATAL_ERR BIT(22) ++#define ESPI_VW_SYSEVT_FATAL_ERR BIT(21) ++#define ESPI_VW_SYSEVT_SLV_BOOT_DONE BIT(20) ++#define ESPI_VW_SYSEVT_OOB_RST_ACK BIT(16) ++#define ESPI_VW_SYSEVT_NMI_OUT BIT(10) ++#define ESPI_VW_SYSEVT_SMI_OUT BIT(9) ++#define ESPI_VW_SYSEVT_HOST_RST_WARN BIT(8) ++#define ESPI_VW_SYSEVT_OOB_RST_WARN BIT(6) ++#define ESPI_VW_SYSEVT_PLTRSTN BIT(5) ++#define ESPI_VW_SYSEVT_SUSPEND BIT(4) ++#define ESPI_VW_SYSEVT_S5_SLEEP BIT(2) ++#define ESPI_VW_SYSEVT_S4_SLEEP BIT(1) ++#define ESPI_VW_SYSEVT_S3_SLEEP BIT(0) ++#define ESPI_VW_GPIO_VAL 0x09c ++#define ESPI_GEN_CAP_N_CONF 0x0a0 ++#define ESPI_CH0_CAP_N_CONF 0x0a4 ++#define ESPI_CH1_CAP_N_CONF 0x0a8 ++#define ESPI_CH2_CAP_N_CONF 0x0ac ++#define ESPI_CH3_CAP_N_CONF 0x0b0 ++#define ESPI_CH3_CAP_N_CONF2 0x0b4 ++#define ESPI_VW_GPIO_DIR 0x0c0 ++#define ESPI_VW_GPIO_GRP 0x0c4 ++#define ESPI_INT_EN_CLR 0x0fc ++#define ESPI_VW_SYSEVT1_INT_EN 0x100 ++#define ESPI_VW_SYSEVT1 0x104 ++#define ESPI_VW_SYSEVT1_SUSPEND_ACK BIT(20) ++#define ESPI_VW_SYSEVT1_SUSPEND_WARN BIT(0) ++#define ESPI_VW_SYSEVT_INT_T0 0x110 ++#define ESPI_VW_SYSEVT_INT_T1 0x114 ++#define ESPI_VW_SYSEVT_INT_T2 0x118 ++#define ESPI_VW_SYSEVT_INT_STS 0x11c ++#define ESPI_VW_SYSEVT1_INT_T0 0x120 ++#define ESPI_VW_SYSEVT1_INT_T1 0x124 ++#define ESPI_VW_SYSEVT1_INT_T2 0x128 ++#define ESPI_VW_SYSEVT1_INT_STS 0x12c ++#define ESPI_OOB_RX_DESC_NUM 0x130 ++#define ESPI_OOB_RX_DESC_RPTR 0x134 ++#define ESPI_OOB_RX_DESC_RPTR_UPDATE BIT(31) ++#define ESPI_OOB_RX_DESC_RPTR_RP GENMASK(11, 0) ++#define ESPI_OOB_RX_DESC_WPTR 0x138 ++#define ESPI_OOB_RX_DESC_WPTR_RECV_EN BIT(31) ++#define ESPI_OOB_RX_DESC_WPTR_SP GENMASK(27, 16) ++#define ESPI_OOB_RX_DESC_WPTR_WP GENMASK(11, 0) ++#define ESPI_OOB_TX_DESC_NUM 0x140 ++#define ESPI_OOB_TX_DESC_RPTR 0x144 ++#define ESPI_OOB_TX_DESC_RPTR_UPDATE BIT(31) ++#define ESPI_OOB_TX_DESC_WPTR 0x148 ++#define ESPI_OOB_TX_DESC_WPTR_SEND_EN BIT(31) ++#define ESPI_MMBI_CTRL 0x800 ++#define ESPI_MMBI_CTRL_INST_SZ GENMASK(10, 8) ++#define ESPI_MMBI_CTRL_TOTAL_SZ GENMASK(6, 4) ++#define ESPI_MMBI_CTRL_EN BIT(0) ++#define ESPI_MMBI_INT_STS 0x808 ++#define ESPI_MMBI_INT_EN 0x80c ++#define ESPI_MMBI_HOST_RWP(x) (0x810 + ((x) << 3)) ++ ++/* collect ESPI_INT_EN bits for convenience */ ++#define ESPI_INT_EN_PERIF \ ++ (ESPI_INT_EN_PERIF_NP_TX_ABT | \ ++ ESPI_INT_EN_PERIF_PC_TX_ABT | \ ++ ESPI_INT_EN_PERIF_NP_RX_ABT | \ ++ ESPI_INT_EN_PERIF_PC_RX_ABT | \ ++ ESPI_INT_EN_PERIF_NP_TX_ERR | \ ++ ESPI_INT_EN_PERIF_PC_TX_ERR | \ ++ ESPI_INT_EN_PERIF_NP_TX_CMPLT | \ ++ ESPI_INT_EN_PERIF_PC_TX_CMPLT | \ ++ ESPI_INT_EN_PERIF_PC_RX_CMPLT) ++ ++#define ESPI_INT_EN_VW \ ++ (ESPI_INT_EN_VW_SYSEVT1 | \ ++ ESPI_INT_EN_VW_GPIO | \ ++ ESPI_INT_EN_VW_SYSEVT) ++ ++#define ESPI_INT_EN_OOB \ ++ (ESPI_INT_EN_OOB_RX_TMOUT | \ ++ ESPI_INT_EN_OOB_TX_ERR | \ ++ ESPI_INT_EN_OOB_TX_ABT | \ ++ ESPI_INT_EN_OOB_RX_ABT | \ ++ ESPI_INT_EN_OOB_TX_CMPLT | \ ++ ESPI_INT_EN_OOB_RX_CMPLT) ++ ++#define ESPI_INT_EN_FLASH \ ++ (ESPI_INT_EN_FLASH_TX_ERR | \ ++ ESPI_INT_EN_FLASH_TX_ABT | \ ++ ESPI_INT_EN_FLASH_RX_ABT | \ ++ ESPI_INT_EN_FLASH_TX_CMPLT | \ ++ ESPI_INT_EN_FLASH_RX_CMPLT) ++ ++/* collect ESPI_INT_STS bits for convenience */ ++#define ESPI_INT_STS_PERIF \ ++ (ESPI_INT_STS_PERIF_NP_TX_ABT | \ ++ ESPI_INT_STS_PERIF_PC_TX_ABT | \ ++ ESPI_INT_STS_PERIF_NP_RX_ABT | \ ++ ESPI_INT_STS_PERIF_PC_RX_ABT | \ ++ ESPI_INT_STS_PERIF_NP_TX_ERR | \ ++ ESPI_INT_STS_PERIF_PC_TX_ERR | \ ++ ESPI_INT_STS_PERIF_NP_TX_CMPLT | \ ++ ESPI_INT_STS_PERIF_PC_TX_CMPLT | \ ++ ESPI_INT_STS_PERIF_PC_RX_CMPLT) ++ ++#define ESPI_INT_STS_VW \ ++ (ESPI_INT_STS_VW_SYSEVT1 | \ ++ ESPI_INT_STS_VW_GPIO | \ ++ ESPI_INT_STS_VW_SYSEVT) ++ ++#define ESPI_INT_STS_OOB \ ++ (ESPI_INT_STS_OOB_RX_TMOUT | \ ++ ESPI_INT_STS_OOB_TX_ERR | \ ++ ESPI_INT_STS_OOB_TX_ABT | \ ++ ESPI_INT_STS_OOB_RX_ABT | \ ++ ESPI_INT_STS_OOB_TX_CMPLT | \ ++ ESPI_INT_STS_OOB_RX_CMPLT) ++ ++#define ESPI_INT_STS_FLASH \ ++ (ESPI_INT_STS_FLASH_TX_ERR | \ ++ ESPI_INT_STS_FLASH_TX_ABT | \ ++ ESPI_INT_STS_FLASH_RX_ABT | \ ++ ESPI_INT_STS_FLASH_TX_CMPLT | \ ++ ESPI_INT_STS_FLASH_RX_CMPLT) ++ ++ ++/* consistent with DTS property "perif-mmbi-instance-size" */ ++enum ast2600_mmbi_instance_size { ++ MMBI_INST_SIZE_8KB = 0x0, ++ MMBI_INST_SIZE_16KB, ++ MMBI_INST_SIZE_32KB, ++ MMBI_INST_SIZE_64KB, ++ MMBI_INST_SIZE_128KB, ++ MMBI_INST_SIZE_256KB, ++ MMBI_INST_SIZE_512KB, ++ MMBI_INST_SIZE_1024KB, ++ MMBI_INST_SIZE_TYPES, ++}; ++ ++/* function operators */ ++void ast2600_espi_pre_init(struct aspeed_espi *espi); ++void ast2600_espi_post_init(struct aspeed_espi *espi); ++void ast2600_espi_deinit(struct aspeed_espi *espi); ++int ast2600_espi_perif_probe(struct aspeed_espi *espi); ++int ast2600_espi_perif_remove(struct aspeed_espi *espi); ++int ast2600_espi_vw_probe(struct aspeed_espi *espi); ++int ast2600_espi_vw_remove(struct aspeed_espi *espi); ++int ast2600_espi_oob_probe(struct aspeed_espi *espi); ++int ast2600_espi_oob_remove(struct aspeed_espi *espi); ++int ast2600_espi_flash_probe(struct aspeed_espi *espi); ++int ast2600_espi_flash_remove(struct aspeed_espi *espi); ++irqreturn_t ast2600_espi_isr(int irq, void *arg); ++#endif +diff --git a/drivers/soc/aspeed/espi/ast2700-espi.c b/drivers/soc/aspeed/espi/ast2700-espi.c +--- a/drivers/soc/aspeed/espi/ast2700-espi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2700-espi.c 2026-09-09 15:51:44.720238041 +0000 +@@ -0,0 +1,1960 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#include "linux/err.h" ++#include "linux/fs.h" ++#include "linux/printk.h" ++#include "linux/spinlock.h" ++#include "linux/stddef.h" ++#include "linux/wait.h" ++#include "linux/kernel.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ast2700-espi.h" ++#include "aspeed-espi-comm.h" ++#include "aspeed-espi.h" ++ ++static DEFINE_IDA(ast2700_espi_ida); ++ ++#define PERIF_MCYC_ALIGN SZ_64K ++ ++#define OOB_DMA_RPTR_KEY 0x4f4f4253 ++#define OOB_DMA_DESC_NUM 8 ++#define OOB_DMA_DESC_CUSTOM 0x4 ++ ++#define FLASH_EDAF_ALIGN SZ_16M ++ ++/* peripheral channel (CH0) */ ++static long ast2700_espi_perif_pc_get_rx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&perif->pc_rx_mtx)) ++ return -EAGAIN; ++ ++ if (!perif->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&perif->pc_rx_mtx); ++ ++ if (!perif->rx_ready) { ++ rc = wait_event_interruptible(perif->wq, perif->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_CH0_PC_RX_CTRL); ++ cyc = FIELD_GET(ESPI_CH0_PC_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_CH0_PC_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_CH0_PC_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ switch (cyc) { ++ case ESPI_PERIF_MSG: ++ pkt_len = sizeof(struct espi_perif_msg); ++ break; ++ case ESPI_PERIF_MSG_D: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_perif_msg); ++ break; ++ case ESPI_PERIF_SUC_CMPLT_D_MIDDLE: ++ case ESPI_PERIF_SUC_CMPLT_D_FIRST: ++ case ESPI_PERIF_SUC_CMPLT_D_LAST: ++ case ESPI_PERIF_SUC_CMPLT_D_ONLY: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_perif_cmplt); ++ break; ++ case ESPI_PERIF_SUC_CMPLT: ++ case ESPI_PERIF_UNSUC_CMPLT: ++ pkt_len = sizeof(struct espi_perif_cmplt); ++ break; ++ default: ++ rc = -EFAULT; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ if (perif->dma.enable) { ++ memcpy(hdr + 1, perif->dma.pc_rx_virt, pkt_len - sizeof(*hdr)); ++ } else { ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ reg = readl(espi->regs + ESPI_CH0_PC_RX_DATA) & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&perif->lock, flags); ++ ++ writel(ESPI_CH0_PC_RX_CTRL_SERV_PEND, espi->regs + ESPI_CH0_PC_RX_CTRL); ++ perif->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&perif->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&perif->pc_rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2700_espi_perif_pc_put_tx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (!mutex_trylock(&perif->pc_tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_CH0_PC_TX_CTRL); ++ if (reg & ESPI_CH0_PC_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (perif->dma.enable) { ++ memcpy(perif->dma.pc_tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_CH0_PC_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_CH0_PC_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_CH0_PC_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_CH0_PC_TX_CTRL_LEN, len) ++ | ESPI_CH0_PC_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_CH0_PC_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_n_out: ++ mutex_unlock(&perif->pc_tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2700_espi_perif_np_put_tx(struct file *fp, ++ struct aspeed_espi_perif *perif, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(perif, struct aspeed_espi, perif); ++ ++ if (!mutex_trylock(&perif->np_tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_CH0_NP_TX_CTRL); ++ if (reg & ESPI_CH0_NP_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (perif->dma.enable) { ++ memcpy(perif->dma.np_tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_CH0_NP_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_CH0_NP_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_CH0_NP_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_CH0_NP_TX_CTRL_LEN, len) ++ | ESPI_CH0_NP_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_CH0_NP_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_n_out: ++ mutex_unlock(&perif->np_tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2700_espi_perif_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_perif *perif; ++ struct aspeed_espi_ioc ioc; ++ ++ perif = container_of(fp->private_data, struct aspeed_espi_perif, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_PERIF_PC_GET_RX: ++ return ast2700_espi_perif_pc_get_rx(fp, perif, &ioc); ++ case ASPEED_ESPI_PERIF_PC_PUT_TX: ++ return ast2700_espi_perif_pc_put_tx(fp, perif, &ioc); ++ case ASPEED_ESPI_PERIF_NP_PUT_TX: ++ return ast2700_espi_perif_np_put_tx(fp, perif, &ioc); ++ default: ++ break; ++ }; ++ ++ return -EINVAL; ++} ++ ++static int ast2700_espi_perif_mmap(struct file *fp, struct vm_area_struct *vma) ++{ ++ struct aspeed_espi_perif *perif; ++ unsigned long vm_size; ++ pgprot_t vm_prot; ++ ++ perif = container_of(fp->private_data, struct aspeed_espi_perif, mdev); ++ if (!perif->mcyc.enable) ++ return -EPERM; ++ ++ vm_size = vma->vm_end - vma->vm_start; ++ vm_prot = vma->vm_page_prot; ++ ++ if (((vma->vm_pgoff << PAGE_SHIFT) + vm_size) > perif->mcyc.size) ++ return -EINVAL; ++ ++ vm_prot = pgprot_noncached(vm_prot); ++ ++ if (remap_pfn_range(vma, vma->vm_start, ++ (perif->mcyc.taddr >> PAGE_SHIFT) + vma->vm_pgoff, ++ vm_size, vm_prot)) ++ return -EAGAIN; ++ ++ return 0; ++} ++ ++static const struct file_operations ast2700_espi_perif_fops = { ++ .owner = THIS_MODULE, ++ .mmap = ast2700_espi_perif_mmap, ++ .unlocked_ioctl = ast2700_espi_perif_ioctl, ++}; ++ ++static void ast2700_espi_perif_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ unsigned long flags; ++ u32 sts; ++ ++ perif = &espi->perif; ++ ++ sts = readl(espi->regs + ESPI_CH0_INT_STS); ++ ++ if (sts & ESPI_CH0_INT_STS_PC_RX_CMPLT) { ++ writel(ESPI_CH0_INT_STS_PC_RX_CMPLT, espi->regs + ESPI_CH0_INT_STS); ++ ++ spin_lock_irqsave(&perif->lock, flags); ++ perif->rx_ready = true; ++ spin_unlock_irqrestore(&perif->lock, flags); ++ ++ wake_up_interruptible(&perif->wq); ++ } ++} ++ ++static void ast2700_espi_perif_sw_reset(struct aspeed_espi *espi) ++{ ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ reg = readl(espi->regs + ESPI_CH0_CTRL); ++ reg &= ~(ESPI_CH0_CTRL_NP_TX_RST ++ | ESPI_CH0_CTRL_NP_RX_RST ++ | ESPI_CH0_CTRL_PC_TX_RST ++ | ESPI_CH0_CTRL_PC_RX_RST ++ | ESPI_CH0_CTRL_NP_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_RX_DMA_EN ++ | ESPI_CH0_CTRL_SW_RDY); ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CH0_CTRL_NP_TX_RST ++ | ESPI_CH0_CTRL_NP_RX_RST ++ | ESPI_CH0_CTRL_PC_TX_RST ++ | ESPI_CH0_CTRL_PC_RX_RST); ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++} ++ ++static void ast2700_espi_perif_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ struct device *dev; ++ u64 mask; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ writel(0x0, espi->regs + ESPI_CH0_INT_EN); ++ writel(0xffffffff, espi->regs + ESPI_CH0_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CH0_CTRL); ++ reg &= ~(ESPI_CH0_CTRL_MCYC_RD_DIS_WDT | ESPI_CH0_CTRL_MCYC_WR_DIS_WDT); ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++ ++ reg = readl(espi->regs + ESPI_CH0_MCYC1_MASKL); ++ reg &= ~ESPI_CH0_MCYC1_MASKL_EN; ++ writel(reg, espi->regs + ESPI_CH0_MCYC1_MASKL); ++ ++ reg = readl(espi->regs + ESPI_CH0_CTRL); ++ reg |= (ESPI_CH0_CTRL_MCYC_RD_DIS | ESPI_CH0_CTRL_MCYC_WR_DIS); ++ reg &= ~(ESPI_CH0_CTRL_NP_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_RX_DMA_EN ++ | ESPI_CH0_CTRL_SW_RDY); ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++ ++ if (perif->mcyc.enable) { ++ mask = ~(perif->mcyc.size - 1); ++ writel(upper_32_bits(mask), espi->regs + ESPI_CH0_MCYC1_MASKH); ++ writel(mask & 0xffffffff, espi->regs + ESPI_CH0_MCYC1_MASKL); ++ writel(upper_32_bits(perif->mcyc.saddr), espi->regs + ESPI_CH0_MCYC1_SADDRH); ++ writel((perif->mcyc.saddr & 0xffffffff), espi->regs + ESPI_CH0_MCYC1_SADDRL); ++ writel(upper_32_bits(perif->mcyc.taddr), espi->regs + ESPI_CH0_MCYC1_TADDRH); ++ writel((perif->mcyc.taddr & 0xffffffff), espi->regs + ESPI_CH0_MCYC1_TADDRL); ++ ++ reg = readl(espi->regs + ESPI_CH0_MCYC1_MASKL) | ESPI_CH0_MCYC1_MASKL_EN; ++ writel(reg, espi->regs + ESPI_CH0_MCYC1_MASKL); ++ ++ reg = readl(espi->regs + ESPI_CH0_CTRL); ++ reg &= ~(ESPI_CH0_CTRL_MCYC_RD_DIS | ESPI_CH0_CTRL_MCYC_WR_DIS); ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++ } ++ ++ if (perif->dma.enable) { ++ writel(upper_32_bits(perif->dma.np_tx_addr), espi->regs + ESPI_CH0_NP_TX_DMAH); ++ writel((perif->dma.np_tx_addr & 0xffffffff), espi->regs + ESPI_CH0_NP_TX_DMAL); ++ writel(upper_32_bits(perif->dma.pc_tx_addr), espi->regs + ESPI_CH0_PC_TX_DMAH); ++ writel((perif->dma.pc_tx_addr & 0xffffffff), espi->regs + ESPI_CH0_PC_TX_DMAL); ++ writel(upper_32_bits(perif->dma.pc_rx_addr), espi->regs + ESPI_CH0_PC_RX_DMAH); ++ writel((perif->dma.pc_rx_addr & 0xffffffff), espi->regs + ESPI_CH0_PC_RX_DMAL); ++ ++ reg = readl(espi->regs + ESPI_CH0_CTRL) ++ | ESPI_CH0_CTRL_NP_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++ } ++ if (perif->rtc_enable) { ++ reg = readl(espi->regs + ESPI_CAP_GEN) ++ | ESPI_CAP_GEN_RTC_SUP; ++ writel(reg, espi->regs + ESPI_CAP_GEN); ++ } ++ ++ writel(ESPI_CH0_INT_EN_PC_RX_CMPLT, espi->regs + ESPI_CH0_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CH0_CTRL) | ESPI_CH0_CTRL_SW_RDY; ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++} ++ ++int ast2700_espi_perif_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ struct device_node *np; ++ struct device *dev; ++ u64 temp; ++ int rc; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ init_waitqueue_head(&perif->wq); ++ ++ spin_lock_init(&perif->lock); ++ ++ mutex_init(&perif->np_tx_mtx); ++ mutex_init(&perif->pc_tx_mtx); ++ mutex_init(&perif->pc_rx_mtx); ++ ++ perif->mcyc.enable = of_property_read_bool(dev->of_node, "perif-mcyc-enable"); ++ if (perif->mcyc.enable) { ++ rc = of_property_read_u64(dev->of_node, "perif-mcyc-src-addr", &temp); ++ if (rc || !IS_ALIGNED(temp, PERIF_MCYC_ALIGN)) { ++ dev_err(dev, "cannot get 64KB-aligned memory cycle host address\n"); ++ return -ENODEV; ++ } ++ perif->mcyc.saddr = temp; ++ ++ rc = of_property_read_u64(dev->of_node, "perif-mcyc-size", &temp); ++ if (rc || !IS_ALIGNED(temp, PERIF_MCYC_ALIGN)) { ++ dev_err(dev, "cannot get 64KB-aligned memory cycle size\n"); ++ return -EINVAL; ++ } ++ perif->mcyc.size = temp; ++ ++ np = of_parse_phandle(dev->of_node, "memory-region", 0); ++ if (np) { ++ of_reserved_mem_device_init(dev); ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_err(dev, "Failed to mask DMA.\n"); ++ return -ENODEV; ++ } ++ } ++ ++ perif->mcyc.virt = dmam_alloc_coherent(dev, perif->mcyc.size, ++ &perif->mcyc.taddr, GFP_KERNEL); ++ if (!perif->mcyc.virt) { ++ dev_err(dev, "cannot allocate memory cycle\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ perif->dma.enable = of_property_read_bool(dev->of_node, "perif-dma-mode"); ++ if (perif->dma.enable) { ++ perif->dma.pc_tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.pc_tx_addr, GFP_KERNEL); ++ if (!perif->dma.pc_tx_virt) { ++ dev_err(dev, "cannot allocate posted TX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ perif->dma.pc_rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.pc_rx_addr, GFP_KERNEL); ++ if (!perif->dma.pc_rx_virt) { ++ dev_err(dev, "cannot allocate posted RX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ perif->dma.np_tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, ++ &perif->dma.np_tx_addr, GFP_KERNEL); ++ if (!perif->dma.np_tx_virt) { ++ dev_err(dev, "cannot allocate non-posted TX DMA buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ perif->rtc_enable = of_property_read_bool(dev->of_node, "perif-rtc-enable"); ++ ++ perif->mdev.parent = dev; ++ perif->mdev.minor = MISC_DYNAMIC_MINOR; ++ perif->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-peripheral%d", DEVICE_NAME, espi->dev_id); ++ perif->mdev.fops = &ast2700_espi_perif_fops; ++ rc = misc_register(&perif->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", perif->mdev.name); ++ return rc; ++ } ++ ++ ast2700_espi_perif_reset(espi); ++ ++ of_platform_populate(dev->of_node, NULL, NULL, dev); ++ return 0; ++} ++ ++int ast2700_espi_perif_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_perif *perif; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ perif = &espi->perif; ++ ++ writel(0x0, espi->regs + ESPI_CH0_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CH0_MCYC0_MASKL); ++ reg &= ~ESPI_CH0_MCYC0_MASKL_EN; ++ writel(reg, espi->regs + ESPI_CH0_MCYC0_MASKL); ++ ++ reg = readl(espi->regs + ESPI_CH0_MCYC1_MASKL); ++ reg &= ~ESPI_CH0_MCYC1_MASKL_EN; ++ writel(reg, espi->regs + ESPI_CH0_MCYC1_MASKL); ++ ++ reg = readl(espi->regs + ESPI_CH0_CTRL); ++ reg |= (ESPI_CH0_CTRL_MCYC_RD_DIS | ESPI_CH0_CTRL_MCYC_WR_DIS); ++ reg &= ~(ESPI_CH0_CTRL_NP_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_RX_DMA_EN ++ | ESPI_CH0_CTRL_SW_RDY); ++ writel(reg, espi->regs + ESPI_CH0_CTRL); ++ ++ if (perif->mcyc.enable) ++ dmam_free_coherent(dev, perif->mcyc.size, perif->mcyc.virt, ++ perif->mcyc.taddr); ++ ++ if (perif->dma.enable) { ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.np_tx_virt, ++ perif->dma.np_tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.pc_tx_virt, ++ perif->dma.pc_tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, perif->dma.pc_rx_virt, ++ perif->dma.pc_rx_addr); ++ } ++ ++ mutex_destroy(&perif->np_tx_mtx); ++ mutex_destroy(&perif->pc_tx_mtx); ++ mutex_destroy(&perif->pc_rx_mtx); ++ ++ misc_deregister(&perif->mdev); ++ ++ return 0; ++} ++ ++/* virtual wire channel (CH1) */ ++static long ast2700_espi_vw_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_vw *vw; ++ struct aspeed_espi *espi; ++ u32 gpio0, gpio1; ++ u8 pch_generic; ++ u32 reg; ++ u32 hw_mode; ++ ++ vw = container_of(fp->private_data, struct aspeed_espi_vw, mdev); ++ espi = container_of(vw, struct aspeed_espi, vw); ++ gpio0 = vw->gpio.val0; ++ gpio1 = vw->gpio.val1; ++ hw_mode = vw->gpio.hw_mode; ++ pch_generic = vw->platform.pch_generic; ++ ++ if (hw_mode) { ++ dev_err(espi->dev, "HW mode: vGPIO reflect on physical GPIO. Get state from GPIO driver.\n"); ++ return -EFAULT; ++ } ++ ++ switch (cmd) { ++ case ASPEED_ESPI_VW_GET_GPIO_VAL: ++ if (put_user(gpio0, (u32 __user *)arg)) { ++ dev_err(espi->dev, "failed to get vGPIO value0\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Get vGPIO value0: 0x%x\n", gpio0); ++ break; ++ ++ case ASPEED_ESPI_VW_PUT_GPIO_VAL: ++ if (get_user(gpio0, (u32 __user *)arg)) { ++ dev_err(espi->dev, "failed to put vGPIO value0\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Put vGPIO value0: 0x%x\n", gpio0); ++ writel(gpio0, espi->regs + ESPI_CH1_GPIO_VAL0); ++ break; ++#ifdef CONFIG_ARM64 ++ case ASPEED_ESPI_VW_GET_GPIO_VAL1: ++ if (put_user(gpio1, (u32 __user *)arg)) { ++ dev_err(espi->dev, "failed to get vGPIO value1\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Get vGPIO value1: 0x%x\n", gpio1); ++ break; ++ ++ case ASPEED_ESPI_VW_PUT_GPIO_VAL1: ++ if (get_user(gpio1, (u32 __user *)arg)) { ++ dev_err(espi->dev, "failed to put vGPIO value1\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Put vGPIO value1: 0x%x\n", gpio1); ++ writel(gpio1, espi->regs + ESPI_CH1_GPIO_VAL1); ++ break; ++#endif ++ case ASPEED_ESPI_VW_GET_PCH_GENERIC: ++ if (put_user(pch_generic, (u8 __user *)arg)) { ++ dev_err(espi->dev, "failed to get PCH generic event status\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Get PCH generic event status: 0x%x\n", pch_generic); ++ break; ++ case ASPEED_ESPI_VW_PUT_PCH_GENERIC: ++ if (get_user(pch_generic, (u8 __user *)arg)) { ++ dev_err(espi->dev, "failed to put PCH generic event status\n"); ++ return -EFAULT; ++ } ++ ++ dev_info(espi->dev, "Put PCH generic event status: 0x%x\n", pch_generic); ++ reg = readl(espi->regs + ESPI_CH1_EVT1); ++ reg &= ~ESPI_CH1_EVT1_BMC_GENE; ++ reg |= FIELD_PREP(ESPI_CH1_EVT1_BMC_GENE, pch_generic); ++ writel(reg, espi->regs + ESPI_CH1_EVT1); ++ break; ++ default: ++ return -EINVAL; ++ }; ++ ++ return 0; ++} ++ ++static const struct file_operations ast2700_espi_vw_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2700_espi_vw_ioctl, ++}; ++ ++static inline struct aspeed_espi_vw *to_ast2700_espi_pltrst(struct file *filp) ++{ ++ return container_of(filp->private_data, struct aspeed_espi_vw, ++ pltrst_mdev); ++} ++ ++static int ast2700_espi_vw_pltrst_open(struct inode *inode, struct file *filp) ++{ ++ struct aspeed_espi_vw *priv = to_ast2700_espi_pltrst(filp); ++ ++ if ((filp->f_flags & O_ACCMODE) != O_RDONLY) ++ return -EACCES; ++ priv->pltrst.pltrst_avail = true ; /*Setting true returns first data after file open*/ ++ ++ return 0; ++} ++ ++static ssize_t ast2700_espi_vw_pltrst_read(struct file *filp, char __user *buf, ++ size_t count, loff_t *offset) ++{ ++ struct aspeed_espi_vw *vw = to_ast2700_espi_pltrst(filp); ++ DECLARE_WAITQUEUE(wait, current); ++ char data, old_sample; ++ int ret = 0; ++ ++ spin_lock_irq(&vw->pltrst.pltrst_lock); ++ ++ if (filp->f_flags & O_NONBLOCK) { ++ if (!vw->pltrst.pltrst_avail) { ++ ret = -EAGAIN; ++ goto out_unlock; ++ } ++ data = vw->pltrst.pltrst_status; ++ vw->pltrst.pltrst_avail = false; ++ } else { ++ add_wait_queue(&vw->pltrst.pltrst_wq, &wait); ++ set_current_state(TASK_INTERRUPTIBLE); ++ ++ old_sample = vw->pltrst.pltrst_status; ++ ++ do { ++ if (old_sample != vw->pltrst.pltrst_status) { ++ data = vw->pltrst.pltrst_status; ++ vw->pltrst.pltrst_avail = false; ++ break; ++ } ++ ++ if (signal_pending(current)) { ++ ret = -ERESTARTSYS; ++ } else { ++ spin_unlock_irq(&vw->pltrst.pltrst_lock); ++ schedule(); ++ spin_lock_irq(&vw->pltrst.pltrst_lock); ++ } ++ } while (!ret); ++ ++ remove_wait_queue(&vw->pltrst.pltrst_wq, &wait); ++ set_current_state(TASK_RUNNING); ++ } ++out_unlock: ++ spin_unlock_irq(&vw->pltrst.pltrst_lock); ++ if (ret) ++ return ret; ++ ++ ret = put_user(data, buf); ++ if (!ret) ++ ret = sizeof(data); ++ ++ return ret; ++} ++ ++static unsigned int ast2700_espi_vw_pltrst_poll(struct file *file, ++ poll_table *wait) ++{ ++ struct aspeed_espi_vw *vw = to_ast2700_espi_pltrst(file); ++ unsigned int mask = 0; ++ ++ poll_wait(file, &vw->pltrst.pltrst_wq, wait); ++ if (vw->pltrst.pltrst_avail) ++ mask |= POLLIN; ++ return mask; ++} ++ ++static const struct file_operations ast2700_espi_vw_pltrst_fops = { ++ .owner = THIS_MODULE, ++ .open = ast2700_espi_vw_pltrst_open, ++ .read = ast2700_espi_vw_pltrst_read, ++ .poll = ast2700_espi_vw_pltrst_poll, ++}; ++ ++static void ast2700_espi_vw_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_vw *vw; ++ u32 sts_evt; ++ u32 sts; ++ u32 evt; ++ unsigned long flags; ++ ++ vw = &espi->vw; ++ ++ sts = readl(espi->regs + ESPI_CH1_INT_STS); ++ ++ if (sts & ESPI_CH1_INT_STS_GPIO) { ++ vw->gpio.val0 = readl(espi->regs + ESPI_CH1_GPIO_VAL0); ++ vw->gpio.val1 = readl(espi->regs + ESPI_CH1_GPIO_VAL1); ++ /* Workaround for AST2700 VW INT_STS */ ++ writel(ESPI_CH1_INT_STS_GPIO_CLR, espi->regs + ESPI_CH1_INT_STS); ++ } ++ ++ if (sts & ESPI_CH1_INT_STS_EVT1) { ++ sts_evt = readl(espi->regs + ESPI_CH1_EVT1_INT_STS); ++ if (sts_evt & ESPI_CH1_EVT1_INT_STS_PCH_GENE) { ++ vw->platform.pch_generic = FIELD_GET(ESPI_CH1_EVT1_PCH_GENE, ++ readl(espi->regs + ESPI_CH1_EVT1)); ++ ++ writel(ESPI_CH1_EVT1_INT_STS_PCH_GENE, ++ espi->regs + ESPI_CH1_EVT1_INT_STS); ++ } ++ ++ writel(ESPI_CH1_INT_STS_EVT1_CLR, espi->regs + ESPI_CH1_INT_STS); ++ } ++ ++ if (sts & ESPI_CH1_INT_STS_EVT0) { ++ sts_evt = readl(espi->regs + ESPI_CH1_EVT0_INT_STS); ++ evt = readl(espi->regs + ESPI_CH1_EVT0); ++ if (sts_evt & ESPI_CH1_EVT0_INT_STS_PLTRSTN || vw->pltrst.pltrst_status == 'U') { ++ spin_lock_irqsave(&vw->pltrst.pltrst_lock, flags); ++ vw->pltrst.pltrst_status = (evt & ESPI_CH1_EVT0_PLTRSTN) ? '1' : '0'; ++ vw->pltrst.pltrst_avail = true; ++ spin_unlock_irqrestore(&vw->pltrst.pltrst_lock, flags); ++ ++ writel(ESPI_CH1_EVT0_INT_STS_PLTRSTN, ++ espi->regs + ESPI_CH1_EVT0_INT_STS); ++ ++ wake_up_interruptible(&vw->pltrst.pltrst_wq); ++ } ++ writel(ESPI_CH1_INT_STS_EVT0, espi->regs + ESPI_CH1_INT_STS); ++ } ++} ++ ++static void ast2700_espi_vw_reset(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ struct aspeed_espi_vw *vw = &espi->vw; ++ ++ writel(0x0, espi->regs + ESPI_CH1_INT_EN); ++ writel(0xffffffff, espi->regs + ESPI_CH1_INT_STS); ++ ++ writel(vw->gpio.grp, espi->regs + ESPI_CH1_GPIO_GRP); ++ writel(vw->gpio.dir0, espi->regs + ESPI_CH1_GPIO_DIR0); ++ writel(vw->gpio.dir1, espi->regs + ESPI_CH1_GPIO_DIR1); ++ ++ vw->gpio.val0 = readl(espi->regs + ESPI_CH1_GPIO_VAL0); ++ vw->gpio.val1 = readl(espi->regs + ESPI_CH1_GPIO_VAL1); ++ vw->platform.pch_generic = FIELD_GET(ESPI_CH1_EVT1_PCH_GENE, ++ readl(espi->regs + ESPI_CH1_EVT1)); ++ ++ writel(ESPI_CH1_EVT1_INT_T2_PCH_GENE, ++ espi->regs + ESPI_CH1_EVT1_INT_T2); ++ writel(ESPI_CH1_EVT1_INT_EN_PCH_GENE, espi->regs + ESPI_CH1_EVT1_INT_EN); ++ ++ if (vw->pltrst.enabled) { ++ spin_lock(&vw->pltrst.pltrst_lock); ++ vw->pltrst.pltrst_status = 'U'; /* Unknown */ ++ vw->pltrst.pltrst_avail = true; ++ spin_unlock(&vw->pltrst.pltrst_lock); ++ ++ writel(ESPI_CH1_EVT0_INT_T2_PLTRSTN, ++ espi->regs + ESPI_CH1_EVT0_INT_T2); ++ writel(ESPI_CH1_EVT0_INT_EN_PLTRSTN, espi->regs + ESPI_CH1_EVT0_INT_EN); ++ writel(ESPI_CH1_INT_EN_GPIO | ESPI_CH1_INT_EN_SYS_EVT1 | ESPI_CH1_INT_EN_SYS_EVT0, ++ espi->regs + ESPI_CH1_INT_EN); ++ } else { ++ writel(ESPI_CH1_INT_EN_GPIO | ESPI_CH1_INT_EN_SYS_EVT1, espi->regs + ESPI_CH1_INT_EN); ++ } ++ ++ reg = readl(espi->regs + ESPI_CH1_CTRL) ++ | ((vw->gpio.hw_mode) ? ESPI_CH1_CTRL_GPIO_HW : 0) ++ | ESPI_CH1_CTRL_SW_RDY; ++ writel(reg, espi->regs + ESPI_CH1_CTRL); ++} ++ ++int ast2700_espi_vw_probe(struct aspeed_espi *espi) ++{ ++ int rc; ++ struct device *dev = espi->dev; ++ struct aspeed_espi_vw *vw = &espi->vw; ++ ++ vw->gpio.hw_mode = of_property_read_bool(dev->of_node, "vw-gpio-hw-mode"); ++ of_property_read_u32(dev->of_node, "vw-gpio-group", &vw->gpio.grp); ++ of_property_read_u32_index(dev->of_node, "vw-gpio-direction", 0, &vw->gpio.dir0); ++ of_property_read_u32_index(dev->of_node, "vw-gpio-direction", 1, &vw->gpio.dir1); ++ ++ vw->pltrst.enabled = of_property_read_bool(dev->of_node, "vw-pltrst-monitor"); ++ if (vw->pltrst.enabled) { ++ spin_lock_init(&vw->pltrst.pltrst_lock); ++ init_waitqueue_head(&vw->pltrst.pltrst_wq); ++ vw->pltrst.pltrst_status = 'U'; /* Unknown */ ++ vw->pltrst.pltrst_avail = false; ++ vw->pltrst_mdev.parent = dev; ++ vw->pltrst_mdev.minor = MISC_DYNAMIC_MINOR; ++ vw->pltrst_mdev.name = ++ devm_kasprintf(dev, GFP_KERNEL, "%s-pltrstn%d", ++ DEVICE_NAME, espi->dev_id); ++ vw->pltrst_mdev.fops = &ast2700_espi_vw_pltrst_fops; ++ rc = misc_register(&vw->pltrst_mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", vw->pltrst_mdev.name); ++ return rc; ++ } ++ } ++ ++ vw->mdev.parent = dev; ++ vw->mdev.minor = MISC_DYNAMIC_MINOR; ++ vw->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-vw%d", DEVICE_NAME, espi->dev_id); ++ vw->mdev.fops = &ast2700_espi_vw_fops; ++ rc = misc_register(&vw->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", vw->mdev.name); ++ return rc; ++ } ++ ++ ast2700_espi_vw_reset(espi); ++ ++ return 0; ++} ++ ++int ast2700_espi_vw_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_vw *vw; ++ ++ vw = &espi->vw; ++ ++ writel(0x0, espi->regs + ESPI_CH1_INT_EN); ++ ++ misc_deregister(&vw->mdev); ++ ++ return 0; ++} ++ ++/* out-of-band channel (CH2) */ ++static long ast2700_espi_oob_dma_get_rx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ struct ast2700_espi_oob_dma_rx_desc *d; ++ struct ast2700_espi_oob_dma_rx_desc *rx_descs; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u32 wptr, pkt_len; ++ unsigned long flags; ++ u8 *pkt; ++ int rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ wptr = FIELD_PREP(ESPI_CH2_RX_DESC_WPTR_WP, readl(espi->regs + ESPI_CH2_RX_DESC_WPTR)); ++ ++ rx_descs = (struct ast2700_espi_oob_dma_rx_desc *)oob->dma.rxd_virt; ++ d = &rx_descs[wptr]; ++ ++ if (!d->dirty) ++ return -EFAULT; ++ ++ pkt_len = ((d->len) ? d->len : ESPI_MAX_PLD_LEN) + sizeof(struct espi_comm_hdr); ++ ++ if (ioc->pkt_len < pkt_len) ++ return -EINVAL; ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) ++ return -ENOMEM; ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = d->cyc; ++ hdr->tag = d->tag; ++ hdr->len_h = d->len >> 8; ++ hdr->len_l = d->len & 0xff; ++ memcpy(hdr + 1, oob->dma.rx_virt + (PAGE_SIZE * wptr), pkt_len - sizeof(*hdr)); ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ ++ /* make current descriptor available again */ ++ d->dirty = 0; ++ ++ wptr = ((wptr + 1) % OOB_DMA_DESC_NUM); ++ writel(wptr | ESPI_CH2_RX_DESC_WPTR_VALID, espi->regs + ESPI_CH2_RX_DESC_WPTR); ++ ++ /* set ready flag base on the next RX descriptor */ ++ oob->rx_ready = rx_descs[wptr].dirty; ++ ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++ return rc; ++} ++ ++static long ast2700_espi_oob_get_rx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&oob->rx_mtx)) ++ return -EAGAIN; ++ ++ if (!oob->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&oob->rx_mtx); ++ ++ if (!oob->rx_ready) { ++ rc = wait_event_interruptible(oob->wq, oob->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ if (oob->dma.enable) { ++ rc = ast2700_espi_oob_dma_get_rx(fp, oob, ioc); ++ goto unlock_mtx_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_CH2_RX_CTRL); ++ cyc = FIELD_GET(ESPI_CH2_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_CH2_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_CH2_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + sizeof(struct espi_comm_hdr); ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ for (i = sizeof(*hdr); i < pkt_len; ++i) { ++ reg = readl(espi->regs + ESPI_CH2_RX_DATA); ++ pkt[i] = reg & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ ++ writel(ESPI_CH2_RX_CTRL_SERV_PEND, espi->regs + ESPI_CH2_RX_CTRL); ++ oob->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&oob->rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2700_espi_oob_dma_put_tx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ struct ast2700_espi_oob_dma_tx_desc *d; ++ struct ast2700_espi_oob_dma_tx_desc *tx_descs; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u32 rptr, wptr; ++ u8 *pkt; ++ int rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ pkt = vzalloc(ioc->pkt_len); ++ if (!pkt) ++ return -ENOMEM; ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* kick HW to update descriptor read/write pointer */ ++ writel(ESPI_CH2_TX_DESC_RPTR_UPT, espi->regs + ESPI_CH2_TX_DESC_RPTR); ++ ++ rptr = readl(espi->regs + ESPI_CH2_TX_DESC_RPTR); ++ wptr = readl(espi->regs + ESPI_CH2_TX_DESC_WPTR); ++ ++ if (((wptr + 1) % OOB_DMA_DESC_NUM) == rptr) { ++ rc = -EBUSY; ++ goto free_n_out; ++ } ++ ++ tx_descs = (struct ast2700_espi_oob_dma_tx_desc *)oob->dma.txd_virt; ++ d = &tx_descs[wptr]; ++ d->cyc = hdr->cyc; ++ d->tag = hdr->tag; ++ d->len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ d->msg_type = OOB_DMA_DESC_CUSTOM; ++ ++ memcpy(oob->dma.tx_virt + (PAGE_SIZE * wptr), hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ ++ dma_wmb(); ++ ++ wptr = (wptr + 1) % OOB_DMA_DESC_NUM; ++ writel(wptr | ESPI_CH2_TX_DESC_WPTR_VALID, espi->regs + ESPI_CH2_TX_DESC_WPTR); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++ return rc; ++} ++ ++static long ast2700_espi_oob_put_tx(struct file *fp, ++ struct aspeed_espi_oob *oob, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(oob, struct aspeed_espi, oob); ++ ++ if (!mutex_trylock(&oob->tx_mtx)) ++ return -EAGAIN; ++ ++ if (oob->dma.enable) { ++ rc = ast2700_espi_oob_dma_put_tx(fp, oob, ioc); ++ goto unlock_mtx_n_out; ++ } ++ ++ reg = readl(espi->regs + ESPI_CH2_TX_CTRL); ++ if (reg & ESPI_CH2_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len > ESPI_MAX_PKT_LEN) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_CH2_TX_DATA); ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_CH2_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_CH2_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_CH2_TX_CTRL_LEN, len) ++ | ESPI_CH2_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_CH2_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&oob->tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2700_espi_oob_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_oob *oob; ++ struct aspeed_espi_ioc ioc; ++ ++ oob = container_of(fp->private_data, struct aspeed_espi_oob, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_OOB_GET_RX: ++ return ast2700_espi_oob_get_rx(fp, oob, &ioc); ++ case ASPEED_ESPI_OOB_PUT_TX: ++ return ast2700_espi_oob_put_tx(fp, oob, &ioc); ++ }; ++ ++ return -EINVAL; ++} ++ ++static const struct file_operations ast2700_espi_oob_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2700_espi_oob_ioctl, ++}; ++ ++static void ast2700_espi_oob_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ unsigned long flags; ++ u32 sts; ++ ++ oob = &espi->oob; ++ ++ sts = readl(espi->regs + ESPI_CH2_INT_STS); ++ ++ if (sts & ESPI_CH2_INT_STS_RX_CMPLT) { ++ writel(ESPI_CH2_INT_STS_RX_CMPLT, espi->regs + ESPI_CH2_INT_STS); ++ ++ spin_lock_irqsave(&oob->lock, flags); ++ oob->rx_ready = true; ++ spin_unlock_irqrestore(&oob->lock, flags); ++ ++ wake_up_interruptible(&oob->wq); ++ } ++} ++ ++static void ast2700_espi_oob_reset(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct ast2700_espi_oob_dma_tx_desc *tx_desc; ++ struct ast2700_espi_oob_dma_rx_desc *rx_desc; ++ dma_addr_t tx_addr, rx_addr; ++ u32 reg; ++ int i; ++ ++ oob = &espi->oob; ++ ++ writel(0x0, espi->regs + ESPI_CH2_INT_EN); ++ writel(0xffffffff, espi->regs + ESPI_CH2_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CH2_CTRL); ++ reg &= ~(ESPI_CH2_CTRL_TX_RST ++ | ESPI_CH2_CTRL_RX_RST ++ | ESPI_CH2_CTRL_TX_DMA_EN ++ | ESPI_CH2_CTRL_RX_DMA_EN ++ | ESPI_CH2_CTRL_SW_RDY); ++ writel(reg, espi->regs + ESPI_CH2_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CH2_CTRL_TX_RST | ESPI_CH2_CTRL_RX_RST); ++ writel(reg, espi->regs + ESPI_CH2_CTRL); ++ ++ if (oob->dma.enable) { ++ tx_addr = oob->dma.tx_addr; ++ rx_addr = oob->dma.rx_addr; ++ ++ tx_desc = (struct ast2700_espi_oob_dma_tx_desc *)oob->dma.txd_virt; ++ rx_desc = (struct ast2700_espi_oob_dma_rx_desc *)oob->dma.rxd_virt; ++ ++ for (i = 0; i < OOB_DMA_DESC_NUM; ++i) { ++ tx_desc[i].data_addrh = upper_32_bits(tx_addr); ++ tx_desc[i].data_addrl = tx_addr & 0xffffffff; ++ tx_addr += PAGE_SIZE; ++ ++ rx_desc[i].data_addrh = upper_32_bits(rx_addr); ++ rx_desc[i].data_addrl = rx_addr & 0xffffffff; ++ rx_desc[i].dirty = 0; ++ rx_addr += PAGE_SIZE; ++ } ++ ++ writel(upper_32_bits(oob->dma.txd_addr), espi->regs + ESPI_CH2_TX_DMAH); ++ writel(oob->dma.txd_addr & 0xffffffff, espi->regs + ESPI_CH2_TX_DMAL); ++ writel(OOB_DMA_RPTR_KEY, espi->regs + ESPI_CH2_TX_DESC_RPTR); ++ writel(0x0, espi->regs + ESPI_CH2_TX_DESC_WPTR); ++ writel(OOB_DMA_DESC_NUM, espi->regs + ESPI_CH2_TX_DESC_EPTR); ++ ++ writel(upper_32_bits(oob->dma.rxd_addr), espi->regs + ESPI_CH2_RX_DMAH); ++ writel(oob->dma.rxd_addr & 0xffffffff, espi->regs + ESPI_CH2_RX_DMAL); ++ writel(OOB_DMA_RPTR_KEY, espi->regs + ESPI_CH2_RX_DESC_RPTR); ++ writel(0x0, espi->regs + ESPI_CH2_RX_DESC_WPTR); ++ writel(OOB_DMA_DESC_NUM, espi->regs + ESPI_CH2_RX_DESC_EPTR); ++ ++ reg = readl(espi->regs + ESPI_CH2_CTRL) ++ | ESPI_CH2_CTRL_TX_DMA_EN ++ | ESPI_CH2_CTRL_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CH2_CTRL); ++ ++ /* activate RX DMA to make OOB_FREE */ ++ reg = readl(espi->regs + ESPI_CH2_RX_DESC_WPTR) | ESPI_CH2_RX_DESC_WPTR_VALID; ++ writel(reg, espi->regs + ESPI_CH2_RX_DESC_WPTR); ++ } ++ ++ writel(ESPI_CH2_INT_EN_RX_CMPLT, espi->regs + ESPI_CH2_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CH2_CTRL) | ESPI_CH2_CTRL_SW_RDY; ++ writel(reg, espi->regs + ESPI_CH2_CTRL); ++} ++ ++int ast2700_espi_oob_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct device *dev; ++ int rc; ++ ++ dev = espi->dev; ++ ++ oob = &espi->oob; ++ ++ init_waitqueue_head(&oob->wq); ++ ++ spin_lock_init(&oob->lock); ++ ++ mutex_init(&oob->tx_mtx); ++ mutex_init(&oob->rx_mtx); ++ ++ oob->dma.enable = of_property_read_bool(dev->of_node, "oob-dma-mode"); ++ if (oob->dma.enable) { ++ oob->dma.txd_virt = dmam_alloc_coherent(dev, ++ sizeof(struct ast2700_espi_oob_dma_tx_desc) * ++ OOB_DMA_DESC_NUM, ++ &oob->dma.txd_addr, GFP_KERNEL); ++ if (!oob->dma.txd_virt) { ++ dev_err(dev, "cannot allocate DMA TX descriptor\n"); ++ return -ENOMEM; ++ } ++ oob->dma.tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, &oob->dma.tx_addr, GFP_KERNEL); ++ if (!oob->dma.tx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ ++ oob->dma.rxd_virt = dmam_alloc_coherent(dev, ++ sizeof(struct ast2700_espi_oob_dma_rx_desc) * ++ OOB_DMA_DESC_NUM, ++ &oob->dma.rxd_addr, GFP_KERNEL); ++ if (!oob->dma.rxd_virt) { ++ dev_err(dev, "cannot allocate DMA RX descriptor\n"); ++ return -ENOMEM; ++ } ++ ++ oob->dma.rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, &oob->dma.rx_addr, GFP_KERNEL); ++ if (!oob->dma.rx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ oob->mdev.parent = dev; ++ oob->mdev.minor = MISC_DYNAMIC_MINOR; ++ oob->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-oob%d", DEVICE_NAME, espi->dev_id); ++ oob->mdev.fops = &ast2700_espi_oob_fops; ++ rc = misc_register(&oob->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", oob->mdev.name); ++ return rc; ++ } ++ ++ ast2700_espi_oob_reset(espi); ++ ++ return 0; ++} ++ ++int ast2700_espi_oob_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_oob *oob; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ oob = &espi->oob; ++ ++ writel(0x0, espi->regs + ESPI_CH2_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CH2_CTRL); ++ reg &= ~(ESPI_CH2_CTRL_TX_DMA_EN ++ | ESPI_CH2_CTRL_RX_DMA_EN ++ | ESPI_CH2_CTRL_SW_RDY); ++ writel(reg, espi->regs + ESPI_CH2_CTRL); ++ ++ if (oob->dma.enable) { ++ dmam_free_coherent(dev, sizeof(struct ast2700_espi_oob_dma_tx_desc) * OOB_DMA_DESC_NUM, ++ oob->dma.txd_virt, oob->dma.txd_addr); ++ dmam_free_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, ++ oob->dma.tx_virt, oob->dma.tx_addr); ++ dmam_free_coherent(dev, sizeof(struct ast2700_espi_oob_dma_rx_desc) * OOB_DMA_DESC_NUM, ++ oob->dma.rxd_virt, oob->dma.rxd_addr); ++ dmam_free_coherent(dev, PAGE_SIZE * OOB_DMA_DESC_NUM, ++ oob->dma.rx_virt, oob->dma.rx_addr); ++ } ++ ++ mutex_destroy(&oob->tx_mtx); ++ mutex_destroy(&oob->rx_mtx); ++ ++ misc_deregister(&oob->mdev); ++ ++ return 0; ++} ++ ++/* flash channel (CH3) */ ++static long ast2700_espi_flash_get_rx(struct file *fp, ++ struct aspeed_espi_flash *flash, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ unsigned long flags; ++ u32 pkt_len; ++ u8 *pkt; ++ int i, rc; ++ ++ rc = 0; ++ ++ espi = container_of(flash, struct aspeed_espi, flash); ++ ++ if (fp->f_flags & O_NONBLOCK) { ++ if (!mutex_trylock(&flash->rx_mtx)) ++ return -EAGAIN; ++ ++ if (!flash->rx_ready) { ++ rc = -ENODATA; ++ goto unlock_mtx_n_out; ++ } ++ } else { ++ mutex_lock(&flash->rx_mtx); ++ ++ if (!flash->rx_ready) { ++ rc = wait_event_interruptible(flash->wq, flash->rx_ready); ++ if (rc == -ERESTARTSYS) { ++ rc = -EINTR; ++ goto unlock_mtx_n_out; ++ } ++ } ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ reg = readl(espi->regs + ESPI_CH3_RX_CTRL); ++ cyc = FIELD_GET(ESPI_CH3_RX_CTRL_CYC, reg); ++ tag = FIELD_GET(ESPI_CH3_RX_CTRL_TAG, reg); ++ len = FIELD_GET(ESPI_CH3_RX_CTRL_LEN, reg); ++ ++ /* ++ * calculate the length of the rest part of the ++ * eSPI packet to be read from HW and copied to ++ * user space. ++ */ ++ switch (cyc) { ++ case ESPI_FLASH_WRITE: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_flash_rwe); ++ break; ++ case ESPI_FLASH_READ: ++ case ESPI_FLASH_ERASE: ++ pkt_len = sizeof(struct espi_flash_rwe); ++ break; ++ case ESPI_FLASH_SUC_CMPLT_D_MIDDLE: ++ case ESPI_FLASH_SUC_CMPLT_D_FIRST: ++ case ESPI_FLASH_SUC_CMPLT_D_LAST: ++ case ESPI_FLASH_SUC_CMPLT_D_ONLY: ++ pkt_len = ((len) ? len : ESPI_MAX_PLD_LEN) + ++ sizeof(struct espi_flash_cmplt); ++ break; ++ case ESPI_FLASH_SUC_CMPLT: ++ case ESPI_FLASH_UNSUC_CMPLT: ++ pkt_len = sizeof(struct espi_flash_cmplt); ++ break; ++ default: ++ rc = -EFAULT; ++ goto unlock_mtx_n_out; ++ } ++ ++ if (ioc->pkt_len < pkt_len) { ++ rc = -EINVAL; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ hdr->cyc = cyc; ++ hdr->tag = tag; ++ hdr->len_h = len >> 8; ++ hdr->len_l = len & 0xff; ++ ++ if (flash->dma.enable) { ++ memcpy(hdr + 1, flash->dma.rx_virt, pkt_len - sizeof(*hdr)); ++ } else { ++ for (i = sizeof(*hdr); i < pkt_len; ++i) ++ pkt[i] = readl(espi->regs + ESPI_CH3_RX_DATA) & 0xff; ++ } ++ ++ if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ spin_lock_irqsave(&flash->lock, flags); ++ ++ writel(ESPI_CH3_RX_CTRL_SERV_PEND, espi->regs + ESPI_CH3_RX_CTRL); ++ flash->rx_ready = 0; ++ ++ spin_unlock_irqrestore(&flash->lock, flags); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&flash->rx_mtx); ++ ++ return rc; ++} ++ ++static long ast2700_espi_flash_put_tx(struct file *fp, ++ struct aspeed_espi_flash *flash, ++ struct aspeed_espi_ioc *ioc) ++{ ++ u32 reg, cyc, tag, len; ++ struct aspeed_espi *espi; ++ struct espi_comm_hdr *hdr; ++ u8 *pkt; ++ int i, rc; ++ ++ espi = container_of(flash, struct aspeed_espi, flash); ++ ++ if (!mutex_trylock(&flash->tx_mtx)) ++ return -EAGAIN; ++ ++ reg = readl(espi->regs + ESPI_CH3_TX_CTRL); ++ if (reg & ESPI_CH3_TX_CTRL_TRIG_PEND) { ++ rc = -EBUSY; ++ goto unlock_mtx_n_out; ++ } ++ ++ pkt = vmalloc(ioc->pkt_len); ++ if (!pkt) { ++ rc = -ENOMEM; ++ goto unlock_mtx_n_out; ++ } ++ ++ hdr = (struct espi_comm_hdr *)pkt; ++ ++ if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) { ++ rc = -EFAULT; ++ goto free_n_out; ++ } ++ ++ /* ++ * common header (i.e. cycle type, tag, and length) ++ * part is written to HW registers ++ */ ++ if (flash->dma.enable) { ++ memcpy(flash->dma.tx_virt, hdr + 1, ioc->pkt_len - sizeof(*hdr)); ++ dma_wmb(); ++ } else { ++ for (i = sizeof(*hdr); i < ioc->pkt_len; ++i) ++ writel(pkt[i], espi->regs + ESPI_CH3_TX_DATA); ++ } ++ ++ cyc = hdr->cyc; ++ tag = hdr->tag; ++ len = (hdr->len_h << 8) | (hdr->len_l & 0xff); ++ ++ reg = FIELD_PREP(ESPI_CH3_TX_CTRL_CYC, cyc) ++ | FIELD_PREP(ESPI_CH3_TX_CTRL_TAG, tag) ++ | FIELD_PREP(ESPI_CH3_TX_CTRL_LEN, len) ++ | ESPI_CH3_TX_CTRL_TRIG_PEND; ++ writel(reg, espi->regs + ESPI_CH3_TX_CTRL); ++ ++ rc = 0; ++ ++free_n_out: ++ vfree(pkt); ++ ++unlock_mtx_n_out: ++ mutex_unlock(&flash->tx_mtx); ++ ++ return rc; ++} ++ ++static long ast2700_espi_flash_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_espi_flash *flash; ++ struct aspeed_espi_ioc ioc; ++ ++ flash = container_of(fp->private_data, struct aspeed_espi_flash, mdev); ++ ++ if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc))) ++ return -EFAULT; ++ ++ if (ioc.pkt_len > ESPI_MAX_PKT_LEN) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case ASPEED_ESPI_FLASH_GET_RX: ++ return ast2700_espi_flash_get_rx(fp, flash, &ioc); ++ case ASPEED_ESPI_FLASH_PUT_TX: ++ return ast2700_espi_flash_put_tx(fp, flash, &ioc); ++ }; ++ ++ return -EINVAL; ++} ++ ++static const struct file_operations ast2700_espi_flash_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = ast2700_espi_flash_ioctl, ++}; ++ ++static void ast2700_espi_flash_isr(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ unsigned long flags; ++ u32 sts; ++ ++ flash = &espi->flash; ++ ++ sts = readl(espi->regs + ESPI_CH3_INT_STS); ++ ++ if (sts & ESPI_CH3_INT_STS_RX_CMPLT) { ++ writel(ESPI_CH3_INT_STS_RX_CMPLT, espi->regs + ESPI_CH3_INT_STS); ++ ++ spin_lock_irqsave(&flash->lock, flags); ++ flash->rx_ready = true; ++ spin_unlock_irqrestore(&flash->lock, flags); ++ ++ wake_up_interruptible(&flash->wq); ++ } ++} ++ ++static void ast2700_espi_flash_reset(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ u64 mask; ++ struct aspeed_espi_flash *flash = &espi->flash; ++ ++ writel(0x0, espi->regs + ESPI_CH3_INT_EN); ++ writel(0xffffffff, espi->regs + ESPI_CH3_INT_STS); ++ ++ reg = readl(espi->regs + ESPI_CH3_CTRL); ++ reg &= ~(ESPI_CH3_CTRL_TX_RST ++ | ESPI_CH3_CTRL_RX_RST ++ | ESPI_CH3_CTRL_TX_DMA_EN ++ | ESPI_CH3_CTRL_RX_DMA_EN ++ | ESPI_CH3_CTRL_SW_RDY); ++ writel(reg, espi->regs + ESPI_CH3_CTRL); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CH3_CTRL_TX_RST | ESPI_CH3_CTRL_RX_RST); ++ writel(reg, espi->regs + ESPI_CH3_CTRL); ++ ++ if (flash->edaf.mode == EDAF_MODE_MIX) { ++ mask = ~(flash->edaf.size - 1); ++ writel(upper_32_bits(mask), espi->regs + ESPI_CH3_EDAF_MASKH); ++ writel(mask & 0xffffffff, espi->regs + ESPI_CH3_EDAF_MASKL); ++ writel(upper_32_bits(flash->edaf.taddr), espi->regs + ESPI_CH3_EDAF_TADDRH); ++ writel(flash->edaf.taddr & 0xffffffff, espi->regs + ESPI_CH3_EDAF_TADDRL); ++ } ++ ++ reg = readl(espi->regs + ESPI_CH3_CTRL) & ~ESPI_CH3_CTRL_EDAF_MODE; ++ reg |= FIELD_PREP(ESPI_CH3_CTRL_EDAF_MODE, flash->edaf.mode); ++ writel(reg, espi->regs + ESPI_CH3_CTRL); ++ ++ if (flash->dma.enable) { ++ writel(upper_32_bits(flash->dma.tx_addr), espi->regs + ESPI_CH3_TX_DMAH); ++ writel(flash->dma.tx_addr & 0xffffffff, espi->regs + ESPI_CH3_TX_DMAL); ++ writel(upper_32_bits(flash->dma.rx_addr), espi->regs + ESPI_CH3_RX_DMAH); ++ writel(flash->dma.rx_addr & 0xffffffff, espi->regs + ESPI_CH3_RX_DMAL); ++ ++ reg = readl(espi->regs + ESPI_CH3_CTRL) ++ | ESPI_CH3_CTRL_TX_DMA_EN ++ | ESPI_CH3_CTRL_RX_DMA_EN; ++ writel(reg, espi->regs + ESPI_CH3_CTRL); ++ } ++ ++ writel(ESPI_CH3_INT_EN_RX_CMPLT, espi->regs + ESPI_CH3_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CH3_CTRL) | ESPI_CH3_CTRL_SW_RDY; ++ writel(reg, espi->regs + ESPI_CH3_CTRL); ++} ++ ++int ast2700_espi_flash_probe(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ struct device_node *np; ++ struct resource res; ++ struct device *dev; ++ void *virt; ++ int rc; ++ ++ dev = espi->dev; ++ ++ flash = &espi->flash; ++ ++ init_waitqueue_head(&flash->wq); ++ ++ spin_lock_init(&flash->lock); ++ ++ mutex_init(&flash->tx_mtx); ++ mutex_init(&flash->rx_mtx); ++ ++ flash->edaf.mode = EDAF_MODE_HW; ++ ++ of_property_read_u32(dev->of_node, "flash-edaf-mode", &flash->edaf.mode); ++ dev_info(dev, "eDAF mode: 0x%x\n", flash->edaf.mode); ++ if (flash->edaf.mode == EDAF_MODE_MIX) { ++ np = of_parse_phandle(dev->of_node, "flash-edaf-tgt-addr", 0); ++ if (!np || of_address_to_resource(np, 0, &res)) { ++ dev_err(dev, "cannot get eDAF memory region\n"); ++ return -ENODEV; ++ } ++ ++ of_node_put(np); ++ ++ flash->edaf.taddr = res.start; ++ flash->edaf.size = resource_size(&res); ++ ++ virt = devm_ioremap_resource(dev, &res); ++ if (!virt) { ++ dev_err(dev, "cannot map eDAF memory region\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ flash->dma.enable = of_property_read_bool(dev->of_node, "flash-dma-mode"); ++ if (flash->dma.enable) { ++ flash->dma.tx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &flash->dma.tx_addr, GFP_KERNEL); ++ if (!flash->dma.tx_virt) { ++ dev_err(dev, "cannot allocate DMA TX buffer\n"); ++ return -ENOMEM; ++ } ++ ++ flash->dma.rx_virt = dmam_alloc_coherent(dev, PAGE_SIZE, &flash->dma.rx_addr, GFP_KERNEL); ++ if (!flash->dma.rx_virt) { ++ dev_err(dev, "cannot allocate DMA RX buffer\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ flash->mdev.parent = dev; ++ flash->mdev.minor = MISC_DYNAMIC_MINOR; ++ flash->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s-flash%d", DEVICE_NAME, espi->dev_id); ++ flash->mdev.fops = &ast2700_espi_flash_fops; ++ rc = misc_register(&flash->mdev); ++ if (rc) { ++ dev_err(dev, "cannot register device %s\n", flash->mdev.name); ++ return rc; ++ } ++ ++ ast2700_espi_flash_reset(espi); ++ ++ return 0; ++} ++ ++int ast2700_espi_flash_remove(struct aspeed_espi *espi) ++{ ++ struct aspeed_espi_flash *flash; ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ flash = &espi->flash; ++ ++ writel(0x0, espi->regs + ESPI_CH3_INT_EN); ++ ++ reg = readl(espi->regs + ESPI_CH3_CTRL); ++ reg &= ~(ESPI_CH3_CTRL_TX_DMA_EN ++ | ESPI_CH3_CTRL_RX_DMA_EN ++ | ESPI_CH3_CTRL_SW_RDY); ++ writel(reg, espi->regs + ESPI_CH3_CTRL); ++ ++ if (flash->dma.enable) { ++ dmam_free_coherent(dev, PAGE_SIZE, flash->dma.tx_virt, flash->dma.tx_addr); ++ dmam_free_coherent(dev, PAGE_SIZE, flash->dma.rx_virt, flash->dma.rx_addr); ++ } ++ ++ mutex_destroy(&flash->tx_mtx); ++ mutex_destroy(&flash->rx_mtx); ++ ++ misc_deregister(&flash->mdev); ++ ++ return 0; ++} ++ ++/* global control */ ++irqreturn_t ast2700_espi_isr(int irq, void *arg) ++{ ++ u32 sts; ++ struct aspeed_espi *espi = (struct aspeed_espi *)arg; ++ ++ sts = readl(espi->regs + ESPI_INT_STS); ++ if (!sts) ++ return IRQ_NONE; ++ ++ if (sts & ESPI_INT_STS_CH0) ++ ast2700_espi_perif_isr(espi); ++ ++ if (sts & ESPI_INT_STS_CH1) ++ ast2700_espi_vw_isr(espi); ++ ++ if (sts & ESPI_INT_STS_CH2) ++ ast2700_espi_oob_isr(espi); ++ ++ if (sts & ESPI_INT_STS_CH3) ++ ast2700_espi_flash_isr(espi); ++ ++ if (sts & ESPI_INT_STS_RST_DEASSERT) { ++ ast2700_espi_perif_sw_reset(espi); ++ ast2700_espi_perif_reset(espi); ++ ast2700_espi_vw_reset(espi); ++ ast2700_espi_oob_reset(espi); ++ ast2700_espi_flash_reset(espi); ++ writel(ESPI_INT_STS_RST_DEASSERT, espi->regs + ESPI_INT_STS); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++void ast2700_espi_pre_init(struct aspeed_espi *espi) ++{ ++ struct device *dev; ++ struct regmap *scu1; ++ u32 reg; ++ int rc; ++ ++ dev = espi->dev; ++ ++ scu1 = syscon_regmap_lookup_by_phandle(dev->of_node, "aspeed,syscon"); ++ if (IS_ERR(scu1)) { ++ dev_err(dev, "failed to find SCU1 regmap, error %ld\n", PTR_ERR(scu1)); ++ return; ++ } ++ rc = regmap_update_bits(scu1, SCU1_DDR, ++ SCU1_DDR_DIS_ESPI0_AHB | SCU1_DDR_DIS_ESPI1_AHB, ++ 0); ++ if (rc) { ++ dev_err(dev, "failed to update SCU1 regmap, error %d\n", rc); ++ return; ++ } ++ ++ espi->dev_id = ida_alloc(&ast2700_espi_ida, GFP_KERNEL); ++ if (espi->dev_id < 0) { ++ dev_err(dev, "cannote allocate device ID, error %d\n", espi->dev_id); ++ return; ++ } ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~ESPI_INT_EN_RST_DEASSERT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++} ++ ++void ast2700_espi_post_init(struct aspeed_espi *espi) ++{ ++ u32 reg; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg |= ESPI_INT_EN_RST_DEASSERT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++} ++ ++void ast2700_espi_deinit(struct aspeed_espi *espi) ++{ ++ struct device *dev; ++ u32 reg; ++ ++ dev = espi->dev; ++ ++ reg = readl(espi->regs + ESPI_INT_EN); ++ reg &= ~ESPI_INT_EN_RST_DEASSERT; ++ writel(reg, espi->regs + ESPI_INT_EN); ++} +diff --git a/drivers/soc/aspeed/espi/ast2700-espi.h b/drivers/soc/aspeed/espi/ast2700-espi.h +--- a/drivers/soc/aspeed/espi/ast2700-espi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2700-espi.h 2026-09-09 15:51:44.720238041 +0000 +@@ -0,0 +1,300 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Register definitions for Aspeed AST2700 eSPI controller ++ * Copyright 2023 Aspeed Technology Inc. ++ */ ++#ifndef _AST2700_ESPI_H_ ++#define _AST2700_ESPI_H_ ++ ++#include ++#include "aspeed-espi.h" ++ ++/* SCU regiseters */ ++#define SCU1_DDR 0x0c8 ++#define SCU1_DDR_DIS_ESPI0_AHB BIT(0) ++#define SCU1_DDR_DIS_ESPI1_AHB BIT(1) ++ ++/* global registers */ ++#define ESPI_CTRL 0x000 ++#define ESPI_STS 0x004 ++#define ESPI_INT_STS 0x008 ++#define ESPI_INT_STS_RST_DEASSERT BIT(31) ++#define ESPI_INT_STS_RST_ASSERT BIT(30) ++#define ESPI_INT_STS_CH3 BIT(3) ++#define ESPI_INT_STS_CH2 BIT(2) ++#define ESPI_INT_STS_CH1 BIT(1) ++#define ESPI_INT_STS_CH0 BIT(0) ++#define ESPI_INT_EN 0x00c ++#define ESPI_INT_EN_RST_DEASSERT BIT(31) ++#define ESPI_INT_EN_RST_ASSERT BIT(30) ++#define ESPI_DEV_ID 0x010 ++#define ESPI_CAP_GEN 0x014 ++#define ESPI_CAP_GEN_RTC_SUP BIT(29) ++#define ESPI_CAP_CH0 0x018 ++#define ESPI_CAP_CH1 0x01c ++#define ESPI_CAP_CH2 0x020 ++#define ESPI_CAP_CH3_0 0x024 ++#define ESPI_CAP_CH3_1 0x028 ++#define ESPI_DEV_STS 0x030 ++#define ESPI_DBG_CTRL 0x034 ++#define ESPI_DBG_ADDRL 0x038 ++#define ESPI_DBG_ADDRH 0x03c ++#define ESPI_DBG_CMD 0x040 ++#define ESPI_DBG_RES 0x044 ++#define ESPI_CH_ACC_CTRL 0x04c ++#define ESPI_CH_ACC_OFST1 0x050 ++#define ESPI_CH_ACC_OFST2 0x054 ++#define ESPI_WPROT0 0x0f8 ++#define ESPI_WPROT1 0x0fc ++ ++/* peripheral channel (ch0) registers */ ++#define ESPI_CH0_CTRL 0x100 ++#define ESPI_CH0_CTRL_NP_TX_RST BIT(31) ++#define ESPI_CH0_CTRL_NP_RX_RST BIT(30) ++#define ESPI_CH0_CTRL_PC_TX_RST BIT(29) ++#define ESPI_CH0_CTRL_PC_RX_RST BIT(28) ++#define ESPI_CH0_CTRL_NP_TX_DMA_EN BIT(19) ++#define ESPI_CH0_CTRL_PC_TX_DMA_EN BIT(17) ++#define ESPI_CH0_CTRL_PC_RX_DMA_EN BIT(16) ++#define ESPI_CH0_CTRL_MCYC_RD_DIS_WDT BIT(9) ++#define ESPI_CH0_CTRL_MCYC_WR_DIS_WDT BIT(8) ++#define ESPI_CH0_CTRL_MCYC_RD_DIS BIT(6) ++#define ESPI_CH0_CTRL_MCYC_WR_DIS BIT(4) ++#define ESPI_CH0_CTRL_SW_RDY BIT(1) ++#define ESPI_CH0_STS 0x104 ++#define ESPI_CH0_INT_STS 0x108 ++#define ESPI_CH0_INT_STS_PC_RX_CMPLT BIT(0) ++#define ESPI_CH0_INT_EN 0x10c ++#define ESPI_CH0_INT_EN_PC_RX_CMPLT BIT(0) ++#define ESPI_CH0_PC_RX_DMAL 0x110 ++#define ESPI_CH0_PC_RX_DMAH 0x114 ++#define ESPI_CH0_PC_RX_CTRL 0x118 ++#define ESPI_CH0_PC_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_CH0_PC_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_CH0_PC_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_CH0_PC_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_CH0_PC_RX_DATA 0x11c ++#define ESPI_CH0_PC_TX_DMAL 0x120 ++#define ESPI_CH0_PC_TX_DMAH 0x124 ++#define ESPI_CH0_PC_TX_CTRL 0x128 ++#define ESPI_CH0_PC_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_CH0_PC_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_CH0_PC_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_CH0_PC_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_CH0_PC_TX_DATA 0x12c ++#define ESPI_CH0_NP_TX_DMAL 0x130 ++#define ESPI_CH0_NP_TX_DMAH 0x134 ++#define ESPI_CH0_NP_TX_CTRL 0x138 ++#define ESPI_CH0_NP_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_CH0_NP_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_CH0_NP_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_CH0_NP_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_CH0_NP_TX_DATA 0x13c ++#define ESPI_CH0_MCYC0_SADDRL 0x140 ++#define ESPI_CH0_MCYC0_SADDRH 0x144 ++#define ESPI_CH0_MCYC0_TADDRL 0x148 ++#define ESPI_CH0_MCYC0_TADDRH 0x14c ++#define ESPI_CH0_MCYC0_MASKL 0x150 ++#define ESPI_CH0_MCYC0_MASKL_EN BIT(0) ++#define ESPI_CH0_MCYC0_MASKH 0x154 ++#define ESPI_CH0_MCYC1_SADDRL 0x158 ++#define ESPI_CH0_MCYC1_SADDRH 0x15c ++#define ESPI_CH0_MCYC1_TADDRL 0x160 ++#define ESPI_CH0_MCYC1_TADDRH 0x164 ++#define ESPI_CH0_MCYC1_MASKL 0x168 ++#define ESPI_CH0_MCYC1_MASKL_EN BIT(0) ++#define ESPI_CH0_MCYC1_MASKH 0x16c ++#define ESPI_CH0_WPROT0 0x1f8 ++#define ESPI_CH0_WPROT1 0x1fc ++ ++/* virtual wire channel (ch1) registers */ ++#define ESPI_CH1_CTRL 0x200 ++#define ESPI_CH1_CTRL_GPIO_HW BIT(9) ++#define ESPI_CH1_CTRL_SW_RDY BIT(1) ++#define ESPI_CH1_STS 0x204 ++#define ESPI_CH1_INT_STS 0x208 ++#define ESPI_CH1_INT_STS_GPIO BIT(1) ++#define ESPI_CH1_INT_STS_GPIO_CLR BIT(2) ++#define ESPI_CH1_INT_STS_EVT1 BIT(2) ++#define ESPI_CH1_INT_STS_EVT1_CLR BIT(1) ++#define ESPI_CH1_INT_STS_EVT0 BIT(0) ++#define ESPI_CH1_INT_EN 0x20c ++#define ESPI_CH1_INT_EN_GPIO BIT(1) ++#define ESPI_CH1_INT_EN_SYS_EVT1 BIT(2) ++#define ESPI_CH1_INT_EN_SYS_EVT0 BIT(0) ++#define ESPI_CH1_EVT0 0x210 ++#define ESPI_CH1_EVT0_PLTRSTN BIT(5) ++#define ESPI_CH1_EVT0_SCI_EVT BIT(24) ++#define ESPI_CH1_EVT0_INT_EN 0x214 ++#define ESPI_CH1_EVT0_INT_EN_PLTRSTN BIT(5) ++#define ESPI_CH1_EVT0_INT_T0 0x218 ++#define ESPI_CH1_EVT0_INT_T0_PLTRSTN BIT(5) ++#define ESPI_CH1_EVT0_INT_T1 0x21c ++#define ESPI_CH1_EVT0_INT_T1_PLTRSTN BIT(5) ++#define ESPI_CH1_EVT0_INT_T2 0x220 ++#define ESPI_CH1_EVT0_INT_T2_PLTRSTN BIT(5) ++#define ESPI_CH1_EVT0_INT_STS 0x224 ++#define ESPI_CH1_EVT0_INT_STS_PLTRSTN BIT(5) ++#define ESPI_CH1_EVT1 0x230 ++#define ESPI_CH1_EVT1_PCH_GENE GENMASK(15, 8) ++#define ESPI_CH1_EVT1_BMC_GENE GENMASK(31, 24) ++#define ESPI_CH1_EVT1_INT_EN 0x234 ++#define ESPI_CH1_EVT1_INT_EN_PCH_GENE GENMASK(15, 8) ++#define ESPI_CH1_EVT1_INT_T0 0x238 ++#define ESPI_CH1_EVT1_INT_T0_PCH_GENE GENMASK(15, 8) ++#define ESPI_CH1_EVT1_INT_T1 0x23c ++#define ESPI_CH1_EVT1_INT_T1_PCH_GENE GENMASK(15, 8) ++#define ESPI_CH1_EVT1_INT_T2 0x240 ++#define ESPI_CH1_EVT1_INT_T2_PCH_GENE GENMASK(15, 8) ++#define ESPI_CH1_EVT1_INT_STS 0x244 ++#define ESPI_CH1_EVT1_INT_STS_PCH_GENE GENMASK(15, 8) ++#define ESPI_CH1_GPIO_VAL0 0x250 ++#define ESPI_CH1_GPIO_VAL1 0x254 ++#define ESPI_CH1_GPIO_DIR0 0x258 ++#define ESPI_CH1_GPIO_DIR1 0x25c ++#define ESPI_CH1_GPIO_RSTSEL0 0x260 ++#define ESPI_CH1_GPIO_RSTSEL1 0x264 ++#define ESPI_CH1_GPIO_GRP 0x268 ++#define ESPI_CH1_GP50_DIR0 0x270 ++#define ESPI_CH1_GP50_DIR1 0x274 ++#define ESPI_CH1_GP50_VAL0 0x278 ++#define ESPI_CH1_GP50_VAL1 0x27c ++#define ESPI_CH1_SW_INT 0x280 ++#define ESPI_CH1_INT_RSTSEL0 0x284 ++#define ESPI_CH1_INT_RSTSEL1 0x288 ++#define ESPI_CH1_WPROT0 0x2f8 ++#define ESPI_CH1_WPROT1 0x2fc ++ ++/* out-of-band channel (ch2) registers */ ++#define ESPI_CH2_CTRL 0x300 ++#define ESPI_CH2_CTRL_TX_RST BIT(31) ++#define ESPI_CH2_CTRL_RX_RST BIT(30) ++#define ESPI_CH2_CTRL_TX_DMA_EN BIT(17) ++#define ESPI_CH2_CTRL_RX_DMA_EN BIT(16) ++#define ESPI_CH2_CTRL_SW_RDY BIT(4) ++#define ESPI_CH2_STS 0x304 ++#define ESPI_CH2_INT_STS 0x308 ++#define ESPI_CH2_INT_STS_RX_CMPLT BIT(0) ++#define ESPI_CH2_INT_EN 0x30c ++#define ESPI_CH2_INT_EN_RX_CMPLT BIT(0) ++#define ESPI_CH2_RX_DMAL 0x310 ++#define ESPI_CH2_RX_DMAH 0x314 ++#define ESPI_CH2_RX_CTRL 0x318 ++#define ESPI_CH2_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_CH2_RX_CTRL_PEC BIT(24) ++#define ESPI_CH2_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_CH2_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_CH2_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_CH2_RX_DATA 0x31c ++#define ESPI_CH2_TX_DMAL 0x320 ++#define ESPI_CH2_TX_DMAH 0x324 ++#define ESPI_CH2_TX_CTRL 0x328 ++#define ESPI_CH2_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_CH2_TX_CTRL_PEC BIT(24) ++#define ESPI_CH2_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_CH2_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_CH2_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_CH2_TX_DATA 0x32c ++#define ESPI_CH2_RX_DESC_EPTR 0x330 ++#define ESPI_CH2_RX_DESC_RPTR 0x334 ++#define ESPI_CH2_RX_DESC_RPTR_UPDATE BIT(31) ++#define ESPI_CH2_RX_DESC_RPTR_RP GENMASK(11, 0) ++#define ESPI_CH2_RX_DESC_WPTR 0x338 ++#define ESPI_CH2_RX_DESC_WPTR_VALID BIT(31) ++#define ESPI_CH2_RX_DESC_WPTR_SP GENMASK(27, 16) ++#define ESPI_CH2_RX_DESC_WPTR_WP GENMASK(11, 0) ++#define ESPI_CH2_RX_DESC_TMOUT 0x33c ++#define ESPI_CH2_TX_DESC_EPTR 0x340 ++#define ESPI_CH2_TX_DESC_RPTR 0x344 ++#define ESPI_CH2_TX_DESC_RPTR_UPT BIT(31) ++#define ESPI_CH2_TX_DESC_WPTR 0x348 ++#define ESPI_CH2_TX_DESC_WPTR_VALID BIT(31) ++#define ESPI_CH2_WPROT0 0x3f8 ++#define ESPI_CH2_WPROT1 0x3fc ++ ++/* flash channel (ch3) registers */ ++#define ESPI_CH3_CTRL 0x400 ++#define ESPI_CH3_CTRL_TX_RST BIT(31) ++#define ESPI_CH3_CTRL_RX_RST BIT(30) ++#define ESPI_CH3_CTRL_TX_DMA_EN BIT(17) ++#define ESPI_CH3_CTRL_RX_DMA_EN BIT(16) ++#define ESPI_CH3_CTRL_EDAF_MODE GENMASK(9, 8) ++#define ESPI_CH3_CTRL_SW_RDY BIT(5) ++#define ESPI_CH3_STS 0x404 ++#define ESPI_CH3_INT_STS 0x408 ++#define ESPI_CH3_INT_STS_RX_CMPLT BIT(0) ++#define ESPI_CH3_INT_EN 0x40c ++#define ESPI_CH3_INT_EN_RX_CMPLT BIT(0) ++#define ESPI_CH3_RX_DMAL 0x410 ++#define ESPI_CH3_RX_DMAH 0x414 ++#define ESPI_CH3_RX_CTRL 0x418 ++#define ESPI_CH3_RX_CTRL_SERV_PEND BIT(31) ++#define ESPI_CH3_RX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_CH3_RX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_CH3_RX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_CH3_RX_DATA 0x41c ++#define ESPI_CH3_TX_DMAL 0x420 ++#define ESPI_CH3_TX_DMAH 0x424 ++#define ESPI_CH3_TX_CTRL 0x428 ++#define ESPI_CH3_TX_CTRL_TRIG_PEND BIT(31) ++#define ESPI_CH3_TX_CTRL_LEN GENMASK(23, 12) ++#define ESPI_CH3_TX_CTRL_TAG GENMASK(11, 8) ++#define ESPI_CH3_TX_CTRL_CYC GENMASK(7, 0) ++#define ESPI_CH3_TX_DATA 0x42c ++#define ESPI_CH3_EDAF_TADDRL 0x430 ++#define ESPI_CH3_EDAF_TADDRH 0x434 ++#define ESPI_CH3_EDAF_MASKL 0x438 ++#define ESPI_CH3_EDAF_MASKH 0x43c ++#define ESPI_CH3_WPROT0 0x4f8 ++#define ESPI_CH3_WPROT1 0x4fc ++ ++/* eDAF filter registers */ ++#define ESPI_EDAF_FLTR_SADDR0 0x510 ++#define ESPI_EDAF_FLTR_EADDR0 0x514 ++#define ESPI_EDAF_FLTR_SADDR1 0x518 ++#define ESPI_EDAF_FLTR_EADDR1 0x51c ++#define ESPI_EDAF_FLTR_SADDR2 0x520 ++#define ESPI_EDAF_FLTR_EADDR2 0x524 ++#define ESPI_EDAF_FLTR_SADDR3 0x528 ++#define ESPI_EDAF_FLTR_EADDR3 0x52c ++#define ESPI_EDAF_FLTR_SADDR4 0x530 ++#define ESPI_EDAF_FLTR_EADDR4 0x534 ++#define ESPI_EDAF_FLTR_SADDR5 0x538 ++#define ESPI_EDAF_FLTR_EADDR5 0x53c ++#define ESPI_EDAF_FLTR_SADDR6 0x540 ++#define ESPI_EDAF_FLTR_EADDR6 0x544 ++#define ESPI_EDAF_FLTR_SADDR7 0x548 ++#define ESPI_EDAF_FLTR_EADDR7 0x54c ++#define ESPI_EDAF_FLTR_SADDR8 0x550 ++#define ESPI_EDAF_FLTR_EADDR8 0x554 ++#define ESPI_EDAF_FLTR_SADDR9 0x558 ++#define ESPI_EDAF_FLTR_EADDR9 0x55c ++#define ESPI_EDAF_FLTR_SADDR10 0x560 ++#define ESPI_EDAF_FLTR_EADDR10 0x564 ++#define ESPI_EDAF_FLTR_SADDR11 0x568 ++#define ESPI_EDAF_FLTR_EADDR11 0x56c ++#define ESPI_EDAF_FLTR_SADDR12 0x570 ++#define ESPI_EDAF_FLTR_EADDR12 0x574 ++#define ESPI_EDAF_FLTR_SADDR13 0x578 ++#define ESPI_EDAF_FLTR_EADDR13 0x57c ++#define ESPI_EDAF_FLTR_SADDR14 0x580 ++#define ESPI_EDAF_FLTR_EADDR14 0x584 ++#define ESPI_EDAF_FLTR_SADDR15 0x588 ++#define ESPI_EDAF_FLTR_EADDR15 0x58c ++#define ESPI_EDAF_WPROT0 0x5f8 ++#define ESPI_EDAF_WPROT1 0x5fc ++ ++void ast2700_espi_pre_init(struct aspeed_espi *espi); ++void ast2700_espi_post_init(struct aspeed_espi *espi); ++void ast2700_espi_deinit(struct aspeed_espi *espi); ++int ast2700_espi_perif_probe(struct aspeed_espi *espi); ++int ast2700_espi_perif_remove(struct aspeed_espi *espi); ++int ast2700_espi_vw_probe(struct aspeed_espi *espi); ++int ast2700_espi_vw_remove(struct aspeed_espi *espi); ++int ast2700_espi_oob_probe(struct aspeed_espi *espi); ++int ast2700_espi_oob_remove(struct aspeed_espi *espi); ++int ast2700_espi_flash_probe(struct aspeed_espi *espi); ++int ast2700_espi_flash_remove(struct aspeed_espi *espi); ++irqreturn_t ast2700_espi_isr(int irq, void *arg); ++ ++#endif +diff --git a/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.c b/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.c +--- a/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.c 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,1352 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) ASPEED Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ast2700-espi.h" ++#include "ast2700-intel-espi-mmbi.h" ++ ++#define HOST_READ_SCI_STATUS_BIT(x) ((x) & ESPI_CH1_EVT0_SCI_EVT) ++#define HOST_READ_RESET_REQ_BIT(x) ((x) & BIT(0)) ++#define HOST_READ_IF_UP_BIT(x) ((x) & BIT(1)) ++#define HOST_READ_RDY_BIT(x) ((x) & BIT(0)) ++#define HOST_READ_BUF_PTR(x) ((x) & GENMASK(31, 2)) ++ ++/* LPC register for SCI interrupt */ ++#define AST_LPC_SWCR0300 0x180 ++#define LPC_BMC_TRIG_WAKEUP_EVT_STS BIT(8) ++ ++#define AST_LPC_SWCR0704 0x184 ++#define LPC_BMC_TRIG_WAKEUP_EVT_EN BIT(8) ++ ++#define AST_LPC_SWCR0B08 0x188 ++#define LPC_BMC_TRIG_WAKEUP_EVT BIT(6) ++ ++#define AST_LPC_ACPIB3B0 0x1A8 ++#define LPC_BMC_TRIG_SCI_EVT_STS BIT(8) ++ ++#define AST_LPC_ACPIB7B4 0x1AC ++#define LPC_BMC_TRIG_SCI_EVT_EN BIT(8) ++ ++#define MMBI_SIGNATURE_STRING "#MMBI$" ++#define MMBI_BUF_TYPE_VPSCB_V1 0x01 /* Variable Packet Size Circular Buffer */ ++#define MMBI_MEMORY_SIZE SZ_64M ++#define MMBI_MAX_SUPPORT_PROTOCOLS 5 ++#define MMBI_HDR_LEN_MASK GENMASK(23, 2) ++#define MMBI_HDR_PADDING_MASK GENMASK(1, 0) ++#define MMBI_HDR_TYPE_MASK GENMASK(27, 24) ++#define AST2700_ESPI_MMBI_MAX_CHANNELS 8 ++ ++enum ast2700_espi_mmbi_state { /* B_U B_R H_U H_R */ ++ ESPI_MMBI_INIT_IN_PROGRESS = 0x00, /* 0 0 0 0 */ ++ ESPI_MMBI_INIT_COMPLETED = 0x08, /* 1 0 0 0 */ ++ ESPI_MMBI_NORMAL_RUNTIME = 0x0A, /* 1 0 1 0 */ ++ ESPI_MMBI_RESET_REQ_BY_BMC = 0x0E, /* 1 1 1 0 */ ++ ESPI_MMBI_RESET_REQ_BY_HOST = 0x0B, /* 1 0 1 1 */ ++ ESPI_MMBI_RESET_ACKED = 0x0F, /* 1 1 1 1 */ ++ ESPI_MMBI_TRANS_TO_INIT = 0x07, /* 0 1 1 1 */ ++ ESPI_MMBI_INIT_MISMATCH = 0x09, /* 1 0 0 1 */ ++ ESPI_MMBI_POWER_UP_OR_ERROR = 0x10, /* other states */ ++}; ++ ++// addresses below, if not specified, are all virtual addresses ++struct aspeed_mmbi_host_rws { ++ u32 host_rst_req: 1; ++ u32 host_if_up: 1; ++ u32 host_buf_wr_ptr: 30; /* Address that host should write data into */ ++ u32 host_rdy: 1; ++ u32 rsvd: 1; ++ u32 bmc_buf_rd_ptr: 30; /* Address that host should read data from */ ++}; ++ ++struct aspeed_mmbi_host_ros { ++ u32 bmc_rst_req: 1; ++ u32 bmc_if_up: 1; ++ u32 bmc_buf_wr_ptr: 30; /* Address that BMC should write data into */ ++ u32 bmc_rdy: 1; ++ u32 rsvd: 1; ++ u32 host_buf_rd_ptr: 30; /* Address that BMC should read data from */ ++}; ++ ++/* MMBI Variable Packet Size Circular Buffer(VPSCB) v1 descriptor ++ * memory addresses are all offset ++ */ ++struct aspeed_mmbi_vpscb_desc { ++ u32 host_ros_pointer; /* Host ROS Offset */ ++ u32 host_rws_pointer; /* Host RWS Offset */ ++ u8 host_int_type; /* how BMC interrupt host */ ++ u8 host_int_location; /* which channel BMC interrupt host given int_type */ ++ u8 rsvd1[3]; ++ u8 host_int_val; /* what BMC will write to interrupt host */ ++ u8 bmc_int_type; /* how host interrupt BMC */ ++ u32 bmc_int_location; /* which address host should write to interrupt BMC */ ++ u8 rsvd2[4]; ++ u8 bmc_int_val; /* what host will write to interrupt BMC */ ++} __packed; ++ ++/* memory addresses are all physical */ ++struct aspeed_mmbi_desc { ++ u8 mmbi_signature[6]; ++ u8 mmbi_version; ++ u8 os_use; ++ u32 b2h_buf_base_addr; ++ u32 h2b_buf_base_addr; ++ u32 b2h_buf_len; ++ u32 h2b_buf_len; ++ u8 buf_type; ++ u8 rsvd1[7]; ++ struct aspeed_mmbi_vpscb_desc buf_desc; ++ u8 rsvd2[8]; ++}; ++ ++struct aspeed_mmbi_protocol_dev { ++ struct miscdevice miscdev; ++ struct aspeed_espi_mmbi_channel *channel; ++ u8 protocol_type; ++ bool data_available; ++ /* ++ * If user space application is opened for read, then only process ++ * the data and copy to userspace. Otherwise, discard the command and ++ * process the remaining commands (can be different protocol type) ++ */ ++ bool process_data; ++ wait_queue_head_t queue; ++}; ++ ++struct aspeed_espi_mmbi_channel { ++ u8 *desc; ++ u8 *host_ros; ++ u8 *host_rws; ++ u8 *b2h_circular_buf; ++ u32 b2h_circular_buf_size; ++ u8 *h2b_circular_buf; ++ u32 h2b_circular_buf_size; ++ struct ast2700_espi_mmbi *priv; ++ ++ struct aspeed_mmbi_protocol_dev protocols[MMBI_MAX_SUPPORT_PROTOCOLS]; ++ ++ enum ast2700_espi_mmbi_state state; ++ ++ bool enabled; ++ int chann_idx; ++}; ++ ++struct ast2700_espi_mmbi { ++ struct regmap *mmbi_map; ++ struct regmap *espi_map; ++ struct regmap *lpc_map; ++ struct device *dev; ++ struct aspeed_espi_mmbi_channel channels[AST2700_ESPI_MMBI_MAX_CHANNELS]; ++ phys_addr_t host_map_addr; ++ dma_addr_t mmbi_phys_addr; ++ u8 *mmbi_virt_addr; ++ resource_size_t mmbi_size; ++ u32 mmbi_instances; ++}; ++ ++struct ast2700_espi_mmbi_hdr { ++ u32 data; ++}; ++ ++static void raise_sci_interrupt(struct aspeed_espi_mmbi_channel *channel) ++{ ++ u32 val; ++ int retry; ++ struct regmap *lpc_regmap = channel->priv->lpc_map; ++ ++ dev_dbg(channel->priv->dev, "Raising SCI interrupt...\n"); ++ ++ regmap_write_bits(lpc_regmap, AST_LPC_ACPIB7B4, LPC_BMC_TRIG_SCI_EVT_EN, ++ LPC_BMC_TRIG_SCI_EVT_EN); ++ ++ regmap_write_bits(lpc_regmap, AST_LPC_SWCR0704, ++ LPC_BMC_TRIG_WAKEUP_EVT_EN, ++ LPC_BMC_TRIG_WAKEUP_EVT_EN); ++ ++ regmap_write_bits(lpc_regmap, AST_LPC_SWCR0B08, LPC_BMC_TRIG_WAKEUP_EVT, ++ LPC_BMC_TRIG_WAKEUP_EVT); ++ ++ /* ++ * Just asserting the SCI VW will trigger the SCI event continuosly. ++ * So BMC must deassert SCI VW to avoid it. ++ * ESPI098[24] reading will confirm Host read data or not. ++ * - 0 means host read the data ++ * - 1 means host not yet read data, so retry with 1us delay. ++ */ ++ retry = 30; ++ while (retry) { ++ if (regmap_read(channel->priv->espi_map, ESPI_CH1_EVT0, &val)) { ++ dev_err(channel->priv->dev, "Unable to read ESPI210\n"); ++ break; ++ } ++ ++ if (HOST_READ_SCI_STATUS_BIT(val) == 0) ++ break; ++ ++ retry--; ++ dev_dbg(channel->priv->dev, ++ "Host SCI handler not invoked(ESPI210: 0x%0x), so retry(%d) after 1us...\n", ++ val, retry); ++ udelay(1); ++ } ++ ++ regmap_write_bits(lpc_regmap, AST_LPC_SWCR0300, ++ LPC_BMC_TRIG_WAKEUP_EVT_STS, ++ LPC_BMC_TRIG_WAKEUP_EVT_STS); ++ ++ regmap_write_bits(lpc_regmap, AST_LPC_ACPIB3B0, ++ LPC_BMC_TRIG_SCI_EVT_STS, ++ LPC_BMC_TRIG_SCI_EVT_STS); ++} ++ ++static void raise_missing_sci(struct aspeed_espi_mmbi_channel *channel) ++{ ++ int chan_idx; ++ u32 h_rwp1; ++ u32 b2h_rp, b2h_wp; ++ struct aspeed_mmbi_host_ros *h_ros_ptr; ++ ++ chan_idx = channel->chann_idx; ++ if (regmap_read(channel->priv->mmbi_map, ESPI_MMBI_HOST_READ_RWP1(chan_idx), &h_rwp1)) { ++ dev_err(channel->priv->dev, "Failed to read Host RWP1\n"); ++ return; ++ } ++ if (!HOST_READ_RDY_BIT(h_rwp1)) { ++ // Host is not ready, no point in raising the SCI ++ return; ++ } ++ ++ h_ros_ptr = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ b2h_wp = h_ros_ptr->bmc_buf_wr_ptr << 2; ++ b2h_rp = HOST_READ_BUF_PTR(h_rwp1); ++ ++ if (b2h_wp == b2h_rp) { ++ // Host has read all outstanding SCI data, ++ // Do not raise another SCI. ++ return; ++ } ++ ++ dev_dbg(channel->priv->dev, ++ "Host not read the data yet, so rising SCI interrupt again...\n"); ++ raise_sci_interrupt(channel); ++} ++ ++static int ast2700_espi_mmbi_parse_hdr(struct aspeed_espi_mmbi_channel *channel, ++ u32 *data_length, u8 *type, ++ u32 *unread_data_len, u8 *padding) ++{ ++ u32 h2b_rp, h2b_wp; ++ struct ast2700_espi_mmbi_hdr header; ++ struct aspeed_mmbi_host_ros *h_ros_ptr; ++ struct aspeed_mmbi_host_rws *h_rws_ptr; ++ ++ h_ros_ptr = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ h_rws_ptr = (struct aspeed_mmbi_host_rws *)channel->host_rws; ++ ++ h2b_wp = h_rws_ptr->host_buf_wr_ptr << 2; ++ h2b_rp = h_ros_ptr->host_buf_rd_ptr << 2; ++ ++ if (h2b_wp >= h2b_rp) ++ *unread_data_len = h2b_wp - h2b_rp; ++ else ++ *unread_data_len = channel->h2b_circular_buf_size - h2b_rp + h2b_wp; ++ ++ if (*unread_data_len < sizeof(struct ast2700_espi_mmbi_hdr)) { ++ dev_dbg(channel->priv->dev, "No data to read(%d -%d)\n", h2b_wp, ++ h2b_rp); ++ return -EAGAIN; ++ } ++ ++ dev_dbg(channel->priv->dev, "READ MMBI header from: 0x%0lx\n", ++ (ssize_t)(channel->h2b_circular_buf + h2b_rp)); ++ ++ if (h2b_rp + sizeof(struct ast2700_espi_mmbi_hdr) <= channel->h2b_circular_buf_size) { ++ memcpy_fromio((void *)&header, channel->h2b_circular_buf + h2b_rp, ++ sizeof(struct ast2700_espi_mmbi_hdr)); ++ } else { ++ ssize_t chunk_len = channel->h2b_circular_buf_size - h2b_rp; ++ ++ memcpy_fromio((void *)&header, channel->h2b_circular_buf + h2b_rp, ++ chunk_len); ++ memcpy_fromio(((u8 *)&header) + chunk_len, channel->h2b_circular_buf, ++ sizeof(struct ast2700_espi_mmbi_hdr) - chunk_len); ++ } ++ ++ *padding = FIELD_GET(MMBI_HDR_PADDING_MASK, header.data); ++ *data_length = FIELD_GET(MMBI_HDR_LEN_MASK, header.data) * 4 - *padding; ++ *type = FIELD_GET(MMBI_HDR_TYPE_MASK, header.data); ++ ++ return 0; ++} ++ ++static void ast2700_espi_mmbi_update_host_ros(struct aspeed_espi_mmbi_channel *channel, ++ u32 read_len, u32 write_len) ++{ ++ struct aspeed_mmbi_host_ros *h_ros_ptr; ++ u32 rd_ptr, wr_ptr; ++ ++ h_ros_ptr = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ rd_ptr = h_ros_ptr->host_buf_rd_ptr << 2; ++ wr_ptr = h_ros_ptr->bmc_buf_wr_ptr << 2; ++ ++ /* Advance the B2H CB offset for next write */ ++ if ((wr_ptr + write_len) <= channel->b2h_circular_buf_size) ++ wr_ptr += write_len; ++ else ++ wr_ptr = wr_ptr + write_len - channel->b2h_circular_buf_size; ++ ++ /* Advance the H2B CB offset till where BMC read data */ ++ if ((rd_ptr + read_len) <= channel->h2b_circular_buf_size) ++ rd_ptr += read_len; ++ else ++ rd_ptr = rd_ptr + read_len - channel->h2b_circular_buf_size; ++ ++ h_ros_ptr->bmc_buf_wr_ptr = wr_ptr >> 2; ++ h_ros_ptr->host_buf_rd_ptr = rd_ptr >> 2; ++ ++ dev_dbg(channel->priv->dev, "Updating HROS - h2b_rp: 0x%0x, b2h_wp: 0x%0x\n", ++ rd_ptr, wr_ptr); ++ ++ if (write_len != 0) ++ raise_sci_interrupt(channel); ++} ++ ++static void ast2700_espi_mmbi_host_ifup_handler(struct aspeed_espi_mmbi_channel *channel) ++{ ++ struct aspeed_mmbi_host_ros *hros; ++ ++ dev_info(channel->priv->dev, ++ "Handle Host interface up request on MMBI channel(%d), state %d\n", ++ channel->chann_idx, channel->state); ++ ++ switch (channel->state) { ++ case ESPI_MMBI_INIT_COMPLETED: ++ hros = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ hros->bmc_rdy = 1; ++ channel->state = ESPI_MMBI_NORMAL_RUNTIME; ++ break; ++ default: ++ dev_err(channel->priv->dev, ++ "MMBI channel(%d) host state error\n", ++ channel->chann_idx); ++ channel->state = ESPI_MMBI_POWER_UP_OR_ERROR; ++ break; ++ } ++} ++ ++static void ast2700_espi_mmbi_host_reset_handler(struct aspeed_espi_mmbi_channel *channel) ++{ ++ dev_info(channel->priv->dev, ++ "Handle Host reset request on MMBI channel(%d), state %d\n", ++ channel->chann_idx, channel->state); ++ ++ switch (channel->state) { ++ case ESPI_MMBI_INIT_COMPLETED: ++ memset(channel->host_ros, 0, sizeof(struct aspeed_mmbi_host_ros)); ++ dev_err(channel->priv->dev, ++ "Initialization mismatch on MMBI channel(%d)\n", ++ channel->chann_idx); ++ channel->state = ESPI_MMBI_INIT_MISMATCH; ++ break; ++ case ESPI_MMBI_NORMAL_RUNTIME: ++ /* Host requested for MMBI buffer reset */ ++ memset(channel->host_ros, 0, sizeof(struct aspeed_mmbi_host_ros)); ++ channel->state = ESPI_MMBI_INIT_IN_PROGRESS; ++ ++ dev_info(channel->priv->dev, ++ "Handle Host reset request on MMBI channel(%d)\n", ++ channel->chann_idx); ++ break; ++ default: ++ dev_err(channel->priv->dev, ++ "Invalid state(%d) on MMBI channel(%d) to handle Host reset request\n", ++ channel->state, channel->chann_idx); ++ channel->state = ESPI_MMBI_POWER_UP_OR_ERROR; ++ break; ++ } ++} ++ ++static void ast2700_espi_mmbi_host_write_handler(struct aspeed_espi_mmbi_channel *channel) ++{ ++ u32 data_length, unread_data_len; ++ u8 type, padding; ++ int rc, i; ++ bool handled = false; ++ ++ dev_info(channel->priv->dev, ++ "Handle Host write on MMBI channel(%d)\n", ++ channel->chann_idx); ++ ++ rc = ast2700_espi_mmbi_parse_hdr(channel, &data_length, &type, &unread_data_len, &padding); ++ if (rc) { ++ dev_dbg(channel->priv->dev, ++ "Failed to parse MMBI header on MMBI channel(%d)\n", ++ channel->chann_idx); ++ return; ++ } ++ ++ for (i = 0; i < MMBI_MAX_SUPPORT_PROTOCOLS; i++) { ++ if (channel->protocols[i].protocol_type == type) { ++ if (channel->protocols[i].process_data) { ++ handled = true; ++ channel->protocols[i].data_available = true; ++ wake_up_interruptible(&channel->protocols[i].queue); ++ } else { ++ ast2700_espi_mmbi_update_host_ros(channel, ++ data_length + ++ sizeof(struct ast2700_espi_mmbi_hdr) + ++ padding, ++ 0); ++ } ++ } ++ } ++ ++ if (!handled) { ++ dev_warn(channel->priv->dev, ++ "No handler for protocol type %d on MMBI channel(%d), discard data\n", ++ type, channel->chann_idx); ++ ast2700_espi_mmbi_update_host_ros(channel, data_length + ++ sizeof(struct ast2700_espi_mmbi_hdr) + ++ padding, 0); ++ raise_sci_interrupt(channel); ++ } ++ ++ dev_dbg(channel->priv->dev, "MMBI header parsed on MMBI channel(%d)\n", ++ channel->chann_idx); ++ dev_dbg(channel->priv->dev, "data len %d unread %d, type %d, padding %d\n", ++ data_length, unread_data_len, type, padding); ++} ++ ++static void ast2700_espi_mmbi_host_rwp0_handler(struct aspeed_espi_mmbi_channel *channel) ++{ ++ int chan_idx; ++ u32 h_rws_cur, h_rws_prev; ++ ++ memcpy_fromio(&h_rws_prev, channel->host_rws, sizeof(h_rws_prev)); ++ chan_idx = channel->chann_idx; ++ if (regmap_read(channel->priv->mmbi_map, ESPI_MMBI_HOST_READ_RWP0(chan_idx), &h_rws_cur)) { ++ dev_err(channel->priv->dev, "Failed to read Host RWP0\n"); ++ return; ++ } ++ ++ dev_dbg(channel->priv->dev, "Host RWP0 changed on MMBI channel(%d): 0x%08x -> 0x%08x\n", ++ channel->chann_idx, h_rws_prev, h_rws_cur); ++ ++ memcpy_fromio(channel->host_rws, &h_rws_cur, sizeof(h_rws_cur)); ++ if (HOST_READ_RESET_REQ_BIT(h_rws_cur) & ~HOST_READ_RESET_REQ_BIT(h_rws_prev)) ++ ast2700_espi_mmbi_host_reset_handler(channel); ++ else if (HOST_READ_IF_UP_BIT(h_rws_cur) & ~HOST_READ_IF_UP_BIT(h_rws_prev)) ++ ast2700_espi_mmbi_host_ifup_handler(channel); ++ else ++ ast2700_espi_mmbi_host_write_handler(channel); ++} ++ ++static void ast2700_espi_mmbi_host_rwp1_handler(struct aspeed_espi_mmbi_channel *channel) ++{ ++ int chan_idx; ++ u32 h_rws_cur, h_rws_prev; ++ ++ memcpy_fromio(&h_rws_prev, channel->host_rws + 4, sizeof(h_rws_prev)); ++ chan_idx = channel->chann_idx; ++ if (regmap_read(channel->priv->mmbi_map, ESPI_MMBI_HOST_READ_RWP1(chan_idx), &h_rws_cur)) { ++ dev_err(channel->priv->dev, "Failed to read Host RWP1\n"); ++ return; ++ } ++ ++ dev_dbg(channel->priv->dev, "Host RWP1 changed on MMBI channel(%d): 0x%08x -> 0x%08x\n", ++ channel->chann_idx, h_rws_prev, h_rws_cur); ++ ++ memcpy_fromio(channel->host_rws + 4, &h_rws_cur, sizeof(h_rws_cur)); ++} ++ ++static void send_bmc_up_request(struct aspeed_espi_mmbi_channel *channel) ++{ ++ struct aspeed_mmbi_host_ros *hros; ++ ++ if (!channel->enabled || channel->state != ESPI_MMBI_INIT_IN_PROGRESS) { ++ dev_err(channel->priv->dev, ++ "Invalid state(%d) on MMBI channel(%d) to send BMC interface up request\n", ++ channel->state, channel->chann_idx); ++ return; ++ } ++ ++ hros = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ hros->bmc_if_up = 1; ++ channel->state = ESPI_MMBI_INIT_COMPLETED; ++ ++ dev_info(channel->priv->dev, ++ "Send BMC interface up request on MMBI channel(%d)\n", ++ channel->chann_idx); ++ ++ raise_sci_interrupt(channel); ++} ++ ++static int send_bmc_reset_request(struct aspeed_espi_mmbi_channel *channel) ++{ ++ struct aspeed_mmbi_host_ros *hros; ++ ++ hros = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ if (!channel->enabled) { ++ dev_err(channel->priv->dev, ++ "Invalid state(%d) on MMBI channel(%d) to send BMC reset request\n", ++ channel->enabled, channel->chann_idx); ++ return -EINVAL; ++ } ++ memset(channel->host_ros, 0, sizeof(struct aspeed_mmbi_host_ros)); ++ hros->bmc_rst_req = 1; ++ raise_sci_interrupt(channel); ++ ++ return 0; ++} ++ ++static struct aspeed_mmbi_protocol_dev *file_aspeed_espi_mmbi(struct file *file) ++{ ++ return container_of(file->private_data, struct aspeed_mmbi_protocol_dev, ++ miscdev); ++} ++ ++static int ast2700_espi_mmbi_open(struct inode *inode, struct file *filp) ++{ ++ return 0; ++} ++ ++static int ast2700_espi_mmbi_release(struct inode *inode, struct file *filp) ++{ ++ return 0; ++} ++ ++static unsigned int ast2700_espi_mmbi_poll(struct file *filp, poll_table *wait) ++{ ++ struct aspeed_mmbi_protocol_dev *protocol = file_aspeed_espi_mmbi(filp); ++ ++ poll_wait(filp, &protocol->queue, wait); ++ ++ return protocol->data_available ? POLLIN : 0; ++} ++ ++static int get_mmbi_config(struct aspeed_espi_mmbi_channel *channel, void __user *userbuf) ++{ ++ struct aspeed_mmbi_host_ros *h_ros_ptr; ++ struct aspeed_mmbi_host_rws *h_rws_ptr; ++ struct aspeed_mmbi_get_config get_conf; ++ ++ h_ros_ptr = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ h_rws_ptr = (struct aspeed_mmbi_host_rws *)channel->host_rws; ++ ++ get_conf.h_rdy = h_rws_ptr->host_rdy ? true : false; ++ get_conf.h2b_wp = h_rws_ptr->host_buf_wr_ptr << 2; ++ get_conf.b2h_rp = h_rws_ptr->bmc_buf_rd_ptr << 2; ++ get_conf.h2b_rp = h_ros_ptr->host_buf_rd_ptr << 2; ++ get_conf.b2h_wp = h_ros_ptr->bmc_buf_wr_ptr << 2; ++ ++ if (copy_to_user(userbuf, &get_conf, sizeof(get_conf))) { ++ dev_err(channel->priv->dev, "copy to user failed\n"); ++ return -EFAULT; ++ } ++ return 0; ++} ++ ++static int get_b2h_empty_space(struct aspeed_espi_mmbi_channel *channel, ++ ssize_t *length) ++{ ++ struct aspeed_mmbi_host_ros *h_ros_ptr; ++ struct aspeed_mmbi_host_rws *h_rws_ptr; ++ ssize_t avail_buf_len; ++ ++ h_ros_ptr = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ h_rws_ptr = (struct aspeed_mmbi_host_rws *)channel->host_rws; ++ ++ if (h_ros_ptr->bmc_buf_wr_ptr >= h_rws_ptr->bmc_buf_rd_ptr) ++ avail_buf_len = channel->b2h_circular_buf_size - (h_ros_ptr->bmc_buf_wr_ptr << 2) + ++ (h_rws_ptr->bmc_buf_rd_ptr << 2); ++ else ++ avail_buf_len = (h_rws_ptr->bmc_buf_rd_ptr << 2) - (h_ros_ptr->bmc_buf_wr_ptr << 2); ++ ++ dev_dbg(channel->priv->dev, "B2H buffer empty space: %ld\n", avail_buf_len); ++ ++ *length = avail_buf_len; ++ ++ return 0; ++} ++ ++static long ast2700_espi_mmbi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ struct aspeed_mmbi_protocol_dev *protocol = file_aspeed_espi_mmbi(filp); ++ struct aspeed_espi_mmbi_channel *channel = protocol->channel; ++ void __user *userbuf = (void __user *)arg; ++ ssize_t length; ++ int ret; ++ ++ switch (cmd) { ++ case ASPEED_MMBI_CTRL_IOCTL_GET_B2H_EMPTY_SPACE: ++ ret = get_b2h_empty_space(channel, &length); ++ if (ret == 0 && copy_to_user(userbuf, &length, sizeof(length))) { ++ dev_err(channel->priv->dev, "copy to user failed\n"); ++ ret = -EFAULT; ++ } ++ break; ++ ++ case ASPEED_MMBI_CTRL_IOCTL_SEND_RESET_REQUEST: ++ ret = send_bmc_reset_request(channel); ++ break; ++ ++ case ASPEED_MMBI_CTRL_IOCTL_GET_CONFIG: ++ ret = get_mmbi_config(channel, userbuf); ++ break; ++ ++ default: ++ dev_err(channel->priv->dev, "Command not found\n"); ++ ret = -ENOTTY; ++ } ++ ++ return ret; ++} ++ ++static ssize_t ast2700_espi_mmbi_read(struct file *filp, char *buff, size_t count, ++ loff_t *offp) ++{ ++ struct aspeed_mmbi_protocol_dev *protocol = file_aspeed_espi_mmbi(filp); ++ struct aspeed_espi_mmbi_channel *channel = protocol->channel; ++ struct ast2700_espi_mmbi *priv = channel->priv; ++ struct aspeed_mmbi_host_ros *h_ros_ptr; ++ u32 data_length, unread_data_len; ++ u32 buf_rd_offset; ++ u8 type, padding; ++ ssize_t rd_offset, rd_len; ++ ssize_t ret; ++ ++ protocol->process_data = true; ++ if (!protocol->data_available && (filp->f_flags & O_NONBLOCK)) { ++ raise_missing_sci(channel); ++ return -EAGAIN; ++ } ++ ++ dev_dbg(priv->dev, "%s: count:%ld, Type: %d\n", __func__, count, ++ protocol->protocol_type); ++ ++ ret = wait_event_interruptible(protocol->queue, protocol->data_available); ++ if (ret == -ERESTARTSYS) { ++ ret = -EINTR; ++ goto err_out; ++ } ++ ++ ret = ast2700_espi_mmbi_parse_hdr(channel, &data_length, &type, &unread_data_len, &padding); ++ if (ret) { ++ dev_err(priv->dev, "%s: Failed to parse MMBI header\n", __func__); ++ goto err_out; ++ } ++ ++ dev_dbg(priv->dev, "%s: PKT Length: %d, Type: %d, Unread Data Length: %d, Padding: %d\n", ++ __func__, data_length, type, unread_data_len, padding); ++ ++ if (data_length > count) { ++ dev_err(priv->dev, "%s: Buffer too small, need %d\n", __func__, ++ data_length); ++ ret = -EMSGSIZE; ++ /* discard this packet */ ++ ast2700_espi_mmbi_update_host_ros(channel, data_length + sizeof(struct ast2700_espi_mmbi_hdr) + padding, 0); ++ goto err_out; ++ } ++ ++ if (type != protocol->protocol_type) { ++ dev_err(priv->dev, "%s: Protocol type mismatch, expected %d\n", ++ __func__, protocol->protocol_type); ++ ret = -EPROTO; ++ goto err_out; ++ } ++ ++ h_ros_ptr = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ buf_rd_offset = h_ros_ptr->host_buf_rd_ptr << 2; ++ if (buf_rd_offset + sizeof(struct ast2700_espi_mmbi_hdr) <= channel->h2b_circular_buf_size) ++ rd_offset = buf_rd_offset + sizeof(struct ast2700_espi_mmbi_hdr); ++ else ++ rd_offset = (buf_rd_offset + sizeof(struct ast2700_espi_mmbi_hdr)) - channel->h2b_circular_buf_size; ++ ++ rd_len = data_length; ++ ++ dev_dbg(priv->dev, "%s: Read from offset: 0x%0lx, length: %ld\n", ++ __func__, rd_offset, rd_len); ++ ++ if (unread_data_len < rd_len + sizeof(struct ast2700_espi_mmbi_hdr)) { ++ dev_err(priv->dev, "%s: Not enough data to read, need %ld\n", ++ __func__, rd_len + sizeof(struct ast2700_espi_mmbi_hdr)); ++ ret = -EAGAIN; ++ goto err_out; ++ } ++ ++ if ((channel->h2b_circular_buf_size - rd_offset) >= rd_len) { ++ if (copy_to_user(buff, channel->h2b_circular_buf + rd_offset, rd_len)) { ++ dev_err(priv->dev, "%s: copy to user failed\n", __func__); ++ ret = -EFAULT; ++ goto err_out; ++ } ++ } else { ++ ssize_t chunk_len = channel->h2b_circular_buf_size - rd_offset; ++ ++ if (copy_to_user(buff, channel->h2b_circular_buf + rd_offset, chunk_len) || ++ copy_to_user(buff + chunk_len, channel->h2b_circular_buf, ++ rd_len - chunk_len)) { ++ dev_err(priv->dev, "%s: copy to user failed\n", __func__); ++ ret = -EFAULT; ++ goto err_out; ++ } ++ } ++ ++ *offp += rd_len; ++ ret = rd_len; ++ ast2700_espi_mmbi_update_host_ros(channel, rd_len + sizeof(struct ast2700_espi_mmbi_hdr) + padding, 0); ++ dev_dbg(priv->dev, "%s: Read %ld bytes from MMBI\n", __func__, rd_len); ++err_out: ++ /* ++ * Raise the missing SCI's by checking pointer for host ++ * read acknowledgment. This will work around the Missing ++ * SCI bug on host side. * ++ */ ++ dev_warn(priv->dev, "%s: Check and raise missing SCI\n", __func__); ++ raise_missing_sci(channel); ++ ++ protocol->data_available = false; ++ ++ ast2700_espi_mmbi_host_write_handler(channel); ++ ++ return ret; ++} ++ ++static ssize_t ast2700_espi_mmbi_write(struct file *filp, const char *buffer, size_t len, ++ loff_t *offp) ++{ ++ struct aspeed_mmbi_protocol_dev *protocol = file_aspeed_espi_mmbi(filp); ++ struct aspeed_espi_mmbi_channel *channel = protocol->channel; ++ struct ast2700_espi_mmbi *priv = channel->priv; ++ struct aspeed_mmbi_host_rws *h_rws_ptr; ++ struct aspeed_mmbi_host_ros *h_ros_ptr; ++ struct ast2700_espi_mmbi_hdr header; ++ ++ ssize_t wr_offset, avail_buf_len, padding, chunk_len; ++ ++ h_rws_ptr = (struct aspeed_mmbi_host_rws *)channel->host_rws; ++ h_ros_ptr = (struct aspeed_mmbi_host_ros *)channel->host_ros; ++ ++ dev_dbg(priv->dev, "%s: length:%ld , type: %d\n", __func__, len, ++ protocol->protocol_type); ++ ++ if (!h_rws_ptr->host_if_up) { ++ dev_err(priv->dev, "%s: Host interface not up\n", __func__); ++ return -EAGAIN; ++ } ++ ++ if (get_b2h_empty_space(channel, &avail_buf_len)) { ++ dev_err(priv->dev, "%s: Failed to get B2H empty space\n", __func__); ++ return -EFAULT; ++ } ++ ++ if ((len + sizeof(struct ast2700_espi_mmbi_hdr)) > avail_buf_len) { ++ dev_err(priv->dev, "%s: No enough space in B2H buffer, need %ld but have %ld\n", ++ __func__, len + sizeof(struct ast2700_espi_mmbi_hdr), avail_buf_len); ++ return -ENOSPC; ++ } ++ padding = (4 - (len % 4)) % 4; ++ header.data = (protocol->protocol_type << 24) | (len & MMBI_HDR_LEN_MASK) | (padding & MMBI_HDR_PADDING_MASK); ++ wr_offset = h_ros_ptr->bmc_buf_wr_ptr << 2; ++ ++ if (wr_offset + sizeof(struct ast2700_espi_mmbi_hdr) <= channel->b2h_circular_buf_size) { ++ memcpy_toio(channel->b2h_circular_buf + wr_offset, (void *)&header, ++ sizeof(struct ast2700_espi_mmbi_hdr)); ++ wr_offset += sizeof(struct ast2700_espi_mmbi_hdr); ++ if (wr_offset == channel->b2h_circular_buf_size) ++ wr_offset = 0; ++ } else { ++ chunk_len = channel->b2h_circular_buf_size - wr_offset; ++ ++ memcpy_toio(channel->b2h_circular_buf + wr_offset, (void *)&header, ++ chunk_len); ++ memcpy_toio(channel->b2h_circular_buf, ((u8 *)&header) + chunk_len, ++ sizeof(struct ast2700_espi_mmbi_hdr) - chunk_len); ++ wr_offset = sizeof(struct ast2700_espi_mmbi_hdr) - chunk_len; ++ } ++ ++ if (wr_offset + len <= channel->b2h_circular_buf_size) { ++ if (copy_from_user(channel->b2h_circular_buf + wr_offset, buffer, len)) { ++ dev_err(priv->dev, "%s: copy from user failed\n", __func__); ++ return -EFAULT; ++ } ++ wr_offset += len; ++ if (wr_offset == channel->b2h_circular_buf_size) ++ wr_offset = 0; ++ } else { ++ chunk_len = channel->b2h_circular_buf_size - wr_offset; ++ ++ if (copy_from_user(channel->b2h_circular_buf + wr_offset, buffer, chunk_len) || ++ copy_from_user(channel->b2h_circular_buf, ++ buffer + chunk_len, len - chunk_len)) { ++ dev_err(priv->dev, "%s: copy from user failed\n", __func__); ++ return -EFAULT; ++ } ++ wr_offset = len - chunk_len; ++ } ++ ++ *offp += len; ++ ast2700_espi_mmbi_update_host_ros(channel, 0, len + sizeof(struct ast2700_espi_mmbi_hdr) + padding); ++ dev_dbg(priv->dev, "%s: Write %ld bytes to MMBI\n", __func__, len); ++ ++ return len; ++} ++ ++static const struct file_operations ast2700_espi_mmbi_fops = { ++ .owner = THIS_MODULE, ++ .open = ast2700_espi_mmbi_open, ++ .release = ast2700_espi_mmbi_release, ++ .read = ast2700_espi_mmbi_read, ++ .write = ast2700_espi_mmbi_write, ++ .unlocked_ioctl = ast2700_espi_mmbi_ioctl, ++ .poll = ast2700_espi_mmbi_poll ++}; ++ ++static const struct regmap_config aspeed_espi_mmbi_regmap_cfg = { ++ .name = "mmbi", ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = 0x04C, ++}; ++ ++static const struct regmap_config aspeed_espi_regmap_cfg = { ++ .name = "espi", ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = 0x7fc, ++}; ++ ++static const struct of_device_id aspeed_espi_mmbi_of_match[] = { ++ { .compatible = "aspeed,ast2700-intel-espi-mmbi" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_espi_mmbi_of_match); ++ ++static char *get_protocol_suffix(u8 type) ++{ ++ switch (type) { ++ case MMBI_PROTOCOL_IPMI: ++ return "ipmi"; ++ case MMBI_PROTOCOL_SEAMLESS: ++ return "seamless"; ++ case MMBI_PROTOCOL_RAS_OFFLOAD: ++ return "ras_offload"; ++ case MMBI_PROTOCOL_MCTP: ++ return "mctp"; ++ case MMBI_PROTOCOL_VENDOR: ++ return "vendor"; ++ default: ++ break; ++ } ++ ++ return NULL; ++} ++ ++static irqreturn_t ast2700_espi_mmbi_irq_handler(int irq, void *arg) ++{ ++ struct ast2700_espi_mmbi *priv = arg; ++ u32 status; ++ int idx; ++ ++ regmap_read(priv->mmbi_map, ESPI_MMBI_INT_STATUS, &status); ++ /* Clear interrupt */ ++ regmap_write(priv->mmbi_map, ESPI_MMBI_INT_STATUS, status); ++ ++ for (idx = 0; idx < AST2700_ESPI_MMBI_MAX_CHANNELS; idx++) { ++ /* ++ * Host RWP 0: It gets updated after Host reads data and also ++ * when host want to send reset MMBI buffer request. So ++ * Handle reset request and ignore read pointer update. ++ * Host RWP 1: It gets updated when host write data on H2B, ++ * So process the request by invoking corresponding device. ++ */ ++ if (!priv->channels[idx].enabled) ++ continue; ++ ++ if ((status >> (idx * 2)) & ESPI_MMBI_HOST_RWS0_INT) { ++ // Host_RWS[0:3] update ++ ast2700_espi_mmbi_host_rwp0_handler(&priv->channels[idx]); ++ } else if (status >> (idx * 2) & ESPI_MMBI_HOST_RWS1_INT) { ++ // Host_RWS[4:7] update ++ ast2700_espi_mmbi_host_rwp1_handler(&priv->channels[idx]); ++ } ++ } ++ dev_info(priv->dev, "MMBI IRQ Status: %d\n", status); ++ ++ return IRQ_HANDLED; ++} ++ ++static void aspeed_espi_mmbi_release_rmem(void *d) ++{ ++ of_reserved_mem_device_release(d); ++} ++ ++static void aspeed_espi_mmbi_dma_finish(struct ast2700_espi_mmbi *priv) ++{ ++ dma_free_coherent(priv->dev, MMBI_MEMORY_SIZE, priv->mmbi_virt_addr, priv->mmbi_phys_addr); ++} ++ ++static int aspeed_espi_mmbi_dma_init(struct ast2700_espi_mmbi *priv) ++{ ++ int rc; ++ ++ rc = dma_set_mask_and_coherent(priv->dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_err(priv->dev, "cannot set 64-bits DMA mask\n"); ++ return rc; ++ } ++ ++ /* DMA pool initialization */ ++ rc = of_reserved_mem_device_init(priv->dev); ++ if (rc) { ++ dev_err(priv->dev, "device does not have specific DMA pool: %d\n", ++ rc); ++ return rc; ++ } ++ ++ rc = devm_add_action_or_reset(priv->dev, aspeed_espi_mmbi_release_rmem, ++ priv->dev); ++ if (rc) { ++ dev_err(priv->dev, "Failed to add action to release reserved memory: %d\n", ++ rc); ++ return rc; ++ } ++ ++ priv->mmbi_virt_addr = dma_alloc_coherent(priv->dev, MMBI_MEMORY_SIZE, ++ &priv->mmbi_phys_addr, GFP_KERNEL); ++ ++ if (!priv->mmbi_virt_addr) { ++ dev_err(priv->dev, "Failed to allocate DMA memory for MMBI\n"); ++ return -ENOMEM; ++ } ++ priv->mmbi_size = MMBI_MEMORY_SIZE; ++ ++ return 0; ++} ++ ++static int aspeed_espi_mmbi_hw_init(struct ast2700_espi_mmbi *priv) ++{ ++ u64 mask; ++ u32 reg; ++ ++ /* disable interrupt and clear status */ ++ regmap_write(priv->espi_map, ESPI_CH0_INT_EN, 0x0); ++ regmap_write(priv->espi_map, ESPI_CH0_INT_STS, 0xffffffff); ++ ++ regmap_write(priv->mmbi_map, ESPI_MMBI_INT_ENABLE, 0); ++ regmap_write(priv->mmbi_map, ESPI_MMBI_INT_STATUS, 0xffffffff); ++ ++ /* disable address masking, in here we use memory cycle 0 */ ++ regmap_read(priv->espi_map, ESPI_CH0_MCYC0_MASKL, ®); ++ reg &= ~ESPI_CH0_MCYC0_MASKL_EN; ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_MASKL, reg); ++ ++ regmap_read(priv->espi_map, ESPI_CH0_MCYC0_MASKL, ®); ++ reg &= ~ESPI_CH0_MCYC0_MASKL_EN; ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_MASKL, reg); ++ ++ /* disable memory w/r and clear eSPI CH0 queues */ ++ regmap_read(priv->espi_map, ESPI_CH0_CTRL, ®); ++ reg |= (ESPI_CH0_CTRL_MCYC_RD_DIS | ESPI_CH0_CTRL_MCYC_WR_DIS); ++ reg &= ~(ESPI_CH0_CTRL_NP_TX_RST ++ | ESPI_CH0_CTRL_NP_RX_RST ++ | ESPI_CH0_CTRL_PC_TX_RST ++ | ESPI_CH0_CTRL_PC_RX_RST ++ | ESPI_CH0_CTRL_NP_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_TX_DMA_EN ++ | ESPI_CH0_CTRL_PC_RX_DMA_EN ++ | ESPI_CH0_CTRL_SW_RDY); ++ regmap_write(priv->espi_map, ESPI_CH0_CTRL, reg); ++ ++ udelay(1); ++ ++ reg |= (ESPI_CH0_CTRL_NP_TX_RST ++ | ESPI_CH0_CTRL_NP_RX_RST ++ | ESPI_CH0_CTRL_PC_TX_RST ++ | ESPI_CH0_CTRL_PC_RX_RST); ++ regmap_write(priv->espi_map, ESPI_CH0_CTRL, reg); ++ ++ /* disable espi-mmbi controller before configuring espi address */ ++ regmap_read(priv->mmbi_map, ESPI_MMBI_CTRL, ®); ++ reg &= ~ESPI_MMBI_CTRL_EN; ++ regmap_write(priv->mmbi_map, ESPI_MMBI_CTRL, reg); ++ ++ mask = ~(priv->mmbi_size - 1); ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_MASKH, mask >> 32); ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_MASKL, mask & 0xffffffff); ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_SADDRH, priv->host_map_addr >> 32); ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_SADDRL, priv->host_map_addr & 0xffffffff); ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_TADDRH, priv->mmbi_phys_addr >> 32); ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_TADDRL, priv->mmbi_phys_addr & 0xffffffff); ++ ++ /* enable espi-mmbi controller and configure instances */ ++ reg = FIELD_PREP(ESPI_MMBI_CTRL_INST_NUM, ilog2(priv->mmbi_instances)) | ESPI_MMBI_CTRL_EN; ++ regmap_write(priv->mmbi_map, ESPI_MMBI_CTRL, reg); ++ ++ /* enable eSPI memory cycle 0 */ ++ regmap_read(priv->espi_map, ESPI_CH0_MCYC0_MASKL, ®); ++ reg |= ESPI_CH0_MCYC0_MASKL_EN; ++ regmap_write(priv->espi_map, ESPI_CH0_MCYC0_MASKL, reg); ++ ++ /* keep Peripheral Channel Mem Read/Write after WDT reset */ ++ regmap_read(priv->espi_map, ESPI_CH0_CTRL, ®); ++ reg &= ~(ESPI_CH0_CTRL_MCYC_RD_DIS_WDT | ESPI_CH0_CTRL_MCYC_WR_DIS_WDT); ++ regmap_write(priv->espi_map, ESPI_CH0_CTRL, reg); ++ ++ return 0; ++} ++ ++static struct aspeed_mmbi_desc aspeed_espi_mmbi_desc_init(struct aspeed_espi_mmbi_channel channel) ++{ ++ struct aspeed_mmbi_desc desc; ++ ++ memset(&desc, 0, sizeof(desc)); ++ memcpy_fromio(desc.mmbi_signature, MMBI_SIGNATURE_STRING, sizeof(desc.mmbi_signature)); ++ desc.mmbi_version = 1; ++ desc.os_use = 1; ++ desc.buf_type = MMBI_BUF_TYPE_VPSCB_V1; ++ ++ desc.buf_desc.host_ros_pointer = (channel.host_ros - channel.desc) >> 3; ++ desc.buf_desc.host_rws_pointer = (channel.host_rws - channel.desc) >> 3; ++ ++ desc.buf_desc.host_int_type = 3; ++ desc.buf_desc.host_int_location = 0x06; ++ desc.buf_desc.host_int_val = 0x11; ++ desc.buf_desc.bmc_int_type = 0; ++ desc.buf_desc.bmc_int_location = 0x00; ++ desc.buf_desc.bmc_int_val = 0x00; ++ ++ desc.b2h_buf_base_addr = (u32)(channel.b2h_circular_buf - channel.desc) >> 3; ++ desc.h2b_buf_base_addr = (u32)(channel.h2b_circular_buf - channel.desc) >> 3; ++ desc.b2h_buf_len = channel.b2h_circular_buf_size; ++ desc.h2b_buf_len = channel.h2b_circular_buf_size; ++ ++ return desc; ++} ++ ++static int aspeed_espi_mmbi_chann_init(struct ast2700_espi_mmbi *priv, struct device_node *node, int seq, u8 idx) ++{ ++ struct aspeed_mmbi_desc desc; ++ int rc, i; ++ u8 protocol_num; ++ u8 protocol_supported[MMBI_MAX_SUPPORT_PROTOCOLS]; ++ u32 b2h_offset, h2b_offset; ++ u32 instance_size; ++ ++ memset(&priv->channels[idx], 0, sizeof(struct aspeed_espi_mmbi_channel)); ++ ++ /* configure virtual memory addresses ++ * In AST2700, memory layout is divided into B2H and H2B, ++ * B2H allocate top half and H2B allocate bottom half. ++ * For multiple instances, each instance is allocated separately in B2H and H2B. ++ * MMBI Capability Descriptor is at the top of B2H ++ */ ++ instance_size = MMBI_MEMORY_SIZE / 2 / priv->mmbi_instances; ++ b2h_offset = idx * instance_size; ++ h2b_offset = MMBI_MEMORY_SIZE / 2 + idx * instance_size; ++ ++ priv->channels[idx].desc = (u8 *)priv->mmbi_virt_addr + b2h_offset; ++ priv->channels[idx].host_ros = priv->channels[idx].desc + sizeof(struct aspeed_mmbi_desc); ++ priv->channels[idx].b2h_circular_buf = priv->channels[idx].host_ros + sizeof(struct aspeed_mmbi_host_ros); ++ priv->channels[idx].host_rws = (u8 *)priv->mmbi_virt_addr + h2b_offset; ++ priv->channels[idx].h2b_circular_buf = priv->channels[idx].host_rws + sizeof(struct aspeed_mmbi_host_rws); ++ ++ /* make sure buffer starts 4byte-aligned */ ++ priv->channels[idx].b2h_circular_buf = (u8 *)ALIGN((uintptr_t)priv->channels[idx].b2h_circular_buf, 4); ++ priv->channels[idx].h2b_circular_buf = (u8 *)ALIGN((uintptr_t)priv->channels[idx].h2b_circular_buf, 4); ++ ++ priv->channels[idx].b2h_circular_buf_size = instance_size - sizeof(struct aspeed_mmbi_desc) - ++ sizeof(struct aspeed_mmbi_host_ros); ++ priv->channels[idx].h2b_circular_buf_size = instance_size - sizeof(struct aspeed_mmbi_host_rws); ++ ++ memset(priv->channels[idx].host_ros, 0, sizeof(struct aspeed_mmbi_host_ros)); ++ memset(priv->channels[idx].host_rws, 0, sizeof(struct aspeed_mmbi_host_rws)); ++ priv->channels[idx].state = ESPI_MMBI_INIT_IN_PROGRESS; ++ ++ dev_dbg(priv->dev, "MMBI: Channel(%d) desc: 0x%llx, host_ros: 0x%llx, host_rws: 0x%llx\n", ++ idx, (unsigned long long)priv->channels[idx].desc, (unsigned long long)priv->channels[idx].host_ros, ++ (unsigned long long)priv->channels[idx].host_rws); ++ dev_dbg(priv->dev, "MMBI: Channel(%d) b2h_circular_buf: 0x%llx, h2b_circular_buf: 0x%llx\n", ++ idx, (unsigned long long)priv->channels[idx].b2h_circular_buf, ++ (unsigned long long)priv->channels[idx].h2b_circular_buf); ++ ++ desc = aspeed_espi_mmbi_desc_init(priv->channels[idx]); ++ memcpy_fromio(priv->channels[idx].desc, &desc, sizeof(desc)); ++ ++ priv->channels[idx].enabled = true; ++ priv->channels[idx].chann_idx = idx; ++ ++ if (!node) { ++ dev_err(priv->dev, "Fail to read instance"); ++ return -ENODEV; ++ } ++ ++ protocol_num = of_property_count_u8_elems(node, "protocols"); ++ if (protocol_num < 0 || protocol_num > MMBI_MAX_SUPPORT_PROTOCOLS) { ++ dev_err(priv->dev, "Invalid number of protocols: %d\n", protocol_num); ++ return -EINVAL; ++ } ++ ++ rc = of_property_read_u8_array(node, "protocols", protocol_supported, protocol_num); ++ if (rc) { ++ dev_err(priv->dev, "Failed to read protocols for channel %d: %d\n", idx, rc); ++ return rc; ++ } ++ ++ memset(priv->channels[idx].protocols, 0, sizeof(priv->channels[idx].protocols)); ++ for (i = 0; i < protocol_num; i++) { ++ u8 proto; ++ char *proto_name; ++ ++ proto = protocol_supported[i]; ++ proto_name = get_protocol_suffix(proto); ++ ++ if (!proto_name) { ++ dev_err(priv->dev, "Unsupported protocol type: %d\n", proto); ++ continue; ++ } ++ ++ priv->channels[idx].protocols[i].miscdev.name = ++ devm_kasprintf(priv->dev, GFP_KERNEL, "%s%d-%s%d-%s", "espi-mmbi", seq, "chann", idx, proto_name); ++ priv->channels[idx].protocols[i].miscdev.minor = MISC_DYNAMIC_MINOR; ++ priv->channels[idx].protocols[i].miscdev.fops = &ast2700_espi_mmbi_fops; /* to be implemented */ ++ priv->channels[idx].protocols[i].miscdev.parent = priv->dev; ++ rc = misc_register(&priv->channels[idx].protocols[i].miscdev); ++ if (rc) { ++ dev_err(priv->dev, "Failed to register miscdev for protocol %s: %d\n", ++ proto_name, rc); ++ continue; ++ } ++ priv->channels[idx].protocols[i].protocol_type = proto; ++ priv->channels[idx].protocols[i].channel = &priv->channels[idx]; ++ priv->channels[idx].protocols[i].data_available = false; ++ priv->channels[idx].protocols[i].process_data = false; ++ ++ init_waitqueue_head(&priv->channels[idx].protocols[i].queue); ++ } ++ priv->channels[idx].priv = priv; ++ ++ dev_info(priv->dev, "MMBI: Channel(%d) protocols: %*ph\n", idx, ++ protocol_num, protocol_supported); ++ ++ return 0; ++} ++ ++static int aspeed_espi_mmbi_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *dev_id; ++ struct ast2700_espi_mmbi *priv; ++ struct aspeed_espi *espi; ++ struct device_node *node; ++ void __iomem *regs; ++ int rc, seq, irq; ++ u32 i, mmbi_irq_en; ++ ++ dev_info(&pdev->dev, "ASPEED eSPI MMBI driver probe\n"); ++ if (!pdev->dev.parent) { ++ dev_err(&pdev->dev, "Device has no parent\n"); ++ return -ENODEV; ++ } ++ ++ espi = dev_get_drvdata(pdev->dev.parent); ++ if (!espi) { ++ dev_err(&pdev->dev, "Failed to get eSPI controller data\n"); ++ return -EPROBE_DEFER; ++ } ++ ++ dev_id = of_match_device(aspeed_espi_mmbi_of_match, &pdev->dev); ++ if (!dev_id) { ++ rc = PTR_ERR(dev_id); ++ goto out; ++ } ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(struct ast2700_espi_mmbi), GFP_KERNEL); ++ if (!priv) { ++ rc = -ENOMEM; ++ goto out; ++ } ++ ++ priv->dev = &pdev->dev; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) { ++ rc = PTR_ERR(regs); ++ goto out; ++ } ++ ++ /* MMBI register map */ ++ priv->mmbi_map = devm_regmap_init_mmio(&pdev->dev, regs, &aspeed_espi_mmbi_regmap_cfg); ++ if (IS_ERR(priv->mmbi_map)) { ++ rc = PTR_ERR(priv->mmbi_map); ++ goto out; ++ } ++ ++ /* ESPI register map */ ++ priv->espi_map = devm_regmap_init_mmio(priv->dev, espi->regs, ++ &aspeed_espi_regmap_cfg); ++ if (IS_ERR(priv->espi_map)) { ++ rc = PTR_ERR(priv->espi_map); ++ goto out; ++ } ++ ++ /* LPC register map */ ++ priv->lpc_map = syscon_regmap_lookup_by_phandle(priv->dev->of_node, ++ "aspeed,lpc"); ++ if (IS_ERR(priv->lpc_map)) { ++ rc = PTR_ERR(priv->lpc_map); ++ goto out; ++ } ++ ++ rc = aspeed_espi_mmbi_dma_init(priv); ++ if (rc) ++ goto out_dma; ++ ++ rc = of_property_read_u64(priv->dev->of_node, "host-map-addr", &priv->host_map_addr); ++ if (rc) { ++ dev_err(priv->dev, "Failed to read host-map-addr property\n"); ++ goto out_dma; ++ } ++ ++ rc = of_property_read_u32(priv->dev->of_node, "instances", &priv->mmbi_instances); ++ if (rc) { ++ dev_err(priv->dev, "Failed to read mmbi instances property, set to 1\n"); ++ priv->mmbi_instances = 1; ++ } ++ ++ /* check priv->mmbi_instances is less than 8 and is power of 2 */ ++ if (priv->mmbi_instances > 8 || !is_power_of_2(priv->mmbi_instances)) { ++ dev_err(priv->dev, "Invalid number of MMBI instances: %d\n", priv->mmbi_instances); ++ rc = -EINVAL; ++ goto out_dma; ++ } ++ ++ dev_info(priv->dev, "MMBI: HostAddr:0x%llx, Physical addr:0x%llx, instances: %d\n", ++ priv->host_map_addr, priv->mmbi_phys_addr, priv->mmbi_instances); ++ ++ rc = aspeed_espi_mmbi_hw_init(priv); ++ if (rc) { ++ dev_err(priv->dev, "Failed to initialize MMBI hardware: %d\n", rc); ++ goto out_dma; ++ } ++ ++ seq = of_alias_get_id(priv->dev->of_node, "espi-mmbi"); ++ if (seq < 0) { ++ dev_err(priv->dev, "Failed to get device sequence id\n"); ++ seq = 0; ++ } ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) { ++ rc = irq; ++ goto out_dma; ++ } ++ ++ rc = devm_request_irq(priv->dev, irq, ast2700_espi_mmbi_irq_handler, IRQF_SHARED, ++ dev_name(priv->dev), priv); ++ if (rc) { ++ dev_err(priv->dev, "Failed to request IRQ: %d\n", rc); ++ goto out_dma; ++ } ++ ++ mmbi_irq_en = 0; ++ for_each_child_of_node(priv->dev->of_node, node) { ++ rc = of_property_read_u32(node, "channel", &i); ++ if (rc || i >= AST2700_ESPI_MMBI_MAX_CHANNELS || priv->channels[i].enabled) ++ continue; ++ rc = aspeed_espi_mmbi_chann_init(priv, node, seq, i); ++ if (rc) ++ dev_err(priv->dev, "MMBI: Channel(%d) init failed\n", ++ i); ++ else ++ mmbi_irq_en += (0x03 << (i * 2)); ++ } ++ ++ regmap_write(priv->mmbi_map, ESPI_MMBI_INT_ENABLE, mmbi_irq_en); ++ ++ for (i = 0; i < AST2700_ESPI_MMBI_MAX_CHANNELS; i++) { ++ if (priv->channels[i].enabled) ++ send_bmc_up_request(&priv->channels[i]); ++ } ++ ++ dev_set_drvdata(priv->dev, priv); ++ return 0; ++out_dma: ++ if (priv->mmbi_virt_addr) ++ aspeed_espi_mmbi_dma_finish(priv); ++out: ++ dev_err(&pdev->dev, "Failed to probe ASPEED eSPI MMBI: %d\n", rc); ++ return rc; ++} ++ ++static void aspeed_espi_mmbi_remove(struct platform_device *pdev) ++{ ++ struct ast2700_espi_mmbi *priv = dev_get_drvdata(&pdev->dev); ++ u32 i, j, reg; ++ ++ if (!priv) ++ return; ++ ++ regmap_write(priv->mmbi_map, ESPI_MMBI_INT_ENABLE, 0); ++ ++ regmap_read(priv->mmbi_map, ESPI_MMBI_CTRL, ®); ++ reg &= ~ESPI_MMBI_CTRL_EN; ++ regmap_write(priv->mmbi_map, ESPI_MMBI_CTRL, reg); ++ ++ for (i = 0; i < AST2700_ESPI_MMBI_MAX_CHANNELS; i++) { ++ if (!priv->channels[i].enabled) ++ continue; ++ ++ for (j = 0; j < MMBI_MAX_SUPPORT_PROTOCOLS; j++) { ++ if (!priv->channels[i].protocols[j].miscdev.this_device) ++ continue; ++ ++ misc_deregister(&priv->channels[i].protocols[j].miscdev); ++ } ++ } ++ ++ if (priv->mmbi_virt_addr) ++ aspeed_espi_mmbi_dma_finish(priv); ++ ++ dev_info(&pdev->dev, "ASPEED eSPI MMBI driver removed\n"); ++} ++ ++static struct platform_driver aspeed_espi_mmbi_driver = { ++ .driver = { ++ .name = "ast2700-intel-espi-mmbi", ++ .of_match_table = aspeed_espi_mmbi_of_match, ++ }, ++ .probe = aspeed_espi_mmbi_probe, ++ .remove = aspeed_espi_mmbi_remove, ++}; ++ ++module_platform_driver(aspeed_espi_mmbi_driver); ++ ++MODULE_AUTHOR("Your Name "); ++MODULE_DESCRIPTION("ASPEED eSPI MMBI driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.h b/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.h +--- a/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2700-intel-espi-mmbi.h 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,53 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright 2025 Aspeed Technology Inc. ++ */ ++#ifndef _AST2700_ESPI_MMBI_H_ ++#define _AST2700_ESPI_MMBI_H_ ++ ++/* MMBI Control registers */ ++#define ESPI_MMBI_CTRL 0x00 ++#define ESPI_MMBI_CTRL_INST_NUM GENMASK(6, 4) ++#define ESPI_MMBI_CTRL_EN BIT(0) ++#define ESPI_MMBI_INT_STATUS 0x08 ++#define ESPI_MMBI_HOST_RWS0_INT BIT(0) ++#define ESPI_MMBI_HOST_RWS1_INT BIT(1) ++#define ESPI_MMBI_INT_ENABLE 0x0C ++ ++#define ESPI_MMBI_HOST_RWP0_VAL 0x10 ++#define ESPI_MMBI_HOST_RWP1_VAL 0x14 ++ ++#define ESPI_MMBI_HOST_READ_RWP0(idx) (((idx) * 8) + ESPI_MMBI_HOST_RWP0_VAL) ++#define ESPI_MMBI_HOST_READ_RWP1(idx) (((idx) * 8) + ESPI_MMBI_HOST_RWP1_VAL) ++ ++struct aspeed_mmbi_get_empty_space { ++ __u32 length; ++}; ++ ++struct aspeed_mmbi_get_config { ++ bool h_rdy; ++ __u32 h2b_wp; ++ __u32 b2h_rp; ++ __u32 h2b_rp; ++ __u32 b2h_wp; ++}; ++ ++#define __ASPEED_MMBI_CTRL_IOCTL_MAGIC 0xbb ++/* ++ * This IOCTL is meant to read empty space in B2H buffer ++ * in a specific channel ++ */ ++#define ASPEED_MMBI_CTRL_IOCTL_GET_B2H_EMPTY_SPACE \ ++ _IOWR(__ASPEED_MMBI_CTRL_IOCTL_MAGIC, 0x00, \ ++ struct aspeed_mmbi_get_empty_space) ++ ++/* This IOCTL to send BMC reset request */ ++#define ASPEED_MMBI_CTRL_IOCTL_SEND_RESET_REQUEST \ ++ _IOW(__ASPEED_MMBI_CTRL_IOCTL_MAGIC, 0x01, int) ++ ++/* This IOCTL is to Get Config in HROP and HRWP */ ++#define ASPEED_MMBI_CTRL_IOCTL_GET_CONFIG \ ++ _IOWR(__ASPEED_MMBI_CTRL_IOCTL_MAGIC, 0x02, \ ++ struct aspeed_mmbi_get_config) ++ ++#endif +diff --git a/drivers/soc/aspeed/espi/ast2700-rtc-over-espi.c b/drivers/soc/aspeed/espi/ast2700-rtc-over-espi.c +--- a/drivers/soc/aspeed/espi/ast2700-rtc-over-espi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/espi/ast2700-rtc-over-espi.c 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,193 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright 2025 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SYNC_INTERVAL_MS_DEFAULT 1000 ++ ++#define RTC_REG0 0x0 ++#define RTC_REG0_MINUTES_ALARM GENMASK(31, 24) ++#define RTC_REG0_MINUTES GENMASK(23, 16) ++#define RTC_REG0_SECONDS_ALARM GENMASK(15, 8) ++#define RTC_REG0_SECONDS GENMASK(7, 0) ++#define RTC_REG1 0x4 ++#define RTC_REG1_DAY_OF_MONTH GENMASK(31, 24) ++#define RTC_REG1_DAY_OF_WEEK GENMASK(23, 16) ++#define RTC_REG1_HOURS_ALARM GENMASK(15, 8) ++#define RTC_REG1_HOURS GENMASK(7, 0) ++#define RTC_REG2 0x8 ++#define RTC_REG2_YEAR GENMASK(15, 8) ++#define RTC_REG2_MONTH GENMASK(7, 0) ++#define RTC_REG_MAX 0xc ++ ++struct rtc_espi_sync { ++ struct rtc_device *rtc_dev; ++ struct device *dev; ++ void __iomem *espi_ram_base; ++ u32 interval_ms; ++ struct delayed_work sync_work; ++ struct workqueue_struct *wq; ++}; ++ ++static void sync_rtc_to_espi_work(struct work_struct *work) ++{ ++ struct rtc_espi_sync *ctx = container_of(to_delayed_work(work), struct rtc_espi_sync, sync_work); ++ struct rtc_time tm; ++ u32 reg, sec, min, hour, day_of_week, day_of_month, month, year; ++ u32 sec_alarm, min_alarm, hour_alarm; ++ int ret; ++ ++ if (!ctx->espi_ram_base) { ++ dev_err(ctx->dev, "eSPI RAM base not mapped\n"); ++ return; ++ } ++ ++ ret = rtc_read_time(ctx->rtc_dev, &tm); ++ if (ret < 0) { ++ dev_err(ctx->dev, "Failed to read RTC time: %d\n", ret); ++ goto reschedule; ++ } ++ dev_dbg(ctx->dev, "%s: tm data: secs=%d, mins=%d, hours=%d, mon=%d, year=%d\n", ++ __func__, tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mon, tm.tm_year); ++ dev_dbg(ctx->dev, "%s: tm data: wday(day of week)=%d, mday(day of month)=%d\n", ++ __func__, tm.tm_wday, tm.tm_mday); ++ ++ /* Write the eSPI RAM registers */ ++ reg = FIELD_PREP(RTC_REG0_SECONDS, tm.tm_sec) | ++ FIELD_PREP(RTC_REG0_MINUTES, tm.tm_min); ++ writel(reg, (ctx->espi_ram_base + RTC_REG0)); ++ ++ reg = FIELD_PREP(RTC_REG1_HOURS, tm.tm_hour) | ++ FIELD_PREP(RTC_REG1_DAY_OF_MONTH, tm.tm_mday) | ++ FIELD_PREP(RTC_REG1_DAY_OF_WEEK, tm.tm_wday + 1); ++ writel(reg, ctx->espi_ram_base + RTC_REG1); ++ ++ reg = FIELD_PREP(RTC_REG2_MONTH, tm.tm_mon + 1) | ++ FIELD_PREP(RTC_REG2_YEAR, tm.tm_year % 100); ++ writel(reg, ctx->espi_ram_base + RTC_REG2); ++ ++ /* Read back the values to verify */ ++ reg = readl(ctx->espi_ram_base + RTC_REG0); ++ sec = FIELD_GET(RTC_REG0_SECONDS, reg); ++ sec_alarm = FIELD_GET(RTC_REG0_SECONDS_ALARM, reg); ++ min = FIELD_GET(RTC_REG0_MINUTES, reg); ++ min_alarm = FIELD_GET(RTC_REG0_MINUTES_ALARM, reg); ++ ++ reg = readl(ctx->espi_ram_base + RTC_REG1); ++ hour = FIELD_GET(RTC_REG1_HOURS, reg); ++ hour_alarm = FIELD_GET(RTC_REG1_HOURS_ALARM, reg); ++ day_of_week = FIELD_GET(RTC_REG1_DAY_OF_WEEK, reg); ++ day_of_month = FIELD_GET(RTC_REG1_DAY_OF_MONTH, reg); ++ ++ reg = readl(ctx->espi_ram_base + RTC_REG2); ++ month = FIELD_GET(RTC_REG2_MONTH, reg); ++ year = FIELD_GET(RTC_REG2_YEAR, reg); ++ ++ dev_dbg(ctx->dev, "%s: eSPI RAM data: secs=%d, mins=%d, hours=%d, ", ++ __func__, sec, min, hour); ++ dev_dbg(ctx->dev, "day_of_week=%d, day_of_month=%d, month=%d, year=%d\n", ++ day_of_week, day_of_month, month, year); ++ dev_dbg(ctx->dev, "%s: eSPI RAM data: secs_alarm=%d, mins_alarm=%d, hours_alarm=%d\n", ++ __func__, sec_alarm, min_alarm, hour_alarm); ++ ++reschedule: ++ queue_delayed_work(ctx->wq, &ctx->sync_work, msecs_to_jiffies(ctx->interval_ms)); ++} ++ ++static int rtc_espi_sync_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct rtc_espi_sync *ctx; ++ struct device_node *np = dev->of_node; ++ struct rtc_device *rtc; ++ struct resource *res; ++ u32 interval = SYNC_INTERVAL_MS_DEFAULT; ++ ++ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return -ENOMEM; ++ ++ /* Ensure the device tree node is valid */ ++ rtc = rtc_class_open("rtc0"); ++ if (!rtc) { ++ dev_info(dev, "RTC not ready, deferring probe\n"); ++ return -EPROBE_DEFER; ++ } ++ ++ /* Check if the RTC device supports the read_time operation */ ++ if (!rtc->ops || !rtc->ops->read_time) { ++ dev_err(dev, "RTC device does not support read_time operation\n"); ++ rtc_class_close(rtc); ++ return -ENODEV; ++ } ++ ++ ctx->rtc_dev = rtc; ++ ctx->dev = dev; ++ ++ /* Get eSPI RAM handle and map */ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ctx->espi_ram_base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(ctx->espi_ram_base)) ++ return PTR_ERR(ctx->espi_ram_base); ++ ++ if (!ctx->espi_ram_base) ++ memset(ctx->espi_ram_base, 0, RTC_REG_MAX); ++ ++ /* Optional: polling interval */ ++ of_property_read_u32(np, "interval-ms", &interval); ++ ctx->interval_ms = interval; ++ ++ ctx->wq = alloc_workqueue("rtc_espi_sync_wq", WQ_UNBOUND, 0); ++ if (!ctx->wq) ++ return -ENOMEM; ++ ++ INIT_DELAYED_WORK(&ctx->sync_work, sync_rtc_to_espi_work); ++ queue_delayed_work(ctx->wq, &ctx->sync_work, 0); ++ ++ dev_info(dev, "RTC-eSPI RAM sync initialized, every %u ms\n", ctx->interval_ms); ++ ++ platform_set_drvdata(pdev, ctx); ++ return 0; ++} ++ ++static void rtc_espi_sync_remove(struct platform_device *pdev) ++{ ++ struct rtc_espi_sync *ctx = platform_get_drvdata(pdev); ++ ++ cancel_delayed_work_sync(&ctx->sync_work); ++ destroy_workqueue(ctx->wq); ++ ++ if (ctx->rtc_dev) ++ rtc_class_close(ctx->rtc_dev); ++} ++ ++static const struct of_device_id rtc_espi_sync_of_match[] = { ++ { .compatible = "aspeed,ast2700-rtc-over-espi" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, rtc_espi_sync_of_match); ++ ++static struct platform_driver rtc_espi_sync_driver = { ++ .driver = { ++ .name = "ast2700-rtc-over-espi", ++ .of_match_table = rtc_espi_sync_of_match, ++ }, ++ .probe = rtc_espi_sync_probe, ++ .remove = rtc_espi_sync_remove, ++}; ++module_platform_driver(rtc_espi_sync_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Kevin Chen "); ++MODULE_DESCRIPTION("RTC to eSPI RAM sync driver"); +diff --git a/drivers/soc/aspeed/mmbi/Kconfig b/drivers/soc/aspeed/mmbi/Kconfig +--- a/drivers/soc/aspeed/mmbi/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/Kconfig 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,17 @@ ++menu "ASPEED MMBI driver" ++ ++config ASPEED_MMBI ++ tristate "ASPEED MMBI driver" ++ help ++ Enable support for the ASPEED MMBI driver. This driver ++ provides a communication channel between the Host and the BMC ++ over PCIe using the MMBI (Memory Mapped Buffer Interface). ++ ++config ASPEED_PCIE_MMBI ++ tristate "ASPEED PCIE MMBI" ++ depends on ASPEED_MMBI ++ ++config ASPEED_HOST_PCIE_MMBI ++ tristate "ASPEED Host PCIE MMBI" ++ ++endmenu +diff --git a/drivers/soc/aspeed/mmbi/Makefile b/drivers/soc/aspeed/mmbi/Makefile +--- a/drivers/soc/aspeed/mmbi/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/Makefile 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,3 @@ ++obj-$(CONFIG_ASPEED_MMBI) += aspeed-mmbi.o aspeed-mmbi-bmc.o aspeed-mmbi-host.o ++obj-$(CONFIG_ASPEED_PCIE_MMBI) += aspeed-pcie-mmbi.o ++obj-$(CONFIG_ASPEED_HOST_PCIE_MMBI) += aspeed-host-pcie-mmbi.o +diff --git a/drivers/soc/aspeed/mmbi/aspeed-host-pcie-mmbi.c b/drivers/soc/aspeed/mmbi/aspeed-host-pcie-mmbi.c +--- a/drivers/soc/aspeed/mmbi/aspeed-host-pcie-mmbi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-host-pcie-mmbi.c 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,287 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* Implements PCIe Transport in host side for MMBI protocol ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "aspeed-mmbi.h" ++ ++#define DRIVER_NAME "aspeed-host-pcie-mmbi" ++#define MAX_MSI_NUM 32 ++/* Value for triggering interrupt */ ++#define ASPEED_E2M_INT_VAL 0xC6 ++ ++struct aspeed_host_pcie_mmbi_ins { ++ struct mmbi_ins_desc mmbi_desc; ++ int irq; ++ int msi_index; ++ int instance_index; ++ int bar_index; ++ const char *dev_name; ++ bool valid; ++}; ++ ++struct aspeed_host_pcie_mmbi { ++ struct device *dev; ++ struct aspeed_host_pcie_mmbi_ins instances[PCI_STD_NUM_BARS]; ++ int msi_nums; ++}; ++ ++/* ++ * app_interface: per-channel application interface selection. ++ * 0 = ioctl (MMBI_APP_INTF_IOCTL) ++ * 1 = mctp-netdev (MMBI_APP_INTF_MCTP_NETDEV) ++ * ++ * One value applies to every channel. Multiple values map by channel index, ++ * and omitted channels keep their defaults. ++ * Examples: ++ * app_interface=1 -> all channels use mctp-netdev ++ * app_interface=1,0,0,0,1 -> ch0/ch4 use mctp-netdev ++ * Invalid values fall back to the default map. ++ */ ++static const uint app_interface_defaults[MMBI_MAX_CHANNELS] = { ++ [0] = MMBI_APP_INTF_MCTP_NETDEV, ++}; ++ ++static uint app_interface[MMBI_MAX_CHANNELS] = { [0] = MMBI_APP_INTF_MCTP_NETDEV }; ++static int app_interface_count; ++module_param_array(app_interface, uint, &app_interface_count, 0444); ++MODULE_PARM_DESC(app_interface, ++ "App interface per channel: 0=ioctl, 1=mctp-netdev; one value applies to all channels"); ++ ++static const char * ++aspeed_host_pcie_mmbi_app_interface_name(enum mmbi_app_interface intf) ++{ ++ switch (intf) { ++ case MMBI_APP_INTF_MCTP_NETDEV: ++ return "mctp-netdev"; ++ case MMBI_APP_INTF_IOCTL: ++ default: ++ return "ioctl"; ++ } ++} ++ ++static int aspeed_host_pcie_mmbi_validate_app_interface(struct device *dev) ++{ ++ int i; ++ ++ for (i = 0; i < app_interface_count; i++) { ++ if (app_interface[i] > MMBI_APP_INTF_MCTP_NETDEV) { ++ dev_warn(dev, ++ "invalid app_interface[%d]=%u, using defaults\n", ++ i, app_interface[i]); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static enum mmbi_app_interface ++aspeed_host_pcie_mmbi_get_app_interface(int channel, bool use_defaults) ++{ ++ if (use_defaults) ++ return app_interface_defaults[channel]; ++ ++ if (app_interface_count == 1) ++ return app_interface[0]; ++ ++ if (channel < app_interface_count) ++ return app_interface[channel]; ++ ++ return app_interface_defaults[channel]; ++} ++ ++static void ++aspeed_host_pcie_mmbi_init_app_interfaces(struct mmbi_ins_desc *mmbi_desc, ++ bool use_defaults) ++{ ++ int i; ++ ++ for (i = 0; i < MMBI_MAX_CHANNELS; i++) ++ mmbi_desc->chan_desc[i].app_interface = ++ aspeed_host_pcie_mmbi_get_app_interface(i, ++ use_defaults); ++} ++ ++static void aspeed_host_pcie_mmbi_log_app_interfaces(struct device *dev, ++ struct mmbi_ins_desc *mmbi_desc, ++ int bar) ++{ ++ struct mmbi_chan_desc *chan_desc; ++ const char *interface; ++ int i; ++ ++ for (i = 0; i < mmbi_desc->num_of_channels; i++) { ++ chan_desc = &mmbi_desc->chan_desc[i]; ++ interface = aspeed_host_pcie_mmbi_app_interface_name(chan_desc->app_interface); ++ dev_info(dev, "BAR %d channel %d application interface: %s\n", ++ bar, i, interface); ++ } ++} ++ ++static int aspeed_pci_host_mmbi_setup(struct pci_dev *pdev) ++{ ++ struct aspeed_host_pcie_mmbi *mmbi = pci_get_drvdata(pdev); ++ struct aspeed_host_pcie_mmbi_ins *ins; ++ struct device *dev = &pdev->dev; ++ struct mmbi_ins_desc *mmbi_desc; ++ int i, ret, instance_id; ++ int last_ret = -ENODEV; ++ bool use_default_app_interfaces; ++ resource_size_t start, size; ++ ++ ret = aspeed_host_pcie_mmbi_validate_app_interface(dev); ++ use_default_app_interfaces = ret != 0; ++ ++ instance_id = 0; ++ for (i = 0; i < PCI_STD_NUM_BARS; i++) { ++ ins = &mmbi->instances[i]; ++ mmbi_desc = &ins->mmbi_desc; ++ start = pci_resource_start(pdev, i); ++ size = pci_resource_len(pdev, i); ++ ++ if (!start || !size) { ++ dev_err(dev, ++ "Invalid BAR %d with start 0x%llx and size 0x%llx\n", ++ i, (unsigned long long)start, ++ (unsigned long long)size); ++ continue; ++ } ++ ++ mmbi_desc->desc_virt = pci_ioremap_bar(pdev, i); ++ if (!mmbi_desc->desc_virt) { ++ dev_err(dev, "Failed to ioremap BAR %d\n", i); ++ continue; ++ } ++ mmbi_desc->dev = dev; ++ mmbi_desc->role = MMBI_ROLE_HOST; ++ ++ aspeed_host_pcie_mmbi_init_app_interfaces(mmbi_desc, ++ use_default_app_interfaces); ++ ret = mmbi_instance_init(mmbi_desc); ++ if (ret) { ++ dev_info(dev, "Failed to initialize MMBI at bar %d, error %d\n", i, ret); ++ /* Unmap the previously mapped BAR */ ++ iounmap(mmbi_desc->desc_virt); ++ last_ret = ret; ++ } else { ++ ins->instance_index = instance_id++; ++ ins->bar_index = i; ++ ins->valid = true; ++ ins->dev_name = devm_kasprintf(dev, GFP_KERNEL, "mmbi_host_ins%d", ins->instance_index); ++ aspeed_host_pcie_mmbi_log_app_interfaces(dev, mmbi_desc, i); ++ ++ if (mmbi_desc->host_int_type == MMBI_HOST_INT_PCIE) { ++ if (mmbi_desc->host_int_location > mmbi->msi_nums) { ++ dev_err(dev, ++ "Invalid host interrupt location %d for MMBI instance at BAR %d, use MSI 0\n", ++ mmbi_desc->host_int_location, ++ i); ++ ins->msi_index = 0; ++ } else { ++ ins->msi_index = mmbi_desc->host_int_location; ++ } ++ } ++ } ++ } ++ ++ if (!instance_id) ++ return last_ret; ++ ++ return 0; ++} ++ ++static int aspeed_pci_host_pcie_mmbi_probe(struct pci_dev *pdev, ++ const struct pci_device_id *id) ++{ ++ struct aspeed_host_pcie_mmbi *mmbi; ++ int rc = 0; ++ ++ mmbi = devm_kzalloc(&pdev->dev, sizeof(*mmbi), GFP_KERNEL); ++ if (!mmbi) ++ return -ENOMEM; ++ ++ mmbi->dev = &pdev->dev; ++ ++ rc = pci_enable_device(pdev); ++ if (rc) ++ return rc; ++ ++ pci_set_master(pdev); ++ pci_set_drvdata(pdev, mmbi); ++ rc = aspeed_pci_host_mmbi_setup(pdev); ++ if (rc) { ++ dev_err(&pdev->dev, "Failed to setup MMBI instances\n"); ++ goto err_pci; ++ } ++ ++ dev_info(&pdev->dev, "ASPEED Host PCIe MMBI device probed successfully\n"); ++ return 0; ++ ++err_pci: ++ pci_disable_device(pdev); ++ return dev_err_probe(&pdev->dev, rc, ++ "Failed to probe ASPEED Host PCIe MMBI device\n"); ++} ++ ++static void aspeed_pci_host_pcie_mmbi_remove(struct pci_dev *pdev) ++{ ++ struct aspeed_host_pcie_mmbi *mmbi; ++ struct aspeed_host_pcie_mmbi_ins *ins; ++ int i; ++ ++ mmbi = pci_get_drvdata(pdev); ++ for (i = 0; i < PCI_STD_NUM_BARS; i++) { ++ ins = &mmbi->instances[i]; ++ if (ins->valid) { ++ mmbi_instance_remove(&ins->mmbi_desc); ++ pci_iounmap(pdev, ins->mmbi_desc.desc_virt); ++ } ++ } ++ ++ pci_disable_device(pdev); ++} ++ ++static struct pci_device_id aspeed_host_pcie_mmbi_pci_ids[] = { ++ { PCI_DEVICE(0x1A03, 0x2402), .class = 0x0C0C00, .class_mask = (0xFFFF00)}, ++ { 0, } ++}; ++ ++MODULE_DEVICE_TABLE(pci, aspeed_host_pcie_mmbi_pci_ids); ++ ++static struct pci_driver aspeed_host_pcie_mmbi_driver = { ++ .name = DRIVER_NAME, ++ .id_table = aspeed_host_pcie_mmbi_pci_ids, ++ .probe = aspeed_pci_host_pcie_mmbi_probe, ++ .remove = aspeed_pci_host_pcie_mmbi_remove, ++}; ++ ++static int __init aspeed_host_pcie_mmbi_init(void) ++{ ++ return pci_register_driver(&aspeed_host_pcie_mmbi_driver); ++} ++ ++static void aspeed_host_pcie_mmbi_exit(void) ++{ ++ /* unregister pci driver */ ++ pci_unregister_driver(&aspeed_host_pcie_mmbi_driver); ++} ++ ++late_initcall(aspeed_host_pcie_mmbi_init); ++module_exit(aspeed_host_pcie_mmbi_exit); ++ ++MODULE_AUTHOR("YH Chung "); ++MODULE_DESCRIPTION("ASPEED Host PCIE MMBI Transport Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.c b/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.c +--- a/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.c 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,216 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* Implements MMBI protocol for BMC side with VPSCB buffer type ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "aspeed-mmbi.h" ++#include "aspeed-mmbi-internal.h" ++#include "aspeed-mmbi-bmc.h" ++ ++#define MMBI_PUT_U32_MSB(base, off, val) \ ++ do { \ ++ for (int __i = 0; __i < 4; __i++) { \ ++ iowrite8((u8)((val) >> (24 - (__i * 8))), \ ++ (base) + ((off)++)); \ ++ } \ ++ } while (0) ++ ++static void mmbi_channel_state_handler(struct mmbi_chan_desc *chan, u8 __iomem *desc_virt) ++{ ++ enum mmbi_state prev_state, cur_state; ++ struct mmbi_buf_vpscb *vpscb; ++ u8 __iomem *host_ros_virt, *host_rws_virt; ++ u32 unhandled_len, avail_len; ++ ++ prev_state = chan->priv->state; ++ ++ vpscb = &chan->buffer_desc; ++ host_ros_virt = desc_virt + vpscb->h_ros_p; ++ host_rws_virt = desc_virt + vpscb->h_rws_p; ++ mmbi_channel_state_update(chan, desc_virt); ++ cur_state = chan->priv->state; ++ ++ switch (cur_state) { ++ case INIT_MISMATCH: ++ memset_io(host_ros_virt, 0, 8); ++ memset_io(host_rws_virt, 0, 8); ++ mmbi_set_up(host_ros_virt); ++ chan->priv->state = INIT_COMPLETED; ++ break; ++ case NORMAL_RUNTIME: ++ mmbi_set_ready(host_ros_virt); ++ ++ unhandled_len = mmbi_channel_unhandled_length(host_ros_virt, host_rws_virt, chan->h2b_l); ++ if (unhandled_len >= MMBI_PKT_MIN_SIZE) ++ chan->priv->rx_ready = true; ++ else ++ chan->priv->rx_ready = false; ++ avail_len = mmbi_channel_avail_length(host_rws_virt, host_ros_virt, chan->b2h_l); ++ if (avail_len <= MMBI_PKT_MIN_SIZE) ++ chan->priv->tx_ready = false; ++ else ++ chan->priv->tx_ready = true; ++ ++ break; ++ case RESET_REQ_BY_HOST: ++ mmbi_clr_ready(host_ros_virt); ++ mmbi_set_rst(host_ros_virt); ++ chan->priv->state = RESET_ACKED; ++ // TODO: consume all pending data from host and then trigger initialization ++ break; ++ case RESET_ACKED: ++ mmbi_clr_up(host_ros_virt); ++ chan->priv->state = TRANS_TO_INIT; ++ memset_io(host_ros_virt, 0, 8); ++ memset_io(host_rws_virt, 0, 8); ++ mmbi_set_up(host_ros_virt); ++ chan->priv->state = INIT_COMPLETED; ++ default: ++ /* other states do not require action from BMC side */ ++ break; ++ } ++ ++ return; ++} ++ ++static int mmbi_buffer_init_vpscb_bmc(u8 __iomem *buf_virt, struct mmbi_buf_vpscb *buf_desc) ++{ ++ u32 offset; ++ ++ offset = 0; ++ ++ iowrite8(MMBI_BUFFER_TYPE_VPSCB & MMBI_BUF_TYPE_MASK, buf_virt + offset++); ++ iowrite8(buf_desc->host_int_val_ch & MMBI_INT_VAL_MASK, buf_virt + offset++); ++ iowrite8(buf_desc->bmc_int_val_ch & MMBI_INT_VAL_MASK, buf_virt + offset++); ++ ++ memset_io(buf_virt + offset, 0, 5); ++ offset += 5; /* reserved bytes */ ++ ++ MMBI_PUT_U32_MSB(buf_virt, offset, MMBI_BUF_ADDR_ALIGN(buf_desc->h_ros_p)); ++ MMBI_PUT_U32_MSB(buf_virt, offset, MMBI_BUF_ADDR_ALIGN(buf_desc->h_rws_p)); ++ ++ pr_info("%s: h_ros_p=0x%x, h_rws_p=0x%x\n", __func__, ++ buf_desc->h_ros_p, buf_desc->h_rws_p); ++ return 0; ++} ++ ++static int mmbi_channel_init_bmc(u8 __iomem *desc_virt, struct mmbi_chan_desc *chan_desc) ++{ ++ u32 offset = 0; ++ struct mmbi_buf_vpscb *buffer_desc; ++ ++ chan_desc->priv->peer_ready = false; ++ MMBI_PUT_U32_MSB(desc_virt, offset, MMBI_BUF_ADDR_ALIGN(chan_desc->b2h_ba_offset)); ++ MMBI_PUT_U32_MSB(desc_virt, offset, MMBI_BUF_ADDR_ALIGN(chan_desc->h2b_ba_offset)); ++ MMBI_PUT_U32_MSB(desc_virt, offset, chan_desc->b2h_l); ++ MMBI_PUT_U32_MSB(desc_virt, offset, chan_desc->h2b_l); ++ ++ pr_info("%s: b2h_ba_offset=0x%x, h2b_ba_offset=0x%x\n", __func__, ++ chan_desc->b2h_ba_offset, chan_desc->h2b_ba_offset); ++ pr_info("%s: b2h_l=0x%x, h2b_l=0x%x\n", __func__, chan_desc->b2h_l, ++ chan_desc->h2b_l); ++ pr_info("%s: buffer_type=%u\n", __func__, chan_desc->buffer_type); ++ ++ if (chan_desc->buffer_type == MMBI_BUFFER_TYPE_VPSCB) { ++ buffer_desc = &chan_desc->buffer_desc; ++ return mmbi_buffer_init_vpscb_bmc(desc_virt + offset, buffer_desc); ++ } ++ ++ pr_err("\t%s: unsupported buffer type %d\n", __func__, ++ chan_desc->buffer_type); ++ return -EINVAL; ++} ++ ++void mmbi_channel_irq_bmc(struct mmbi_chan_desc *chan) ++{ ++ mmbi_channel_state_handler(chan, chan->mmbi->desc_virt); ++} ++EXPORT_SYMBOL_GPL(mmbi_channel_irq_bmc); ++ ++int mmbi_instance_init_bmc(struct mmbi_ins_desc *mmbi) ++{ ++ int rc, offset; ++ u32 i; ++ u8 __iomem *desc_virt; ++ struct mmbi_buf_vpscb *vpscb; ++ ++ if (mmbi->num_of_channels > MMBI_MAX_CHANNELS) { ++ dev_err(mmbi->dev, "%s: invalid number of instances %d\n", ++ __func__, mmbi->num_of_channels); ++ return -EINVAL; ++ } ++ ++ desc_virt = mmbi->desc_virt; ++ i = FIELD_PREP((u32)MMBI_NOI_MASK, mmbi->num_of_channels); ++ i |= FIELD_PREP((u32)MMBI_OS_USE_MASK, mmbi->os_use); ++ dev_info(mmbi->dev, "%s: MMBI version 0x%x, os_use %u, num_of_channels %u i %u\n", ++ __func__, mmbi->mmbi_version, mmbi->os_use, mmbi->num_of_channels, i); ++ ++ memcpy_toio(desc_virt, MMBI_SIGNATURE, sizeof(MMBI_SIGNATURE) - 1); ++ desc_virt += sizeof(MMBI_SIGNATURE) - 1; ++ ++ iowrite8(mmbi->mmbi_version & MMBI_VERSION_MASK, desc_virt++); ++ iowrite8(i, desc_virt++); ++ ++ mmbi->chan_desc[0].priv->state = INIT_IN_PROGRESS; ++ mmbi->chan_desc[0].index = 0; ++ mmbi->chan_desc[0].mmbi = mmbi; ++ rc = mmbi_channel_init_bmc(desc_virt, &mmbi->chan_desc[0]); ++ if (rc) { ++ dev_err(mmbi->dev, "%s: failed to init channel 0\n", __func__); ++ return rc; ++ } ++ desc_virt += MMBI_DESC_SIZE_CHANNEL; ++ ++ iowrite8(mmbi->host_int_type & MMBI_INT_TYPE_MASK, desc_virt++); ++ iowrite8(mmbi->host_int_location, desc_virt++); ++ ++ memset_io(desc_virt, 0, 3); ++ desc_virt += 3; /* reserved bytes */ ++ iowrite8(mmbi->host_int_value, desc_virt++); ++ iowrite8(mmbi->bmc_int_type & MMBI_INT_TYPE_MASK, desc_virt++); ++ offset = desc_virt - mmbi->desc_virt; ++ desc_virt += sizeof(mmbi->bmc_int_location); ++ MMBI_PUT_U32_MSB(mmbi->desc_virt, offset, mmbi->bmc_int_location); ++ ++ memset_io(desc_virt, 0, 4); ++ desc_virt += 4; /* reserved bytes */ ++ ++ iowrite8(mmbi->bmc_int_value, desc_virt++); ++ ++ memset_io(desc_virt, 0, 8); ++ desc_virt += 8; /* reserved bytes */ ++ ++ if (mmbi->num_of_channels > 1) { ++ for (i = 1; i < mmbi->num_of_channels; i++) { ++ mmbi->chan_desc[i].priv->state = INIT_IN_PROGRESS; ++ mmbi->chan_desc[i].index = i; ++ mmbi->chan_desc[i].mmbi = mmbi; ++ rc = mmbi_channel_init_bmc(desc_virt, &mmbi->chan_desc[i]); ++ if (rc) { ++ dev_err(mmbi->dev, "%s: failed to init channel %d\n", __func__, i); ++ return rc; ++ } ++ desc_virt += MMBI_DESC_SIZE_CHANNEL; ++ } ++ } ++ ++ for (i = 0; i < mmbi->num_of_channels; i++) { ++ vpscb = &mmbi->chan_desc[i].buffer_desc; ++ memset_io(mmbi->desc_virt + vpscb->h_ros_p, 0, 8); ++ memset_io(mmbi->desc_virt + vpscb->h_rws_p, 0, 8); ++ ++ mmbi_set_up(mmbi->desc_virt + vpscb->h_ros_p); ++ mmbi_set_ready(mmbi->desc_virt + vpscb->h_ros_p); ++ mmbi->chan_desc[i].priv->state = INIT_COMPLETED; ++ } ++ ++ return rc; ++} ++EXPORT_SYMBOL_GPL(mmbi_instance_init_bmc); +diff --git a/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.h b/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.h +--- a/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-mmbi-bmc.h 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++#ifndef __ASPEED_MMBI_BMC_H__ ++#define __ASPEED_MMBI_BMC_H__ ++ ++#include "aspeed-mmbi.h" ++ ++int mmbi_instance_init_bmc(struct mmbi_ins_desc *mmbi); ++void mmbi_channel_irq_bmc(struct mmbi_chan_desc *chan); ++ ++#endif /* __ASPEED_MMBI_BMC_H__ */ +diff --git a/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.c b/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.c +--- a/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.c 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,230 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* Implements MMBI protocol for Host side with VPSCB buffer type ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "aspeed-mmbi.h" ++#include "aspeed-mmbi-internal.h" ++#include "aspeed-mmbi-host.h" ++ ++static void mmbi_channel_state_handler(struct mmbi_chan_desc *chan, u8 __iomem *desc_virt) ++{ ++ enum mmbi_state prev_state, cur_state; ++ struct mmbi_buf_vpscb *vpscb; ++ u8 __iomem *host_rws_virt; ++ u8 __iomem *host_ros_virt; ++ u32 host_int_location; ++ u32 unhandled_len, avail_len; ++ ++ prev_state = chan->priv->state; ++ ++ host_int_location = chan->mmbi->host_int_location; ++ vpscb = &chan->buffer_desc; ++ host_rws_virt = desc_virt + vpscb->h_rws_p; ++ host_ros_virt = desc_virt + vpscb->h_ros_p; ++ mmbi_channel_state_update(chan, desc_virt); ++ cur_state = chan->priv->state; ++ ++ switch (cur_state) { ++ case INIT_COMPLETED: ++ mmbi_set_ready(host_rws_virt); ++ mmbi_set_up(host_rws_virt); ++ chan->priv->state = NORMAL_RUNTIME; ++ break; ++ case NORMAL_RUNTIME: ++ mmbi_set_ready(host_rws_virt); ++ ++ unhandled_len = mmbi_channel_unhandled_length(host_rws_virt, ++ host_ros_virt, chan->b2h_l); ++ if (unhandled_len >= MMBI_PKT_MIN_SIZE) ++ chan->priv->rx_ready = true; ++ else ++ chan->priv->rx_ready = false; ++ avail_len = mmbi_channel_avail_length(host_ros_virt, host_rws_virt, chan->h2b_l); ++ if (avail_len <= MMBI_PKT_MIN_SIZE) ++ chan->priv->tx_ready = false; ++ else ++ chan->priv->tx_ready = true; ++ break; ++ case RESET_REQ_BY_BMC: ++ // TODO: consume all data from B2H buffer ++ mmbi_clr_ready(host_rws_virt); ++ mmbi_set_rst(host_rws_virt); ++ chan->priv->state = RESET_ACKED; ++ break; ++ default: ++ /* other states do not require action from host side */ ++ break; ++ } ++} ++ ++static int mmbi_instance_signature_check(u8 __iomem *desc_virt) ++{ ++ char signature[sizeof(MMBI_SIGNATURE) - 1]; ++ ++ memcpy_fromio(signature, desc_virt, sizeof(MMBI_SIGNATURE) - 1); ++ if (memcmp(signature, MMBI_SIGNATURE, sizeof(MMBI_SIGNATURE) - 1) != 0) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static int mmbi_chan_init_vpscb_host(u8 __iomem *buf_virt, struct mmbi_buf_vpscb *buf_desc) ++{ ++ u32 offset = 0; ++ ++ buf_desc->host_int_val_ch = ioread8(buf_virt + offset++) & MMBI_INT_VAL_MASK; ++ buf_desc->bmc_int_val_ch = ioread8(buf_virt + offset++) & MMBI_INT_VAL_MASK; ++ ++ offset += 5; /* reserved bytes */ ++ ++ memcpy_fromio(&buf_desc->h_ros_p, buf_virt + offset, sizeof(buf_desc->h_ros_p)); ++ memcpy_fromio(&buf_desc->h_rws_p, buf_virt + offset + 4, sizeof(buf_desc->h_rws_p)); ++ buf_desc->h_ros_p = be32_to_cpu(buf_desc->h_ros_p); ++ buf_desc->h_rws_p = be32_to_cpu(buf_desc->h_rws_p); ++ buf_desc->h_ros_p = MMBI_BUF_ADDR_ALIGN(buf_desc->h_ros_p); ++ buf_desc->h_rws_p = MMBI_BUF_ADDR_ALIGN(buf_desc->h_rws_p); ++ ++ pr_info("%s: h_ros_p=0x%x, h_rws_p=0x%x\n", __func__, ++ buf_desc->h_ros_p, buf_desc->h_rws_p); ++ return 0; ++} ++ ++static int mmbi_channel_init_host(u8 __iomem *desc_virt, struct mmbi_chan_desc *chan_desc) ++{ ++ struct mmbi_buf_vpscb *buffer_desc; ++ int rc; ++ ++ if (!desc_virt || !IS_ALIGNED((unsigned long)desc_virt, sizeof(long))) { ++ pr_err("%s: invalid desc_virt address %p\n", __func__, desc_virt); ++ return -EINVAL; ++ } ++ ++ chan_desc->priv->peer_ready = false; ++ memcpy_fromio(&chan_desc->b2h_ba_offset, desc_virt, sizeof(chan_desc->b2h_ba_offset)); ++ memcpy_fromio(&chan_desc->h2b_ba_offset, desc_virt + 4, sizeof(chan_desc->h2b_ba_offset)); ++ memcpy_fromio(&chan_desc->b2h_l, desc_virt + 8, sizeof(chan_desc->b2h_l)); ++ memcpy_fromio(&chan_desc->h2b_l, desc_virt + 12, sizeof(chan_desc->h2b_l)); ++ chan_desc->b2h_ba_offset = be32_to_cpu(chan_desc->b2h_ba_offset); ++ chan_desc->h2b_ba_offset = be32_to_cpu(chan_desc->h2b_ba_offset); ++ chan_desc->b2h_l = be32_to_cpu(chan_desc->b2h_l); ++ chan_desc->h2b_l = be32_to_cpu(chan_desc->h2b_l); ++ chan_desc->b2h_ba_offset = MMBI_BUF_ADDR_ALIGN(chan_desc->b2h_ba_offset); ++ chan_desc->h2b_ba_offset = MMBI_BUF_ADDR_ALIGN(chan_desc->h2b_ba_offset); ++ desc_virt += 16; ++ chan_desc->buffer_type = ioread8(desc_virt++) & MMBI_BUF_TYPE_MASK; ++ ++ pr_info("%s: b2h_ba_offset=0x%x, h2b_ba_offset=0x%x\n", __func__, ++ chan_desc->b2h_ba_offset, chan_desc->h2b_ba_offset); ++ pr_info("%s: b2h_l=0x%x, h2b_l=0x%x\n", __func__, chan_desc->b2h_l, chan_desc->h2b_l); ++ pr_info("%s: buffer_type=%u\n", __func__, chan_desc->buffer_type); ++ ++ if (chan_desc->buffer_type == MMBI_BUFFER_TYPE_VPSCB) { ++ buffer_desc = &chan_desc->buffer_desc; ++ rc = mmbi_chan_init_vpscb_host(desc_virt, buffer_desc); ++ } ++ ++ if (!rc) ++ mmbi_channel_state_handler(chan_desc, chan_desc->mmbi->desc_virt); ++ ++ return rc; ++} ++ ++void mmbi_channel_irq_host(struct mmbi_chan_desc *chan) ++{ ++ mmbi_channel_state_handler(chan, chan->mmbi->desc_virt); ++} ++EXPORT_SYMBOL_GPL(mmbi_channel_irq_host); ++ ++int mmbi_instance_init_host(struct mmbi_ins_desc *mmbi) ++{ ++ struct device *dev = mmbi->dev; ++ struct mmbi_chan_desc *chan; ++ int rc; ++ u8 __iomem *desc_virt; ++ u8 chan_idx, val; ++ ++ desc_virt = mmbi->desc_virt; ++ if (mmbi_instance_signature_check(desc_virt)) { ++ dev_err(dev, "Invalid MMBI signature\n"); ++ return -EINVAL; ++ } ++ ++ desc_virt += sizeof(MMBI_SIGNATURE) - 1; // exclude signature from descriptor parsing ++ chan_idx = 0; ++ ++ mmbi->mmbi_version = ioread8(desc_virt++) & MMBI_VERSION_MASK; ++ if (mmbi->mmbi_version < MMBI_VERSION_1_0 || mmbi->mmbi_version > MMBI_VERSION_1_1) { ++ dev_err(dev, "Unsupported MMBI version: %d\n", mmbi->mmbi_version); ++ return -EINVAL; ++ } ++ ++ if (mmbi->mmbi_version == MMBI_VERSION_1_1) { ++ val = ioread8(desc_virt++); ++ mmbi->os_use = val & MMBI_OS_USE_MASK; ++ mmbi->num_of_channels = (val & MMBI_NOI_MASK) >> 4; ++ if (mmbi->num_of_channels > MMBI_MAX_CHANNELS || mmbi->num_of_channels == 0) { ++ dev_err(dev, "Invalid number of instances: %d\n", mmbi->num_of_channels); ++ return -EINVAL; ++ } ++ } else { ++ mmbi->os_use = ioread8(desc_virt++) & MMBI_OS_USE_MASK; ++ } ++ dev_info(dev, "MMBI version 0x%x, os_use %u, num_of_channels %u\n", ++ mmbi->mmbi_version, mmbi->os_use, mmbi->num_of_channels); ++ ++ chan = &mmbi->chan_desc[chan_idx]; ++ chan->index = chan_idx; ++ chan->mmbi = mmbi; ++ rc = mmbi_channel_init_host(desc_virt, chan); ++ if (rc) { ++ dev_err(dev, "Failed to initialize MMBI channel %d\n", chan_idx); ++ return rc; ++ } ++ ++ desc_virt += MMBI_DESC_SIZE_CHANNEL; ++ chan_idx++; ++ ++ mmbi->host_int_type = ioread8(desc_virt++) & MMBI_INT_TYPE_MASK; ++ mmbi->host_int_location = ioread8(desc_virt++); ++ desc_virt += 3; // reserved 3 bytes ++ mmbi->host_int_value = ioread8(desc_virt++); ++ mmbi->bmc_int_type = ioread8(desc_virt++) & MMBI_INT_TYPE_MASK; ++ memcpy_fromio(&mmbi->bmc_int_location, desc_virt, sizeof(mmbi->bmc_int_location)); ++ mmbi->bmc_int_location = be32_to_cpu(mmbi->bmc_int_location); ++ desc_virt += sizeof(mmbi->bmc_int_location) + 4; // reserved 4 bytes ++ mmbi->bmc_int_value = ioread8(desc_virt++); ++ ++ desc_virt += 8; // reserved 8 bytes ++ ++ dev_info(dev, "host_int_type=%u, host_int_location=0x%x, host_int_value=0x%x\n", ++ mmbi->host_int_type, mmbi->host_int_location, mmbi->host_int_value); ++ dev_info(dev, "bmc_int_type=%u, bmc_int_location=0x%x, bmc_int_value=0x%x\n", ++ mmbi->bmc_int_type, mmbi->bmc_int_location, mmbi->bmc_int_value); ++ dev_info(dev, "offset after init header=0x%lx\n", ++ (unsigned long)(desc_virt - mmbi->desc_virt)); ++ ++ if (chan_idx < mmbi->num_of_channels) { ++ dev_info(dev, "Initializing additional MMBI channels\n"); ++ for (; chan_idx < mmbi->num_of_channels; chan_idx++) { ++ chan = &mmbi->chan_desc[chan_idx]; ++ chan->index = chan_idx; ++ chan->mmbi = mmbi; ++ rc = mmbi_channel_init_host(desc_virt, chan); ++ if (rc) { ++ dev_err(dev, "Failed to initialize MMBI channel %d\n", chan_idx); ++ return rc; ++ } ++ desc_virt += MMBI_DESC_SIZE_CHANNEL; ++ } ++ } ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mmbi_instance_init_host); +diff --git a/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.h b/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.h +--- a/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-mmbi-host.h 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++#ifndef __ASPEED_MMBI_HOST_H__ ++#define __ASPEED_MMBI_HOST_H__ ++ ++#include "aspeed-mmbi.h" ++ ++int mmbi_instance_init_host(struct mmbi_ins_desc *mmbi); ++void mmbi_channel_irq_host(struct mmbi_chan_desc *chan); ++ ++#endif /* __ASPEED_MMBI_HOST_H__ */ +diff --git a/drivers/soc/aspeed/mmbi/aspeed-mmbi-internal.h b/drivers/soc/aspeed/mmbi/aspeed-mmbi-internal.h +--- a/drivers/soc/aspeed/mmbi/aspeed-mmbi-internal.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-mmbi-internal.h 2026-09-09 15:51:44.721238026 +0000 +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++#ifndef __ASPEED_MMBI_INTERNAL_H__ ++#define __ASPEED_MMBI_INTERNAL_H__ ++ ++#include ++ ++#include "aspeed-mmbi.h" ++ ++struct net_device; ++ ++struct mmbi_chan_priv { ++ enum mmbi_state state; /* Current MMBI state of this channel */ ++ struct miscdevice miscdev; /* MMBI char device for this channel */ ++ wait_queue_head_t rx_wait; /* Wait queue for rx data available */ ++ wait_queue_head_t tx_wait; /* Wait queue for tx data available */ ++ bool rx_ready; /* Flag indicating if has data to be read */ ++ bool tx_ready; /* Flag indicating if ready to transmit data */ ++ spinlock_t rx_lock; /* Lock to prevent rx ready flag race */ ++ spinlock_t tx_lock; /* Lock to prevent tx ready flag race */ ++ u32 read_ptr; /* Current cached read pointer */ ++ u32 write_ptr; /* Current cached write pointer */ ++ bool peer_ready; /* Flag indicating peer ready bit */ ++ struct delayed_work poll_work; /* Work struct for polling check status */ ++ bool running; ++ struct mmbi_chan_desc *chan; /* Back pointer used by misc open */ ++ struct net_device *ndev; /* MCTP netdev when the channel selects it */ ++}; ++ ++#endif /* __ASPEED_MMBI_INTERNAL_H__ */ +diff --git a/drivers/soc/aspeed/mmbi/aspeed-mmbi.c b/drivers/soc/aspeed/mmbi/aspeed-mmbi.c +--- a/drivers/soc/aspeed/mmbi/aspeed-mmbi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-mmbi.c 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,939 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * ASPEED MMBI (Memory Mapped Bus Interface) Protocol Driver ++ * ++ * Copyright (c) 2026 ASPEED Technology Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "aspeed-mmbi.h" ++#include "aspeed-mmbi-internal.h" ++#include "aspeed-mmbi-bmc.h" ++#include "aspeed-mmbi-host.h" ++ ++static DEFINE_IDA(mmbi_ida); ++ ++#define MMBI_MCTP_MIN_MTU (sizeof(struct mctp_hdr) + 64) ++#define MMBI_MCTP_RX_BUF_SIZE SZ_4K ++ ++struct mmbi_mctp_netdev { ++ struct mmbi_chan_desc *chan; ++}; ++ ++static void mmbi_channel_start_polling(struct mmbi_chan_desc *chan_desc) ++{ ++ if (chan_desc->priv->running) ++ return; ++ ++ schedule_delayed_work(&chan_desc->priv->poll_work, ++ msecs_to_jiffies(chan_desc->poll_interval_ms)); ++ chan_desc->priv->running = true; ++} ++ ++static void mmbi_channel_stop_polling(struct mmbi_chan_desc *chan_desc) ++{ ++ if (!chan_desc->priv->running) ++ return; ++ ++ cancel_delayed_work_sync(&chan_desc->priv->poll_work); ++ chan_desc->priv->running = false; ++} ++ ++static void mmbi_update_rd_ptr(u8 __iomem *read_ptr, u32 read_size, u32 read_buf_size) ++{ ++ u32 reg, low, addr, reg_rb; ++ u32 retry_cnt; ++ if (!read_size || (read_size & 3)) { ++ pr_err("%s: read_size must be non-zero and multiple of 4\n", __func__); ++ return; ++ } ++ ++ read_ptr += sizeof(u32); /* read pointer is located at offset 4 in the structure */ ++ reg = ioread32(read_ptr); ++ ++ low = reg & ~MMBI_PTR_ADDR_MASK; /* bits [1:0] */ ++ addr = reg & MMBI_PTR_ADDR_MASK; /* bits [31:2] */ ++ ++ addr = (addr + read_size) % read_buf_size; ++ addr &= MMBI_PTR_ADDR_MASK; ++ retry_cnt = 0; ++ iowrite32(addr | low, read_ptr); ++ do { ++ reg_rb = ioread32(read_ptr); ++ retry_cnt++; ++ if (retry_cnt > 5) { ++ pr_info("%s: read pointer update not reflected after %u retries, reg 0x%x, expected 0x%x\n", ++ __func__, retry_cnt, reg_rb, addr | low); ++ break; ++ } ++ } while (reg_rb != (addr | low)); ++} ++ ++static void mmbi_update_wr_ptr(u8 __iomem *write_ptr, u32 write_size, u32 write_buf_size) ++{ ++ u32 reg, low, addr, reg_rb; ++ u32 retry_cnt; ++ if (!write_size || (write_size & 3)) { ++ pr_err("%s: write_size must be non-zero and multiple of 4\n", __func__); ++ return; ++ } ++ ++ reg = ioread32(write_ptr); ++ ++ low = reg & ~MMBI_PTR_ADDR_MASK; /* bits [1:0] */ ++ addr = reg & MMBI_PTR_ADDR_MASK; /* bits [31:2] */ ++ ++ addr = (addr + write_size) % write_buf_size; ++ addr &= MMBI_PTR_ADDR_MASK; ++ ++ retry_cnt = 0; ++ iowrite32(addr | low, write_ptr); ++ do { ++ reg_rb = ioread32(write_ptr); ++ retry_cnt++; ++ if (retry_cnt > 5) { ++ pr_info("%s: write pointer update not reflected after %u retries, reg 0x%x, expected 0x%x\n", ++ __func__, retry_cnt, reg_rb, addr | low); ++ break; ++ } ++ } while (reg_rb != (addr | low)); ++} ++ ++static void mmbi_parse_hdr(u8 __iomem *buf_virt, u32 *payload_len, u8 *padding, ++ u8 *protocol) ++{ ++ u8 hdr[MMBI_PKT_HDR_SIZE]; ++ u32 word24; ++ int i; ++ ++ /* Read header bytes via ioread8() */ ++ for (i = 0; i < MMBI_PKT_HDR_SIZE; i++) ++ hdr[i] = ioread8(buf_virt + i); ++ ++ /* Byte0..2 form the 24-bit field: [23:2]=len, [1:0]=pad */ ++ word24 = ((u32)hdr[0] << 16) | ((u32)hdr[1] << 8) | (u32)hdr[2]; ++ ++ *padding = word24 & 0x3; ++ *payload_len = (word24 >> 2) * 4 - *padding; ++ *protocol = hdr[3] & MMBI_PKT_PROTOCOL_MASK; ++} ++ ++static int mmbi_read(struct mmbi_chan_desc *chan, u8 *data, size_t read_len) ++{ ++ struct mmbi_ins_desc *mmbi = chan->mmbi; ++ u32 read_ptr, write_ptr, read_offset, buf_size, unhandled_len, base_addr; ++ u32 data_len, pkt_len; ++ u8 protocol, padding; ++ size_t first_part; ++ u8 __iomem *desc_virt; ++ ++ if (read_len == 0) ++ return -EINVAL; ++ ++ if (mmbi->role == MMBI_ROLE_BMC) { ++ read_ptr = chan->buffer_desc.h_ros_p; ++ write_ptr = chan->buffer_desc.h_rws_p; ++ buf_size = chan->h2b_l; ++ base_addr = chan->h2b_ba_offset; ++ } else { ++ read_ptr = chan->buffer_desc.h_rws_p; ++ write_ptr = chan->buffer_desc.h_ros_p; ++ buf_size = chan->b2h_l; ++ base_addr = chan->b2h_ba_offset; ++ } ++ ++ unhandled_len = mmbi_channel_unhandled_length(mmbi->desc_virt + read_ptr, ++ mmbi->desc_virt + write_ptr, ++ buf_size); ++ ++ if (unhandled_len == 0) ++ return 0; ++ ++ desc_virt = mmbi->desc_virt + base_addr; ++ data_len = 0; ++ read_offset = MMBI_PTR_ADDR_ALIGN(be32_to_cpu(ioread32be(mmbi->desc_virt + read_ptr + sizeof(u32)))); ++ /* MMBI read/write has 4byte unit, so reading header will never have bundary issues */ ++ mmbi_parse_hdr(desc_virt + read_offset, &data_len, &padding, &protocol); ++ ++ if (data_len > read_len) { ++ pr_warn("%s: not enough space(%ld) to read data(%u) for chan %u\n", ++ __func__, read_len, data_len, chan->index); ++ pr_warn("%s: read_offset %d padding %d protocol %d buf_size %d", ++ __func__, read_offset, padding, protocol, buf_size); ++ return -EINVAL; ++ } ++ pkt_len = MMBI_PKT_HDR_SIZE + data_len + padding; ++ ++ if (unhandled_len > pkt_len) { ++ pr_warn("%s: more data unhandled than current packet length for chan %u, unhandled_len %u pkt_len %u\n", ++ __func__, chan->index, unhandled_len, pkt_len); ++ } ++ ++ /* move offset to payload */ ++ read_offset = (read_offset + MMBI_PKT_HDR_SIZE) % buf_size; ++ if (protocol == MMBI_PROTOCOL_MCTP) { ++ if (read_offset + data_len <= buf_size) { ++ memcpy_fromio(data, desc_virt + read_offset, data_len); ++ } else { ++ first_part = buf_size - read_offset; ++ memcpy_fromio(data, desc_virt + read_offset, first_part); ++ memcpy_fromio(data + first_part, desc_virt, data_len - first_part); ++ } ++ } else { ++ pr_warn("%s: unsupported protocol %u on chan %u, discard packet\n", ++ __func__, protocol, chan->index); ++ } ++ mmbi_update_rd_ptr(mmbi->desc_virt + read_ptr, pkt_len, buf_size); ++ ++ return protocol == MMBI_PROTOCOL_MCTP ? data_len : 0; ++} ++ ++static int mmbi_write(struct mmbi_chan_desc *chan, const u8 *data, size_t data_len, u8 protocol) ++{ ++ struct mmbi_ins_desc *mmbi = chan->mmbi; ++ u32 read_ptr, write_ptr, write_offset, buf_size, avail_len, val, base_addr, total_len; ++ u8 __iomem *desc_virt; ++ size_t first_part; ++ u8 padding = 0; ++ ++ if (data_len == 0) ++ return -EINVAL; ++ ++ if (!chan->priv->peer_ready) { ++ pr_info("%s: chan %d peer not ready to receive data", __func__, ++ chan->index); ++ return -EAGAIN; ++ } ++ ++ if (mmbi->role == MMBI_ROLE_BMC) { ++ read_ptr = chan->buffer_desc.h_rws_p; ++ write_ptr = chan->buffer_desc.h_ros_p; ++ buf_size = chan->b2h_l; ++ base_addr = chan->b2h_ba_offset; ++ } else { ++ read_ptr = chan->buffer_desc.h_ros_p; ++ write_ptr = chan->buffer_desc.h_rws_p; ++ buf_size = chan->h2b_l; ++ base_addr = chan->h2b_ba_offset; ++ } ++ ++ avail_len = mmbi_channel_avail_length(mmbi->desc_virt + read_ptr, ++ mmbi->desc_virt + write_ptr, ++ buf_size); ++ ++ total_len = data_len + ((protocol == MMBI_PROTOCOL_MCTP) ? 4 : 0); ++ ++ /* at least 8bytes for 4byte header and 4byte data */ ++ if (avail_len < MMBI_PKT_MIN_SIZE) { ++ pr_info("%s: no available buffer to write for chan %u\n", ++ __func__, chan->index); ++ return -EAGAIN; ++ } ++ ++ if (total_len > avail_len) { ++ pr_info("%s: no available buffer to write for chan %u, requested %d, avail %d\n", ++ __func__, chan->index, total_len, avail_len); ++ return -EAGAIN; ++ } ++ ++ write_offset = MMBI_PTR_ADDR_ALIGN(be32_to_cpu(ioread32be(mmbi->desc_virt + write_ptr))); ++ desc_virt = mmbi->desc_virt + base_addr; ++ /* Multi-protocol header */ ++ if (protocol == MMBI_PROTOCOL_MCTP) { ++ padding = (4 - (data_len & 3)) & 3; /* Pad to 4 bytes */ ++ total_len += padding; ++ val = (data_len + padding) >> 2; ++ val &= 0x3FFFFF; /* 22 bits for length */ ++ iowrite8((val >> 14) & 0xff, desc_virt + write_offset); ++ write_offset++; ++ iowrite8((val >> 6) & 0xff, desc_virt + write_offset); ++ write_offset++; ++ iowrite8(((val << 2) & 0xff) | padding, desc_virt + write_offset); ++ write_offset++; ++ iowrite8(MMBI_PKT_PROTOCOL_MASK & protocol, desc_virt + write_offset); ++ write_offset++; ++ } ++ ++ write_offset %= buf_size; ++ if (write_offset + data_len < buf_size) { ++ memcpy_toio(desc_virt + write_offset, data, data_len); ++ } else { ++ first_part = buf_size - write_offset; ++ memcpy_toio(desc_virt + write_offset, data, first_part); ++ memcpy_toio(desc_virt, data + first_part, data_len - first_part); ++ } ++ mmbi_update_wr_ptr(mmbi->desc_virt + write_ptr, total_len, buf_size); ++ return data_len; ++} ++ ++static int mmbi_misc_open(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *miscdev = file->private_data; ++ struct mmbi_chan_priv *priv = container_of(miscdev, struct mmbi_chan_priv, ++ miscdev); ++ struct mmbi_chan_desc *chan_desc = priv->chan; ++ ++ file->private_data = chan_desc; ++ mmbi_channel_state_update(chan_desc, chan_desc->mmbi->desc_virt); ++ mmbi_channel_start_polling(chan_desc); ++ return 0; ++} ++ ++static int mmbi_misc_release(struct inode *inode, struct file *file) ++{ ++ struct mmbi_chan_desc *chan_desc = file->private_data; ++ ++ mmbi_channel_stop_polling(chan_desc); ++ return 0; ++} ++ ++static ssize_t mmbi_misc_read(struct file *file, char __user *buf, size_t count, ++ loff_t *ppos) ++{ ++ struct mmbi_chan_desc *chan_desc = file->private_data; ++ ssize_t ret; ++ void *kbuf; ++ ++ if (count == 0) ++ return 0; ++ ++ kbuf = kmalloc(count, GFP_KERNEL); ++ if (!kbuf) ++ return -ENOMEM; ++ ++ spin_lock(&chan_desc->priv->rx_lock); ++ chan_desc->priv->rx_ready = false; ++ ret = mmbi_read(chan_desc, kbuf, count); ++ spin_unlock(&chan_desc->priv->rx_lock); ++ if (ret > 0) { ++ if (copy_to_user(buf, kbuf, ret)) { ++ pr_err("%s: chan %u copy %zd bytes to user failed\n", ++ __func__, chan_desc->index, ret); ++ ret = -EFAULT; ++ } ++ } else { ++ pr_err("%s: read failed for chan %u ret %zd\n", __func__, chan_desc->index, ret); ++ } ++ kfree(kbuf); ++ return ret; ++} ++ ++static ssize_t mmbi_misc_write(struct file *file, const char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mmbi_chan_desc *chan_desc = file->private_data; ++ ssize_t ret; ++ void *kbuf; ++ ++ if (count == 0) ++ return 0; ++ ++ kbuf = memdup_user(buf, count); ++ if (IS_ERR(kbuf)) ++ return PTR_ERR(kbuf); ++ ++ /* currently only MCTP defined in the spec */ ++ spin_lock(&chan_desc->priv->tx_lock); ++ ret = mmbi_write(chan_desc, kbuf, count, MMBI_PROTOCOL_MCTP); ++ spin_unlock(&chan_desc->priv->tx_lock); ++ kfree(kbuf); ++ return ret; ++} ++ ++static __poll_t mmbi_misc_poll(struct file *file, struct poll_table_struct *pt) ++{ ++ struct mmbi_chan_desc *chan_desc = file->private_data; ++ struct mmbi_ins_desc *mmbi = chan_desc->mmbi; ++ __poll_t ret, requested_events; ++ ++ ret = 0; ++ requested_events = poll_requested_events(pt); ++ mmbi_channel_state_update(chan_desc, mmbi->desc_virt); ++ ++ if (chan_desc->priv->state != NORMAL_RUNTIME && chan_desc->priv->state != RESET_REQ_BY_BMC) { ++ pr_info("%s: chan %u invalid state 0x%x, not ready for polling\n", ++ __func__, chan_desc->index, chan_desc->priv->state); ++ return ret; ++ } ++ ++ if (requested_events & POLL_IN) ++ poll_wait(file, &chan_desc->priv->rx_wait, pt); ++ if (requested_events & POLL_OUT) ++ poll_wait(file, &chan_desc->priv->tx_wait, pt); ++ ++ if (chan_desc->priv->rx_ready) ++ ret |= POLL_IN | POLLRDNORM; ++ if (chan_desc->priv->tx_ready) ++ ret |= POLL_OUT | POLLWRNORM; ++ ++ return ret; ++} ++ ++static const struct file_operations mmbi_fops = { ++ .owner = THIS_MODULE, ++ .open = mmbi_misc_open, ++ .read = mmbi_misc_read, ++ .write = mmbi_misc_write, ++ .poll = mmbi_misc_poll, ++ .release = mmbi_misc_release, ++}; ++ ++static void mmbi_channel_refresh_locked(struct mmbi_chan_desc *chan_desc) ++{ ++ if (chan_desc->mmbi->role == MMBI_ROLE_BMC) ++ mmbi_channel_irq_bmc(chan_desc); ++ else ++ mmbi_channel_irq_host(chan_desc); ++} ++ ++#if IS_ENABLED(CONFIG_MCTP) ++static void mmbi_mctp_update_queue_state(struct mmbi_chan_desc *chan_desc) ++{ ++ struct net_device *ndev = chan_desc->priv->ndev; ++ ++ if (!ndev || !netif_running(ndev)) ++ return; ++ ++ if (chan_desc->priv->state == NORMAL_RUNTIME) { ++ netif_carrier_on(ndev); ++ if (chan_desc->priv->tx_ready) ++ netif_wake_queue(ndev); ++ else ++ netif_stop_queue(ndev); ++ } else { ++ netif_carrier_off(ndev); ++ netif_stop_queue(ndev); ++ } ++} ++ ++static void mmbi_mctp_handle_rx(struct mmbi_chan_desc *chan_desc) ++{ ++ struct net_device *ndev = chan_desc->priv->ndev; ++ u8 *tmp_buf; ++ ++ if (!ndev || !netif_running(ndev)) ++ goto out; ++ ++ tmp_buf = kmalloc(MMBI_MCTP_RX_BUF_SIZE, GFP_KERNEL); ++ if (!tmp_buf) { ++ ndev->stats.rx_dropped++; ++ goto out; ++ } ++ ++ while (netif_running(ndev)) { ++ struct sk_buff *skb; ++ struct mctp_skb_cb *cb; ++ int ret; ++ ++ if (!READ_ONCE(chan_desc->priv->rx_ready)) ++ break; ++ ++ spin_lock(&chan_desc->priv->rx_lock); ++ spin_lock(&chan_desc->priv->tx_lock); ++ ret = mmbi_read(chan_desc, tmp_buf, MMBI_MCTP_RX_BUF_SIZE); ++ mmbi_channel_refresh_locked(chan_desc); ++ spin_unlock(&chan_desc->priv->tx_lock); ++ spin_unlock(&chan_desc->priv->rx_lock); ++ ++ if (ret <= 0) { ++ if (ret < 0) ++ ndev->stats.rx_dropped++; ++ break; ++ } ++ ++ skb = netdev_alloc_skb(ndev, ret); ++ if (!skb) { ++ ndev->stats.rx_dropped++; ++ break; ++ } ++ ++ skb_put_data(skb, tmp_buf, ret); ++ skb->protocol = htons(ETH_P_MCTP); ++ skb_reset_mac_header(skb); ++ skb_reset_network_header(skb); ++ ++ cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++ netif_wake_queue(ndev); ++ ndev->stats.rx_packets++; ++ ndev->stats.rx_bytes += ret; ++ if (netif_rx(skb) == NET_RX_DROP) ++ ndev->stats.rx_dropped++; ++ } ++ ++ kfree(tmp_buf); ++out: ++ mmbi_mctp_update_queue_state(chan_desc); ++} ++ ++static netdev_tx_t mmbi_mctp_ndo_start_xmit(struct sk_buff *skb, ++ struct net_device *ndev) ++{ ++ struct mmbi_mctp_netdev *mmbi_ndev = netdev_priv(ndev); ++ struct mmbi_chan_desc *chan_desc = mmbi_ndev->chan; ++ int ret; ++ ++ spin_lock(&chan_desc->priv->rx_lock); ++ spin_lock(&chan_desc->priv->tx_lock); ++ ret = mmbi_write(chan_desc, skb->data, skb->len, MMBI_PROTOCOL_MCTP); ++ mmbi_channel_refresh_locked(chan_desc); ++ spin_unlock(&chan_desc->priv->tx_lock); ++ spin_unlock(&chan_desc->priv->rx_lock); ++ ++ if (ret == -EAGAIN) { ++ netif_stop_queue(ndev); ++ return NETDEV_TX_BUSY; ++ } ++ ++ if (ret < 0) { ++ ndev->stats.tx_dropped++; ++ dev_kfree_skb_any(skb); ++ return NETDEV_TX_OK; ++ } ++ ++ ndev->stats.tx_packets++; ++ ndev->stats.tx_bytes += ret; ++ dev_kfree_skb_any(skb); ++ mmbi_mctp_update_queue_state(chan_desc); ++ ++ return NETDEV_TX_OK; ++} ++ ++static int mmbi_mctp_ndo_open(struct net_device *ndev) ++{ ++ struct mmbi_mctp_netdev *mmbi_ndev = netdev_priv(ndev); ++ struct mmbi_chan_desc *chan_desc = mmbi_ndev->chan; ++ ++ spin_lock(&chan_desc->priv->rx_lock); ++ spin_lock(&chan_desc->priv->tx_lock); ++ mmbi_channel_refresh_locked(chan_desc); ++ spin_unlock(&chan_desc->priv->tx_lock); ++ spin_unlock(&chan_desc->priv->rx_lock); ++ ++ mmbi_channel_start_polling(chan_desc); ++ mmbi_mctp_handle_rx(chan_desc); ++ mmbi_mctp_update_queue_state(chan_desc); ++ return 0; ++} ++ ++static int mmbi_mctp_ndo_stop(struct net_device *ndev) ++{ ++ struct mmbi_mctp_netdev *mmbi_ndev = netdev_priv(ndev); ++ ++ netif_stop_queue(ndev); ++ mmbi_channel_stop_polling(mmbi_ndev->chan); ++ return 0; ++} ++ ++static const struct net_device_ops mmbi_mctp_netdev_ops = { ++ .ndo_open = mmbi_mctp_ndo_open, ++ .ndo_stop = mmbi_mctp_ndo_stop, ++ .ndo_start_xmit = mmbi_mctp_ndo_start_xmit, ++}; ++ ++static void mmbi_mctp_net_setup(struct net_device *ndev) ++{ ++ ndev->type = ARPHRD_MCTP; ++ ndev->flags = IFF_NOARP; ++ ndev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; ++ ndev->hard_header_len = 0; ++ ndev->addr_len = 0; ++ ndev->netdev_ops = &mmbi_mctp_netdev_ops; ++} ++ ++static int mmbi_channel_init_mctp_netdev(struct mmbi_chan_desc *chan_desc) ++{ ++ struct mmbi_ins_desc *mmbi = chan_desc->mmbi; ++ struct mmbi_mctp_netdev *mmbi_ndev; ++ struct net_device *ndev; ++ char ifname[IFNAMSIZ]; ++ u32 max_mtu; ++ int ret; ++ ++ max_mtu = min(chan_desc->b2h_l, chan_desc->h2b_l); ++ if (max_mtu <= MMBI_PKT_HDR_SIZE || ++ max_mtu - MMBI_PKT_HDR_SIZE < MMBI_MCTP_MIN_MTU) { ++ dev_err(mmbi->dev, ++ "channel %u MMBI buffers are too small for MCTP netdev\n", ++ chan_desc->index); ++ return -EINVAL; ++ } ++ ++ snprintf(ifname, sizeof(ifname), "mctpmm%dch%d", ++ mmbi->ins_id, chan_desc->index); ++ ndev = alloc_netdev(sizeof(*mmbi_ndev), ifname, NET_NAME_UNKNOWN, ++ mmbi_mctp_net_setup); ++ if (!ndev) ++ return -ENOMEM; ++ ++ SET_NETDEV_DEV(ndev, mmbi->dev); ++ ndev->dev.of_node = mmbi->dev->of_node; ++ ndev->min_mtu = MMBI_MCTP_MIN_MTU; ++ ndev->max_mtu = max_mtu - MMBI_PKT_HDR_SIZE; ++ ndev->mtu = ndev->max_mtu; ++ ++ mmbi_ndev = netdev_priv(ndev); ++ mmbi_ndev->chan = chan_desc; ++ chan_desc->priv->ndev = ndev; ++ ++ ret = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_MMBI); ++ if (ret) { ++ dev_err(mmbi->dev, ++ "failed to register MCTP netdev for channel %u: %d\n", ++ chan_desc->index, ret); ++ chan_desc->priv->ndev = NULL; ++ free_netdev(ndev); ++ return ret; ++ } ++ ++ dev_info(mmbi->dev, "registered MCTP netdev %s for channel %u\n", ++ ndev->name, chan_desc->index); ++ ++ return 0; ++} ++ ++static void mmbi_channel_remove_mctp_netdev(struct mmbi_chan_desc *chan_desc) ++{ ++ struct net_device *ndev = chan_desc->priv->ndev; ++ ++ if (!ndev) ++ return; ++ ++ mctp_unregister_netdev(ndev); ++ free_netdev(ndev); ++ chan_desc->priv->ndev = NULL; ++} ++#else ++static void mmbi_mctp_handle_rx(struct mmbi_chan_desc *chan_desc) ++{ ++} ++ ++static int mmbi_channel_init_mctp_netdev(struct mmbi_chan_desc *chan_desc) ++{ ++ struct mmbi_ins_desc *mmbi = chan_desc->mmbi; ++ ++ dev_err(mmbi->dev, "MCTP support is not reachable for MMBI netdev mode\n"); ++ return -EOPNOTSUPP; ++} ++ ++static void mmbi_channel_remove_mctp_netdev(struct mmbi_chan_desc *chan_desc) ++{ ++} ++#endif ++ ++static int mmbi_channel_init_miscdev(struct mmbi_chan_desc *chan_desc) ++{ ++ struct mmbi_ins_desc *mmbi = chan_desc->mmbi; ++ int ret; ++ ++ chan_desc->priv->miscdev.name = ++ devm_kasprintf(mmbi->dev, GFP_KERNEL, "mmbi%d-ch%d", ++ mmbi->ins_id, chan_desc->index); ++ ++ if (!chan_desc->priv->miscdev.name) { ++ pr_err("%s: failed to allocate misc device name for chan %u\n", ++ __func__, chan_desc->index); ++ return -ENOMEM; ++ } ++ chan_desc->priv->miscdev.minor = MISC_DYNAMIC_MINOR; ++ chan_desc->priv->miscdev.fops = &mmbi_fops; ++ chan_desc->priv->miscdev.parent = mmbi->dev; ++ ++ ret = misc_register(&chan_desc->priv->miscdev); ++ if (ret) ++ pr_err("%s: failed to register misc device for chan %u\n", ++ __func__, chan_desc->index); ++ ++ return ret; ++} ++ ++static void mmbi_channel_remove_miscdev(struct mmbi_chan_desc *chan_desc) ++{ ++ misc_deregister(&chan_desc->priv->miscdev); ++} ++ ++static int mmbi_channel_init_user_interface(struct mmbi_chan_desc *chan_desc) ++{ ++ switch (chan_desc->app_interface) { ++ case MMBI_APP_INTF_MCTP_NETDEV: ++ return mmbi_channel_init_mctp_netdev(chan_desc); ++ case MMBI_APP_INTF_IOCTL: ++ default: ++ return mmbi_channel_init_miscdev(chan_desc); ++ } ++} ++ ++static void mmbi_channel_remove_user_interface(struct mmbi_chan_desc *chan_desc) ++{ ++ switch (chan_desc->app_interface) { ++ case MMBI_APP_INTF_MCTP_NETDEV: ++ mmbi_channel_remove_mctp_netdev(chan_desc); ++ break; ++ case MMBI_APP_INTF_IOCTL: ++ default: ++ mmbi_channel_remove_miscdev(chan_desc); ++ break; ++ } ++} ++ ++static int mmbi_instance_init_user_interface(struct mmbi_ins_desc *mmbi) ++{ ++ int i, ret; ++ ++ for (i = 0; i < mmbi->num_of_channels; i++) { ++ ret = mmbi_channel_init_user_interface(&mmbi->chan_desc[i]); ++ if (ret) ++ goto err_remove_interfaces; ++ } ++ ++ return 0; ++ ++err_remove_interfaces: ++ while (i > 0) { ++ i--; ++ mmbi_channel_remove_user_interface(&mmbi->chan_desc[i]); ++ } ++ ++ return ret; ++} ++ ++static void mmbi_instance_remove_user_interface(struct mmbi_ins_desc *mmbi) ++{ ++ int i; ++ ++ for (i = 0; i < mmbi->num_of_channels; i++) ++ mmbi_channel_remove_user_interface(&mmbi->chan_desc[i]); ++} ++ ++static void mmbi_channel_poll_handler(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct mmbi_chan_priv *priv = container_of(dwork, struct mmbi_chan_priv, poll_work); ++ struct mmbi_chan_desc *chan_desc = priv->chan; ++ struct mmbi_ins_desc *mmbi = chan_desc->mmbi; ++ u8 __iomem *desc_virt = mmbi->desc_virt; ++ ++ u32 state_location, read_ptr, write_ptr; ++ ++ state_location = (mmbi->role == MMBI_ROLE_BMC) ? chan_desc->buffer_desc.h_rws_p : ++ chan_desc->buffer_desc.h_ros_p; ++ ++ write_ptr = ioread32(desc_virt + state_location); ++ read_ptr = ioread32(desc_virt + state_location + sizeof(u32)); ++ ++ if (write_ptr != chan_desc->priv->write_ptr || read_ptr != chan_desc->priv->read_ptr) { ++ chan_desc->priv->write_ptr = write_ptr; ++ chan_desc->priv->read_ptr = read_ptr; ++ ++ spin_lock(&chan_desc->priv->rx_lock); ++ spin_lock(&chan_desc->priv->tx_lock); ++ mmbi_channel_refresh_locked(chan_desc); ++ spin_unlock(&chan_desc->priv->tx_lock); ++ spin_unlock(&chan_desc->priv->rx_lock); ++ ++ } ++ ++ if (chan_desc->app_interface == MMBI_APP_INTF_MCTP_NETDEV) { ++ mmbi_mctp_handle_rx(chan_desc); ++ } else { ++ if (chan_desc->priv->rx_ready) ++ wake_up_interruptible(&chan_desc->priv->rx_wait); ++ if (chan_desc->priv->tx_ready) ++ wake_up_interruptible(&chan_desc->priv->tx_wait); ++ } ++ ++ /* Reschedule the work */ ++ schedule_delayed_work(&chan_desc->priv->poll_work, msecs_to_jiffies(chan_desc->poll_interval_ms)); ++} ++ ++static int mmbi_instance_init_channel_priv(struct mmbi_ins_desc *mmbi) ++{ ++ int i; ++ struct mmbi_chan_desc *chan_desc; ++ ++ for (i = 0; i < mmbi->num_of_channels; i++) { ++ chan_desc = &mmbi->chan_desc[i]; ++ INIT_DELAYED_WORK(&chan_desc->priv->poll_work, mmbi_channel_poll_handler); ++ spin_lock_init(&chan_desc->priv->rx_lock); ++ spin_lock_init(&chan_desc->priv->tx_lock); ++ init_waitqueue_head(&chan_desc->priv->rx_wait); ++ init_waitqueue_head(&chan_desc->priv->tx_wait); ++ chan_desc->priv->tx_ready = true; ++ chan_desc->priv->rx_ready = false; ++ chan_desc->priv->read_ptr = 0; ++ chan_desc->priv->write_ptr = 0; ++ chan_desc->priv->running = false; ++ if (chan_desc->poll_interval_ms == 0) ++ chan_desc->poll_interval_ms = MMBI_POLL_INTERVAL_MS; ++ chan_desc->priv->ndev = NULL; ++ } ++ return 0; ++} ++ ++int mmbi_channel_avail_length(u8 __iomem *read_structure, ++ u8 __iomem *write_structure, u32 buf_size) ++{ ++ u32 __read_offset = ioread32be(read_structure + sizeof(u32)); ++ u32 __write_offset = ioread32be(write_structure); ++ ++ __read_offset = MMBI_PTR_ADDR_ALIGN(be32_to_cpu(__read_offset)); ++ __write_offset = MMBI_PTR_ADDR_ALIGN(be32_to_cpu(__write_offset)); ++ ++ return (__write_offset >= __read_offset) ? ++ (buf_size - __write_offset + __read_offset) : ++ (__read_offset - __write_offset); ++} ++EXPORT_SYMBOL_GPL(mmbi_channel_avail_length); ++ ++int mmbi_channel_unhandled_length(u8 __iomem *read_structure, ++ u8 __iomem *write_structure, u32 buf_size) ++{ ++ u32 __read_offset = ioread32be(read_structure + sizeof(u32)); ++ u32 __write_offset = ioread32be(write_structure); ++ ++ __read_offset = MMBI_PTR_ADDR_ALIGN(be32_to_cpu(__read_offset)); ++ __write_offset = MMBI_PTR_ADDR_ALIGN(be32_to_cpu(__write_offset)); ++ return (__write_offset >= __read_offset) ? ++ (__write_offset - __read_offset) : ++ (buf_size - __read_offset + __write_offset); ++} ++EXPORT_SYMBOL_GPL(mmbi_channel_unhandled_length); ++ ++void mmbi_channel_state_update(struct mmbi_chan_desc *chan, u8 __iomem *desc_virt) ++{ ++ u8 __iomem *host_rws_vmem; ++ u8 __iomem *host_ros_vmem; ++ u8 host_rws_val; ++ u8 host_ros_val; ++ struct mmbi_buf_vpscb *buf_desc; ++ enum mmbi_state prev_state; ++ ++ if (chan->buffer_type == MMBI_BUFFER_TYPE_VPSCB) { ++ prev_state = chan->priv->state; ++ buf_desc = &chan->buffer_desc; ++ host_rws_vmem = desc_virt + buf_desc->h_rws_p; ++ host_ros_vmem = desc_virt + buf_desc->h_ros_p; ++ host_rws_val = ioread8(host_rws_vmem); ++ host_ros_val = ioread8(host_ros_vmem); ++ chan->priv->state = (MMBI_STATE_GET_IF_UP(host_ros_val) << 2) | ++ (MMBI_STATE_GET_RST(host_ros_val) << 2) | ++ MMBI_STATE_GET_IF_UP(host_rws_val) | ++ MMBI_STATE_GET_RST(host_rws_val); ++ ++ chan->priv->peer_ready = (chan->mmbi->role == MMBI_ROLE_BMC) ? ++ mmbi_get_ready(host_rws_vmem) : ++ mmbi_get_ready(host_ros_vmem); ++ } else { ++ pr_err("\t%s: unsupported buffer type %d\n", ++ __func__, chan->buffer_type); ++ chan->priv->state = POWER_UP_OR_ERROR; ++ } ++} ++EXPORT_SYMBOL_GPL(mmbi_channel_state_update); ++ ++int mmbi_instance_init(struct mmbi_ins_desc *mmbi) ++{ ++ int rc, i; ++ ++ if (!mmbi) { ++ rc = -EINVAL; ++ goto out_fail; ++ } ++ ++ if (mmbi->role == MMBI_ROLE_BMC) { ++ if (mmbi->mmbi_version != MMBI_VERSION_1_0 && ++ mmbi->mmbi_version != MMBI_VERSION_1_1) { ++ rc = -EINVAL; ++ goto out_fail; ++ } ++ } ++ ++ mmbi->ins_id = ida_alloc(&mmbi_ida, GFP_KERNEL); ++ if (mmbi->ins_id < 0) { ++ rc = mmbi->ins_id; ++ goto out_fail; ++ } ++ ++ for (i = 0; i < MMBI_MAX_CHANNELS; i++) { ++ mmbi->chan_desc[i].priv = devm_kzalloc(mmbi->dev, sizeof(*mmbi->chan_desc[i].priv), GFP_KERNEL); ++ if (!mmbi->chan_desc[i].priv) { ++ rc = -ENOMEM; ++ goto out_fail; ++ } ++ mmbi->chan_desc[i].priv->chan = &mmbi->chan_desc[i]; ++ } ++ ++ if (mmbi->role == MMBI_ROLE_BMC) ++ rc = mmbi_instance_init_bmc(mmbi); ++ else ++ rc = mmbi_instance_init_host(mmbi); ++ ++ if (rc) ++ goto ida_free; ++ ++ rc = mmbi_instance_init_channel_priv(mmbi); ++ if (rc) ++ goto ida_free; ++ ++ rc = mmbi_instance_init_user_interface(mmbi); ++ if (rc) ++ goto ida_free; ++ ++ return 0; ++ ++ida_free: ++ ida_free(&mmbi_ida, mmbi->ins_id); ++out_fail: ++ pr_err("%s: failed to init MMBI instance\n", ++ __func__); ++ return rc; ++} ++EXPORT_SYMBOL_GPL(mmbi_instance_init); ++ ++void mmbi_instance_remove(struct mmbi_ins_desc *mmbi) ++{ ++ int i; ++ u8 __iomem *struct_desc; ++ ++ if (!mmbi) ++ return; ++ ++ for (i = 0; i < mmbi->num_of_channels; i++) ++ mmbi_channel_stop_polling(&mmbi->chan_desc[i]); ++ ++ mmbi_instance_remove_user_interface(mmbi); ++ ++ for (i = 0; i < mmbi->num_of_channels; i++) { ++ struct_desc = ++ mmbi->desc_virt + ++ (mmbi->role == MMBI_ROLE_BMC ? ++ mmbi->chan_desc[i].buffer_desc.h_ros_p : ++ mmbi->chan_desc[i].buffer_desc.h_rws_p); ++ mmbi_clr_ready(struct_desc); ++ mmbi_clr_up(struct_desc); ++ } ++ ++ ida_free(&mmbi_ida, mmbi->ins_id); ++} ++EXPORT_SYMBOL_GPL(mmbi_instance_remove); +diff --git a/drivers/soc/aspeed/mmbi/aspeed-mmbi.h b/drivers/soc/aspeed/mmbi/aspeed-mmbi.h +--- a/drivers/soc/aspeed/mmbi/aspeed-mmbi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-mmbi.h 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,174 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright 2026 Aspeed Technology Inc. ++ */ ++#ifndef __ASPEED_MMBI_H__ ++#define __ASPEED_MMBI_H__ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define MMBI_SIGNATURE "#MMBI$" ++#define MMBI_DESC_SIZE_PREFIX 8 ++#define MMBI_DESC_SIZE_CHANNEL 32 ++#define MMBI_DESC_SIZE_INTERRUPT 24 ++ ++#define MMBI_VERSION_1_0 0x01 ++#define MMBI_VERSION_1_1 0x02 ++#define MMBI_VERSION_MASK GENMASK(3, 0) ++#define MMBI_MAX_CHANNELS 7 ++#define MMBI_NOI_MASK GENMASK(6, 4) ++#define MMBI_OS_USE_MASK BIT(0) ++#define MMBI_BUF_ADDR_ALIGN(addr) (ALIGN_DOWN(addr, 8)) ++#define MMBI_BUF_TYPE_MASK GENMASK(3, 0) ++#define MMBI_INT_VAL_MASK GENMASK(3, 0) ++#define MMBI_INT_TYPE_MASK GENMASK(2, 0) ++#define MMBI_PTR_ADDR_MASK GENMASK(31, 2) ++#define MMBI_PTR_ADDR_ALIGN(addr) (ALIGN_DOWN(addr, 4)) ++#define MMBI_PKT_PADDING_MASK GENMASK(1, 0) ++#define MMBI_PKT_PROTOCOL_MASK GENMASK(3, 0) ++#define MMBI_PKT_MIN_SIZE 8 ++#define MMBI_PKT_HDR_SIZE 4 ++ ++#define MMBI_STATE_RDY_MSK BIT(0) ++#define MMBI_STATE_RST_MSK BIT(0) ++#define MMBI_STATE_IF_UP_MSK BIT(1) ++ ++#define MMBI_STATE_GET_RST(val) ((val) & MMBI_STATE_RST_MSK) ++#define MMBI_STATE_GET_IF_UP(val) ((val) & MMBI_STATE_IF_UP_MSK) ++ ++#define MMBI_POLL_INTERVAL_MS 20 ++ ++static __always_inline u8 mmbi_get_ready(u8 __iomem *devm_virt) ++{ ++ return ioread8(devm_virt + sizeof(u32)) & MMBI_STATE_RDY_MSK; ++} ++ ++static __always_inline void mmbi_set_ready(u8 __iomem *devm_virt) ++{ ++ iowrite8(ioread8(devm_virt + sizeof(u32)) | MMBI_STATE_RDY_MSK, ++ devm_virt + sizeof(u32)); ++} ++ ++static __always_inline void mmbi_clr_ready(u8 __iomem *devm_virt) ++{ ++ iowrite8(ioread8(devm_virt + sizeof(u32)) & ~MMBI_STATE_RDY_MSK, ++ devm_virt + sizeof(u32)); ++} ++ ++static __always_inline void mmbi_set_up(u8 __iomem *devm_virt) ++{ ++ iowrite8(ioread8(devm_virt) | MMBI_STATE_IF_UP_MSK, devm_virt); ++} ++ ++static __always_inline void mmbi_clr_up(u8 __iomem *devm_virt) ++{ ++ iowrite8(ioread8(devm_virt) & ~MMBI_STATE_IF_UP_MSK, devm_virt); ++} ++ ++static __always_inline void mmbi_set_rst(u8 __iomem *devm_virt) ++{ ++ iowrite8(ioread8(devm_virt) | MMBI_STATE_RST_MSK, devm_virt); ++} ++ ++static __always_inline void mmbi_clr_rst(u8 __iomem *devm_virt) ++{ ++ iowrite8(ioread8(devm_virt) & ~MMBI_STATE_RST_MSK, devm_virt); ++} ++ ++enum mmbi_role { ++ MMBI_ROLE_HOST = 0, ++ MMBI_ROLE_BMC = 1, ++}; ++ ++enum mmbi_host_int_type { ++ MMBI_HOST_INT_NONE = 0, ++ MMBI_HOST_INT_PCIE = 1, ++ MMBI_HOST_INT_GPIO = 2, ++ MMBI_HOST_INT_ESPI_VW = 3, ++}; ++ ++enum mmbi_bmc_int_type { ++ MMBI_BMC_INT_NONE = 0, ++ MMBI_BMC_INT_MEM = 1, ++ MMBI_BMC_INT_INBAND = 2, ++}; ++ ++enum mmbi_state { /* B_U B_R H_U H_R */ ++ INIT_IN_PROGRESS = 0x00, /* 0 0 0 0 */ ++ INIT_COMPLETED = 0x08, /* 1 0 0 0 */ ++ NORMAL_RUNTIME = 0x0A, /* 1 0 1 0 */ ++ RESET_REQ_BY_BMC = 0x0E, /* 1 1 1 0 */ ++ RESET_REQ_BY_HOST = 0x0B, /* 1 0 1 1 */ ++ RESET_ACKED = 0x0F, /* 1 1 1 1 */ ++ TRANS_TO_INIT = 0x07, /* 0 1 1 1 */ ++ INIT_MISMATCH = 0x09, /* 1 0 0 1 */ ++ POWER_UP_OR_ERROR = 0x80000000, ++}; ++ ++enum mmbi_buffer_type { ++ MMBI_BUFFER_TYPE_VPSCB = 0x01, ++}; ++ ++enum mmbi_multi_protocol { ++ MMBI_PROTOCOL_MCTP = 0x04, ++}; ++ ++enum mmbi_app_interface { ++ MMBI_APP_INTF_IOCTL = 0, ++ MMBI_APP_INTF_MCTP_NETDEV, ++}; ++ ++struct mmbi_chan_priv; ++ ++struct mmbi_buf_vpscb { ++ u32 h_ros_p; /* Host Read Pointer address offset */ ++ u32 h_rws_p; /* Host Read Write Pointer address offset */ ++ u8 host_int_val_ch; /* Host interrupt value, only used in MMBI v1.1 */ ++ u8 bmc_int_val_ch; /* Host interrupt value, only used in MMBI v1.1 */ ++}; ++ ++struct mmbi_chan_desc { ++ u32 b2h_ba_offset; /* B2H Buffer Base Address */ ++ u32 h2b_ba_offset; /* H2B Buffer Base Address */ ++ u32 b2h_l; /* B2H Buffer Length */ ++ u32 h2b_l; /* H2B Buffer Length */ ++ u8 buffer_type; /* Buffer Type defined by enum mmbi_buffer_type */ ++ u8 index; /* Channel Index filled by mmbi_instance_init */ ++ enum mmbi_app_interface app_interface; /* User interface for this channel */ ++ struct mmbi_buf_vpscb buffer_desc; /* buffer descriptor information */ ++ struct mmbi_ins_desc *mmbi; /* Back pointer to instance descriptor */ ++ struct mmbi_chan_priv *priv; /* Internal runtime state */ ++ u32 poll_interval_ms; /* polling interval in ms, default 10ms */ ++}; ++ ++struct mmbi_ins_desc { ++ u8 __iomem *desc_virt; /* MMBI Instance Base Address */ ++ u8 mmbi_version; ++ u8 num_of_channels; /* NOI field, recording number of channels */ ++ u8 os_use; ++ u8 host_int_type; ++ u8 host_int_location; ++ u8 host_int_value; ++ u8 bmc_int_type; ++ u8 bmc_int_value; ++ u32 bmc_int_location; ++ int ins_id; ++ struct mmbi_chan_desc chan_desc[MMBI_MAX_CHANNELS]; ++ struct device *dev; ++ enum mmbi_role role; ++}; ++ ++void mmbi_channel_state_update(struct mmbi_chan_desc *chan, ++ u8 __iomem *desc_virt); ++int mmbi_channel_avail_length(u8 __iomem *read_structure, ++ u8 __iomem *write_structure, u32 buf_size); ++int mmbi_channel_unhandled_length(u8 __iomem *read_structure, ++ u8 __iomem *write_structure, u32 buf_size); ++int mmbi_instance_init(struct mmbi_ins_desc *mmbi); ++void mmbi_instance_remove(struct mmbi_ins_desc *mmbi); ++ ++#endif +diff --git a/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.c b/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.c +--- a/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.c 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,481 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright (C) ASPEED Technology Inc. ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "aspeed-pcie-mmbi.h" ++#include "aspeed-mmbi.h" ++ ++/* AST2700 E2M */ ++#define ASPEED_E2M_EVENT 0x0D0 ++#define ASPEED_E2M_EVENT_SET 0x0D4 ++#define ASPEED_E2M_EVENT_CLR 0x0D8 ++#define ASPEED_E2M_EVENT_EN 0x0DC ++#define ASPEED_E2M_ADRMAP00 0x100 ++#define ASPEED_E2M_WIRQA0 0x180 ++#define ASPEED_E2M_WIRQV0 0x1C0 ++#define ASPEED_E2M_SPROT_SIDG0 0x210 ++#define ASPEED_E2M_SPROT_CTL0 0x280 ++#define ASPEED_E2M_SPROT_ADR0 0x2C0 ++/* Value for triggering interrupt */ ++#define ASPEED_E2M_INT_VAL 0xC6 ++ ++/* AST2700 SCU */ ++#define ASPEED_SCU_DECODE_DEV BIT(18) ++#define ASPEED_SCU_INT_EN BIT(23) ++struct aspeed_platform { ++ int (*mmbi_init)(struct platform_device *pdev); ++}; ++ ++#define ASPEED_MMBI_APP_INTF_PROP "aspeed,mmbi-interface" ++ ++struct aspeed_pcie_mmbi { ++ struct device *dev; ++ struct regmap *device; ++ struct regmap *e2m; ++ const struct aspeed_platform *platform; ++ /* E2M index */ ++ int id; ++ int pid; ++ int msi_index; ++ int scu_bar_offset; ++ int e2m_index; ++ int e2m_h2b_int; ++ int num_of_channels; ++ bool host_int_en; ++ ++ /* Memory Mapping */ ++ void __iomem *mem_virt; ++ dma_addr_t mem_phy; ++ phys_addr_t mem_size; ++ ++ struct mmbi_ins_desc mmbi_desc; ++}; ++ ++static const char * ++aspeed_pcie_mmbi_app_interface_name(enum mmbi_app_interface app_interface) ++{ ++ switch (app_interface) { ++ case MMBI_APP_INTF_MCTP_NETDEV: ++ return "mctp-netdev"; ++ case MMBI_APP_INTF_IOCTL: ++ default: ++ return "ioctl"; ++ } ++} ++ ++static int aspeed_pcie_mmbi_parse_app_interface_string(struct device *dev, ++ const char *interface, ++ enum mmbi_app_interface *app_interface) ++{ ++ if (!strcmp(interface, "ioctl")) { ++ *app_interface = MMBI_APP_INTF_IOCTL; ++ return 0; ++ } ++ ++ if (!strcmp(interface, "mctp-netdev")) { ++ *app_interface = MMBI_APP_INTF_MCTP_NETDEV; ++ return 0; ++ } ++ ++ dev_err(dev, "invalid %s value '%s'\n", ++ ASPEED_MMBI_APP_INTF_PROP, interface); ++ return -EINVAL; ++} ++ ++static int aspeed_pcie_mmbi_parse_app_interface(struct device *dev, ++ struct mmbi_ins_desc *mmbi_desc) ++{ ++ int count, i, ret; ++ ++ for (i = 0; i < mmbi_desc->num_of_channels; i++) ++ mmbi_desc->chan_desc[i].app_interface = MMBI_APP_INTF_IOCTL; ++ ++ if (!of_find_property(dev->of_node, ASPEED_MMBI_APP_INTF_PROP, NULL)) ++ return 0; ++ ++ count = of_property_count_strings(dev->of_node, ASPEED_MMBI_APP_INTF_PROP); ++ if (count < 0) { ++ dev_err(dev, "invalid %s property\n", ASPEED_MMBI_APP_INTF_PROP); ++ return count; ++ } ++ ++ if (count != 1 && count != mmbi_desc->num_of_channels) { ++ dev_err(dev, "%s must contain either 1 or %u strings\n", ++ ASPEED_MMBI_APP_INTF_PROP, ++ (u32)mmbi_desc->num_of_channels); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < mmbi_desc->num_of_channels; i++) { ++ const char *interface; ++ int index = count == 1 ? 0 : i; ++ ++ ret = of_property_read_string_index(dev->of_node, ++ ASPEED_MMBI_APP_INTF_PROP, ++ index, &interface); ++ if (ret) ++ return ret; ++ ++ ret = aspeed_pcie_mmbi_parse_app_interface_string(dev, interface, ++ &mmbi_desc->chan_desc[i].app_interface); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++/** ++ * Detailed Buffer Layout: ++ * ++ * +---------------------------------------+ ++ * | HOST READ ONLY REGION (50%) | ++ * +---------------------------------------+ ++ * | mmbi_desc | ++ * | - Size: 32 + 32*noi bytes | ++ * +---------------------------------------+ ++ * | Interrupt Region (8 bytes) | ++ * | - trigger_int (1 byte) | ++ * | - reserved (7 bytes) | ++ * +---------------------------------------+ ++ * | Channel 0: | ++ * | hos_ro (8 bytes) | ++ * | - First 8 bytes after interrupt | ++ * | b2h_buffer | ++ * | - Channel 0 B2H buffer space | ++ * +---------------------------------------+ ++ * | Channel 1: | ++ * | hos_ro (8 bytes) | ++ * | b2h_buffer | ++ * | - Channel 1 B2H buffer space | ++ * +---------------------------------------+ ++ * | ... | ++ * +---------------------------------------+ ++ * | Channel 6: | ++ * | hos_ro (8 bytes) | ++ * | b2h_buffer | ++ * | - Channel 6 B2H buffer space | ++ * +---------------------------------------+ ++ * | HOST READ/WRITE REGION (50%) | ++ * +---------------------------------------+ ++ * | Channel 0: | ++ * | hos_rw (8 bytes) | ++ * | - First 8 bytes of this region | ++ * | h2b_buffer | ++ * | - Channel 0 H2B buffer space | ++ * +---------------------------------------+ ++ * | Channel 1: | ++ * | hos_rw (8 bytes) | ++ * | h2b_buffer | ++ * | - Channel 1 H2B buffer space | ++ * +---------------------------------------+ ++ * | ... | ++ * +---------------------------------------+ ++ * | Channel 6: | ++ * | hos_rw (8 bytes) | ++ * | h2b_buffer | ++ * | - Channel 6 H2B buffer space | ++ * +---------------------------------------+ ++ */ ++static int aspeed_pcie_mmbi_init(struct aspeed_pcie_mmbi *mmbi) ++{ ++ struct mmbi_ins_desc *mmbi_desc = &mmbi->mmbi_desc; ++ struct mmbi_buf_vpscb *vpscb; ++ struct device *dev = mmbi->dev; ++ u32 desc_size, b2h_buf_size, h2b_buf_size; ++ int i, ret; ++ ++ desc_size = MMBI_DESC_SIZE_PREFIX + MMBI_DESC_SIZE_INTERRUPT + ++ MMBI_DESC_SIZE_CHANNEL * mmbi->num_of_channels + 8; ++ ++ if ((mmbi->mem_size >> 1) <= desc_size) { ++ dev_err(dev, "MMBI memory size too small for B2H buffer\n"); ++ return -EINVAL; ++ } ++ ++ b2h_buf_size = ++ rounddown_pow_of_two(((mmbi->mem_size >> 1) - desc_size) / ++ mmbi->num_of_channels); ++ ++ h2b_buf_size = rounddown_pow_of_two((mmbi->mem_size >> 1) / mmbi->num_of_channels); ++ ++ mmbi_desc->desc_virt = mmbi->mem_virt; ++ mmbi_desc->mmbi_version = MMBI_VERSION_1_1; ++ mmbi_desc->os_use = 1; ++ mmbi_desc->num_of_channels = mmbi->num_of_channels; ++ mmbi_desc->host_int_type = MMBI_HOST_INT_PCIE; ++ mmbi_desc->host_int_location = mmbi->msi_index; ++ mmbi_desc->host_int_value = 0; /* cleared to 0 */ ++ mmbi_desc->bmc_int_type = MMBI_BMC_INT_INBAND; ++ mmbi_desc->bmc_int_location = 0; /* reserved */ ++ mmbi_desc->bmc_int_value = 0; /* cleared to 0 */ ++ mmbi_desc->dev = dev; ++ mmbi_desc->role = MMBI_ROLE_BMC; ++ ++ for (i = 0; i < mmbi_desc->num_of_channels; i++) { ++ vpscb = &mmbi_desc->chan_desc[i].buffer_desc; ++ vpscb->h_ros_p = desc_size + i * b2h_buf_size; ++ vpscb->h_rws_p = (mmbi->mem_size >> 1) + i * h2b_buf_size; ++ vpscb->host_int_val_ch = 0; ++ vpscb->bmc_int_val_ch = 0; ++ ++ mmbi_desc->chan_desc[i].b2h_ba_offset = vpscb->h_ros_p + 8; ++ mmbi_desc->chan_desc[i].h2b_ba_offset = vpscb->h_rws_p + 8; ++ mmbi_desc->chan_desc[i].b2h_l = b2h_buf_size - 8; ++ mmbi_desc->chan_desc[i].h2b_l = h2b_buf_size - 8; ++ // mmbi_desc->chan_desc[i].h2b_ba_offset = vpscb->h_rws_p + 16; ++ // mmbi_desc->chan_desc[i].b2h_l = 0x100; ++ // mmbi_desc->chan_desc[i].h2b_l = 0x100; ++ mmbi_desc->chan_desc[i].buffer_type = MMBI_BUFFER_TYPE_VPSCB; ++ } ++ ret = mmbi_instance_init(mmbi_desc); ++ if (ret) ++ dev_err(dev, "MMBI instance init failed: %d\n", ret); ++ ++ return ret; ++} ++ ++/* ++ * AST2700 PCIe MMBI (SCU & E2M) ++ * SoC | 0 | 1 | ++ * PCI class | MFD (0xFF_00_00) | MMBI (0x0C_0C_00) | ++ * Node | 0 1 | 0 | ++ * PID | 3 4 5 6 11 12 13 14 | 2 3 4 5 6 7 | ++ * E2M index | 0 1 2 3 4 5 6 7 | 0 1 2 3 4 5 | ++ * BAR index | 2 3 4 5 2 3 4 5 | 0 1 2 3 4 5 | ++ * SCU BAR | 3c 4c 5c 6c 3c 4c 5c 6c | 1c 50 3c 4c 5c 6c | ++ * E2M H2B Int | 0 1 2 3 0 1 2 3 | 0 1 2 3 4 5 | (bit) ++ */ ++static int aspeed_ast2700_pcie_mmbi_init(struct platform_device *pdev) ++{ ++ struct aspeed_pcie_mmbi *mmbi = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u32 e2m_index, pid; ++ struct resource res; ++ int ret, i; ++ ++ /* Get register map*/ ++ mmbi->e2m = syscon_node_to_regmap(dev->of_node->parent); ++ if (IS_ERR(mmbi->e2m)) { ++ dev_err(dev, "failed to find e2m regmap\n"); ++ return PTR_ERR(mmbi->e2m); ++ } ++ if (of_address_to_resource(dev->of_node->parent, 0, &res)) { ++ dev_err(dev, "Failed to get e2m resource\n"); ++ return -EINVAL; ++ } ++ if (res.start == 0x14c1d000) ++ mmbi->id = 2; ++ else if (res.start == 0x12c22000) ++ mmbi->id = 1; ++ else ++ mmbi->id = 0; /* 0x12c21000 */ ++ ++ mmbi->device = syscon_regmap_lookup_by_phandle(dev->of_node->parent, "aspeed,device"); ++ if (IS_ERR(mmbi->device)) { ++ dev_err(dev, "failed to find device regmap\n"); ++ return PTR_ERR(mmbi->device); ++ } ++ ++ ret = of_property_read_u32(dev->of_node, "index", &mmbi->e2m_index); ++ if (ret < 0) { ++ dev_err(dev, "cannot get mmbi index value\n"); ++ return ret; ++ } ++ ++ ret = of_property_read_u32(dev->of_node, "pid", &mmbi->pid); ++ if (ret < 0) { ++ dev_err(dev, "cannot get mmbi pid value\n"); ++ return ret; ++ } ++ ++ ret = of_property_read_u32(dev->of_node, "bar", &mmbi->scu_bar_offset); ++ if (ret < 0) { ++ dev_err(dev, "cannot get mmbi bar value\n"); ++ return ret; ++ } ++ ++ e2m_index = mmbi->e2m_index; ++ pid = mmbi->pid; ++ mmbi->e2m_h2b_int += mmbi->e2m_index; ++ if (mmbi->id < 2) { ++ /* PCIe device class, sub-class, protocol and reversion */ ++ regmap_write(mmbi->device, 0x18, 0x0C0C0027); ++ } else { ++ regmap_write(mmbi->device, 0x18, 0x0C0C0027); ++ regmap_write(mmbi->device, 0x78, ASPEED_SCU_INT_EN | ASPEED_SCU_DECODE_DEV); ++ } ++ ++ /* MSI */ ++ regmap_update_bits(mmbi->device, 0x74, GENMASK(7, 4), BIT(7) | (5 << 4)); ++ ++ regmap_update_bits(mmbi->device, 0x70, BIT(25) | BIT(17) | BIT(9) | BIT(1), ++ BIT(25) | BIT(17) | BIT(9) | BIT(1)); ++ ++ /* Calculate the BAR Size */ ++ for (i = 1; i < 16; i++) { ++ /* bar size check for 4k align */ ++ if ((mmbi->mem_size / 4096) == (1 << (i - 1))) ++ break; ++ } ++ if (i == 16) { ++ i = 0; ++ dev_warn(dev, "Bar size not align for 4K : %dK\n", (u32)mmbi->mem_size / 1024); ++ } ++ regmap_write(mmbi->device, mmbi->scu_bar_offset, (mmbi->mem_phy >> 4) | i); ++ regmap_write(mmbi->e2m, ASPEED_E2M_ADRMAP00 + (4 * pid), (mmbi->mem_phy >> 4) | i); ++ ++ ret = aspeed_pcie_mmbi_init(mmbi); ++ if (ret < 0) { ++ dev_err(dev, "Initialize MMBI device failed.\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++struct aspeed_platform ast2700_platform = { ++ .mmbi_init = aspeed_ast2700_pcie_mmbi_init, ++}; ++ ++static const struct of_device_id aspeed_pcie_mmbi_of_matches[] = { ++ { .compatible = "aspeed,ast2700-pcie-mmbi", .data = &ast2700_platform }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_pcie_mmbi_of_matches); ++ ++static int aspeed_pcie_mmbi_probe(struct platform_device *pdev) ++{ ++ struct aspeed_pcie_mmbi *mmbi; ++ struct mmbi_ins_desc *mmbi_desc; ++ struct device *dev = &pdev->dev; ++ struct resource res; ++ struct device_node *np; ++ const void *md; ++ int i, ret = 0; ++ ++ md = of_device_get_match_data(dev); ++ if (!md) ++ return -ENODEV; ++ ++ mmbi = devm_kzalloc(&pdev->dev, sizeof(struct aspeed_pcie_mmbi), GFP_KERNEL); ++ if (!mmbi) ++ return -ENOMEM; ++ dev_set_drvdata(dev, mmbi); ++ ++ mmbi->dev = dev; ++ mmbi->platform = md; ++ ++ /* Get MMBI memory size */ ++ np = of_parse_phandle(dev->of_node, "memory-region", 0); ++ if (!np || of_address_to_resource(np, 0, &res)) { ++ dev_err(dev, "Failed to find memory-region.\n"); ++ ret = -ENOMEM; ++ goto out_region; ++ } ++ ++ of_node_put(np); ++ ++ mmbi->mem_phy = res.start; ++ mmbi->mem_size = resource_size(&res); ++ mmbi->mem_virt = ioremap(mmbi->mem_phy, mmbi->mem_size); ++ if (!mmbi->mem_virt) { ++ dev_err(dev, "cannot map mmbi memory region\n"); ++ ret = -ENOMEM; ++ goto out_region; ++ } ++ ++ mmbi_desc = &mmbi->mmbi_desc; ++ memset(mmbi_desc, 0, sizeof(struct mmbi_ins_desc)); ++ ++ ret = of_property_read_u32(dev->of_node, "noi", &mmbi->num_of_channels); ++ if (ret || mmbi->num_of_channels == 0 || ++ mmbi->num_of_channels > MMBI_MAX_CHANNELS) { ++ dev_err(dev, "ret %d, MMBI NOI %d\n", ret, mmbi->num_of_channels); ++ goto out_unmap; ++ } ++ mmbi_desc->num_of_channels = mmbi->num_of_channels; ++ ++ ret = aspeed_pcie_mmbi_parse_app_interface(dev, mmbi_desc); ++ if (ret) ++ goto out_unmap; ++ ++ /* B2H Interrupt */ ++ mmbi->host_int_en = true; ++ ret = of_property_read_u32(dev->of_node, "msi", &mmbi->msi_index); ++ if (ret) { ++ dev_err(dev, "cannot get valid MMBI B2H interrupt location\n"); ++ mmbi->host_int_en = false; ++ } ++ ++ ret = mmbi->platform->mmbi_init(pdev); ++ if (ret) { ++ dev_err(dev, "Initialize pcie mmbi failed\n"); ++ goto out_unmap; ++ } ++ ++ for (i = 0; i < mmbi_desc->num_of_channels; i++) ++ dev_info(dev, "channel %d application interface: %s\n", ++ i, aspeed_pcie_mmbi_app_interface_name(mmbi_desc->chan_desc[i].app_interface)); ++ dev_info(dev, "ASPEED PCIe MMBI Dev %d: driver successfully loaded.\n", mmbi->id); ++ ++ return 0; ++out_unmap: ++ iounmap(mmbi->mem_virt); ++out_region: ++ devm_kfree(dev, mmbi); ++ dev_warn(dev, "aspeed pcie mmbi: driver init failed (ret=%d)!\n", ret); ++ return ret; ++} ++ ++static void aspeed_pcie_mmbi_remove(struct platform_device *pdev) ++{ ++ struct aspeed_pcie_mmbi *mmbi = platform_get_drvdata(pdev); ++ ++ mmbi_instance_remove(&mmbi->mmbi_desc); ++ iounmap(mmbi->mem_virt); ++ devm_kfree(&pdev->dev, mmbi); ++} ++ ++static struct platform_driver aspeed_pcie_mmbi_driver = { ++ .probe = aspeed_pcie_mmbi_probe, ++ .remove = aspeed_pcie_mmbi_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_pcie_mmbi_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_pcie_mmbi_driver); ++ ++MODULE_AUTHOR("Jacky Chou "); ++MODULE_DESCRIPTION("ASPEED PCI-E MMBI Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.h b/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.h +--- a/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/mmbi/aspeed-pcie-mmbi.h 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright 2024 Aspeed Technology Inc. ++ */ ++#ifndef __ASPEED_PCIE_MMBI_H__ ++#define __ASPEED_PCIE_MMBI_H__ ++ ++struct aspeed_pcie_mmbi; ++ ++struct aspeed_mmbi_channel { ++ struct aspeed_pcie_mmbi *mmbi; ++ struct device *dev; ++ ++ bool bmc_int_en; ++ u8 bmc_int_value; ++ u32 bmc_int_location; ++ u8 __iomem *bmc_int_vmem; ++ ++ bool host_int_en; ++ u8 host_int_location; ++ u8 host_int_value; ++}; ++ ++#endif +diff --git a/drivers/soc/aspeed/rvas/Kconfig b/drivers/soc/aspeed/rvas/Kconfig +--- a/drivers/soc/aspeed/rvas/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/Kconfig 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,9 @@ ++menu "ASPEED RVAS drivers" ++ ++config ASPEED_RVAS ++ tristate "ASPEED RVAS driver" ++ default n ++ help ++ Driver for ASPEED RVAS Engine ++ ++endmenu +diff --git a/drivers/soc/aspeed/rvas/Makefile b/drivers/soc/aspeed/rvas/Makefile +--- a/drivers/soc/aspeed/rvas/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/Makefile 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,3 @@ ++obj-$(CONFIG_ASPEED_RVAS) += rvas.o ++rvas-y := video_main.o hardware_engines.o video_engine.o ++ +diff --git a/drivers/soc/aspeed/rvas/hardware_engines.c b/drivers/soc/aspeed/rvas/hardware_engines.c +--- a/drivers/soc/aspeed/rvas/hardware_engines.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/hardware_engines.c 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,2201 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * File Name : hardware_engines.c ++ * Description : AST2600 frame grabber hardware engines ++ * ++ * Copyright (C) 2019-2021 ASPEED Technology Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "hardware_engines.h" ++#include "video.h" ++#include "video_debug.h" ++ ++static u32 dwBucketSizeRegOffset[BSE_MAX_BUCKET_SIZE_REGS] = { 0x20, 0x24, 0x28, ++ 0x2c, 0x30, 0x34, 0x38, 0x3c, 0x40, 0x44, 0x48, 0x4c, 0x50, 0x54, 0x58, ++ 0x5c }; ++static u32 arrBuckSizeRegIndex[16] = { 3, 5, 8, 6, 1, 7, 11, 10, 14, 13, 2, 4, ++ 9, 12, 0, 15 }; ++ ++static struct Resolution resTable1[0x3B - 0x30 + 1] = { { 800, 600 }, { 1024, 768 }, { ++ 1280, 1024 }, { 1600, 1200 }, { 1920, 1200 }, { 1280, 800 }, ++ { 1440, 900 }, { 1680, 1050 }, { 1920, 1080 }, { 1366, 768 }, { 1600, ++ 900 }, { 1152, 864 }, }; ++ ++static struct Resolution resTable2[0x52 - 0x50 + 1] = { { 320, 240 }, { 400, 300 }, { ++ 512, 384 }, }; ++ ++static void prepare_bse_descriptor_2(struct Descriptor *pDAddress, ++ phys_addr_t source_addr, ++ phys_addr_t dest_addr, ++ bool bNotLastEntry, ++ u16 wStride, ++ u8 bytesPerPixel, ++ u32 dwFetchWidthPixels, ++ u32 dwFetchHeight, ++ bool bInterrupt, ++ u8 byBuckSizeRegIndex); ++ ++static struct BSEAggregateRegister set_up_bse_bucket_2(struct AstRVAS *pAstRVAS, ++ u8 *abyBitIndexes, ++ u8 byTotalBucketCount, ++ u8 byBSBytesPerPixel, ++ u32 dwFetchWidthPixels, ++ u32 dwFetchHeight, ++ u32 dwBucketSizeIndex); ++ ++static inline u32 ast_video_read(void __iomem *video_reg_base, u32 reg) ++{ ++ u32 val = readl(video_reg_base + reg); ++ ++ return val; ++} ++ ++// Get color depth ++static void ast_video_get_color_mode(u8 byNewColorMode, struct VideoGeometry *pvg) ++{ ++ switch (byNewColorMode) { ++ case MODE_EGA: ++ pvg->gmt = VGAGraphicsMode; //4pp mode12/mode6A ++ pvg->byBitsPerPixel = 4; ++ break; ++ ++ case MODE_VGA: ++ pvg->gmt = VGAGraphicsMode; //mode 13 ++ pvg->byBitsPerPixel = 8; ++ break; ++ ++ case MODE_BPP16: ++ pvg->gmt = AGAGraphicsMode; ++ pvg->byBitsPerPixel = 16; ++ break; ++ ++ case MODE_BPP32: ++ pvg->gmt = AGAGraphicsMode; ++ pvg->byBitsPerPixel = 32; ++ break; ++ ++ case MODE_TEXT: ++ pvg->gmt = TextMode; ++ pvg->byBitsPerPixel = 0; ++ break; ++ ++ case MODE_CGA: ++ break; ++ ++ default: ++ pvg->byBitsPerPixel = 8; ++ break; ++ } ++} ++ ++//Mode ID mapping - use ID as index to the resolution table ++static void ast_video_get_indexed_mode(struct ModeInfo *pModeInfo, struct VideoGeometry *pvg) ++{ ++ u8 byModeIndex = (pModeInfo->byModeID & 0xf0); ++ ++ HW_ENG_DBG("Mode ID %#x\n", pModeInfo->byModeID); ++ pvg->byModeID = pModeInfo->byModeID; ++ ++ if (pModeInfo->byModeID == 0x12) { ++ pvg->wScreenWidth = 640; ++ pvg->wScreenHeight = 480; ++ } else if (byModeIndex == 0x20) { ++ pvg->wScreenWidth = 640; ++ pvg->wScreenHeight = 480; ++ } else if (byModeIndex == 0x30) { ++ pvg->wScreenWidth = ++ resTable1[pModeInfo->byModeID & 0x0f].wWidth; ++ pvg->wScreenHeight = ++ resTable1[pModeInfo->byModeID & 0x0f].wHeight; ++ } else if (byModeIndex == 0x50) { ++ pvg->wScreenWidth = ++ resTable2[pModeInfo->byModeID & 0x03].wWidth; ++ pvg->wScreenHeight = ++ resTable2[pModeInfo->byModeID & 0x03].wHeight; ++ } else if (byModeIndex == 0x60) { ++ pvg->wScreenWidth = 800; ++ pvg->wScreenHeight = 600; ++ } else { ++ HW_ENG_DBG("Mode ID %#x\n", pModeInfo->byModeID); ++ pvg->wScreenWidth = 0; ++ pvg->wScreenHeight = 0; ++ } ++} ++ ++//check special modes ++static void ast_video_set_special_modes(struct ModeInfo *pModeInfo, struct AstRVAS *pAstRVAS) ++{ ++ u8 byVGACR1 = readb(pAstRVAS->grce_reg_base + GRCE_CRTC_OFFSET + 0x1); //number of chars per line ++ u8 byVGACR7 = readb(pAstRVAS->grce_reg_base + GRCE_CRTC_OFFSET + 0x7); ++ u8 byVGACR12 = readb(pAstRVAS->grce_reg_base + GRCE_CRTC_OFFSET + 0x12); ++ u8 byVGASR1 = readb(pAstRVAS->grce_reg_base + GRCE_SEQ_OFFSET + 0x1); ++ struct VideoGeometry *pvg = &pAstRVAS->current_vg; ++ u32 dwHorizontalDisplayEnd = 0; ++ u32 dwVerticalDisplayEnd = 0; ++ ++ dwHorizontalDisplayEnd = (byVGACR1 + 1) << 3; ++ dwVerticalDisplayEnd = (((byVGACR7 & 0x40) << 3) ++ | ((byVGACR7 & 0x2) << 7) | byVGACR12) + 1; ++ ++ HW_ENG_DBG("byVGACR1=0x%x,byVGACR7=0x%x,byVGACR12=0x%x\n", byVGACR1, ++ byVGACR7, byVGACR12); ++ HW_ENG_DBG("Mode ID %#x, dwHorizontalDisplayEnd 0x%x, dwVerticalDisplayEnd 0x%x\n", ++ pModeInfo->byModeID, dwHorizontalDisplayEnd, ++ dwVerticalDisplayEnd); ++ ++ // set up special mode ++ if (VGAGraphicsMode == pvg->gmt && pvg->byBitsPerPixel == 8) { // mode 13 ++ pvg->wScreenHeight = 200; ++ pvg->wScreenWidth = 320; ++ pvg->wStride = 320; ++ } else if (TextMode == pvg->gmt) { // text mode ++ pvg->wScreenHeight = dwVerticalDisplayEnd; ++ pvg->wScreenWidth = dwHorizontalDisplayEnd; ++ ++ if (!(byVGASR1 & 0x1)) ++ pvg->wScreenWidth += (byVGACR1 + 1); ++ ++ pvg->wStride = pvg->wScreenWidth; ++ } else if (pvg->byBitsPerPixel == 4) { ++ pvg->wStride = pvg->wScreenWidth; ++ } ++} ++ ++static u32 ast_video_get_pitch(struct AstRVAS *pAstRVAS) ++{ ++ u32 dwPitch = 0; ++ u8 byVGACR13 = 0; ++ u8 byVGACR14 = 0; ++ u8 byVGACR17 = 0; ++ u16 wOffsetUpper = 0; ++ u16 wOffset = 0; ++ struct VideoGeometry *pvg = &pAstRVAS->current_vg; ++ ++ //read actual register ++ byVGACR13 = readb(pAstRVAS->grce_reg_base + GRCE_CRTC_OFFSET + 0x13); ++ byVGACR14 = readb(pAstRVAS->grce_reg_base + GRCE_CRTC_OFFSET + 0x14); ++ byVGACR17 = readb(pAstRVAS->grce_reg_base + GRCE_CRTC_OFFSET + 0x17); ++ wOffsetUpper = readb(pAstRVAS->grce_reg_base + 0xb0); ++ ++ wOffset = (wOffsetUpper << 8) | byVGACR13; ++ HW_ENG_DBG("wOffsetUpper= %#x, byVGACR13= %#x, byVGACR14= %#x, byVGACR17= %#x, wOffset= %#x\n", ++ wOffsetUpper, byVGACR13, byVGACR14, byVGACR17, wOffset); ++ ++ if (byVGACR14 & 0x40) ++ dwPitch = wOffset << 3; //DW mode ++ else if (byVGACR17 & 0x40) ++ dwPitch = wOffset << 1; //byte mode ++ else ++ dwPitch = wOffset << 2; //word mode ++ ++ if (pvg->gmt != TextMode) { ++ u8 byBppPowerOfTwo = 0; ++ ++ if (pvg->byBitsPerPixel == 32) ++ byBppPowerOfTwo = 2; ++ else if (pvg->byBitsPerPixel == 16) ++ byBppPowerOfTwo = 1; ++ else if (pvg->byBitsPerPixel == 8) ++ byBppPowerOfTwo = 0; ++ else ++ byBppPowerOfTwo = 3; // 4bpp ++ ++ //convert it to logic width in pixel ++ if (pvg->byBitsPerPixel > 4) ++ dwPitch >>= byBppPowerOfTwo; ++ else ++ dwPitch <<= byBppPowerOfTwo; ++ } ++ ++ return dwPitch; ++} ++ ++void update_video_geometry(struct AstRVAS *ast_rvas) ++{ ++ struct ModeInfo *pModeInfo; ++ struct NewModeInfoHeader *pNMIH; ++ struct DisplayEnd *pDE; ++ u8 byNewColorMode = 0; ++ u32 VGA_Scratch_Register_350 = 0; //VIDEO_NEW_MODE_INFO_HEADER ++ u32 VGA_Scratch_Register_354 = 0; //VIDEO_HDE ++ u32 VGA_Scratch_Register_34C = 0; //VIDEO_HDE ++ struct VideoGeometry *cur_vg = &ast_rvas->current_vg; ++ ++ VGA_Scratch_Register_350 = ast_video_read(ast_rvas->grce_reg_base, ++ AST_VIDEO_SCRATCH_350); ++ VGA_Scratch_Register_34C = ast_video_read(ast_rvas->grce_reg_base, ++ AST_VIDEO_SCRATCH_34C); ++ VGA_Scratch_Register_354 = ast_video_read(ast_rvas->grce_reg_base, ++ AST_VIDEO_SCRATCH_354); ++ ++ pModeInfo = (struct ModeInfo *)(&VGA_Scratch_Register_34C); ++ pNMIH = (struct NewModeInfoHeader *)(&VGA_Scratch_Register_350); ++ pDE = (struct DisplayEnd *)(&VGA_Scratch_Register_354); ++ HW_ENG_DBG("pModeInfo: byColorMode: %#x byModeID: %#x byRefreshRateIndex: %#x byScanLines: %#x\n", ++ pModeInfo->byColorMode, pModeInfo->byModeID, ++ pModeInfo->byRefreshRateIndex, pModeInfo->byScanLines); ++ HW_ENG_DBG("pNMIH: byColorDepth: %#x byDisplayInfo: %#x byMhzPixelClock: %#x byReserved: %#x\n", ++ pNMIH->byColorDepth, pNMIH->byDisplayInfo, ++ pNMIH->byMhzPixelClock, pNMIH->byReserved); ++ HW_ENG_DBG("pDE: HDE: %#x VDE: %#x\n", pDE->HDE, pDE->VDE); ++ ++ byNewColorMode = ((pModeInfo->byColorMode) & 0xf0) >> 4; ++ HW_ENG_DBG("byNewColorMode= %#x,byModeID=0x%x\n", byNewColorMode, ++ pModeInfo->byModeID); ++ ast_video_get_color_mode(byNewColorMode, cur_vg); ++ ++ if (pNMIH->byDisplayInfo == MODE_GET_INFO_DE) { ++ cur_vg->wScreenWidth = pDE->HDE; ++ cur_vg->wScreenHeight = pDE->VDE; ++ cur_vg->byBitsPerPixel = pNMIH->byColorDepth; ++ cur_vg->byModeID = pModeInfo->byModeID; ++ } else { ++ ast_video_get_indexed_mode(pModeInfo, cur_vg); ++ } ++ ++ cur_vg->wStride = (u16)ast_video_get_pitch(ast_rvas); ++ HW_ENG_DBG("Calculated pitch in pixels= %u\n", cur_vg->wStride); ++ ++ if (cur_vg->wStride < cur_vg->wScreenWidth) ++ cur_vg->wStride = cur_vg->wScreenWidth; ++ ++ HW_ENG_DBG("Before current display width %u, height %u, pitch %u, color depth %u, mode %d\n", ++ cur_vg->wScreenWidth, cur_vg->wScreenHeight, ++ cur_vg->wStride, cur_vg->byBitsPerPixel, cur_vg->gmt); ++ ++ if (cur_vg->gmt == TextMode || ++ (cur_vg->gmt == VGAGraphicsMode && pModeInfo->byModeID == 0x13)) { ++ ast_video_set_special_modes(pModeInfo, ast_rvas); ++ } ++ ++ //mode transition ++ if (cur_vg->wScreenHeight < 200 || cur_vg->wScreenWidth < 320) ++ cur_vg->gmt = InvalidMode; ++ ++ if (cur_vg->gmt == TextMode) { ++ u8 byVGACR9 = readb(ast_rvas->grce_reg_base + GRCE_CRTC_OFFSET + 0x9); ++ u32 dwCharacterHeight = ((byVGACR9) & 0x1f) + 1; ++ ++ HW_ENG_DBG("byModeID=0x%x,dwCharacterHeight=%d\n", ++ cur_vg->byModeID, dwCharacterHeight); ++ ++ if (dwCharacterHeight != 8 && dwCharacterHeight != 14 && ++ dwCharacterHeight != 16) ++ cur_vg->gmt = InvalidMode; ++ ++ if (cur_vg->wScreenWidth > 720 || cur_vg->wScreenHeight > 400) ++ cur_vg->gmt = InvalidMode; ++ } ++ ++ HW_ENG_DBG("current display width %u, height %u, pitch %u, color depth %u, mode %d\n", ++ cur_vg->wScreenWidth, cur_vg->wScreenHeight, ++ cur_vg->wStride, cur_vg->byBitsPerPixel, cur_vg->gmt); ++} ++ ++//check and update current video geometry ++bool video_geometry_change(struct AstRVAS *ast_rvas, u32 dwGRCEStatus) ++{ ++ bool b_geometry_changed = false; ++ struct VideoGeometry *cur_vg = &ast_rvas->current_vg; ++ struct VideoGeometry pre_vg; ++ ++ memcpy(&pre_vg, cur_vg, sizeof(pre_vg)); ++ update_video_geometry(ast_rvas); ++ b_geometry_changed = memcmp(&pre_vg, cur_vg, sizeof(struct VideoGeometry)) ++ != 0; ++ HW_ENG_DBG("b_geometry_changed: %d\n", b_geometry_changed); ++ return b_geometry_changed; ++} ++ ++void ioctl_get_video_geometry(struct RvasIoctl *ri, struct AstRVAS *ast_rvas) ++{ ++ memcpy(&ri->vg, &ast_rvas->current_vg, sizeof(struct VideoGeometry)); ++// HW_ENG_DBG("b_geometry_changed: %d\n", b_geometry_changed); ++} ++ ++void print_frame_buffer(u32 dwSizeByBytes, struct VGAMemInfo FBInfo) ++{ ++ u32 iter = 0; ++ phys_addr_t *frame_buffer_base = NULL; ++ u32 dwNumMappedPages = 0; ++ ++ dwNumMappedPages = ((dwSizeByBytes + 4095) >> 12); ++ frame_buffer_base = (phys_addr_t *)ioremap(FBInfo.qwFBPhysStart, dwNumMappedPages << 12); ++ ++ if (frame_buffer_base) { ++ HW_ENG_DBG("==============%s===========\n", __func__); ++ ++ for (iter = 0; iter < (dwSizeByBytes >> 2); iter++) { ++ HW_ENG_DBG("0x%x, ", frame_buffer_base[iter]); ++ ++ if ((iter % 16) == 0) ++ HW_ENG_DBG("\n"); ++ } ++ ++ HW_ENG_DBG("===========END=============\n"); ++ iounmap((void *)frame_buffer_base); ++ } ++} ++ ++void ioctl_get_grc_register(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ void *virt_add = 0; ++ u32 size = 0; ++ ++ HW_ENG_DBG("Start\n"); ++ virt_add = get_virt_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ size = ri->rmh1_mem_size; ++ ++ if (virt_is_valid_rsvd_mem((u32)ri->rmh, size, pAstRVAS)) { ++ memcpy((void *)virt_add, ++ (const void *)(pAstRVAS->grce_reg_base), 0x40); ++ memset((void *)(((u8 *)virt_add) + 0x40), 0x0, 0x20); ++ memcpy((void *)(((u8 *)virt_add) + 0x60), ++ (const void *)(pAstRVAS->grce_reg_base + 0x60), ++ GRCE_SIZE - 0x60); ++ ri->rs = SuccessStatus; ++ } else { ++ ri->rs = InvalidMemoryHandle; ++ } ++} ++ ++void ioctl_read_snoop_map(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ struct ContextTable *pct = get_context_entry(ri->rc, pAstRVAS); ++ void *virt_add = 0; ++ u32 size = 0; ++ ++ virt_add = get_virt_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ size = ri->rmh_mem_size; ++ ++ disable_grce_tse_interrupt(pAstRVAS); ++ HW_ENG_DBG("Start\n"); ++ ++ if (pct) { ++ if (virt_is_valid_rsvd_mem((u32)ri->rmh, size, pAstRVAS)) { ++ update_all_snoop_context(pAstRVAS); ++ memcpy((void *)virt_add, pct->aqwSnoopMap, ++ sizeof(pct->aqwSnoopMap)); ++ ++ if (ri->flag) { ++ ///get the context snoop address ++ memset(pct->aqwSnoopMap, 0x00, ++ sizeof(pct->aqwSnoopMap)); ++ memset(&pct->sa, 0x00, sizeof(pct->sa)); ++ } ++ ri->rs = SuccessStatus; ++ } else { ++ ri->rs = InvalidMemoryHandle; ++ } ++ } else { ++ ri->rs = InvalidContextHandle; ++ } ++ ++ enable_grce_tse_interrupt(pAstRVAS); ++} ++ ++void ioctl_read_snoop_aggregate(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ struct ContextTable *pct = get_context_entry(ri->rc, pAstRVAS); ++ ++ disable_grce_tse_interrupt(pAstRVAS); ++ ++ if (pct) { ++ update_all_snoop_context(pAstRVAS); ++ memcpy(&ri->sa, &pct->sa, sizeof(pct->sa)); ++ HW_ENG_DBG("ri->sa.qwCol: %#llx qwRow: %#llx flag: %u\n", ++ ri->sa.qwCol, ri->sa.qwRow, ri->flag); ++ ++ if (ri->flag) ++ memset(&pct->sa, 0x00, sizeof(pct->sa)); ++ ++ ri->rs = SuccessStatus; ++ } else { ++ ri->rs = InvalidContextHandle; ++ HW_ENG_DBG("Invalid Context\n"); ++ } ++ ++ enable_grce_tse_interrupt(pAstRVAS); ++} ++ ++void ioctl_set_tse_tsicr(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ void __iomem *addrTSICR; ++ ++ pAstRVAS->tse_tsicr = ri->tse_counter; ++ addrTSICR = pAstRVAS->fg_reg_base + TSE_TileSnoop_Interrupt_Count; ++ writel(pAstRVAS->tse_tsicr, addrTSICR);// max wait time before interrupt ++ ri->rs = SuccessStatus; ++} ++ ++void ioctl_get_tse_tsicr(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ ri->tse_counter = pAstRVAS->tse_tsicr; ++ ri->rs = SuccessStatus; ++} ++ ++// Get the screen offset from the GRC registers ++u32 get_screen_offset(struct AstRVAS *pAstRVAS) ++{ ++ u32 dwScreenOffset = 0; ++ void __iomem *addrVGACRC = pAstRVAS->grce_reg_base + GRCE_CRTC + 0xC; // Ch ++ void __iomem *addrVGACRD = pAstRVAS->grce_reg_base + GRCE_CRTC + 0xD; // Dh ++ void __iomem *addrVGACRAF = pAstRVAS->grce_reg_base + GRCE_CRTCEXT + 0x2F; ++ ++ if (pAstRVAS->current_vg.gmt == AGAGraphicsMode) { ++ dwScreenOffset = ((readb(addrVGACRAF) << 16) | ((readb(addrVGACRC)) << 8) | ++ (readb(addrVGACRD))); ++ dwScreenOffset *= pAstRVAS->current_vg.byBitsPerPixel >> 3; ++ } ++ ++ HW_ENG_DBG("ScreenOffset: %#8.8x\n", dwScreenOffset); ++ ++ return dwScreenOffset; ++} ++ ++void reset_snoop_engine(struct AstRVAS *pAstRVAS) ++{ ++ void __iomem *addr_snoop = pAstRVAS->fg_reg_base + TSE_SnoopMap_Offset; ++ u32 reg_value = 0; ++ u32 iter; ++ ++ writel(0x0, pAstRVAS->fg_reg_base + TSE_SnoopCommand_Register_Offset); ++ writel(0x3, pAstRVAS->fg_reg_base + TSE_Status_Register_Offset); ++ reg_value = readl(pAstRVAS->fg_reg_base + TSE_Status_Register_Offset); ++ reg_value = readl(pAstRVAS->fg_reg_base + TSE_CS0Reg); ++ reg_value = readl(pAstRVAS->fg_reg_base + TSE_CS1Reg); ++ reg_value = readl(pAstRVAS->fg_reg_base + TSE_RS0Reg); ++ reg_value = readl(pAstRVAS->fg_reg_base + TSE_RS1Reg); ++ ++ //Clear TSRR00 to TSRR126 (TSRR01 to TSRR127), Snoop Map ++ for (iter = 0; iter < 0x80; ++iter) { ++ reg_value = readl(addr_snoop) + 1; ++ writel(reg_value, addr_snoop); ++ } ++ ++ reg_value = readl(pAstRVAS->fg_reg_base + TSE_TileCount_Register_Offset); ++} ++ ++void set_snoop_engine(bool b_geom_chg, struct AstRVAS *pAstRVAS) ++{ ++ void __iomem *tscmd_reg = pAstRVAS->fg_reg_base + TSE_SnoopCommand_Register_Offset; ++ void __iomem *tsfbsa_reg = pAstRVAS->fg_reg_base + TSE_FrameBuffer_Offset; ++ void __iomem *tsulr_reg = pAstRVAS->fg_reg_base + TSE_UpperLimit_Offset; ++ u32 new_tsfbsa = 0; ++ u32 tscmd = 0; ++ u8 byBytesPerPixel = 0x0; ++ u8 byTSCMDBytesPerPixel = 0x0; ++ int cContext; ++ u32 dwStride; ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ ++ // Calculate Start Address into the Frame Buffer ++ new_tsfbsa = get_screen_offset(pAstRVAS); ++ tscmd = readl(tscmd_reg); ++ ++ tscmd &= (1 << TSCMD_INT_ENBL_BIT); ++ ++ HW_ENG_DBG("Latest TSFBSA: %#8.8x\n", new_tsfbsa); ++ HW_ENG_DBG("pAstRVAS->current_vg: bpp %u Mode:%#x gmt:%d Width:%u Height:%u Stride:%u\n", ++ pAstRVAS->current_vg.byBitsPerPixel, ++ pAstRVAS->current_vg.byModeID, pAstRVAS->current_vg.gmt, ++ pAstRVAS->current_vg.wScreenWidth, ++ pAstRVAS->current_vg.wScreenHeight, ++ pAstRVAS->current_vg.wStride); ++ ++ if (b_geom_chg || (readl(tsfbsa_reg) != new_tsfbsa)) { ++ byBytesPerPixel = pAstRVAS->current_vg.byBitsPerPixel >> 3; ++ ++ if (pAstRVAS->current_vg.gmt == VGAGraphicsMode || ++ pAstRVAS->current_vg.byBitsPerPixel == 4) { ++ byTSCMDBytesPerPixel = 0; ++ } else { ++ switch (byBytesPerPixel) { ++ case 1: ++ byTSCMDBytesPerPixel = 0; ++ break; ++ ++ case 2: ++ byTSCMDBytesPerPixel = 1; ++ break; ++ ++ case 3: ++ case 4: ++ byTSCMDBytesPerPixel = 2; ++ break; ++ } ++ } ++ dwStride = pAstRVAS->current_vg.wStride; ++ ++ if (byBytesPerPixel == 3) ++ dwStride = (dwStride + dwStride + dwStride) >> 2; ++ else if (pAstRVAS->current_vg.byBitsPerPixel == 4) ++ dwStride >>= 1; ++ ++ // set TSE SCR ++ // start the tile snoop engine ++ // flip the 15 bit ++ if (!(readl(tscmd_reg) & TSCMD_SCREEN_OWNER)) ++ tscmd |= TSCMD_SCREEN_OWNER; ++ ++ tscmd |= (dwStride << TSCMD_PITCH_BIT) | (1 << TSCMD_CPT_BIT) ++ | (1 << TSCMD_RPT_BIT) ++ | (byTSCMDBytesPerPixel << TSCMD_BPP_BIT) ++ | (1 << TSCMD_VGA_MODE_BIT) | (1 << TSCMD_TSE_ENBL_BIT); ++ HW_ENG_DBG("tscmd: %#8.8x\n", tscmd); ++ // set the TSFBSA & TSULR ++ writel(new_tsfbsa, tsfbsa_reg); ++ writel(BSE_UPPER_LIMIT, tsulr_reg); ++ writel(tscmd, tscmd_reg); ++ //reset snoop information ++ get_snoop_map_data(pAstRVAS); ++ memset((void *)pAstRVAS->accrued_sm, 0, ++ sizeof(pAstRVAS->accrued_sm)); ++ memset((void *)&pAstRVAS->accrued_sa, 0, ++ sizeof(pAstRVAS->accrued_sa)); ++ ++ for (cContext = 0; cContext < MAX_NUM_CONTEXT; cContext++) { ++ if (ppctContextTable[cContext]) { ++ memset(ppctContextTable[cContext]->aqwSnoopMap, ++ 0, ++ sizeof(ppctContextTable[cContext]->aqwSnoopMap)); ++ memset(&ppctContextTable[cContext]->sa, 0, ++ sizeof(ppctContextTable[cContext]->sa)); ++ } ++ } // for each context ++ } // if ++} ++ ++// ++// ReadSnoopMap to Clear ++// ++void get_snoop_map_data(struct AstRVAS *pAstRVAS) ++{ ++ u32 dwSMDword; ++ u64 aqwSnoopMap[SNOOP_MAP_QWORD_COUNT]; ++ //u32 dw_iter; ++ ++ get_snoop_aggregate(pAstRVAS); ++ memcpy((void *)aqwSnoopMap, ++ (const void *)(pAstRVAS->fg_reg_base + TSE_SnoopMap_Offset), ++ sizeof(aqwSnoopMap)); ++ ++ //HW_ENG_DBG("Snoop Map:\n"); ++ //HW_ENG_DBG("==========\n"); ++ ++ //for (dw_iter = 0; dw_iter < SNOOP_MAP_QWORD_COUNT; ++dw_iter) ++ //HW_ENG_DBG("[%2u]: 0x%16.16llx\n", dw_iter, aqwSnoopMap[dw_iter]); ++ ++ //HW_ENG_DBG("==========\n\n"); ++ ++ // copy 512 snoop map ++ for (dwSMDword = 0; dwSMDword < SNOOP_MAP_QWORD_COUNT; ++dwSMDword) ++ pAstRVAS->accrued_sm[dwSMDword] |= aqwSnoopMap[dwSMDword]; ++} ++ ++void get_snoop_aggregate(struct AstRVAS *pAstRVAS) ++{ ++ u64 qwRow = 0; ++ u64 qwCol = 0; ++ ++ // copy the snoop aggregate,row 64 bits ++ qwRow = readl(pAstRVAS->fg_reg_base + TSE_RS1Reg); ++ qwRow = qwRow << 32; ++ qwRow |= readl(pAstRVAS->fg_reg_base + TSE_RS0Reg); ++ ++ // column ++ qwCol = readl(pAstRVAS->fg_reg_base + TSE_CS1Reg); ++ qwCol = qwCol << 32; ++ qwCol |= readl(pAstRVAS->fg_reg_base + TSE_CS0Reg); ++ ++ HW_ENG_DBG("Snoop Aggregate Row: 0x%16.16llx\n", qwRow); ++ HW_ENG_DBG("Snoop Aggregate Col: 0x%16.16llx\n", qwCol); ++ HW_ENG_DBG("DRIVER:: %s\n", __func__); ++ HW_ENG_DBG("DRIVER:: row [%#llx]\n", qwRow); ++ HW_ENG_DBG("DRIVER:: col [%#llx]\n", qwCol); ++ ++ pAstRVAS->accrued_sa.qwCol |= qwCol; ++ pAstRVAS->accrued_sa.qwRow |= qwRow; ++} ++ ++u64 reinterpret_32bpp_snoop_row_as_24bpp(u64 theSnoopRow) ++{ ++ u64 qwResult = 0; ++ u64 qwSourceBit = 1; ++ u32 cSourceBit; ++ u64 qwBitResult = 0; ++ ++ for (cSourceBit = 0; cSourceBit < 64; ++cSourceBit) { ++ if (theSnoopRow & qwSourceBit) { ++ qwBitResult = ((cSourceBit * 128) / 96); ++ qwResult |= (((u64)3) << qwBitResult); ++ } ++ ++ qwSourceBit <<= 1; ++ } ++ ++ return qwResult; ++} ++ ++// ++//one tile: 32x32, ++// ++void convert_snoop_map(struct AstRVAS *pAstRVAS) ++{ ++ u32 dwAllRows = (pAstRVAS->current_vg.wScreenHeight + 31) >> 5; ++ u32 cRow; ++ ++ for (cRow = 0; cRow < dwAllRows; ++cRow) ++ pAstRVAS->accrued_sm[cRow] = ++ reinterpret_32bpp_snoop_row_as_24bpp(pAstRVAS->accrued_sm[cRow]); ++ ++ pAstRVAS->accrued_sa.qwCol = ++ reinterpret_32bpp_snoop_row_as_24bpp(pAstRVAS->accrued_sa.qwCol); ++} ++ ++void update_all_snoop_context(struct AstRVAS *pAstRVAS) ++{ ++ u32 cContext; ++ u32 iSMDword; ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ ++ if (pAstRVAS->current_vg.byBitsPerPixel == 24) ++ convert_snoop_map(pAstRVAS); ++ ++ for (cContext = 0; cContext < MAX_NUM_CONTEXT; cContext++) ++ if (ppctContextTable[cContext]) { ++ for (iSMDword = 0; iSMDword < SNOOP_MAP_QWORD_COUNT; ++ iSMDword++) ++ ppctContextTable[cContext]->aqwSnoopMap[iSMDword] |= ++ pAstRVAS->accrued_sm[iSMDword]; ++ ++ ppctContextTable[cContext]->sa.qwRow |= ++ pAstRVAS->accrued_sa.qwRow; ++ ppctContextTable[cContext]->sa.qwCol |= ++ pAstRVAS->accrued_sa.qwCol; ++ } ++ ++ //reset snoop map and aggregate ++ memset((void *)pAstRVAS->accrued_sm, 0, sizeof(pAstRVAS->accrued_sm)); ++ memset((void *)&pAstRVAS->accrued_sa, 0x00, ++ sizeof(pAstRVAS->accrued_sa)); ++} ++ ++static u32 setup_tfe_cr(struct FetchOperation *pfo) ++{ ++ u32 dwTFECR = 0; ++ ++ if (pfo->bEnableRLE) ++ dwTFECR = (pfo->byRLETripletCode << 24) ++ | (pfo->byRLERepeatCode << 16); ++ ++ dwTFECR &= TFCTL_DESCRIPTOR_IN_DDR_MASK; ++ dwTFECR |= 1; ++ dwTFECR |= 1 << 1; // enabled IRQ ++ HW_ENG_DBG("dwTFECR: %#x\n", dwTFECR); ++ return dwTFECR; ++} ++ ++static void start_skip_mode_skip(struct Descriptor *desc_virt, ++ phys_addr_t desc_phys, ++ phys_addr_t source_phys, phys_addr_t dest_addr, u16 wStride, ++ u8 bytesPerPixel, u32 dwFetchWidthPixels, ++ u32 dwFetchHeight, bool bRLEOverFLow) ++{ ++ struct Descriptor *pVirtDesc = desc_virt; ++ ++ // Fetch Skipping data to a temp buffer ++ prepare_tfe_descriptor(pVirtDesc, source_phys, dest_addr, true, 1, ++ false, wStride, bytesPerPixel, ++ dwFetchWidthPixels, dwFetchHeight, ++ LowByteMode, bRLEOverFLow, 0); ++ ++ dest_addr += dwFetchWidthPixels * dwFetchHeight; ++ pVirtDesc++; ++ ++ if (bytesPerPixel == 3 || bytesPerPixel == 4) { ++ prepare_tfe_descriptor(pVirtDesc, source_phys, dest_addr, ++ true, 1, false, wStride, bytesPerPixel, ++ dwFetchWidthPixels, dwFetchHeight, ++ MiddleByteMode, bRLEOverFLow, 0); ++ ++ dest_addr += dwFetchWidthPixels * dwFetchHeight; ++ pVirtDesc++; ++ } ++ ++ prepare_tfe_descriptor(pVirtDesc, source_phys, dest_addr, false, 1, ++ false, wStride, bytesPerPixel, ++ dwFetchWidthPixels, dwFetchHeight, ++ TopByteMode, bRLEOverFLow, 1); ++} ++ ++// calculate pure fetch size ++static u32 calculate_fetch_size(enum SelectedByteMode sbm, u8 bytesPerPixel, ++ u32 dwFetchWidthPixels, u32 dwFetchHeight) ++{ ++ u32 dwFetchSize = 0; ++ ++ switch (sbm) { ++ case AllBytesMode: ++ dwFetchSize = dwFetchWidthPixels * dwFetchHeight ++ * bytesPerPixel; ++ break; ++ ++ case SkipMode: ++ if (bytesPerPixel == 3 || bytesPerPixel == 4) ++ dwFetchSize = dwFetchWidthPixels * dwFetchHeight * 3; ++ else ++ dwFetchSize = dwFetchWidthPixels * dwFetchHeight ++ * bytesPerPixel; ++ break; ++ ++ case PlanarToPackedMode: ++ dwFetchSize = (dwFetchWidthPixels * dwFetchHeight); ++ break; ++ ++ case PackedToPackedMode: ++ break; ++ ++ default: ++ HW_ENG_DBG("Mode= %d is not supported\n", sbm); ++ break; ++ } //switch ++ return dwFetchSize; ++} ++ ++static void display_fetch_info(struct FetchVideoTilesArg *pFVTDescriptor, u32 dwCD) ++{ ++ struct FetchRegion *pfr = NULL; ++ ++ pfr = &pFVTDescriptor->pfo[dwCD].fr; ++ HW_ENG_DBG("FETCH - 1 dwCD: %u\n", dwCD); ++ HW_ENG_DBG("pfr->wLeftX :%d\n", pfr->wLeftX); ++ HW_ENG_DBG("pfr->wTopY :%d\n", pfr->wTopY); ++ HW_ENG_DBG("pfr->wRightX :%d\n", pfr->wRightX); ++ HW_ENG_DBG("pfr->wBottomY :%d\n", pfr->wBottomY); ++ HW_ENG_DBG(" bEanbleRLE %d\n", pFVTDescriptor->pfo[dwCD].bEnableRLE); ++ HW_ENG_DBG("Stride : %d\n", pFVTDescriptor->vg.wStride); ++} ++ ++void ioctl_fetch_video_tiles(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ struct FetchVideoTilesArg *pFVTDescriptor; ++ u32 dwCD = 0; ++ struct Descriptor *pdesc_virt; ++ phys_addr_t qw_desc_phys; ++ phys_addr_t qw_source_phys; ++ phys_addr_t qw_destination_phys; ++ u8 bytesPerPixel; ++ struct FetchRegion *pfr; ++ bool bNotLastEntry = false; ++ u32 dwTFECR = 0; ++ u32 dwTotalFetchSize = 0; ++ u32 dwRLESize = 0; ++ bool bRLEOverFLow = false; ++ u32 dwFetchWidthPixels = 0; ++ u32 dwFetchHeight = 0; ++ phys_addr_t arg_phys = 0; ++ phys_addr_t data_phys_out = 0; ++ phys_addr_t data_phys_temp = 0; ++ u16 stride = 0; ++ bool bSkippingMode = false; ++ void *desc_virt = NULL; ++ phys_addr_t desc_phy = 0; ++ struct ContextTable *ctx_entry = NULL; ++ ++ HW_ENG_DBG("DRIVER:::: TILE FETCH CHAINING\n"); ++ ctx_entry = get_context_entry(ri->rc, pAstRVAS); ++ ++ if (ctx_entry) { ++ desc_virt = ctx_entry->desc_virt; ++ desc_phy = ctx_entry->desc_phy; ++ } else { ++ HW_ENG_DBG("Returning with invalid Context handle: 0x%p\n", ri->rc); ++ ri->rs = InvalidContextHandle; ++ return; ++ } ++ ++ ri->rs = SuccessStatus; ++ //struct FetchVideoTilesArg buffer ++ arg_phys = get_phys_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ //Fetch final dest buffer ++ data_phys_out = get_phys_add_rsvd_mem((u32)ri->rmh1, pAstRVAS); ++ //Intermediate Buffer ++ data_phys_temp = get_phys_add_rsvd_mem((u32)ri->rmh2, pAstRVAS); ++ ++ qw_destination_phys = data_phys_out; ++ pFVTDescriptor = (struct FetchVideoTilesArg *)get_virt_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ HW_ENG_DBG("Destination virtual Add: 0x%llx\n", get_virt_add_rsvd_mem((u32)ri->rmh, pAstRVAS)); ++ HW_ENG_DBG("Destination Physical Add: %llx\n", qw_destination_phys); ++ memset(desc_virt, 0x00, PAGE_SIZE); ++ ++ if (arg_phys && data_phys_out && data_phys_temp) { ++ pdesc_virt = (struct Descriptor *)desc_virt; ++ qw_desc_phys = desc_phy; ++ HW_ENG_DBG("Descriptor Virtual Addr: %llx\n", ++ (phys_addr_t)desc_virt); ++ HW_ENG_DBG("Descriptor Physical Addr: %llx\n", qw_desc_phys); ++ stride = pFVTDescriptor->vg.wStride; ++ ++ if (pFVTDescriptor->vg.byBitsPerPixel == 4) { ++ bytesPerPixel = 1; ++ stride >>= 1; ++ } else { ++ bytesPerPixel = pFVTDescriptor->vg.byBitsPerPixel >> 3; ++ } ++ ++ HW_ENG_DBG("u8 per pixel:%u\n", bytesPerPixel); ++ // fetch all data to Destination 1 without RLE ++ HW_ENG_DBG("FETCH - 0\n"); ++ HW_ENG_DBG("COUNT OF Operation: %u\n", pFVTDescriptor->cfo); ++ ++ for (dwCD = 0; dwCD < pFVTDescriptor->cfo; dwCD++) { ++ display_fetch_info(pFVTDescriptor, dwCD); ++ // Set up Control Register. ++ dwTFECR = setup_tfe_cr(&pFVTDescriptor->pfo[dwCD]); ++ pfr = &pFVTDescriptor->pfo[dwCD].fr; ++ // find Source Address ++ if (pFVTDescriptor->vg.byBitsPerPixel == 4) { ++ qw_source_phys = get_phy_fb_start_address(pAstRVAS) ++ + ((pfr->wLeftX * bytesPerPixel) >> 1) ++ + pfr->wTopY * stride ++ * bytesPerPixel; ++ ++ dwFetchWidthPixels = (pfr->wRightX - pfr->wLeftX + 1) >> 1; ++ } else { ++ qw_source_phys = get_phy_fb_start_address(pAstRVAS) ++ + pfr->wLeftX * bytesPerPixel ++ + pfr->wTopY * stride ++ * bytesPerPixel; ++ ++ dwFetchWidthPixels = (pfr->wRightX - pfr->wLeftX + 1); ++ } ++ HW_ENG_DBG("dwCD: %u qw_source_phys: %#x\n", dwCD, ++ qw_source_phys); ++ dwFetchHeight = pfr->wBottomY - pfr->wTopY + 1; ++ ++ HW_ENG_DBG("DESCRIPTOR virtual ADDRESS: 0x%p\n", ++ pdesc_virt); ++ if (pFVTDescriptor->vg.byBitsPerPixel == 4) ++ pFVTDescriptor->pfo[dwCD].sbm = ++ PlanarToPackedMode; ++ ++ pFVTDescriptor->pfo[dwCD].dwFetchSize = ++ calculate_fetch_size(pFVTDescriptor->pfo[dwCD].sbm, ++ bytesPerPixel, dwFetchWidthPixels, ++ dwFetchHeight); ++ bSkippingMode = ++ (pFVTDescriptor->pfo[dwCD].sbm == SkipMode) ? ++ true : false; ++ ++ if (bSkippingMode && bytesPerPixel > 1) { ++ u32 skipSrcAddr = qw_source_phys; ++ u32 skipDestAddr = qw_destination_phys; ++ u8 byPostBytesPerPixel = ++ (bytesPerPixel == 2) ? 2 : 3; ++ HW_ENG_DBG("In SkippingMode...\n"); ++ ++ if (pFVTDescriptor->pfo[dwCD].bEnableRLE) { ++ //skip data to intermediate buffer ++ skipDestAddr = data_phys_temp; ++ } ++ ++ start_skip_mode_skip(pdesc_virt, ++ qw_desc_phys, skipSrcAddr, ++ skipDestAddr, ++ pFVTDescriptor->vg.wStride, ++ bytesPerPixel, dwFetchWidthPixels, ++ dwFetchHeight, bRLEOverFLow); ++ ++ if (pFVTDescriptor->pfo[dwCD].bEnableRLE) { ++ u32 rleSrcAddr = skipDestAddr; ++ u32 rleDesAddr = qw_destination_phys; ++ ++ ///// take second look at skip mode for using map single ++ if (sleep_on_tfe_busy(pAstRVAS, ++ qw_desc_phys, // Descriptor physical Address ++ dwTFECR, // control register value ++ pFVTDescriptor->pfo[dwCD].dwFetchSize, // bandwidth limitor value ++ &dwRLESize, // out:: rle size ++ &pFVTDescriptor->pfo[dwCD].dwCheckSum ++ ) == false) { // out:: cs size ++ ri->rs = GenericError; ++ return; ++ } ++ ++ // perform RLE from Temp buffer to qw_destination_phys ++ //HW_ENG_DBG("skip rle\n"); ++ prepare_tfe_descriptor(pdesc_virt, ++ rleSrcAddr, rleDesAddr, ++ bNotLastEntry, 1, ++ pFVTDescriptor->pfo[dwCD].bEnableRLE, ++ dwFetchWidthPixels, ++ byPostBytesPerPixel, ++ dwFetchWidthPixels, ++ dwFetchHeight, AllBytesMode, ++ bRLEOverFLow, 1); ++ } ++ } else { ++ HW_ENG_DBG("Preparing TFE Descriptor with no skipping...\n"); ++ prepare_tfe_descriptor(pdesc_virt, ++ qw_source_phys, qw_destination_phys, ++ bNotLastEntry, 1, ++ pFVTDescriptor->pfo[dwCD].bEnableRLE, ++ stride, bytesPerPixel, ++ dwFetchWidthPixels, dwFetchHeight, ++ pFVTDescriptor->pfo[dwCD].sbm, ++ bRLEOverFLow, 1); ++ HW_ENG_DBG("Successfully prepared TFE Descriptor with no skipping\n"); ++ } ++ HW_ENG_DBG("Sleeping while TFE is busy...\n"); ++ ++ if (sleep_on_tfe_busy(pAstRVAS, qw_desc_phys, // Descriptor physical Address ++ dwTFECR, // control register value ++ pFVTDescriptor->pfo[dwCD].dwFetchSize, // bandwidth limitor value ++ &dwRLESize, // out:: rle size ++ &pFVTDescriptor->pfo[dwCD].dwCheckSum ++ ) == false) { // out:: cs size ++ ri->rs = GenericError; ++ return; ++ } ++ ++ HW_ENG_DBG("After sleep where TFE was busy\n"); ++ ++ //HW_ENG_DBG("skip rle end\n"); ++ if (!pFVTDescriptor->pfo[dwCD].bEnableRLE) { // RLE not enabled ++ HW_ENG_DBG("RLE is off\n"); ++ pFVTDescriptor->pfo[dwCD].bRLEFailed = false; ++ dwRLESize = ++ pFVTDescriptor->pfo[dwCD].dwFetchSize; ++ dwTotalFetchSize += ++ pFVTDescriptor->pfo[dwCD].dwFetchSize; ++ } else { // RLE enabled ++ HW_ENG_DBG("RLE Enabled\n"); ++ if (dwRLESize ++ >= pFVTDescriptor->pfo[dwCD].dwFetchSize) { // FAILED ++ HW_ENG_DBG("DRVIER:: RLE failed RLE: %u > %u\n", ++ dwRLESize, ++ pFVTDescriptor->pfo[dwCD].dwFetchSize); ++ pFVTDescriptor->pfo[dwCD].bRLEFailed = ++ true; ++ ++ if (bSkippingMode) { ++ phys_addr_t skip_source_addr = ++ qw_source_phys; ++ phys_addr_t skip_dest_addr = ++ qw_destination_phys; ++ ++ start_skip_mode_skip(pdesc_virt, ++ qw_desc_phys, ++ skip_source_addr, ++ skip_dest_addr, ++ pFVTDescriptor->vg.wStride, ++ bytesPerPixel, ++ dwFetchWidthPixels, ++ dwFetchHeight, ++ bRLEOverFLow); ++ } else { ++ HW_ENG_DBG(" FETCH - 4\n"); ++ prepare_tfe_descriptor(pdesc_virt, ++ qw_source_phys, ++ qw_destination_phys, ++ bNotLastEntry, 1, false, ++ pFVTDescriptor->vg.wStride, ++ bytesPerPixel, ++ dwFetchWidthPixels, ++ dwFetchHeight, ++ pFVTDescriptor->pfo[dwCD].sbm, ++ bRLEOverFLow, 1); ++ } ++ ++ if (sleep_on_tfe_busy(pAstRVAS, ++ qw_desc_phys, // Descriptor physical Address ++ dwTFECR, // control register value ++ pFVTDescriptor->pfo[dwCD].dwFetchSize, // bandwidth limitor value ++ &dwRLESize, // out:: rle size ++ &pFVTDescriptor->pfo[dwCD].dwCheckSum ++ ) == false) { // out:: cs size ++ ri->rs = GenericError; ++ return; ++ } ++ ++ dwTotalFetchSize += ++ pFVTDescriptor->pfo[dwCD].dwFetchSize; ++ dwRLESize = ++ pFVTDescriptor->pfo[dwCD].dwFetchSize; ++ } else { //RLE successful ++ pFVTDescriptor->pfo[dwCD].bRLEFailed = ++ false; ++ dwTotalFetchSize += dwRLESize; ++ dwTotalFetchSize = (dwTotalFetchSize ++ + 0x3) & 0xfffffffc; ++ } ++ } //RLE Enabled ++ ++ pFVTDescriptor->pfo[dwCD].dwFetchRLESize = dwRLESize; ++ HW_ENG_DBG("DRIVER:: RLE: %u, nonRLE: %u\n", dwRLESize, ++ pFVTDescriptor->pfo[dwCD].dwFetchSize); ++ HW_ENG_DBG("FETCH:: loop FETCH size: %u\n", dwTotalFetchSize); ++ qw_destination_phys = data_phys_out + dwTotalFetchSize; ++ } //for TFE ++ ++ pFVTDescriptor->dwTotalOutputSize = dwTotalFetchSize; ++ HW_ENG_DBG("Fetch Size: %#x\n", dwTotalFetchSize); ++ } else { ++ dev_err(pAstRVAS->pdev, "Memory allocation failure\n"); ++ ri->rs = InvalidMemoryHandle; ++ } ++} // End - ioctl_fetch_video_tiles ++ ++void prepare_ldma_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, u32 dwLDMASize, u8 byNotLastEntry) ++{ ++ u8 byInterrupt = 0; ++ ++ HW_ENG_DBG("pDAddress: 0x%p\n", pDAddress); ++ ++ // initialize to 0 ++ pDAddress->dw0General = 0x00; ++ pDAddress->dw1FetchWidthLine = 0x00; ++ pDAddress->dw2SourceAddr = 0x00; ++ pDAddress->dw3DestinationAddr = 0x00; ++ ++ // initialize to 0 ++ if (!byNotLastEntry) ++ byInterrupt = 0x1; ++ ++ pDAddress->dw0General = ((dwLDMASize - 1) << 8) | (byNotLastEntry << 1) ++ | byInterrupt; ++ pDAddress->dw2SourceAddr = (u32)source_addr; ++ pDAddress->dw3DestinationAddr = (u32)dest_addr; ++ ++ HW_ENG_DBG("u32 0: 0x%x\n", pDAddress->dw0General); ++ HW_ENG_DBG("u32 1: 0x%x\n", pDAddress->dw1FetchWidthLine); ++ HW_ENG_DBG("u32 2: 0x%x\n", pDAddress->dw2SourceAddr); ++ HW_ENG_DBG("u32 3: 0x%x\n", pDAddress->dw3DestinationAddr); ++} ++ ++// ++// ioctl_run_length_encode_data - encode buffer data ++// ++void ioctl_run_length_encode_data(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ struct Descriptor *pDescriptorAdd = NULL; ++ struct Descriptor *pDescriptorAddPhys = NULL; ++ u8 bytesPerPixel; ++ bool bNotLastEntry = true; ++ u32 dwTFECR = 0; ++ bool bRLEOverFLow = false; ++ u32 dwFetchWidthPixels = 0; ++ u32 dwFetchHeight = 0; ++ u32 dwPhysAddIn; ++ u32 dwPhysAddOut; ++ u32 data_size = 0; ++ void *desc_virt = NULL; ++ u32 desc_phy = 0; ++ struct ContextTable *ctx_entry = NULL; ++ ++ ctx_entry = get_context_entry(ri->rc, pAstRVAS); ++ if (ctx_entry) { ++ desc_virt = ctx_entry->desc_virt; ++ desc_phy = ctx_entry->desc_phy; ++ } else { ++ ri->rs = InvalidContextHandle; ++ return; ++ } ++ ++ ri->rs = SuccessStatus; ++ ++ dwPhysAddIn = get_phys_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ dwPhysAddOut = get_phys_add_rsvd_mem((u32)ri->rmh1, pAstRVAS); ++ ++ data_size = ri->rmh_mem_size; ++ pDescriptorAdd = (struct Descriptor *)ctx_entry->desc_virt; ++ pDescriptorAddPhys = (struct Descriptor *)ctx_entry->desc_phy; ++ ++ HW_ENG_DBG("pDescriptorAdd=%#x, phy=%#x\n", (u32)pDescriptorAdd, ++ (u32)pDescriptorAddPhys); ++ ++ if (dwPhysAddIn && dwPhysAddOut) { ++ // Enable TFE ++ dwTFECR = (ri->encode & 0xffff0000) << 16; ++ dwTFECR |= 1; ++ dwTFECR &= TFCTL_DESCRIPTOR_IN_DDR_MASK; ++ ++ // triplet code and repeat code ++ bNotLastEntry = false; ++ bRLEOverFLow = true; ++ dwFetchWidthPixels = TILE_SIZE; ++ dwFetchHeight = data_size / TILE_SIZE; ++ bytesPerPixel = 1; ++ ++ prepare_tfe_descriptor(pDescriptorAdd, dwPhysAddIn, ++ dwPhysAddOut, bNotLastEntry, 1, ++ 1, dwFetchWidthPixels, ++ bytesPerPixel, dwFetchWidthPixels, ++ dwFetchHeight, AllBytesMode, bRLEOverFLow, 1); ++ ++ if (sleep_on_tfe_busy(pAstRVAS, (phys_addr_t)pDescriptorAddPhys, ++ dwTFECR, data_size, &ri->rle_len, ++ &ri->rle_checksum) == false) { ++ ri->rs = GenericError; ++ dev_err(pAstRVAS->pdev, "%s sleep_on_tfe_busy ERROR\n", __func__); ++ return; ++ } ++ } else { ++ ri->rs = InvalidMemoryHandle; ++ } ++} ++ ++static u32 get_video_slice_fetch_width(u8 cBuckets) ++{ ++ u32 dwFetchWidthPixels = 0; ++ ++ switch (cBuckets) { ++ case 3: ++ dwFetchWidthPixels = ((TILE_SIZE << 5) * 3) >> 3; ++ break; ++ ++ case 8: ++ dwFetchWidthPixels = TILE_SIZE << 5; ++ break; ++ ++ case 16: ++ dwFetchWidthPixels = (TILE_SIZE << 5) * 2; ++ break; ++ ++ case 24: ++ dwFetchWidthPixels = (TILE_SIZE << 5) * 3; ++ break; ++ ++ default: ++ dwFetchWidthPixels = TILE_SIZE << 2; ++ break; ++ } ++ ++ return dwFetchWidthPixels; ++} ++ ++void ioctl_fetch_video_slices(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ struct FetchVideoSlicesArg *pFVSA; ++ u32 dwCD; ++ struct Descriptor *pdesc_virt; ++ phys_addr_t qw_desc_phys; ++ phys_addr_t source_addr; ++ phys_addr_t slice_dest_addr; ++ u8 bytesPerPixel; ++ bool bNotLastEntry = true; ++ bool bInterrupt = false; ++ u32 dwTFECR = 0; ++ u32 dwFetchSize = 0; ++ bool bRLEOverFLow = false; ++ u32 dwFetchWidthPixels = 0; ++ u32 dwFetchHeight = 0; ++ phys_addr_t arg_phys = 0; ++ phys_addr_t data_phys_out = 0; ++ phys_addr_t data_phys_rle = 0; ++ struct BSEAggregateRegister aBSEAR; ++ struct Descriptor *pNextDescriptor = 0; ++ phys_addr_t dest_next_addr = 0; ++ u32 dwBucketSizeIter = 0; ++ bool bBucketSizeEnable = 0; ++ void __iomem *addrBSCR = pAstRVAS->fg_reg_base + BSE_Command_Register; ++ void *desc_virt = NULL; ++ phys_addr_t desc_phy = 0; ++ struct ContextTable *ctx_entry = get_context_entry(ri->rc, pAstRVAS); ++ ++ HW_ENG_DBG("Start\n"); ++ ++ if (ctx_entry) { ++ desc_virt = ctx_entry->desc_virt; ++ desc_phy = ctx_entry->desc_phy; ++ } else { ++ pr_err("BSE: Cannot get valid context\n"); ++ ri->rs = InvalidContextHandle; ++ return; ++ } ++ ++ arg_phys = get_phys_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ data_phys_out = get_phys_add_rsvd_mem((u32)ri->rmh1, pAstRVAS); ++ data_phys_rle = get_phys_add_rsvd_mem((u32)ri->rmh2, pAstRVAS); ++ ++ if (!arg_phys || !data_phys_out || !data_phys_rle) { ++ pr_err("BSE: Invalid memory handle\n"); ++ ri->rs = InvalidMemoryHandle; ++ return; ++ } ++ ri->rs = SuccessStatus; ++ slice_dest_addr = data_phys_out; ++ pFVSA = (struct FetchVideoSlicesArg *)get_virt_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ ++ HW_ENG_DBG("bEnableRLE: %d cBuckets: %u cfr: %u\n", pFVSA->bEnableRLE, ++ pFVSA->cBuckets, pFVSA->cfr); ++ ++ if (pFVSA->cfr > 1) { ++ writel(readl(addrBSCR) | BSE_ENABLE_MULT_BUCKET_SZS, addrBSCR); ++ bBucketSizeEnable = 1; ++ } else { ++ writel(readl(addrBSCR) & (~BSE_ENABLE_MULT_BUCKET_SZS), addrBSCR); ++ bBucketSizeEnable = 0; ++ } ++ ++ HW_ENG_DBG("*pdwBSCR: %#x bBucketSizeEnable: %d\n", readl(addrBSCR), ++ bBucketSizeEnable); ++ ++ pdesc_virt = ctx_entry->desc_virt; ++ qw_desc_phys = ctx_entry->desc_phy; ++ bytesPerPixel = pFVSA->vg.byBitsPerPixel >> 3; ++ ++ HW_ENG_DBG("BSE:: u8 per pixel: %d\n", bytesPerPixel); ++ HW_ENG_DBG("BSE:: cfr: %u bucket size: %d\n", pFVSA->cfr, pFVSA->cBuckets); ++ ++ pNextDescriptor = pdesc_virt; ++ dest_next_addr = slice_dest_addr; ++ // Prepare BSE Descriptors for all Regions ++ HW_ENG_DBG("pNextDescriptor 0x%p dest_next_addr: %#x\n", pNextDescriptor, ++ dest_next_addr); ++ ++ for (dwCD = 0; dwCD < pFVSA->cfr; dwCD++) { ++ HW_ENG_DBG("dwCD: %u\n", dwCD); ++ HW_ENG_DBG("pfr->wLeftX :%d\n", pFVSA->pfr[dwCD].wLeftX); ++ HW_ENG_DBG("pfr->wTopY :%d\n", pFVSA->pfr[dwCD].wTopY); ++ HW_ENG_DBG("pfr->wRightX :%d\n", pFVSA->pfr[dwCD].wRightX); ++ HW_ENG_DBG("pfr->wBottomY :%d\n", pFVSA->pfr[dwCD].wBottomY); ++ ++ source_addr = get_phy_fb_start_address(pAstRVAS) ++ + pFVSA->pfr[dwCD].wLeftX * bytesPerPixel ++ + pFVSA->pfr[dwCD].wTopY * pFVSA->vg.wStride ++ * bytesPerPixel; ++ dwFetchWidthPixels = (pFVSA->pfr[dwCD].wRightX ++ - pFVSA->pfr[dwCD].wLeftX + 1); ++ dwFetchHeight = pFVSA->pfr[dwCD].wBottomY ++ - pFVSA->pfr[dwCD].wTopY + 1; ++ ++ HW_ENG_DBG("BSE Width in Pixel: %d\n", dwFetchWidthPixels); ++ HW_ENG_DBG("BSE Height: %d bBucketSizeEnable: %d\n", dwFetchHeight, ++ bBucketSizeEnable); ++ ++ if (!bBucketSizeEnable) { ++ bNotLastEntry = false; ++ bInterrupt = true; ++ prepare_bse_descriptor(pdesc_virt, ++ source_addr, slice_dest_addr, ++ bNotLastEntry, pFVSA->vg.wStride, bytesPerPixel, ++ dwFetchWidthPixels, dwFetchHeight, bInterrupt); ++ dwFetchSize += (pFVSA->cBuckets ++ * (dwFetchWidthPixels * dwFetchHeight) >> 3); ++ aBSEAR = setUp_bse_bucket(pFVSA->abyBitIndexes, ++ pFVSA->cBuckets, bytesPerPixel, ++ dwFetchWidthPixels, dwFetchHeight); ++ ++ } else { ++ if (dwCD == pFVSA->cfr - 1) { ++ bNotLastEntry = false; ++ bInterrupt = true; ++ } else { ++ bNotLastEntry = true; ++ bInterrupt = false; ++ } ++ ++ prepare_bse_descriptor_2(pNextDescriptor, ++ source_addr, ++ dest_next_addr, bNotLastEntry, ++ pFVSA->vg.wStride, bytesPerPixel, ++ dwFetchWidthPixels, dwFetchHeight, ++ bInterrupt, ++ arrBuckSizeRegIndex[dwBucketSizeIter]); ++ ++ aBSEAR = set_up_bse_bucket_2(pAstRVAS, ++ pFVSA->abyBitIndexes, pFVSA->cBuckets, ++ bytesPerPixel, dwFetchWidthPixels, ++ dwFetchHeight, ++ arrBuckSizeRegIndex[dwBucketSizeIter]); ++ ++ dwBucketSizeIter++; ++ pNextDescriptor++; ++ dwFetchSize += pFVSA->cBuckets ++ * ((dwFetchWidthPixels * dwFetchHeight) >> 3); //each bucket size ++ dest_next_addr = slice_dest_addr ++ + dwFetchSize; ++ } ++ } ++ ++ //bse now ++ if (pFVSA->cBuckets <= FULL_BUCKETS_COUNT) { ++ if (bBucketSizeEnable) ++ aBSEAR.dwBSDBS = 0x80000000; ++ ++ HW_ENG_DBG("Sleeping on BSE to complete\n"); ++ ++ if (sleep_on_bse_busy(pAstRVAS, qw_desc_phys, aBSEAR, ++ dwFetchSize) == false) { ++ dev_err(pAstRVAS->pdev, ".....BSE Timeout\n"); ++ ri->rs = GenericError; ++ return; ++ } ++ } ++ HW_ENG_DBG("Fetched the bit slices\n"); ++ //RLE ++ pFVSA->dwSlicedSize = dwFetchSize; ++ pFVSA->dwSlicedRLESize = pFVSA->dwSlicedSize; ++ ++ // do RLE if RLE is on. Fetch from Destination 1 to Destination 2 with RLE on ++ bNotLastEntry = false; ++ ++ if (pFVSA->bEnableRLE) { ++ HW_ENG_DBG("BSE - 3 (RLE Enabled)\n"); ++ // Enable TFE ++ dwTFECR = ((pFVSA->byRLETripletCode << 24) ++ | (pFVSA->byRLERepeatCode << 16)); ++ dwTFECR |= ((0x1 << 1) | 1); ++ dwTFECR &= TFCTL_DESCRIPTOR_IN_DDR_MASK; ++ ++ bRLEOverFLow = true; ++ bytesPerPixel = 1; ++ ++ dwFetchWidthPixels = get_video_slice_fetch_width(pFVSA->cBuckets); ++ dwFetchHeight = dwFetchSize / dwFetchWidthPixels; ++ ++ prepare_tfe_descriptor(pdesc_virt, data_phys_out, ++ data_phys_rle, bNotLastEntry, 1, pFVSA->bEnableRLE, ++ dwFetchWidthPixels, bytesPerPixel, dwFetchWidthPixels, ++ dwFetchHeight, 0, bRLEOverFLow, 1); ++ ++ HW_ENG_DBG("TFE-RLE Control Register value: 0x%x\n", dwTFECR); ++ ++ if (sleep_on_tfe_busy(pAstRVAS, qw_desc_phys, // Descriptor physical Address ++ dwTFECR, // control register value ++ dwFetchSize, // bandwidth limiter value ++ &pFVSA->dwSlicedRLESize, // out:: rle size ++ &pFVSA->dwCheckSum ++ ) == false) { ++ ri->rs = GenericError; ++ return; ++ } ++ ++ HW_ENG_DBG("Finishing RLE Fetching\n"); ++ ++ if (pFVSA->dwSlicedRLESize >= pFVSA->dwSlicedSize) ++ pFVSA->bRLEFailed = true; ++ else ++ pFVSA->bRLEFailed = false; ++ } // RLE enabled ++ ++ memcpy((void *)&dwFetchSize, (void *)&pFVSA->dwSlicedRLESize, 4); ++} ++ ++void ioctl_fetch_text_data(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ bool bRLEOn = ri->tfm.bEnableRLE; ++ ++ ri->rs = SuccessStatus; ++ ++ // first time fetch ++ on_fetch_text_data(ri, bRLEOn, pAstRVAS); ++} ++ ++void on_fetch_text_data(struct RvasIoctl *ri, bool bRLEOn, struct AstRVAS *pAstRVAS) ++{ ++ struct Descriptor *pDescriptorAdd; ++ struct Descriptor *pDescriptorAddPhys; ++ u32 dwScreenOffset = 0x00; ++ phys_addr_t source_addr = get_phy_fb_start_address(pAstRVAS); ++ phys_addr_t dest_addr; ++ bool bRLEOverFlow = false; ++ bool bInterrupt = true; ++ u32 wFetchLines = 0; ++ u8 byCharacterPerLine = 0; ++ u16 wFetchWidthInBytes = 0; ++ phys_addr_t data_phys = 0; ++ phys_addr_t data_phys_rle = 0; ++ phys_addr_t data_phys_temp = 0; ++ u32 dwCtrlRegValue = 0; ++ u32 dwMinBufSize = 0; ++ void *desc_virt = NULL; ++ phys_addr_t desc_phy = 0; ++ struct ContextTable *ctx_entry = NULL; ++ ++ HW_ENG_DBG("Start\n"); ++ ctx_entry = get_context_entry(ri->rc, pAstRVAS); ++ if (ctx_entry) { ++ desc_virt = ctx_entry->desc_virt; ++ desc_phy = ctx_entry->desc_phy; ++ } else { ++ ri->rs = InvalidContextHandle; ++ return; ++ } ++ ++ wFetchLines = get_text_mode_fetch_lines(pAstRVAS, ri->vg.wScreenHeight); ++ byCharacterPerLine = get_text_mode_character_per_line(pAstRVAS, ++ ri->vg.wScreenWidth); ++ ++ data_phys = get_phys_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ data_phys_rle = get_phys_add_rsvd_mem((u32)ri->rmh1, pAstRVAS); ++ ++ if (!data_phys || !data_phys_rle) { ++ ri->rs = InvalidMemoryHandle; ++ dev_err(pAstRVAS->pdev, "Fetch Text: Invalid Memoryhandle\n"); ++ return; ++ } ++ ++ dwMinBufSize = (byCharacterPerLine * wFetchLines) << 1; ++ ++ if (ri->rmh_mem_size < dwMinBufSize) { ++ //either buffer is too small or invalid data in registers ++ ri->rs = GenericError; ++ dev_err(pAstRVAS->pdev, "Fetch Text: required buffer len:0x%x\n", dwMinBufSize); ++ return; ++ } ++ memset(desc_virt, 0x00, MAX_DESC_SIZE); ++ pDescriptorAdd = desc_virt; ++ pDescriptorAddPhys = (struct Descriptor *)desc_phy; ++ dest_addr = data_phys; ++ ++ // Enable TFE ++ dwCtrlRegValue |= 1; ++ dwCtrlRegValue &= TFCTL_DESCRIPTOR_IN_DDR_MASK; ++ // set up the text alignment ++ dwScreenOffset = get_screen_offset(pAstRVAS); ++ source_addr += dwScreenOffset; ++ HW_ENG_DBG("screen offset:%#x, Source start Addr: %%llx\n", dwScreenOffset, ++ source_addr); ++ if (ri->tfm.dpm == AttrMode) { // ATTR and ASCII ++ data_phys_temp = data_phys_rle; ++ wFetchWidthInBytes = byCharacterPerLine << 3; ++ // must fetch both ascii & attr ++ HW_ENG_DBG("Attribute and ASCII\n"); ++ prepare_tfe_text_descriptor(desc_virt, source_addr, ++ data_phys_temp, ++ false, wFetchWidthInBytes, wFetchLines, ++ ri->tfm.dpm, bRLEOverFlow, bInterrupt); ++ ri->tfm.dwFetchSize = (byCharacterPerLine * wFetchLines) << 1; ++ } else if (ri->tfm.dpm == AsciiOnlyMode) { ++ wFetchWidthInBytes = byCharacterPerLine << 3; ++ HW_ENG_DBG("ASCII Only\n"); ++ prepare_tfe_text_descriptor(desc_virt, source_addr, ++ dest_addr, ++ false, wFetchWidthInBytes, wFetchLines, ++ ri->tfm.dpm, bRLEOverFlow, bInterrupt); ++ ri->tfm.dwFetchSize = byCharacterPerLine * wFetchLines; ++ } else if (ri->tfm.dpm == FontFetchMode) { ++ wFetchWidthInBytes = byCharacterPerLine << 2; ++ HW_ENG_DBG("Font Only\n"); ++ prepare_tfe_text_descriptor(desc_virt, source_addr, ++ dest_addr, ++ false, wFetchWidthInBytes, ++ wFetchLines + 256, ++ ri->tfm.dpm, bRLEOverFlow, bInterrupt); ++ ++ ri->tfm.dwFetchSize = MAX_TEXT_DATA_SIZE; ++ } ++ dwCtrlRegValue |= 1 << 1; // enabled IRQ ++ if (ri->tfm.dpm == AttrMode) { ++ if (sleep_on_tfe_text_busy(pAstRVAS, desc_phy, dwCtrlRegValue, // control register value ++ ri->tfm.dwFetchSize, // bandwidth limitor value ++ &ri->tfm.dwFetchRLESize, // out:: rle size ++ &ri->tfm.dwCheckSum) == false) { ++ dev_err(pAstRVAS->pdev, "Could not sleep_on_tfe_busy for attributes\n"); ++ ri->rs = GenericError; ++ return; ++ } ++ } else { ++ if (sleep_on_tfe_text_busy(pAstRVAS, desc_phy, dwCtrlRegValue, ++ ri->tfm.dwFetchSize, &ri->tfm.dwFetchRLESize, ++ &ri->tfm.dwCheckSum) == false) { ++ ri->rs = GenericError; ++ dev_err(pAstRVAS->pdev, "Could not sleep_on_tfe_busy for others\n"); ++ return; ++ } ++ } ++ ++ if (ri->tfm.dpm == AttrMode) { ++ //separate ATTR from ATTR+ASCII ++ source_addr = data_phys_temp; ++ dest_addr = data_phys; ++ prepare_tfe_descriptor(desc_virt, data_phys_temp, data_phys, ++ false, //not last entry? ++ 1, //checksum ++ false, //RLE? ++ byCharacterPerLine, ++ 2, //byBpp, ++ byCharacterPerLine, wFetchLines, TopByteMode, ++ bRLEOverFlow, bInterrupt); ++ ++ ri->tfm.dwFetchSize = byCharacterPerLine * wFetchLines; ++ ++ dwCtrlRegValue |= 1 << 1; // enabled IRQ ++ if (sleep_on_tfe_text_busy(pAstRVAS, (phys_addr_t)pDescriptorAddPhys, ++ dwCtrlRegValue, ri->tfm.dwFetchSize, ++ &ri->tfm.dwFetchRLESize, &ri->tfm.dwCheckSum) == false) { ++ dev_err(pAstRVAS->pdev, "Could not sleep_on_tfe_busy for attributes # 2\n"); ++ ri->rs = GenericError; ++ return; ++ } ++ } ++ // RLE enabled ++ if (bRLEOn) { ++ bRLEOverFlow = true; ++ dwCtrlRegValue = 1; ++ dwCtrlRegValue |= (ri->tfm.byRLETripletCode << 24) ++ | (ri->tfm.byRLERepeatCode << 16); ++ source_addr = dest_addr; ++ dest_addr = data_phys_rle; ++ ++ // RLE only ++ prepare_tfe_descriptor(pDescriptorAdd, source_addr, ++ dest_addr, ++ false, //not last entry? ++ 1, //checksum ++ bRLEOn, //RLE? ++ ri->tfm.dwFetchSize / wFetchLines, 1, ++ ri->tfm.dwFetchSize / wFetchLines, wFetchLines, ++ AllBytesMode, bRLEOverFlow, bInterrupt); ++ ++ dwCtrlRegValue |= 1 << 1; // enabled IRQ ++ ++ if (sleep_on_tfe_busy(pAstRVAS, (phys_addr_t)pDescriptorAddPhys, // Descriptor physical Address ++ dwCtrlRegValue, // control register value ++ ri->tfm.dwFetchSize, // bandwidth limitor value ++ &ri->tfm.dwFetchRLESize, // out:: rle size ++ &ri->tfm.dwCheckSum) == false) { // out:: cs size ++ dev_err(pAstRVAS->pdev, "Could not sleep_on_tfe_busy for RLE for Text Mode\n"); ++ ri->rs = GenericError; ++ return; ++ } //sleeponTFEBusy ++ } ++ if (bRLEOn) { ++ ri->tfm.bRLEFailed = ++ (ri->tfm.dwFetchRLESize < ri->tfm.dwFetchSize) ? ++ false : true; ++ } ++} ++ ++u8 get_text_mode_character_per_line(struct AstRVAS *pAstRVAS, u16 wScreenWidth) ++{ ++ u8 byCharPerLine = 0x00; ++ u8 byCharWidth = 0; ++ u8 byVGASR1 = readb(pAstRVAS->grce_reg_base + GRCE_SEQ + 0x1); ++ ++ byCharWidth = (byVGASR1 & 0x1) ? 8 : 9; ++ byCharPerLine = wScreenWidth / byCharWidth; ++ ++ return byCharPerLine; ++} ++ ++u16 get_text_mode_fetch_lines(struct AstRVAS *pAstRVAS, u16 wScreenHeight) ++{ ++ u8 byVGACR9 = readb(pAstRVAS->grce_reg_base + GRCE_CRTC + 0x9); ++ u8 byFontHeight = (byVGACR9 & 0x1F) + 1; ++ u16 wFetchLines; ++ ++ wFetchLines = wScreenHeight / byFontHeight; ++ ++ return wFetchLines; ++} ++ ++// ++// HELPER Functions ++// ++ ++void prepare_bse_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, bool bNotLastEntry, ++ u16 wStride, u8 bytesPerPixel, ++ u32 dwFetchWidthPixels, u32 dwFetchHeight, ++ bool bInterrupt) ++{ ++ u16 wDestinationStride; ++ ++ // initialize to 0 ++ pDAddress->dw0General = 0x00; ++ pDAddress->dw1FetchWidthLine = 0x00; ++ pDAddress->dw2SourceAddr = 0x00; ++ pDAddress->dw3DestinationAddr = 0x00; ++ ++ wDestinationStride = dwFetchWidthPixels >> 3; ++ ++ // initialize to 0 ++ pDAddress->dw0General = ((wStride * bytesPerPixel) << 16) ++ | (wDestinationStride << 8) | (bNotLastEntry << 1) | bInterrupt; ++ pDAddress->dw1FetchWidthLine = ((dwFetchHeight - 1) << 16) ++ | (dwFetchWidthPixels * bytesPerPixel - 1); ++ pDAddress->dw2SourceAddr = (u32)source_addr & 0xfffffffc; ++ pDAddress->dw3DestinationAddr = (u32)dest_addr & 0xfffffffc; ++ ++ HW_ENG_DBG("After SETTING BSE Descriptor\n"); ++ HW_ENG_DBG("u32 0: 0x%x\n", pDAddress->dw0General); ++ HW_ENG_DBG("u32 1: 0x%x\n", pDAddress->dw1FetchWidthLine); ++ HW_ENG_DBG("u32 2: 0x%x\n", pDAddress->dw2SourceAddr); ++ HW_ENG_DBG("u32 3: 0x%x\n", pDAddress->dw3DestinationAddr); ++} ++ ++//for descriptor chaining ++void prepare_bse_descriptor_2(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, bool bNotLastEntry, ++ u16 wStride, u8 bytesPerPixel, ++ u32 dwFetchWidthPixels, u32 dwFetchHeight, ++ bool bInterrupt, u8 byBuckSizeRegIndex) ++{ ++ u16 wDestinationStride; ++ ++ // initialize to 0 ++ pDAddress->dw0General = 0x00; ++ pDAddress->dw1FetchWidthLine = 0x00; ++ pDAddress->dw2SourceAddr = 0x00; ++ pDAddress->dw3DestinationAddr = 0x00; ++ ++ wDestinationStride = dwFetchWidthPixels >> 3; ++ ++ // initialize to 0 ++ pDAddress->dw0General = ((wStride * bytesPerPixel) << 16) ++ | (wDestinationStride << 8) ++ | (byBuckSizeRegIndex << BSE_BUCK_SZ_INDEX_POS) ++ | (bNotLastEntry << 1) | bInterrupt; ++ pDAddress->dw1FetchWidthLine = ((dwFetchHeight - 1) << 16) ++ | (dwFetchWidthPixels * bytesPerPixel - 1); ++ pDAddress->dw2SourceAddr = (u32)source_addr & 0xfffffffc; ++ pDAddress->dw3DestinationAddr = (u32)dest_addr & 0xfffffffc; ++ ++ HW_ENG_DBG("AFter SETTING BSE Descriptor\n"); ++ HW_ENG_DBG("u32 0: 0x%x\n", pDAddress->dw0General); ++ HW_ENG_DBG("u32 1: 0x%x\n", pDAddress->dw1FetchWidthLine); ++ HW_ENG_DBG("u32 2: 0x%x\n", pDAddress->dw2SourceAddr); ++ HW_ENG_DBG("u32 3: 0x%x\n", pDAddress->dw3DestinationAddr); ++} ++ ++struct BSEAggregateRegister set_up_bse_bucket_2(struct AstRVAS *pAstRVAS, u8 *abyBitIndexes, ++ u8 byTotalBucketCount, u8 byBSBytesPerPixel, ++ u32 dwFetchWidthPixels, u32 dwFetchHeight, ++ u32 dwBucketSizeIndex) ++{ ++ struct BSEAggregateRegister aBSEAR = { 0 }; ++ void __iomem *addrBSDBS = 0; ++ void __iomem *addrBSCR = pAstRVAS->fg_reg_base + BSE_Command_Register; ++ ++ if (dwBucketSizeIndex >= BSE_MAX_BUCKET_SIZE_REGS) { ++ dev_err(pAstRVAS->pdev, "Video::BSE bucket size index %d too big!", ++ dwBucketSizeIndex); ++ return aBSEAR; ++ } ++ ++ addrBSDBS = pAstRVAS->fg_reg_base + BSE_REG_BASE + dwBucketSizeRegOffset[dwBucketSizeIndex]; ++ ++ // initialize ++ memset((void *)&aBSEAR, 0x00, sizeof(struct BSEAggregateRegister)); ++ aBSEAR = setUp_bse_bucket(abyBitIndexes, byTotalBucketCount, ++ byBSBytesPerPixel, dwFetchWidthPixels, dwFetchHeight); ++ ++ writel(aBSEAR.dwBSDBS, addrBSDBS); ++ aBSEAR.dwBSCR |= readl(addrBSCR) & (BSE_ENABLE_MULT_BUCKET_SZS); ++ HW_ENG_DBG("BSE Bucket size register index %d, [%#x], readback 0x%x\n", ++ dwBucketSizeIndex, aBSEAR.dwBSDBS, readl(addrBSCR)); ++ ++ return aBSEAR; ++} ++ ++struct BSEAggregateRegister setUp_bse_bucket(u8 *abyBitIndexes, u8 byTotalBucketCount, ++ u8 byBSBytesPerPixel, u32 dwFetchWidthPixels, ++ u32 dwFetchHeight) ++{ ++ struct BSEAggregateRegister aBSEAR; ++ u32 dwSrcBucketSize = MAX_LMEM_BUCKET_SIZE; ++ u32 dwDestBucketSize = dwFetchWidthPixels * dwFetchHeight >> 3; //each bucket size ++ u8 byRegisterPosition = 0; ++ u8 cBucket; ++ ++ // initialize ++ memset((void *)&aBSEAR, 0x00, sizeof(struct BSEAggregateRegister)); ++ ++ for (cBucket = 0; cBucket < byTotalBucketCount; cBucket++) { ++ if (cBucket < 6) { ++ HW_ENG_DBG("BUCKET: 0x%x, Bit Position: 0x%x\n", cBucket, ++ abyBitIndexes[cBucket]); ++ HW_ENG_DBG("BSBPS0 Position: 0x%x\n", byRegisterPosition); ++ aBSEAR.adwBSBPS[0] |= abyBitIndexes[cBucket] ++ << byRegisterPosition; ++ ++ byRegisterPosition += 5; ++ } else if (cBucket >= 6 && cBucket < 12) { ++ if (cBucket == 6) ++ byRegisterPosition = 0; ++ ++ HW_ENG_DBG("BUCKET: 0x%x, Bit Position: 0x%x\n", cBucket, ++ abyBitIndexes[cBucket]); ++ HW_ENG_DBG("BSBPS1 Position: 0x%x\n", byRegisterPosition); ++ aBSEAR.adwBSBPS[1] |= abyBitIndexes[cBucket] ++ << byRegisterPosition; ++ byRegisterPosition += 5; ++ } else { ++ if (cBucket == 12) ++ byRegisterPosition = 0; ++ ++ HW_ENG_DBG("BUCKET: 0x%x, Bit Position: 0x%x\n", cBucket, ++ abyBitIndexes[cBucket]); ++ HW_ENG_DBG("BSBPS2 Position: 0x%x\n", byRegisterPosition); ++ aBSEAR.adwBSBPS[2] |= abyBitIndexes[cBucket] ++ << byRegisterPosition; ++ byRegisterPosition += 5; ++ } ++ } ++ ++ aBSEAR.dwBSCR = (((byTotalBucketCount - 1) << 8) ++ | ((byBSBytesPerPixel - 1) << 4) | (0x0 << 3) ++ | (0x1 << 1) | 0x1) & BSCMD_MASK; ++ aBSEAR.dwBSDBS = ((dwSrcBucketSize << 24) | dwDestBucketSize) ++ & 0xfcfffffc; ++ ++ HW_ENG_DBG("dwFetchWidthPixels [%#x], dwFetchHeight [%#x]\n", ++ dwFetchWidthPixels, dwFetchHeight); ++ HW_ENG_DBG("BSE Destination Bucket Size [%#x]\n", dwDestBucketSize); ++ HW_ENG_DBG("BSE Control [%#x]\n", aBSEAR.dwBSCR); ++ HW_ENG_DBG("BSE BSDBS [%#x]\n", aBSEAR.dwBSDBS); ++ HW_ENG_DBG("BSE BSBPS0 [%#x]\n", aBSEAR.adwBSBPS[0]); ++ HW_ENG_DBG("BSE BSBPS1 [%#x]\n", aBSEAR.adwBSBPS[1]); ++ HW_ENG_DBG("BSE BSBPS2 [%#x]\n", aBSEAR.adwBSBPS[2]); ++ ++ return aBSEAR; ++} ++ ++void prepare_tfe_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, bool bNotLastEntry, u8 bCheckSum, ++ bool bEnabledRLE, u16 wStride, u8 bytesPerPixel, ++ u32 dwFetchWidthPixels, u32 dwFetchHeight, ++ enum SelectedByteMode sbm, bool bRLEOverFLow, ++ bool bInterrupt) ++{ ++ enum SkipByteMode skipBM = NoByteSkip; ++ enum DataProccessMode dpm = NormalTileMode; ++ enum StartBytePosition sbp = StartFromByte0; ++ ++ HW_ENG_DBG("BEFORE SETTING TFE Descriptor\n"); ++ // initialize to 0 ++ pDAddress->dw0General = 0x00; ++ pDAddress->dw1FetchWidthLine = 0x00; ++ pDAddress->dw2SourceAddr = 0x00; ++ pDAddress->dw3DestinationAddr = 0x00; ++ ++ if (dwFetchHeight & 0x3) ++ dwFetchHeight = ((dwFetchHeight + 3) >> 2) << 2; ++ ++ switch (sbm) { ++ case AllBytesMode: ++ break; ++ ++ case LowByteMode: ++ dpm = SplitByteMode; ++ if (bytesPerPixel == 2) ++ skipBM = SkipOneByte; ++ else if (bytesPerPixel == 3) ++ skipBM = SkipTwoByte; ++ else if (bytesPerPixel == 4) ++ skipBM = SkipThreeByte; ++ break; ++ ++ case MiddleByteMode: ++ dpm = SplitByteMode; ++ if (bytesPerPixel == 2) { ++ skipBM = SkipOneByte; ++ sbp = StartFromByte1; ++ } else if (bytesPerPixel == 3) { ++ skipBM = SkipTwoByte; ++ sbp = StartFromByte1; ++ } else if (bytesPerPixel == 4) { ++ skipBM = SkipThreeByte; ++ sbp = StartFromByte1; ++ } ++ break; ++ ++ case TopByteMode: ++ dpm = SplitByteMode; ++ if (bytesPerPixel == 2) { ++ skipBM = SkipOneByte; ++ sbp = StartFromByte1; ++ } else if (bytesPerPixel == 3) { ++ skipBM = SkipTwoByte; ++ sbp = StartFromByte2; ++ } else if (bytesPerPixel == 4) { ++ skipBM = SkipThreeByte; ++ sbp = StartFromByte2; ++ } ++ break; ++ ++ case PlanarToPackedMode: ++ dpm = FourBitPlanarMode; ++ break; ++ ++ case PackedToPackedMode: ++ dpm = FourBitPackedMode; ++ break; ++ ++ default: ++ break; ++ } ++ ++ if (dwFetchWidthPixels > wStride) ++ wStride = dwFetchWidthPixels; ++ ++ pDAddress->dw0General = ((wStride * bytesPerPixel) << 16) | (dpm << 13) ++ | (sbp << 10) | (skipBM << 8) | (bRLEOverFLow << 7) ++ | (bCheckSum << 5) | (bEnabledRLE << 4) | (bNotLastEntry << 1) ++ | bInterrupt; ++ pDAddress->dw1FetchWidthLine = ((dwFetchHeight - 1) << 16) ++ | (dwFetchWidthPixels * bytesPerPixel - 1); ++ pDAddress->dw2SourceAddr = (u32)source_addr & 0xfffffffc; ++ pDAddress->dw3DestinationAddr = (u32)dest_addr & 0xfffffffc; ++ ++ HW_ENG_DBG("After SETTING TFE Descriptor\n"); ++ HW_ENG_DBG("u32 0: 0x%x\n", pDAddress->dw0General); ++ HW_ENG_DBG("u32 1: 0x%x\n", pDAddress->dw1FetchWidthLine); ++ HW_ENG_DBG("u32 2: 0x%x\n", pDAddress->dw2SourceAddr); ++ HW_ENG_DBG("u32 3: 0x%x\n", pDAddress->dw3DestinationAddr); ++} ++ ++void prepare_tfe_text_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, bool bEnabledRLE, u32 dwFetchWidth, ++ u32 dwFetchHeight, enum DataProccessMode dpm, ++ bool bRLEOverFLow, bool bInterrupt) ++{ ++ // initialize to 0 ++ pDAddress->dw0General = 0x00; ++ pDAddress->dw1FetchWidthLine = 0x00; ++ pDAddress->dw2SourceAddr = 0x00; ++ pDAddress->dw3DestinationAddr = 0x00; ++ ++ if (dwFetchHeight & 0x3) ++ dwFetchHeight = ((dwFetchHeight + 3) >> 2) << 2; ++ ++ pDAddress->dw0General = (dwFetchWidth << 16) | (dpm << 13) ++ | (bRLEOverFLow << 7) | (1 << 5) | (bEnabledRLE << 4) ++ | bInterrupt; ++ pDAddress->dw1FetchWidthLine = ((dwFetchHeight - 1) << 16) ++ | (dwFetchWidth - 1); ++ pDAddress->dw2SourceAddr = (u32)source_addr & 0xfffffffc; ++ pDAddress->dw3DestinationAddr = (u32)dest_addr & 0xfffffffc; ++ ++ HW_ENG_DBG("u32 0: 0x%x\n", pDAddress->dw0General); ++ HW_ENG_DBG("u32 1: 0x%x\n", pDAddress->dw1FetchWidthLine); ++ HW_ENG_DBG("u32 2: 0x%x\n", pDAddress->dw2SourceAddr); ++ HW_ENG_DBG("u32 3: 0x%x\n", pDAddress->dw3DestinationAddr); ++} ++ ++void on_fetch_mode_13_data(struct AstRVAS *pAstRVAS, struct RvasIoctl *ri, bool bRLEOn) ++{ ++ struct Descriptor *pDescriptorAdd; ++ struct Descriptor *pDescriptorAddPhys; ++ phys_addr_t source_addr = get_phy_fb_start_address(pAstRVAS); ++ phys_addr_t dest_addr; ++ bool bRLEOverFlow = false; ++ bool bNotLastEntry = false; ++ bool bInterrupt = 1; ++ u32 dwFetchHeight = MODE13_HEIGHT; ++ u32 dwFetchWidth = MODE13_WIDTH; ++ phys_addr_t data_phys = 0; ++ phys_addr_t data_phys_rle = 0; ++ u32 dwCtrlRegValue = 0x55AA0080; ++ void *desc_virt = NULL; ++ phys_addr_t desc_phy = 0; ++ struct ContextTable *ctx_entry = NULL; ++ ++ HW_ENG_DBG("Start, bRLEOn: %d\n", bRLEOn); ++ ++ ctx_entry = get_context_entry(ri->rc, pAstRVAS); ++ ++ if (ctx_entry) { ++ desc_virt = ctx_entry->desc_virt; ++ desc_phy = ctx_entry->desc_phy; ++ } else { ++ pr_err("Mode 13: Failed to get context\n"); ++ ri->rs = InvalidContextHandle; ++ return; ++ } ++ ++ ri->tfm.dwFetchSize = MODE13_HEIGHT * MODE13_WIDTH; ++ ++ data_phys = get_phys_add_rsvd_mem((u32)ri->rmh, pAstRVAS); ++ data_phys_rle = get_phys_add_rsvd_mem((u32)ri->rmh1, pAstRVAS); ++ ++ if (!data_phys || !data_phys_rle) { ++ ri->rs = InvalidMemoryHandle; ++ dev_err(pAstRVAS->pdev, "Fetch Text: Invalid Memoryhandle\n"); ++ return; ++ } ++ if (!data_phys || (bRLEOn && !data_phys_rle)) { ++ pr_err("Mode 13: Invalid memory handle\n"); ++ ri->rs = InvalidMemoryHandle; ++ return; ++ } ++ ++ pDescriptorAdd = desc_virt; ++ pDescriptorAddPhys = (struct Descriptor *)desc_phy; ++ ++ HW_ENG_DBG("\n===========MODE 13 FETCHED DATA===========\n"); ++ ++ // Enable TFE ++ dwCtrlRegValue |= 1; ++ dwCtrlRegValue &= TFCTL_DESCRIPTOR_IN_DDR_MASK; ++ dest_addr = data_phys; ++ prepare_tfe_descriptor(pDescriptorAdd, source_addr, ++ dest_addr, ++ false, //is last entry ++ 1, //checksum ++ false, //No RLE ++ dwFetchWidth, ++ 1, //bytes per pixel ++ dwFetchWidth, dwFetchHeight, ++ PackedToPackedMode, bRLEOverFlow, ++ 1); ++ ++ dwCtrlRegValue |= 1 << 1; // enabled IRQ ++ ++ if (sleep_on_tfe_busy(pAstRVAS, (phys_addr_t)pDescriptorAddPhys, // Descriptor physical Address ++ dwCtrlRegValue, // control register value ++ ri->tfm.dwFetchSize, // bandwidth limitor value ++ &ri->tfm.dwFetchRLESize, // out:: rle size ++ &ri->tfm.dwCheckSum) == false) { // out:: cs size ++ ri->rs = GenericError; ++ return; ++ } ++ ++ // RLE enabled ++ if (bRLEOn) { ++ bRLEOverFlow = true; ++ dwCtrlRegValue = 1; ++ dwCtrlRegValue |= (ri->tfm.byRLETripletCode << 24) ++ | (ri->tfm.byRLERepeatCode << 16); ++ source_addr = data_phys; ++ dest_addr = data_phys_rle; ++ HW_ENG_DBG("RLE is on\n"); ++ ++ prepare_tfe_descriptor(pDescriptorAdd, source_addr, ++ dest_addr, ++ bNotLastEntry, //not last entry? ++ 1, //checksum ++ bRLEOn, //RLE? ++ dwFetchWidth, 1, dwFetchWidth, dwFetchHeight, ++ AllBytesMode, bRLEOverFlow, bInterrupt); ++ ++ dwCtrlRegValue |= 1 << 1; // enabled IRQ ++ ++ if (sleep_on_tfe_busy(pAstRVAS, (phys_addr_t)pDescriptorAddPhys, // Descriptor physical Address ++ dwCtrlRegValue, // control register value ++ ri->tfm.dwFetchSize, // bandwidth limitor value ++ &ri->tfm.dwFetchRLESize, // out:: rle size ++ &ri->tfm.dwCheckSum) == false) { // out:: cs size ++ ri->rs = GenericError; ++ return; ++ } //sleeponTFEBusy ++ } ++ ++ if (bRLEOn) ++ ri->tfm.bRLEFailed = ++ (ri->tfm.dwFetchRLESize < ri->tfm.dwFetchSize) ? ++ false : true; ++} ++ ++void ioctl_fetch_mode_13_data(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ bool bRLEOn = ri->tfm.bEnableRLE; ++ ++ ri->rs = SuccessStatus; ++ ++ // first time fetch ++ on_fetch_mode_13_data(pAstRVAS, ri, bRLEOn); ++ ++ if (ri->rs != SuccessStatus) ++ return; ++ ++ //if RLE fail. need to TFE without RLE to first buffer ++ if (ri->tfm.bEnableRLE & ri->tfm.bRLEFailed) { ++ bRLEOn = false; ++ on_fetch_mode_13_data(pAstRVAS, ri, bRLEOn); ++ } ++} ++ ++// Enable Snoop Interrupts and TSE, Disable FIQ ++static void enable_tse_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_val = 0; ++ void __iomem *reg_addr = pAstRVAS->fg_reg_base ++ + TSE_SnoopCommand_Register_Offset; ++ ++ reg_val = readl(reg_addr); ++ reg_val |= SNOOP_IRQ_MASK; ++ reg_val &= ~SNOOP_FIQ_MASK; ++ ++ HW_ENG_DBG("Enabled TSE Interrupts[%#X]\n", reg_val); ++ writel(reg_val, reg_addr); ++ pAstRVAS->tse_tsicr = TSE_INTR_COUNT; ++ reg_addr = pAstRVAS->fg_reg_base ++ + TSE_TileSnoop_Interrupt_Count; ++ //set max wait time before interrupt ++ writel(pAstRVAS->tse_tsicr, reg_addr); ++} ++ ++//disable tse interrupt ++static void disable_tse_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_val = 0; ++ void __iomem *reg_addr = pAstRVAS->fg_reg_base + TSE_SnoopCommand_Register_Offset; ++ ++ // Disable Snoop Interrupts and TSE, Disable FIQ ++ reg_val = readl(reg_addr); ++ HW_ENG_DBG("disable interrupt\n"); ++ reg_val &= ~(SNOOP_IRQ_MASK | SNOOP_FIQ_MASK); ++ writel(reg_val, reg_addr); ++} ++ ++static void enable_grce_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_val = 0; ++ void __iomem *reg_addr = pAstRVAS->grce_reg_base + GRCE_CTL0; ++ ++ reg_val = readl(reg_addr); ++ reg_val |= GRC_IRQ_MASK; ++ writel(reg_val, reg_addr); ++ HW_ENG_DBG("Enabled GRC Interrupts[%#X]\n", reg_val); ++} ++ ++//enable all interrupts ++void enable_grce_tse_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ enable_grce_interrupt(pAstRVAS); ++ enable_tse_interrupt(pAstRVAS); ++} ++ ++void disable_grce_tse_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_val = 0; ++ ++ HW_ENG_DBG("disable_interrupts- grce_reg_base: %p GRCE_CTL0: %#x\n", ++ pAstRVAS->grce_reg_base, GRCE_CTL0); ++ reg_val = readl(pAstRVAS->grce_reg_base + GRCE_CTL0); ++ writel(reg_val & (~GRC_IRQ_MASK), pAstRVAS->grce_reg_base + GRCE_CTL0); ++ disable_tse_interrupt(pAstRVAS); ++} ++ ++u32 clear_tse_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 tse_sts = 0; ++ u32 tse_tile_status = 0; ++ u32 tse_snoop_ctrl = 0; ++ void __iomem *tse_ctrl_addr = pAstRVAS->fg_reg_base + TSE_SnoopCommand_Register_Offset; ++ ++ HW_ENG_DBG("clear tse inerrupt"); ++ tse_sts = readl(pAstRVAS->fg_reg_base + TSE_Status_Register_Offset); ++ tse_snoop_ctrl = readl(pAstRVAS->fg_reg_base + TSE_SnoopCommand_Register_Offset); ++ ++ if (tse_sts & (TSSTS_TC_SCREEN0 | TSSTS_TC_SCREEN1)) { ++ if (tse_sts & TSSTS_TC_SCREEN0) { ++ HW_ENG_DBG("Snoop** Update Screen 0\n"); ++ // clear interrupt and switch to screen 1 ++ tse_snoop_ctrl |= TSCMD_SCREEN_OWNER; ++ writel(tse_sts, pAstRVAS->fg_reg_base + TSE_Status_Register_Offset); ++ writel(tse_snoop_ctrl, tse_ctrl_addr); ++ ++ } else if (tse_sts & TSSTS_TC_SCREEN1) { ++ HW_ENG_DBG("Snoop** Update Screen 1\n"); ++ tse_snoop_ctrl &= ~TSCMD_SCREEN_OWNER; // snap shutter ++ // clear status ++ writel(tse_sts, pAstRVAS->fg_reg_base + TSE_Status_Register_Offset); ++ // clear interrupt and switch to screen 1 ++ writel(tse_snoop_ctrl, tse_ctrl_addr); ++ } ++ // read clear interrupt ++ tse_tile_status = readl(pAstRVAS->fg_reg_base ++ + TSE_TileCount_Register_Offset); ++ ++ if (tse_sts & TSSTS_FIFO_OVFL) { ++ //need to send full frame ++ dev_err(pAstRVAS->pdev, "TSE snoop fifo overflow\n"); ++ writel(TSSTS_FIFO_OVFL, pAstRVAS->fg_reg_base + TSE_Status_Register_Offset); ++ memset((void *)pAstRVAS->accrued_sm, 0xff, sizeof(pAstRVAS->accrued_sm)); ++ memset((void *)&pAstRVAS->accrued_sa, 0xff, ++ sizeof(pAstRVAS->accrued_sa)); ++ } else { ++ get_snoop_map_data(pAstRVAS); ++ } ++ } ++ return tse_sts; ++} ++ ++// LDMA interrupt ++bool clear_ldma_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 ldma_sts = 0; ++ ++ ldma_sts = readl(pAstRVAS->fg_reg_base + LDMA_Status_Register); ++ ++ if (ldma_sts & 0x02) { ++ //HW_ENG_DBG("Got a LDMA interrupt\n"); ++ // write 1 to clear the interrupt ++ writel(0x2, pAstRVAS->fg_reg_base + LDMA_Status_Register); ++ return true; ++ } ++ return false; ++} ++ ++bool clear_tfe_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 tfe_sts = 0; ++ ++ tfe_sts = readl(pAstRVAS->fg_reg_base + TFE_Status_Register); ++ ++ if (tfe_sts & 0x02) { ++ // HW_ENG_DBG("Debug: TFSTS Interrupt is triggered\n"); ++ writel(0x2, pAstRVAS->fg_reg_base + TFE_Status_Register); ++ return true; ++ } ++ return false; ++} ++ ++bool clear_bse_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 bse_sts = 0; ++ ++ bse_sts = readl(pAstRVAS->fg_reg_base + BSE_Status_Register); ++ ++ if (bse_sts & 0x02) { ++ writel(0x2, pAstRVAS->fg_reg_base + BSE_Status_Register); ++ return true; ++ } ++ return false; ++} ++ ++void setup_lmem(struct AstRVAS *pAstRVAS) ++{ ++ writel(0x0, pAstRVAS->fg_reg_base + LMEM_BASE_REG_3); ++ writel(0x2000, pAstRVAS->fg_reg_base + LMEM_LIMIT_REG_3); ++ writel(0x9c89c8, pAstRVAS->fg_reg_base + LMEM11_P0); ++ writel(0x9c89c8, pAstRVAS->fg_reg_base + LMEM12_P0); ++ writel(0xf3cf3c, pAstRVAS->fg_reg_base + LMEM11_P1); ++ writel(0x067201, pAstRVAS->fg_reg_base + LMEM11_P2); ++ writel(0x00F3CF3C, pAstRVAS->fg_reg_base + LMEM10_P1); ++ writel(0x00067201, pAstRVAS->fg_reg_base + LMEM10_P2); ++} ++ ++bool host_suspended(struct AstRVAS *pAstRVAS) ++{ ++ u32 GRCE18 = readl(pAstRVAS->grce_reg_base + GRCE_ATTR_VGAIR0_OFFSET); ++ ++ // VGAER is GRCE19 ++ // VGAER bit[0]:0 - vga disabled (host suspended) ++ // 1 - vga enabled ++ HW_ENG_DBG("GRCE18:%#x\n", GRCE18); ++ if (GRCE18 & 0x100) ++ return false; ++ else ++ return true; ++} ++ +diff --git a/drivers/soc/aspeed/rvas/hardware_engines.h b/drivers/soc/aspeed/rvas/hardware_engines.h +--- a/drivers/soc/aspeed/rvas/hardware_engines.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/hardware_engines.h 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,554 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * This file is part of the ASPEED Linux Device Driver for ASPEED Baseboard Management Controller. ++ * Refer to the README file included with this package for driver version and adapter compatibility. ++ * ++ * Copyright (C) 2019-2021 ASPEED Technology Inc. All rights reserved. ++ */ ++ ++#ifndef __HARDWAREENGINES_H__ ++#define __HARDWAREENGINES_H__ ++ ++#include ++#include ++#include "video_ioctl.h" ++ ++#define MAX_NUM_CONTEXT (8) ++#define MAX_NUM_MEM_TBL (24)//each context has 3 ++ ++#define MAX_DESC_SIZE (PAGE_SIZE) // (0x400) ++ ++#define ENGINE_TIMEOUT_IN_SECONDS (3) ++#define TFE_TIMEOUT_IN_MS (750) ++#define DESCRIPTOR_SIZE (16) ++#define TILE_SIZE (32) ++#define MAX_LMEM_BUCKET_SIZE (0x80) ++ ++#define EIGHT_BYTE_ALIGNMENT_MASK (0xfffffff7) ++#define SIXTEEN_BYTE_ALIGNMENT_MASK (0x8) ++#define TFCTL_DESCRIPTOR_IN_DDR_MASK (0xffffff7f) ++#define BSCMD_MASK (0xffff0f37) ++ ++#define TEXT_MODE_BUFFER_ALIGNMENT (16) ++#define MODE_13_CHAR_WIDTH (32) ++#define BSE_MEMORY_ACCESS_MASK (0x00ffffff) ++#define MEM_TABLE_SIZE_INCR (8) ++#define MEMORY_TABLE_GROW_INCR (8) ++ ++#define MAX_TEXT_DATA_SIZE (8192) ++ ++// For 2700 ++//#define SCU200_System_Reset_Control_Register (0x200) ++#define SCU204_System_Reset_Control_Clear_Register (0x204) ++#define SCU240_Clock_Stop_Control_Register (0x240) ++#define SCU244_Clock_Stop_Control_Clear_Register (0x244) ++//#define SCU500_Hardware_Strap1_Register (0x500) ++//TO DO local monitor on off ++//single node - vga and dp ++//dual node- node 0- vga only, node 1- dp only ++#define SCU000_Silicon_Revision_ID (0x0) ++#define SCU448_Pin_Ctrl (0x448) ++//#define SCU0C0_Misc1_Ctrl (0x0C0) ++//#define SCU0D0_Misc3_Ctrl (0x0D0) ++ //SCU448 IO ++#define VGAVS_ENBL_27 (0x70000000) ++#define VGAHS_ENBL_27 (0x7000000) ++//SCU0C0 ++#define VGA0_CRT_DISBL BIT(1) ++#define VGA1_CRT_DISBL BIT(2) ++//SCU0D0 IO ++#define VGA0_PWR_OFF_VDAC BIT(2) ++#define VGA1_PWR_OFF_VDAC BIT(3) ++ ++#define SCU_RVAS1_ENGINE_BIT BIT(10) ++#define SCU_RVAS0_ENGINE_BIT BIT(9) ++#define SCU_RVAS1_STOP_CLOCK_BIT BIT(28) ++#define SCU_RVAS0_STOP_CLOCK_BIT BIT(25) ++ ++// For 2600 ++//SCU ++#define SCU000_Protection_Key_Register (0x000) ++#define SCU040_Module_Reset_Control_Register_Set_1 (0x040) ++#define SCU044_Module_Reset_Control_Clear_Register_1 (0x044) ++#define SCU080_Clock_Stop_Control_Register_Set_1 (0x080) ++#define SCU084_Clock_Stop_Control_Clear_Register (0x084) ++#define SCU500_Hardware_Strap1_Register (0x500) ++#define SCU418_Pin_Ctrl (0x418) ++#define SCU0C0_Misc1_Ctrl (0x0C0) ++#define SCU0D0_Misc3_Ctrl (0x0D0) ++//SCU418 ++#define VGAVS_ENBL BIT(31) ++#define VGAHS_ENBL BIT(30) ++//SCU0C0 ++#define VGA_CRT_DISBL BIT(6) ++//SCU0D0 ++#define PWR_OFF_VDAC BIT(3) ++ ++#define SCU_UNLOCK_PWD (0x1688A8A8) ++#define SCU_RVAS_ENGINE_BIT BIT(9) ++#define SCU_RVAS_STOP_CLOCK_BIT BIT(25) ++// ++//MCR -edac ++#define MCR_CONF 0x04 /* configuration register */ ++ ++#define MCR_CONF_AST27XX 0x10 /* configuration register */ ++#define MCR_GRAPHIC_MEM_CONF_AST27XX 0x100 /* graphic memory configuration register */ ++#define MCR_GRAPHIC_MEM_BASE_AST27XX 0x10C /* graphic memory base: [25:17]vga1 in 16MB; [9:1]vga0 in 16MB */ ++ ++//DP ++#define DPTX_Configuration_Register (0x100) ++#define DPTX_PHY_Configuration_Register (0x104) ++//DPTX100 ++#define AUX_RESETN (24) ++//DPTX104 ++#define DP_TX_I_MAIN_ON (8) ++ ++//TOP REG ++#define TOP_REG_OFFSET (0x0) ++#define TOP_REG_CTL (TOP_REG_OFFSET + 0x00) ++#define TOP_REG_STS (TOP_REG_OFFSET + 0x04) ++#define LMEM_BASE_REG_3 (TOP_REG_OFFSET + 0x2c) ++#define LMEM_LIMIT_REG_3 (TOP_REG_OFFSET + 0x3c) ++#define LMEM11_P0 (TOP_REG_OFFSET + 0x4c) ++#define LMEM12_P0 (TOP_REG_OFFSET + 0x50) ++#define LMEM10_P1 (TOP_REG_OFFSET + 0x80) ++#define LMEM11_P1 (TOP_REG_OFFSET + 0x84) ++#define LMEM10_P2 (TOP_REG_OFFSET + 0xA0) ++#define LMEM11_P2 (TOP_REG_OFFSET + 0xA4) ++ ++#define TSE_SnoopCommand_Register_Offset (0x0400) ++#define TSE_TileCount_Register_Offset (0x0418) ++#define TSE_Status_Register_Offset (0x0404) ++#define TSE_CS0Reg (0x0408) ++#define TSE_CS1Reg (0x040c) ++#define TSE_RS0Reg (0x0410) ++#define TSE_RS1Reg (0x0414) ++#define TSE_TileSnoop_Interrupt_Count (0x0420) ++#define TSE_FrameBuffer_Offset (0x041c) ++#define TSE_UpperLimit_Offset (0x0424) ++#define TSE_SnoopMap_Offset (0x0600) ++ ++#define TFE_Descriptor_Table_Offset (0x0108) ++#define TFE_Descriptor_Control_Resgister (0x0100) ++#define TFE_Status_Register (0x0104) ++#define TFE_RLE_CheckSum (0x010C) ++#define TFE_RLE_Byte_Count (0x0110) ++#define TFE_RLE_LIMITOR (0x0114) ++ ++#define BSE_REG_BASE (0x0200) ++#define BSE_Command_Register (0x0200) ++#define BSE_Status_Register (0x0204) ++#define BSE_Descriptor_Table_Base_Register (0x0208) ++#define BSE_Destination_Buket_Size_Resgister (0x020c) ++#define BSE_Bit_Position_Register_0 (0x0210) ++#define BSE_Bit_Position_Register_1 (0x0214) ++#define BSE_Bit_Position_Register_2 (0x0218) ++#define BSE_LMEM_Temp_Buffer_Offset (0x0000) ++#define BSE_ENABLE_MULT_BUCKET_SZS BIT(12) ++#define BSE_BUCK_SZ_INDEX_POS (4) ++#define BSE_MAX_BUCKET_SIZE_REGS (16) ++#define BSE_BIT_MASK_Register_Offset (0x54) ++ ++#define LDMA_Control_Register (0x0300) ++#define LDMA_Status_Register (0x0304) ++#define LDMA_Descriptor_Table_Base_Register (0x0308) ++#define LDMA_CheckSum_Register (0x030c) ++#define LDMA_LMEM_Descriptor_Offset (0x4000) ++ ++//Shadow ++#define GRCE_SIZE (0x800) ++#define GRCE_ATTR_OFFSET (0x0) ++#define GRCE_ATTR_VGAIR0_OFFSET (0x18) ++#define GRCE_SEQ_OFFSET (0x20) ++#define GRCE_GCTL_OFFSET (0x30) ++#define GRCE_GRCCTL0_OFFSET (0x58) ++#define GRCE_GRCSTS_OFFSET (0x5c) ++#define GRCE_CRTC_OFFSET (0x60) ++#define GRCE_CRTCEXT_OFFSET (0x80) ++#define GRCE_XCURCTL_OFFSET (0xc8) ++#define GRCE_PAL_OFFSET (0x400) ++//size ++#define GRCELT_RAM_SIZE (0x400) ++#define GRCE_XCURCOL_SIZE (0x40) ++#define GRCE_XCURCTL_SIZE (0x40) ++#define GRCE_CRTC_SIZE (0x40) ++#define GRCE_CRTCEXT_SIZE (0x8) ++#define GRCE_SEQ_SIZE (0x8) ++#define GRCE_GCTL_SIZE (0x8) ++#define GRCE_ATTR_SIZE (0x20) ++ ++#define GRCELT_RAM (GRCE_PAL_OFFSET) ++#define GRCE_XCURCTL (GRCE_XCURCTL_OFFSET) ++#define GRCE_CRTC (GRCE_CRTC_OFFSET) ++#define GRCE_CRTCEXT (GRCE_CRTCEXT_OFFSET) ++#define GRCE_SEQ (GRCE_SEQ_OFFSET) ++#define GRCE_GCTL (GRCE_GCTL_OFFSET) ++#define GRCE_CTL0 (GRCE_GRCCTL0_OFFSET) ++#define GRCE_STATUS_REGISTER (GRCE_GRCSTS_OFFSET) ++#define GRCE_ATTR (GRCE_ATTR_OFFSET) ++#define AST_VIDEO_SCRATCH_34C (0x8c) ++#define AST_VIDEO_SCRATCH_350 (0x90) ++#define AST_VIDEO_SCRATCH_354 (0x94) ++#define MODE_GET_INFO_DE (0xA8) ++ ++//GRC interrupt ++#define GRC_FIQ_MASK (0x000003ff) ++#define GRC_IRQ_MASK (0x000003ff) ++#define GRC_INT_STS_MASK (0x000003ff) ++#define GRCSTS_XCUR_POS BIT(9) ++#define GRCSTS_XCUR_DDR BIT(8) ++#define GRCSTS_XCUR_CTL BIT(7) ++#define GRCSTS_PLT_RAM BIT(6) ++#define GRCSTS_XCRTC BIT(5) ++#define GRCSTS_CRTC BIT(4) ++#define GRCSTS_GCTL BIT(3) ++#define GRCSTS_SEQ BIT(2) ++#define GRCSTS_ATTR1 BIT(1) ++#define GRCSTS_ATTR0 BIT(0) ++#define SNOOP_RESTART (GRCSTS_XCUR_CTL | GRCSTS_XCRTC | GRCSTS_CRTC | GRCSTS_GCTL) ++ ++//snoop TSE ++#define SNOOP_TSE_MASK (0x00000001) ++#define SNOOP_IRQ_MASK (0x00000100) ++#define SNOOP_FIQ_MASK (0x00000200) ++#define TSCMD_SCREEN_OWNER BIT(15) ++#define TSCMD_PITCH_BIT (16) ++#define TSCMD_INT_ENBL_BIT (8) ++#define TSCMD_CPT_BIT (6) ++#define TSCMD_RPT_BIT (4) ++#define TSCMD_BPP_BIT (2) ++#define TSCMD_VGA_MODE_BIT (1) ++#define TSCMD_TSE_ENBL_BIT (0) ++#define TSSTS_FIFO_OVFL BIT(5) ++#define TSSTS_FONT BIT(4) ++#define TSSTS_ATTR BIT(3) ++#define TSSTS_ASCII BIT(2) ++#define TSSTS_TC_SCREEN1 BIT(1) ++#define TSSTS_TC_SCREEN0 BIT(0) ++#define TSSTS_ALL (0x3f) ++ ++#define TSE_INTR_COUNT (0xCB700) //50MHz clock ~1/60 sec ++//#define TSE_INTR_COUNT (0x196E00) //50MHz clock ~1/30 sec ++#define TIMER_INTR_COUNT (0x65000) // 25MHz clock ~1/60 sec ++ ++#ifdef CONFIG_MACH_ASPEED_G6 ++//Timer ++/* Register byte offsets */ ++// AST2600 Timer registers ++#define TIMER_STATUS_BIT(x) (1 << ((x) - 1)) ++ ++#define OFFSET_TIMER1 0x00 /* * timer 1 offset */ ++#define OFFSET_TIMER2 0x10 /* * timer 2 offset */ ++#define OFFSET_TIMER3 0x20 /* * timer 3 offset */ ++#define OFFSET_TIMER4 0x40 /* * timer 4 offset */ ++#define OFFSET_TIMER5 0x50 /* * timer 5 offset */ ++#define OFFSET_TIMER6 0x60 /* * timer 6 offset */ ++#define OFFSET_TIMER7 0x70 /* * timer 7 offset */ ++#define OFFSET_TIMER8 0x80 /* * timer 8 offset */ ++ ++#define OFF_TIMER_REG_CURR_CNT 0x00 ++#define OFF_TIMER_REG_LOAD_CNT 0x04 ++#define OFF_TIMER_REG_EO0 0x08 /* Read to clear interrupt */ ++#define OFF_TIMER_REG_EOI 0x0c /* Read to clear interrupt */ ++#define OFF_TIMER_REG_STAT 0x10 /* Timer Interrupt Status */ ++#define OFF_TIMER_REG_CONTROL 0x30 /* Control Register */ ++#define OFF_TIMER_REG_STATUS 0x34 /* Status Register */ ++#define OFF_TIMER_REG_CLEAR_CONTROL 0x3C /* Control Register */ ++#define RB_OFF_TIMERS_STAT 0xA0 /* * timers status offset */ ++ ++#define CTRL_TIMER1 (0) ++#define CTRL_TIMER2 (4) ++#define CTRL_TIMER3 (8) ++#define CTRL_TIMER4 (12) ++#define CTRL_TIMER5 (16) ++#define CTRL_TIMER6 (20) ++#define CTRL_TIMER7 (24) ++#define CTRL_TIMER8 (28) ++#define BIT_TIMER_ENBL BIT(0) ++#define BIT_TIMER_CLK_SEL BIT(1) ++#define BIT_INTERRUPT_ENBL BIT(2) ++#define BIT_TIMER_STAT BIT(0) ++#endif ++ ++#define SNOOP_MAP_QWORD_COUNT (64) ++#define BSE_UPPER_LIMIT (0x900000) //(0x540000) ++#define FULL_BUCKETS_COUNT (16) ++#define MODE13_HEIGHT (200) ++#define MODE13_WIDTH (320) ++ ++#define NUM_SNOOP_ROWS (64) ++ ++//vga memory information ++#define DDR_SIZE_CONFIG_BITS (0x3) ++#define VGA_MEM_SIZE_CONFIG_BITS (0x3) ++#define DDR_BASE_27 (0x400000000) ++#define DDR_BASE (0x80000000) ++ ++//grce ++#define VGACR0_REG (0x60) ++#define VGACR9F_REG (0x9F) ++ ++//display out ++#define VGA_OUT BIT(0) ++#define DP_OUT BIT(1) ++ ++struct ContextTable { ++ struct inode *pin; ++ struct file *pf; ++ struct SnoopAggregate sa; ++ u64 aqwSnoopMap[NUM_SNOOP_ROWS]; ++ void *rc; ++ struct EventMap emEventWaiting; ++ struct EventMap emEventReceived; ++ u32 dwEventWaitInMs; ++ void *desc_virt; ++ phys_addr_t desc_phy; ++}; ++ ++struct MemoryMapTable { ++ struct file *pf; ++ void *pvVirtualAddr; ++ dma_addr_t mem_phys; ++ u32 dwLength; ++ u8 byDmaAlloc; ++ u8 byReserved[3]; ++}; ++ ++union EmDwordUnion { ++ struct EventMap em; ++ u32 dw; ++}; ++ ++struct Descriptor { ++ u32 dw0General; ++ u32 dw1FetchWidthLine; ++ u32 dw2SourceAddr; ++ u32 dw3DestinationAddr; ++}; ++ ++struct BSEAggregateRegister { ++ u32 dwBSCR; ++ u32 dwBSDBS; ++ u32 adwBSBPS[3]; ++}; ++ ++enum SkipByteMode { ++ NoByteSkip = 0, SkipOneByte = 1, SkipTwoByte = 2, SkipThreeByte = 3 ++}; ++ ++enum StartBytePosition { ++ StartFromByte0 = 0, ++ StartFromByte1 = 1, ++ StartFromByte2 = 2, ++ StartFromByte3 = 3 ++}; ++ ++struct VGAMemInfo { ++ u32 dwVGASize; ++ u64 qwDRAMSize; ++ phys_addr_t qwFBPhysStart; ++}; ++ ++struct VideoDataBufferInfo { ++ u32 dwSize; ++ phys_addr_t dwPhys; ++ phys_addr_t dwVirt; ++}; ++ ++enum ColorMode { ++ MODE_EGA = 0x0, //4bpp eg. mode 12/6A ++ MODE_VGA = 0x1, //mode 13 ++ MODE_BPP15 = 0x2, ++ MODE_BPP16 = 0x3, ++ MODE_BPP32 = 0x4, ++ MODE_TEXT = 0xE, ++ MODE_CGA = 0xF ++}; ++ ++struct ModeInfo { ++ u8 byColorMode; ++ u8 byRefreshRateIndex; ++ u8 byModeID; ++ u8 byScanLines; ++}; ++ ++struct NewModeInfoHeader { ++ u8 byReserved; ++ u8 byDisplayInfo; ++ u8 byColorDepth; ++ u8 byMhzPixelClock; ++}; ++ ++struct DisplayEnd { ++ u16 HDE; ++ u16 VDE; ++}; ++ ++struct Resolution { ++ u16 wWidth; ++ u16 wHeight; ++}; ++ ++struct Video_OsSleepStruct { ++ wait_queue_head_t queue; ++ struct timer_list tim; ++ u8 Timeout; ++}; ++ ++struct EngineInfo { ++ struct semaphore sem; ++ struct Video_OsSleepStruct wait; ++ u8 finished; ++}; ++ ++struct VideoMem { ++ dma_addr_t phy; ++ void *pVirt; ++ u32 size; ++}; ++ ++struct VideoEngineMem { ++ struct VideoMem captureBuf0; ++ struct VideoMem captureBuf1; ++ struct VideoMem jpegTable; ++}; ++ ++struct aspeed_rvas_config { ++ u8 version; ++ const u64 *dram_table; ++}; ++ ++struct AstRVAS { ++ struct miscdevice rvas_dev; ++ struct aspeed_rvas_config *config; ++ void *pdev; ++ int irq_fge; //FrameGrabber IRQ number ++ int irq_vga; // VGA IRQ number ++ int irq_video; ++ void __iomem *fg_reg_base; ++ void __iomem *grce_reg_base; ++ void __iomem *video_reg_base; ++ struct regmap *scu; ++ struct regmap *scu_io; ++ struct reset_control *rvas_reset; ++ struct reset_control *video_engine_reset; ++ struct VGAMemInfo FBInfo; ++ u64 accrued_sm[SNOOP_MAP_QWORD_COUNT]; ++ struct SnoopAggregate accrued_sa; ++ struct VideoGeometry current_vg; ++ u32 snoop_stride; ++ u32 tse_tsicr; ++ struct EngineInfo tfe_engine; ++ struct EngineInfo bse_engine; ++ struct EngineInfo ldma_engine; ++ struct EngineInfo video_engine; ++ struct semaphore mem_sem; ++ struct semaphore context_sem; ++ struct Video_OsSleepStruct video_wait; ++ u8 video_intr_occurred; ++ u8 timer_irq_requested; ++ u8 display_out; ++ u8 rvas_index; ++ struct ContextTable *ppctContextTable[MAX_NUM_CONTEXT]; ++ u32 dwMemoryTableSize; ++ u32 dwScreenOffset; ++ struct MemoryMapTable *ppmmtMemoryTable[MAX_NUM_MEM_TBL]; ++ struct completion video_compression_complete; ++ struct completion video_capture_complete; ++ struct clk *vclk; ++ struct clk *eclk; ++ struct clk *rvasclk; ++ void __iomem *dp_base; ++ u32 sequence; ++ struct VideoEngineMem vem; ++ u8 veClkOn; ++}; ++ ++// ++// IOCTL functions ++// ++void ioctl_get_video_geometry(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_wait_for_video_event(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_get_grc_register(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_read_snoop_map(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_read_snoop_aggregate(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_set_tse_tsicr(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_get_tse_tsicr(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_reset_video_engine(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++ ++//vidoe fetch functions ++void ioctl_fetch_video_tiles(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_fetch_video_slices(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_run_length_encode_data(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_fetch_text_data(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++void ioctl_fetch_mode_13_data(struct RvasIoctl *ri, struct AstRVAS *ast_rvas); ++phys_addr_t get_phy_fb_start_address(struct AstRVAS *ast_rvas); ++bool video_geometry_change(struct AstRVAS *ast_rvas, u32 dwGRCEStatus); ++void update_video_geometry(struct AstRVAS *ast_rvas); ++ ++//interrupts ++void enable_grce_tse_interrupt(struct AstRVAS *ast_rvas); ++void disable_grce_tse_interrupt(struct AstRVAS *ast_rvas); ++u32 clear_tse_interrupt(struct AstRVAS *ast_rvas); ++bool clear_ldma_interrupt(struct AstRVAS *ast_rvas); ++bool clear_tfe_interrupt(struct AstRVAS *ast_rvas); ++bool clear_bse_interrupt(struct AstRVAS *ast_rvas); ++u32 get_screen_offset(struct AstRVAS *ast_rvas); ++// ++void setup_lmem(struct AstRVAS *ast_rvas); ++// ++// helper functions ++// ++ ++struct BSEAggregateRegister setUp_bse_bucket(u8 *abyBitIndexes, u8 byTotalBucketCount, ++ u8 byBSBytesPerPixel, u32 dwFetchWidthPixels, ++ u32 dwFetchHeight); ++void prepare_bse_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, bool bNotLastEntry, ++ u16 wStride, u8 bytesPerPixel, ++ u32 dwFetchWidthPixels, u32 dwFetchHeight, ++ bool bInterrupt); ++ ++void prepare_tfe_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, bool bNotLastEntry, u8 bCheckSum, ++ bool bEnabledRLE, u16 wStride, u8 bytesPerPixel, ++ u32 dwFetchWidthPixels, u32 dwFetchHeight, ++ enum SelectedByteMode sbm, bool bRLEOverFLow, ++ bool bInterrupt); ++void prepare_tfe_text_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, bool bEnabledRLE, u32 dwFetchWidth, ++ u32 dwFetchHeight, enum DataProccessMode dpm, ++ bool bRLEOverFLow, bool bInterrupt); ++void prepare_ldma_descriptor(struct Descriptor *pDAddress, phys_addr_t source_addr, ++ phys_addr_t dest_addr, u32 dwLDMASize, u8 byNotLastEntry); ++ ++u8 get_text_mode_character_per_line(struct AstRVAS *ast_rvas, u16 wScreenWidth); ++u16 get_text_mode_fetch_lines(struct AstRVAS *ast_rvas, u16 wScreenHeight); ++void on_fetch_text_data(struct RvasIoctl *ri, bool bRLEOn, struct AstRVAS *ast_rvas); ++ ++void reset_snoop_engine(struct AstRVAS *ast_rvas); ++void set_snoop_engine(bool b_geom_chg, struct AstRVAS *ast_rvas); ++u64 reinterpret_32bpp_snoop_row_as_24bpp(u64 theSnoopRow); ++ ++void convert_snoop_map(struct AstRVAS *ast_rvas); ++void update_all_snoop_context(struct AstRVAS *ast_rvas); ++void get_snoop_map_data(struct AstRVAS *ast_rvas); ++void get_snoop_aggregate(struct AstRVAS *ast_rvas); ++ ++void sleep_on_ldma_busy(struct AstRVAS *ast_rvas, phys_addr_t desc_addr_phys); ++bool sleep_on_tfe_busy(struct AstRVAS *ast_rvas, phys_addr_t desc_addr_phys, ++ u32 dwTFEControlR, u32 dwTFERleLimitor, u32 *pdwRLESize, ++ u32 *pdwCheckSum); ++ ++bool sleep_on_tfe_text_busy(struct AstRVAS *ast_rvas, phys_addr_t desc_addr_phys, ++ u32 dwTFEControlR, u32 dwTFERleLimitor, u32 *pdwRLESize, ++ u32 *pdwCheckSum); ++ ++bool sleep_on_bse_busy(struct AstRVAS *ast_rvas, phys_addr_t desc_addr_phys, ++ struct BSEAggregateRegister aBSEAR, u32 size); ++ ++void enable_grce_tse_interrupt(struct AstRVAS *ast_rvas); ++void disable_grce_tse_interrupt(struct AstRVAS *ast_rvas); ++ ++bool host_suspended(struct AstRVAS *pAstRVAS); ++#endif // __HARDWAREENGINES_H__ +diff --git a/drivers/soc/aspeed/rvas/video.h b/drivers/soc/aspeed/rvas/video.h +--- a/drivers/soc/aspeed/rvas/video.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/video.h 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/****************************************************************************** ++ * video.h ++ * ++ * This file is part of the ASPEED Linux Device Driver for ASPEED Baseboard Management Controller. ++ * Refer to the README file included with this package for driver version and adapter compatibility. ++ * ++ * Copyright (C) 2019-2021 ASPEED Technology Inc. All rights reserved. ++ */ ++ ++#ifndef __RVAS_VIDEO_H__ ++#define __RVAS_VIDEO_H__ ++ ++#define RVAS_DRIVER_NAME "rvas" ++#define Stringify(x) #x ++ ++// ++//functions ++// ++void ioctl_new_context(struct file *file, struct RvasIoctl *pri, struct AstRVAS *pAstRVAS); ++void ioctl_delete_context(struct RvasIoctl *pri, struct AstRVAS *pAstRVAS); ++void ioctl_alloc(struct file *file, struct RvasIoctl *pri, struct AstRVAS *pAstRVAS); ++void ioctl_free(struct RvasIoctl *pri, struct AstRVAS *pAstRVAS); ++void ioctl_update_lms(u8 lms_on, struct AstRVAS *ast_rvas); ++void ioctl_update_lms_2700(u8 lms_on, struct AstRVAS *ast_rvas); ++u32 ioctl_get_lm_status(struct AstRVAS *ast_rvas); ++u32 ioctl_get_lm_status_2700(struct AstRVAS *ast_rvas); ++ ++//void* get_from_rsvd_mem(u32 size, u32 *phys_add, struct AstRVAS *pAstRVAS); ++void *get_virt_add_rsvd_mem(u32 index, struct AstRVAS *pAstRVAS); ++dma_addr_t get_phys_add_rsvd_mem(u32 index, struct AstRVAS *pAstRVAS); ++u32 get_len_rsvd_mem(u32 index, struct AstRVAS *pAstRVAS); ++ ++//int release_rsvd_mem(u32 size, u32 phys_add); ++bool virt_is_valid_rsvd_mem(u32 index, u32 size, struct AstRVAS *pAstRVAS); ++ ++struct ContextTable *get_new_context_table_entry(struct AstRVAS *pAstRVAS); ++struct ContextTable *get_context_entry(const void *crc, struct AstRVAS *pAstRVAS); ++bool remove_context_table_entry(const void *crmh, struct AstRVAS *pAstRVAS); ++ ++#endif // __RVAS_VIDEO_H__ +diff --git a/drivers/soc/aspeed/rvas/video_debug.h b/drivers/soc/aspeed/rvas/video_debug.h +--- a/drivers/soc/aspeed/rvas/video_debug.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/video_debug.h 2026-09-09 15:51:44.722238011 +0000 +@@ -0,0 +1,38 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2019-2021 ASPEED Technology Inc. ++ */ ++ ++#ifndef AST_VIDEO_DEBUG_H_ ++#define AST_VIDEO_DEBUG_H_ ++ ++#include ++#include ++#include ++ ++//#define RVAS_VIDEO_DEBUG ++//#define VIDEO_ENGINE_DEBUG ++//#define HARDWARE_ENGINE_DEBUG ++ ++#ifdef RVAS_VIDEO_DEBUG ++#define VIDEO_DBG(fmt, args...) \ ++dev_info(pAstRVAS->pdev, "%s() " fmt, __func__, ##args) ++#else ++#define VIDEO_DBG(...) ++#endif // RVAS_VIDEO_DEBUG ++ ++#ifdef VIDEO_ENGINE_DEBUG ++#define VIDEO_ENG_DBG(fmt, args...) \ ++dev_info(pAstRVAS->pdev, "%s() " fmt, __func__, ##args) ++#else ++#define VIDEO_ENG_DBG(...) ++#endif // RVAS_VIDEO_DEBUG ++ ++#ifdef HARDWARE_ENGINE_DEBUG ++#define HW_ENG_DBG(fmt, args...) \ ++pr_info("%s() " fmt, __func__, ##args) ++#else ++#define HW_ENG_DBG(...) ++#endif // RVAS_VIDEO_DEBUG ++ ++#endif // AST_VIDEO_DEBUG_H_ +diff --git a/drivers/soc/aspeed/rvas/video_engine.c b/drivers/soc/aspeed/rvas/video_engine.c +--- a/drivers/soc/aspeed/rvas/video_engine.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/video_engine.c 2026-09-09 15:51:44.723237996 +0000 +@@ -0,0 +1,1338 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * File Name : video_engines.c ++ * Description : AST2600 video engines ++ * ++ * Copyright (C) 2019-2021 ASPEED Technology Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "video_ioctl.h" ++#include "video_engine.h" ++#include "video_debug.h" ++#include "hardware_engines.h" ++ ++// ++//functions ++// ++static inline void video_write(struct AstRVAS *pAstRVAS, u32 val, u32 reg); ++static inline u32 video_read(struct AstRVAS *pAstRVAS, u32 reg); ++ ++static u32 get_vga_mem_base(struct AstRVAS *pAstRVAS); ++static int reserve_video_engine_memory(struct AstRVAS *pAstRVAS); ++static void init_jpeg_table(struct AstRVAS *pAstRVAS); ++static void video_set_scaling(struct AstRVAS *pAstRVAS); ++static int video_capture_trigger(struct AstRVAS *pAstRVAS); ++static void dump_buffer(phys_addr_t qwPhyStreamAddress, u32 size); ++ ++// ++// function definitions ++// ++/** ++ * _make_addr - make address fit for ast2700 ++ * @addr: dma address for hardware to work ++ * ++ * Return: 32bit format of address ++ */ ++static inline u32 _make_addr(dma_addr_t addr) ++{ ++#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT ++ // In ast2700, it store higt byte[35:32] in low byte[3:0] ++ return (addr >> 32) | (u32)(addr); ++#else ++ return addr; ++#endif ++} ++ ++void ioctl_get_video_engine_config(struct VideoConfig *pVideoConfig, struct AstRVAS *pAstRVAS) ++{ ++ u32 VR004_SeqCtrl = video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL); ++ u32 VR060_ComCtrl = video_read(pAstRVAS, AST_VIDEO_COMPRESS_CTRL); ++ ++ // status ++ pVideoConfig->rs = SuccessStatus; ++ ++ pVideoConfig->engine = 0; // engine = 1 is Video Management ++ pVideoConfig->capture_format = 0; ++ pVideoConfig->compression_mode = 0; ++ ++ pVideoConfig->compression_format = (VR004_SeqCtrl >> 13) & 0x1; ++ pVideoConfig->YUV420_mode = (VR004_SeqCtrl >> 10) & 0x3; ++ pVideoConfig->AutoMode = (VR004_SeqCtrl >> 5) & 0x1; ++ ++ pVideoConfig->rc4_enable = (VR060_ComCtrl >> 5) & 0x1; ++ pVideoConfig->Visual_Lossless = (VR060_ComCtrl >> 16) & 0x1; ++ pVideoConfig->Y_JPEGTableSelector = VIDEO_GET_DCT_LUM(VR060_ComCtrl); ++ pVideoConfig->AdvanceTableSelector = (VR060_ComCtrl >> 27) & 0xf; ++} ++ ++void ioctl_set_video_engine_config(struct VideoConfig *pVideoConfig, struct AstRVAS *pAstRVAS) ++{ ++ int i, base = 0; ++ u32 ctrl = 0; //for VR004, VR204 ++ u32 compress_ctrl = 0x00080000; ++ u32 *tlb_table = pAstRVAS->vem.jpegTable.pVirt; ++ ++ // status ++ pVideoConfig->rs = SuccessStatus; ++ ++ VIDEO_ENG_DBG("\n"); ++ ++ ctrl = video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL); ++ ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) & ++ ~(G6_VIDEO_FRAME_CT_MASK | G6_VIDEO_MULTI_JPEG_MODE | G6_VIDEO_MULTI_JPEG_FLAG_MODE), AST_VIDEO_PASS_CTRL); ++ ++ ctrl &= ~VIDEO_AUTO_COMPRESS; ++ ctrl |= G5_VIDEO_COMPRESS_JPEG_MODE; ++ ctrl &= ~VIDEO_COMPRESS_FORMAT_MASK; //~(3<<10) bit 4 is set to 0 ++ ++ if (pVideoConfig->YUV420_mode) ++ ctrl |= VIDEO_COMPRESS_FORMAT(YUV420); ++ ++ if (pVideoConfig->rc4_enable) ++ compress_ctrl |= VIDEO_ENCRYP_ENABLE; ++ ++ switch (pVideoConfig->compression_mode) { ++ case 0: //DCT only ++ compress_ctrl |= VIDEO_DCT_ONLY_ENCODE; ++ break; ++ case 1: //DCT VQ mix 2-color ++ compress_ctrl &= ~(VIDEO_4COLOR_VQ_ENCODE | VIDEO_DCT_ONLY_ENCODE); ++ break; ++ case 2: //DCT VQ mix 4-color ++ compress_ctrl |= VIDEO_4COLOR_VQ_ENCODE; ++ break; ++ default: ++ dev_err(pAstRVAS->pdev, "unknown compression mode:%d\n", pVideoConfig->compression_mode); ++ break; ++ } ++ ++ if (pVideoConfig->Visual_Lossless) { ++ compress_ctrl |= VIDEO_HQ_ENABLE; ++ compress_ctrl |= VIDEO_HQ_DCT_LUM(pVideoConfig->AdvanceTableSelector); ++ compress_ctrl |= VIDEO_HQ_DCT_CHROM((pVideoConfig->AdvanceTableSelector + 16)); ++ } else { ++ compress_ctrl &= ~VIDEO_HQ_ENABLE; ++ } ++ ++ video_write(pAstRVAS, ctrl, AST_VIDEO_SEQ_CTRL); ++ // we are using chrominance quantization table instead of luminance quantization table ++ video_write(pAstRVAS, compress_ctrl | VIDEO_DCT_LUM(pVideoConfig->Y_JPEGTableSelector) | VIDEO_DCT_CHROM(pVideoConfig->Y_JPEGTableSelector + 16), AST_VIDEO_COMPRESS_CTRL); ++ VIDEO_ENG_DBG("VR04: %#X\n", video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL)); ++ VIDEO_ENG_DBG("VR60: %#X\n", video_read(pAstRVAS, AST_VIDEO_COMPRESS_CTRL)); ++ ++ // chose a table for JPEG or multi-JPEG ++ if (pVideoConfig->compression_format >= 1) { ++ VIDEO_ENG_DBG("Choose a JPEG Table\n"); ++ for (i = 0; i < 12; i++) { ++ base = (1024 * i); ++ //base = (256 * i); ++ if (pVideoConfig->YUV420_mode) //yuv420 ++ tlb_table[base + 46] = 0x00220103; //for YUV420 mode ++ else ++ tlb_table[base + 46] = 0x00110103; //for YUV444 mode) ++ } ++ } ++ ++ video_set_scaling(pAstRVAS); ++} ++ ++// ++void ioctl_get_video_engine_data_2700(struct MultiJpegConfig *pArrayMJConfig, struct AstRVAS *pAstRVAS, dma_addr_t dwPhyStreamAddress) ++{ ++ u32 yuv_shift; ++ u32 scan_lines; ++ int timeout = 0; ++ u32 x0; ++ u32 y0; ++ phys_addr_t start_addr; ++ u32 frame_count = 0; ++ u32 old_src_addr, new_src_addr; ++ u32 offset; ++ ++ pArrayMJConfig->rs = SuccessStatus; ++ ++ VIDEO_ENG_DBG("\n"); ++ VIDEO_ENG_DBG("before Stream buffer: %#llx\n", dwPhyStreamAddress); ++ //dump_buffer(dwPhyStreamAddress,100); ++ ++ video_write(pAstRVAS, _make_addr(dwPhyStreamAddress), AST_VIDEO_STREAM_BUFF); ++ ++ if (host_suspended(pAstRVAS)) { ++ pArrayMJConfig->rs = HostSuspended; ++ VIDEO_ENG_DBG("HostSuspended Timeout\n"); ++ return; ++ } ++ ++ VIDEO_ENG_DBG("irq status: %#x\n", video_read(pAstRVAS, AST_VIDEO_INT_STS)); ++ ++#ifdef AUTO_COMPRESS ++ if (video_cc_auto_trigger(pAstRVAS) == 0) { ++ VIDEO_ENG_DBG("auto Ccc Timeout\n"); ++ pArrayMJConfig->multi_jpeg_frames = 0; ++ pArrayMJConfig->rs = CompressionTimedOut; ++ return; ++ } ++#else ++ if (video_capture_trigger(pAstRVAS) == 0) { ++ dev_err(pAstRVAS->pdev, " capture timeout sts %x\n", video_read(pAstRVAS, AST_VIDEO_INT_STS)); ++ pArrayMJConfig->multi_jpeg_frames = 0; ++ pArrayMJConfig->rs = CaptureTimedOut; ++ return; ++ } ++ // clear all the interrupt since there is a bug in HW engine ast2700 ++ // only capture interrupt is enable, but compression intterupt is generated ++ video_write(pAstRVAS, VIDEO_CAPTURE_COMPLETE | VIDEO_COMPRESS_COMPLETE, AST_VIDEO_INT_STS); ++#endif ++ ++ // start compression setup ++ video_write(pAstRVAS, VIDEO_COMPRESS_COMPLETE, AST_VIDEO_INT_EN); ++ init_completion(&pAstRVAS->video_compression_complete); ++ ++ scan_lines = video_read(pAstRVAS, AST_VIDEO_SOURCE_SCAN_LINE); ++ frame_count = video_read(pAstRVAS, AST_VIDEO_COMPRESS_FRAME_COUNT_RB); ++ ++ // VR07C odd , get source from 0x4C ++ // VR07C even, get source from 0x44 ++ old_src_addr = (frame_count & 0x01) ? AST_VIDEO_SOURCE_BUFF0 : AST_VIDEO_SOURCE_BUFF1; ++ new_src_addr = (frame_count & 0x01) ? AST_VIDEO_SOURCE_BUFF1 : AST_VIDEO_SOURCE_BUFF0; ++ ++ start_addr = video_read(pAstRVAS, old_src_addr); ++ // make sure BCD is disable ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_BCD_CTRL) & ~VIDEO_BCD_CHG_EN, AST_VIDEO_BCD_CTRL); ++ ++ if (video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & VIDEO_COMPRESS_FORMAT(YUV420)) { ++ // YUV 420 ++ VIDEO_ENG_DBG("Debug: YUV420\n"); ++ yuv_shift = 4; ++ } else { ++ VIDEO_ENG_DBG("Debug: YUV444\n"); ++ yuv_shift = 3; ++ } ++ ++ //update compress window ++ video_write(pAstRVAS, ++ pArrayMJConfig->frame[0].wWidthPixels << 16 | ++ pArrayMJConfig->frame[0].wHeightPixels, AST_VIDEO_COMPRESS_WIN); ++ ++ x0 = pArrayMJConfig->frame[0].wXPixels; ++ y0 = pArrayMJConfig->frame[0].wYPixels; ++ ++ offset = (scan_lines * y0) + ((256 * x0) >> yuv_shift); ++ ++ video_write(pAstRVAS, start_addr + offset, new_src_addr); ++ VIDEO_ENG_DBG("write to %#x, with address: %#x", start_addr + offset, new_src_addr); ++ // trigger compression ++ VIDEO_ENG_DBG("trigger compression\n"); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) | ++ VIDEO_COMPRESS_TRIGGER, AST_VIDEO_SEQ_CTRL); ++ ++ timeout = wait_for_completion_interruptible_timeout(&pAstRVAS->video_compression_complete, HZ); ++ ++ if (timeout == 0) { ++ dev_err(pAstRVAS->pdev, " compression timeout sts %x\n", video_read(pAstRVAS, AST_VIDEO_INT_STS)); ++ pArrayMJConfig->multi_jpeg_frames = 0; ++ pArrayMJConfig->rs = CompressionTimedOut; ++ } ++ pArrayMJConfig->multi_jpeg_frames = 1; ++ pArrayMJConfig->frame[0].dwSizeInBytes = video_read(pAstRVAS, AST_VIDEO_JPEG_SIZE); ++ ++ VIDEO_ENG_DBG("compressed size: %d\n", pArrayMJConfig->frame[0].dwSizeInBytes); ++ ++ //clear ++ video_write(pAstRVAS, (video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & ++ ~(G5_VIDEO_COMPRESS_JPEG_MODE | VIDEO_CAPTURE_MULTI_FRAME | VIDEO_COMPRESS_TRIGGER)) ++ , AST_VIDEO_SEQ_CTRL); ++ ++ pAstRVAS->sequence++; ++ if (pAstRVAS->sequence & 0x01) { ++ video_write(pAstRVAS, _make_addr(pAstRVAS->vem.captureBuf1.phy), AST_VIDEO_SOURCE_BUFF0);//44h ++ video_write(pAstRVAS, _make_addr(pAstRVAS->vem.captureBuf0.phy), AST_VIDEO_SOURCE_BUFF1);//4Ch ++ } else { ++ video_write(pAstRVAS, _make_addr(pAstRVAS->vem.captureBuf0.phy), AST_VIDEO_SOURCE_BUFF0);//44h ++ video_write(pAstRVAS, _make_addr(pAstRVAS->vem.captureBuf1.phy), AST_VIDEO_SOURCE_BUFF1);//4Ch ++ } ++ VIDEO_ENG_DBG("[%#x]: %#x\n", AST_VIDEO_SOURCE_BUFF0, video_read(pAstRVAS, AST_VIDEO_SOURCE_BUFF0)); ++ VIDEO_ENG_DBG("[%#x]: %#x\n", AST_VIDEO_SOURCE_BUFF1, video_read(pAstRVAS, AST_VIDEO_SOURCE_BUFF1)); ++ ++ //TODO: kernel dump here... ++ //dump_buffer(dwPhyStreamAddress,100); ++} ++ ++void ioctl_get_video_engine_data(struct MultiJpegConfig *pArrayMJConfig, struct AstRVAS *pAstRVAS, phys_addr_t dwPhyStreamAddress) ++{ ++ u32 yuv_shift; ++ u32 yuv_msk; ++ u32 scan_lines; ++ int timeout = 0; ++ u32 x0; ++ u32 y0; ++ int i = 0; ++ u32 dw_w_h; ++ phys_addr_t start_addr; ++ u32 multi_jpeg_data = 0; ++ u32 VR044; ++ u32 nextFrameOffset = 0; ++ ++ pArrayMJConfig->rs = SuccessStatus; ++ ++ VIDEO_ENG_DBG("\n"); ++ VIDEO_ENG_DBG("before Stream buffer:\n"); ++ //dump_buffer(dwPhyStreamAddress,100); ++ ++ video_write(pAstRVAS, dwPhyStreamAddress, AST_VIDEO_STREAM_BUFF); ++ ++ if (host_suspended(pAstRVAS)) { ++ pArrayMJConfig->rs = HostSuspended; ++ VIDEO_ENG_DBG("HostSuspended Timeout\n"); ++ return; ++ } ++ ++ if (video_capture_trigger(pAstRVAS) == 0) { ++ pArrayMJConfig->rs = CaptureTimedOut; ++ VIDEO_ENG_DBG("Capture Timeout\n"); ++ return; ++ } ++ //dump_buffer(dwPhyStreamAddress,100); ++ // start compression setup ++ video_write(pAstRVAS, VIDEO_COMPRESS_COMPLETE, AST_VIDEO_INT_EN); ++ init_completion(&pAstRVAS->video_compression_complete); ++ VIDEO_ENG_DBG("capture complete buffer:\n"); ++ ++ //dump_buffer(vem.captureBuf0.phy,100); ++ VR044 = video_read(pAstRVAS, AST_VIDEO_SOURCE_BUFF0); ++ ++ scan_lines = video_read(pAstRVAS, AST_VIDEO_SOURCE_SCAN_LINE); ++ VIDEO_ENG_DBG("scan_lines: %#x\n", scan_lines); ++ ++ if (video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & VIDEO_COMPRESS_FORMAT(YUV420)) { ++ // YUV 420 ++ VIDEO_ENG_DBG("Debug: YUV420\n"); ++ yuv_shift = 4; ++ yuv_msk = 0xf; ++ } else { ++ // YUV 444 ++ VIDEO_ENG_DBG("Debug: YUV444\n"); ++ yuv_shift = 3; ++ yuv_msk = 0x7; ++ } ++ ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) | G6_VIDEO_MULTI_JPEG_FLAG_MODE | ++ (G6_VIDEO_JPEG__COUNT(pArrayMJConfig->multi_jpeg_frames - 1) | G6_VIDEO_MULTI_JPEG_MODE), AST_VIDEO_PASS_CTRL); ++ ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_BCD_CTRL) & ~VIDEO_BCD_CHG_EN, AST_VIDEO_BCD_CTRL); ++ ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_CTRL) | VIDEO_CTRL_ADDRESS_MAP_MULTI_JPEG, AST_VIDEO_CTRL); ++ ++ for (i = 0; i < pArrayMJConfig->multi_jpeg_frames; i++) { ++ VIDEO_ENG_DBG("Debug: Before: [%d]: x: %#x y: %#x w: %#x h: %#x\n", i, ++ pArrayMJConfig->frame[i].wXPixels, ++ pArrayMJConfig->frame[i].wYPixels, ++ pArrayMJConfig->frame[i].wWidthPixels, ++ pArrayMJConfig->frame[i].wHeightPixels); ++ x0 = pArrayMJConfig->frame[i].wXPixels; ++ y0 = pArrayMJConfig->frame[i].wYPixels; ++ dw_w_h = SET_FRAME_W_H(pArrayMJConfig->frame[i].wWidthPixels, pArrayMJConfig->frame[i].wHeightPixels); ++ ++ start_addr = VR044 + (scan_lines * y0) + ((256 * x0) / (1 << yuv_shift)); ++ ++ VIDEO_ENG_DBG("VR%x dw_w_h: %#x, VR%x : addr : %#x, x0 %d, y0 %d\n", ++ AST_VIDEO_MULTI_JPEG_SRAM + (8 * i), dw_w_h, ++ AST_VIDEO_MULTI_JPEG_SRAM + (8 * i) + 4, start_addr, x0, y0); ++ video_write(pAstRVAS, dw_w_h, AST_VIDEO_MULTI_JPEG_SRAM + (8 * i)); ++ video_write(pAstRVAS, start_addr, AST_VIDEO_MULTI_JPEG_SRAM + (8 * i) + 4); ++ } ++ ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & ~(VIDEO_CAPTURE_TRIGGER | VIDEO_COMPRESS_FORCE_IDLE | VIDEO_COMPRESS_TRIGGER), AST_VIDEO_SEQ_CTRL); ++ ++ //set mode for multi-jpeg mode VR004[5:3] ++ video_write(pAstRVAS, (video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & ~VIDEO_AUTO_COMPRESS) ++ | VIDEO_CAPTURE_MULTI_FRAME | G5_VIDEO_COMPRESS_JPEG_MODE, AST_VIDEO_SEQ_CTRL); ++ ++ //If CPU is too fast, pleas read back and trigger ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) | VIDEO_COMPRESS_TRIGGER, AST_VIDEO_SEQ_CTRL); ++ VIDEO_ENG_DBG("wait_for_completion_interruptible_timeout...\n"); ++ ++ timeout = wait_for_completion_interruptible_timeout(&pAstRVAS->video_compression_complete, HZ / 2); ++ ++ if (timeout == 0) { ++ dev_err(pAstRVAS->pdev, "multi compression timeout sts %x\n", video_read(pAstRVAS, AST_VIDEO_INT_STS)); ++ pArrayMJConfig->multi_jpeg_frames = 0; ++ pArrayMJConfig->rs = CompressionTimedOut; ++ } else { ++ VIDEO_ENG_DBG("400 %x , 404 %x\n", video_read(pAstRVAS, AST_VIDEO_MULTI_JPEG_SRAM), video_read(pAstRVAS, AST_VIDEO_MULTI_JPEG_SRAM + 4)); ++ VIDEO_ENG_DBG("408 %x , 40c %x\n", video_read(pAstRVAS, AST_VIDEO_MULTI_JPEG_SRAM + 8), video_read(pAstRVAS, AST_VIDEO_MULTI_JPEG_SRAM + 0xC)); ++ VIDEO_ENG_DBG("done reading 408\n"); ++ ++ for (i = 0; i < pArrayMJConfig->multi_jpeg_frames; i++) { ++ pArrayMJConfig->frame[i].dwOffsetInBytes = nextFrameOffset; ++ ++ multi_jpeg_data = video_read(pAstRVAS, AST_VIDEO_MULTI_JPEG_SRAM + (8 * i) + 4); ++ if (multi_jpeg_data & BIT(7)) { ++ pArrayMJConfig->frame[i].dwSizeInBytes = video_read(pAstRVAS, AST_VIDEO_MULTI_JPEG_SRAM + (8 * i)) & 0xffffff; ++ nextFrameOffset = (multi_jpeg_data & ~BIT(7)) >> 1; ++ } else { ++ pArrayMJConfig->frame[i].dwSizeInBytes = 0; ++ nextFrameOffset = 0; ++ } ++ VIDEO_ENG_DBG("[%d] size %d, dwOffsetInBytes %x\n", i, pArrayMJConfig->frame[i].dwSizeInBytes, pArrayMJConfig->frame[i].dwOffsetInBytes); ++ } //for ++ } ++ ++ video_write(pAstRVAS, (video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & ~(G5_VIDEO_COMPRESS_JPEG_MODE | VIDEO_CAPTURE_MULTI_FRAME)) ++ | VIDEO_AUTO_COMPRESS, AST_VIDEO_SEQ_CTRL); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) & ++ ~(G6_VIDEO_FRAME_CT_MASK | G6_VIDEO_MULTI_JPEG_MODE), AST_VIDEO_PASS_CTRL); ++ ++ //VIDEO_ENG_DBG("after Stream buffer:\n"); ++ //dump_buffer(dwPhyStreamAddress,100); ++} ++ ++irqreturn_t ast_video_isr(int this_irq, void *dev_id) ++{ ++ u32 status; ++ u32 enabled_irq; ++ struct AstRVAS *pAstRVAS = dev_id; ++ ++ status = video_read(pAstRVAS, AST_VIDEO_INT_STS); ++ enabled_irq = video_read(pAstRVAS, AST_VIDEO_INT_EN); ++ ++ VIDEO_ENG_DBG("sts: %#x enabled: %#x\n", status, enabled_irq); ++ ++ status &= enabled_irq; ++ if (status & VIDEO_COMPRESS_COMPLETE) { ++ video_write(pAstRVAS, VIDEO_COMPRESS_COMPLETE, AST_VIDEO_INT_STS); ++ complete(&pAstRVAS->video_compression_complete); ++ } ++ if (status & VIDEO_CAPTURE_COMPLETE) { ++ video_write(pAstRVAS, VIDEO_CAPTURE_COMPLETE, AST_VIDEO_INT_STS); ++ VIDEO_ENG_DBG("capture complete\n"); ++ complete(&pAstRVAS->video_capture_complete); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++void enable_video_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ u32 intCtrReg = video_read(pAstRVAS, AST_VIDEO_INT_EN); ++ ++ intCtrReg = (VIDEO_COMPRESS_COMPLETE | VIDEO_CAPTURE_COMPLETE); ++ video_write(pAstRVAS, intCtrReg, AST_VIDEO_INT_EN); ++} ++ ++void disable_video_interrupt(struct AstRVAS *pAstRVAS) ++{ ++ video_write(pAstRVAS, 0, AST_VIDEO_INT_EN); ++ video_write(pAstRVAS, 0xffffffff, AST_VIDEO_INT_STS); ++} ++ ++void video_engine_rc4Reset(struct AstRVAS *pAstRVAS) ++{ ++ //rc4 init reset .. ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_CTRL) | VIDEO_CTRL_RC4_RST, AST_VIDEO_CTRL); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_CTRL) & ~VIDEO_CTRL_RC4_RST, AST_VIDEO_CTRL); ++} ++ ++// setup functions ++int video_engine_reserveMem(struct AstRVAS *pAstRVAS) ++{ ++ int result = 0; ++ ++ // reserve mem ++ result = reserve_video_engine_memory(pAstRVAS); ++ if (result < 0) { ++ dev_err(pAstRVAS->pdev, "Error Reserving Video Engine Memory\n"); ++ return result; ++ } ++ return 0; ++} ++ ++int free_video_engine_memory(struct AstRVAS *pAstRVAS) ++{ ++ int size = pAstRVAS->vem.captureBuf0.size + pAstRVAS->vem.captureBuf1.size + pAstRVAS->vem.jpegTable.size; ++ ++ if (size && pAstRVAS->vem.captureBuf0.pVirt) { ++ dma_free_coherent(pAstRVAS->pdev, size, ++ pAstRVAS->vem.captureBuf0.pVirt, ++ pAstRVAS->vem.captureBuf0.phy); ++ } else { ++ return -1; ++ } ++ VIDEO_ENG_DBG("After dma_free_coherent\n"); ++ ++ return 0; ++} ++ ++// this function needs to be called when graphic mode change ++void video_set_Window(struct AstRVAS *pAstRVAS) ++{ ++ u32 scan_line; ++ u32 screenHeightAligned = ((pAstRVAS->current_vg.wScreenHeight + 0x1f) & (~0x1f)); ++ ++ VIDEO_ENG_DBG("\n"); ++ ++ //set direct mode ++ if (pAstRVAS->config->version == 7) { ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) & ~(VIDEO_AUTO_FETCH | VIDEO_DIRECT_FETCH), AST_VIDEO_PASS_CTRL); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) | VIDEO_AUTO_FETCH | VIDEO_DIRECT_FETCH, AST_VIDEO_PASS_CTRL); ++ video_write(pAstRVAS, _make_addr(get_vga_mem_base(pAstRVAS)), AST_VIDEO_DIRECT_BASE); ++ video_write(pAstRVAS, VIDEO_FETCH_TIMING(0) | VIDEO_FETCH_LINE_OFFSET(pAstRVAS->current_vg.wStride * 4), AST_VIDEO_DIRECT_CTRL); ++ } ++ //compression x,y ++ video_write(pAstRVAS, VIDEO_COMPRESS_H(pAstRVAS->current_vg.wStride) | VIDEO_COMPRESS_V(screenHeightAligned), AST_VIDEO_COMPRESS_WIN); ++ VIDEO_ENG_DBG("reg offset[%#x]: %#x\n", AST_VIDEO_COMPRESS_WIN, video_read(pAstRVAS, AST_VIDEO_COMPRESS_WIN)); ++ ++ if (pAstRVAS->current_vg.wStride == 1680) ++ video_write(pAstRVAS, VIDEO_CAPTURE_H(1728) | VIDEO_CAPTURE_V(screenHeightAligned), AST_VIDEO_CAPTURE_WIN); ++ else ++ video_write(pAstRVAS, VIDEO_CAPTURE_H(pAstRVAS->current_vg.wStride) | VIDEO_CAPTURE_V(screenHeightAligned), AST_VIDEO_CAPTURE_WIN); ++ ++ VIDEO_ENG_DBG("reg offset[%#x]: %#x\n", AST_VIDEO_CAPTURE_WIN, video_read(pAstRVAS, AST_VIDEO_CAPTURE_WIN)); ++ ++ // set scan_line VR048 ++ if ((pAstRVAS->current_vg.wStride % 8) == 0) { ++ video_write(pAstRVAS, pAstRVAS->current_vg.wStride * 4, AST_VIDEO_SOURCE_SCAN_LINE); ++ } else { ++ scan_line = pAstRVAS->current_vg.wStride; ++ scan_line = scan_line + 16 - (scan_line % 16); ++ scan_line = scan_line * 4; ++ video_write(pAstRVAS, scan_line, AST_VIDEO_SOURCE_SCAN_LINE); ++ } ++} ++ ++void set_direct_mode(struct AstRVAS *pAstRVAS) ++{ ++ int Direct_Mode = 0; ++ u32 ColorDepthIndex; ++ u32 VGA_Scratch_Register_350, VGA_Scratch_Register_354, VGA_Scratch_Register_34C, Color_Depth; ++ ++ VIDEO_ENG_DBG("\n"); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) & ~(VIDEO_AUTO_FETCH | VIDEO_DIRECT_FETCH), AST_VIDEO_PASS_CTRL); ++ ++ VGA_Scratch_Register_350 = video_read(pAstRVAS, AST_VIDEO_E_SCRATCH_350); ++ VGA_Scratch_Register_34C = video_read(pAstRVAS, AST_VIDEO_E_SCRATCH_34C); ++ VGA_Scratch_Register_354 = video_read(pAstRVAS, AST_VIDEO_E_SCRATCH_354); ++ ++ if (((VGA_Scratch_Register_350 & 0xff00) >> 8) == 0xA8) { ++ Color_Depth = ((VGA_Scratch_Register_350 & 0xff0000) >> 16); ++ ++ if (Color_Depth < 15) ++ Direct_Mode = 0; ++ else ++ Direct_Mode = 1; ++ ++ } else { //Original mode information ++ ColorDepthIndex = (VGA_Scratch_Register_34C >> 4) & 0x0F; ++ ++ if (ColorDepthIndex == 0xe || ColorDepthIndex == 0xf) { ++ Direct_Mode = 0; ++ } else { ++ if (ColorDepthIndex > 2) ++ Direct_Mode = 1; ++ else ++ Direct_Mode = 0; ++ } ++ } ++ ++ if (Direct_Mode) { ++ VIDEO_ENG_DBG("Direct Mode\n"); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) | VIDEO_AUTO_FETCH | VIDEO_DIRECT_FETCH, AST_VIDEO_PASS_CTRL); ++ video_write(pAstRVAS, _make_addr(get_vga_mem_base(pAstRVAS)), AST_VIDEO_DIRECT_BASE); ++ video_write(pAstRVAS, VIDEO_FETCH_TIMING(0) | VIDEO_FETCH_LINE_OFFSET(pAstRVAS->current_vg.wStride * 4), AST_VIDEO_DIRECT_CTRL); ++ } else { ++ VIDEO_ENG_DBG("Sync None Direct Mode\n"); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) & ~(VIDEO_AUTO_FETCH | VIDEO_DIRECT_FETCH), AST_VIDEO_PASS_CTRL); ++ } ++} ++ ++// return timeout 0 - timeout; non 0 is successful ++static int video_capture_trigger(struct AstRVAS *pAstRVAS) ++{ ++ int timeout = 0; ++ ++ VIDEO_ENG_DBG("\n"); ++ ++ // only enable capture interrupt ++ video_write(pAstRVAS, VIDEO_CAPTURE_COMPLETE, AST_VIDEO_INT_EN); ++ ++ init_completion(&pAstRVAS->video_capture_complete); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) | ++ VIDEO_SET_CAPTURE_FORMAT(1) | VIDEO_DIRECT_FETCH, AST_VIDEO_PASS_CTRL); ++ ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_BCD_CTRL) & ~VIDEO_BCD_CHG_EN, AST_VIDEO_BCD_CTRL); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & ~(VIDEO_CAPTURE_TRIGGER | VIDEO_COMPRESS_FORCE_IDLE | VIDEO_COMPRESS_TRIGGER | VIDEO_AUTO_COMPRESS), AST_VIDEO_SEQ_CTRL); ++ //If CPU is too fast, pleas read back and trigger ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) | G5_VIDEO_COMPRESS_JPEG_MODE | VIDEO_CAPTURE_TRIGGER, AST_VIDEO_SEQ_CTRL); ++ ++ timeout = wait_for_completion_interruptible_timeout(&pAstRVAS->video_capture_complete, HZ / 2); ++ ++ if (timeout == 0) ++ dev_err(pAstRVAS->pdev, "Capture timeout sts %x\n", video_read(pAstRVAS, AST_VIDEO_INT_STS)); ++ ++ //clear ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_SEQ_CTRL) & ~(VIDEO_CAPTURE_TRIGGER | VIDEO_COMPRESS_FORCE_IDLE | VIDEO_COMPRESS_TRIGGER | VIDEO_AUTO_COMPRESS), AST_VIDEO_SEQ_CTRL); ++ ++ //dump_buffer(pAstRVAS->vem.captureBuf0.phy, 1024); ++ return timeout; ++} ++ ++// ++// static functions ++// ++static u32 get_vga_mem_base(struct AstRVAS *pAstRVAS) ++{ ++ u32 vga_mem_size, mem_size; ++ ++ mem_size = pAstRVAS->FBInfo.qwDRAMSize; ++ vga_mem_size = pAstRVAS->FBInfo.dwVGASize; ++ VIDEO_ENG_DBG("VGA Info : MEM Size %dMB, VGA Mem Size %dMB\n", mem_size / 1024 / 1024, vga_mem_size / 1024 / 1024); ++ return (mem_size - vga_mem_size); ++} ++ ++static void dump_buffer(phys_addr_t dwPhyStreamAddress, u32 size) ++{ ++ u32 iC; ++ u32 val = 0; ++ ++ for (iC = 0; iC < size; iC += 4) { ++ val = readl((void *)(dwPhyStreamAddress + iC)); ++ VIDEO_ENG_DBG("%#x, ", val); ++ } ++} ++ ++static void video_set_scaling(struct AstRVAS *pAstRVAS) ++{ ++ u32 ctrl = video_read(pAstRVAS, AST_VIDEO_CTRL); ++ //no scaling ++ ctrl &= ~VIDEO_CTRL_DWN_SCALING_MASK; ++ ++ VIDEO_ENG_DBG("Scaling Disable\n"); ++ video_write(pAstRVAS, 0x00200000, AST_VIDEO_SCALING0); ++ video_write(pAstRVAS, 0x00200000, AST_VIDEO_SCALING1); ++ video_write(pAstRVAS, 0x00200000, AST_VIDEO_SCALING2); ++ video_write(pAstRVAS, 0x00200000, AST_VIDEO_SCALING3); ++ ++ video_write(pAstRVAS, 0x10001000, AST_VIDEO_SCAL_FACTOR); ++ video_write(pAstRVAS, ctrl, AST_VIDEO_CTRL); ++ ++ video_set_Window(pAstRVAS); ++} ++ ++void video_ctrl_init(struct AstRVAS *pAstRVAS) ++{ ++ u8 inputdelay = 0x4; ++ ++ VIDEO_ENG_DBG("\n"); ++ if (pAstRVAS->config->version == 7) { ++ VIDEO_ENG_DBG("reg address: 0x%llx\n", pAstRVAS->video_reg_base); ++ /* Unlock VE registers */ ++ video_write(pAstRVAS, VIDEO_PROTECT_UNLOCK, AST_VIDEO_PROTECT); ++ inputdelay = 0x1; ++ /* Clear the offset */ ++ video_write(pAstRVAS, 0, AST_VIDEO_COMPRESS_PRO); ++ video_write(pAstRVAS, 0, AST_VIDEO_COMPRESS_READ); ++ } ++ ++ /* disable interrupts */ ++ video_write(pAstRVAS, 0, AST_VIDEO_INT_EN); ++ video_write(pAstRVAS, 0xffffffff, AST_VIDEO_INT_STS); ++ video_write(pAstRVAS, 0, AST_VIDEO_BCD_CTRL); ++ ++ /*write src addr and jped addr to register*/ ++ pAstRVAS->sequence = 1; ++ video_write(pAstRVAS, _make_addr(pAstRVAS->vem.captureBuf0.phy), AST_VIDEO_SOURCE_BUFF0);//44h ++ video_write(pAstRVAS, _make_addr(pAstRVAS->vem.captureBuf1.phy), AST_VIDEO_SOURCE_BUFF1);//4Ch ++ video_write(pAstRVAS, pAstRVAS->vem.jpegTable.phy, AST_VIDEO_JPEG_HEADER_BUFF); //40h ++ video_write(pAstRVAS, 0, AST_VIDEO_COMPRESS_READ); //3Ch ++ ++ // ============================= JPEG init =========================================== ++ init_jpeg_table(pAstRVAS); ++ VIDEO_ENG_DBG("JpegTable in Memory:0x%llx\n", pAstRVAS->vem.jpegTable.pVirt); ++ //dump_buffer(pAstRVAS->vem.jpegTable.phy, 80); ++ ++ // =================================================================================== ++ //Specification define bit 12:13 must always 0; ++ video_write(pAstRVAS, (video_read(pAstRVAS, AST_VIDEO_PASS_CTRL) & ++ ~(VIDEO_DUAL_EDGE_MODE | VIDEO_18BIT_SINGLE_EDGE)) | ++ VIDEO_DVO_INPUT_DELAY(inputdelay), ++ AST_VIDEO_PASS_CTRL); ++ ++ video_write(pAstRVAS, VIDEO_STREAM_PKT_N(STREAM_32_PKTS) | ++ VIDEO_STREAM_PKT_SIZE(STREAM_128KB), AST_VIDEO_STREAM_SIZE); ++ //rc4 init reset .. ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_CTRL) | VIDEO_CTRL_RC4_RST, AST_VIDEO_CTRL); ++ video_write(pAstRVAS, video_read(pAstRVAS, AST_VIDEO_CTRL) & ~VIDEO_CTRL_RC4_RST, AST_VIDEO_CTRL); ++ ++ //CRC/REDUCE_BIT register clear ++ video_write(pAstRVAS, 0, AST_VIDEO_CRC1); ++ video_write(pAstRVAS, 0, AST_VIDEO_CRC2); ++ video_write(pAstRVAS, 0, AST_VIDEO_DATA_TRUNCA); ++ video_write(pAstRVAS, 0, AST_VIDEO_COMPRESS_READ); ++} ++ ++static int reserve_video_engine_memory(struct AstRVAS *pAstRVAS) ++{ ++ u32 size; ++ dma_addr_t phys_add = 0; ++ void *virt_add = 0; ++ ++ memset(&pAstRVAS->vem, 0, sizeof(struct VideoEngineMem)); ++ pAstRVAS->vem.captureBuf0.size = VIDEO_CAPTURE_BUFFER_SIZE; //size 10M ++ pAstRVAS->vem.captureBuf1.size = VIDEO_CAPTURE_BUFFER_SIZE; //size 10M ++ pAstRVAS->vem.jpegTable.size = VIDEO_JPEG_TABLE_SIZE; //size 1M ++ ++ size = pAstRVAS->vem.captureBuf0.size + pAstRVAS->vem.captureBuf1.size + pAstRVAS->vem.jpegTable.size; ++ VIDEO_ENG_DBG("Allocating memory size: 0x%x\n", size); ++ virt_add = dma_alloc_coherent(pAstRVAS->pdev, size, &phys_add, ++ GFP_KERNEL); ++ ++ if (!virt_add) { ++ pr_err("Cannot alloc buffer for video engine\n"); ++ return -ENOMEM; ++ } ++ ++ pAstRVAS->vem.captureBuf0.phy = phys_add; ++ pAstRVAS->vem.captureBuf1.phy = phys_add + pAstRVAS->vem.captureBuf0.size; ++ pAstRVAS->vem.jpegTable.phy = phys_add + pAstRVAS->vem.captureBuf0.size + pAstRVAS->vem.captureBuf1.size; ++ ++ pAstRVAS->vem.captureBuf0.pVirt = (void *)virt_add; ++ pAstRVAS->vem.captureBuf1.pVirt = (void *)(virt_add + pAstRVAS->vem.captureBuf0.size); ++ pAstRVAS->vem.jpegTable.pVirt = (void *)(virt_add + pAstRVAS->vem.captureBuf0.size + pAstRVAS->vem.captureBuf1.size); ++ ++ VIDEO_ENG_DBG("Allocated: phys: 0x%llx\n", phys_add); ++ VIDEO_ENG_DBG("Phy: Buf0:0x%llx; Buf1:0x%llx; jpegT:0x%llx\n", pAstRVAS->vem.captureBuf0.phy, pAstRVAS->vem.captureBuf1.phy, pAstRVAS->vem.jpegTable.phy); ++ VIDEO_ENG_DBG("Virt: Buf0:0x%llx; Buf1:0x%llx; JpegT:0x%llx\n", pAstRVAS->vem.captureBuf0.pVirt, pAstRVAS->vem.captureBuf1.pVirt, pAstRVAS->vem.jpegTable.pVirt); ++ ++ return 0; ++} ++ ++/************************************************ JPEG ***************************************************************************************/ ++static void init_jpeg_table(struct AstRVAS *pAstRVAS) ++{ ++ int i = 0; ++ int base = 0; ++ u32 *tlb_table = pAstRVAS->vem.jpegTable.pVirt; ++ ++ //JPEG header default value: ++ for (i = 0; i < 12; i++) { ++ base = (256 * i); ++ tlb_table[base + 0] = 0xE0FFD8FF; ++ tlb_table[base + 1] = 0x464A1000; ++ tlb_table[base + 2] = 0x01004649; ++ tlb_table[base + 3] = 0x60000101; ++ tlb_table[base + 4] = 0x00006000; ++ tlb_table[base + 5] = 0x0F00FEFF; ++ tlb_table[base + 6] = 0x00002D05; ++ tlb_table[base + 7] = 0x00000000; ++ tlb_table[base + 8] = 0x00000000; ++ tlb_table[base + 9] = 0x00DBFF00; ++ tlb_table[base + 44] = 0x081100C0; ++ tlb_table[base + 45] = 0x00000000; ++ tlb_table[base + 47] = 0x03011102; ++ tlb_table[base + 48] = 0xC4FF0111; ++ tlb_table[base + 49] = 0x00001F00; ++ tlb_table[base + 50] = 0x01010501; ++ tlb_table[base + 51] = 0x01010101; ++ tlb_table[base + 52] = 0x00000000; ++ tlb_table[base + 53] = 0x00000000; ++ tlb_table[base + 54] = 0x04030201; ++ tlb_table[base + 55] = 0x08070605; ++ tlb_table[base + 56] = 0xFF0B0A09; ++ tlb_table[base + 57] = 0x10B500C4; ++ tlb_table[base + 58] = 0x03010200; ++ tlb_table[base + 59] = 0x03040203; ++ tlb_table[base + 60] = 0x04040505; ++ tlb_table[base + 61] = 0x7D010000; ++ tlb_table[base + 62] = 0x00030201; ++ tlb_table[base + 63] = 0x12051104; ++ tlb_table[base + 64] = 0x06413121; ++ tlb_table[base + 65] = 0x07615113; ++ tlb_table[base + 66] = 0x32147122; ++ tlb_table[base + 67] = 0x08A19181; ++ tlb_table[base + 68] = 0xC1B14223; ++ tlb_table[base + 69] = 0xF0D15215; ++ tlb_table[base + 70] = 0x72623324; ++ tlb_table[base + 71] = 0x160A0982; ++ tlb_table[base + 72] = 0x1A191817; ++ tlb_table[base + 73] = 0x28272625; ++ tlb_table[base + 74] = 0x35342A29; ++ tlb_table[base + 75] = 0x39383736; ++ tlb_table[base + 76] = 0x4544433A; ++ tlb_table[base + 77] = 0x49484746; ++ tlb_table[base + 78] = 0x5554534A; ++ tlb_table[base + 79] = 0x59585756; ++ tlb_table[base + 80] = 0x6564635A; ++ tlb_table[base + 81] = 0x69686766; ++ tlb_table[base + 82] = 0x7574736A; ++ tlb_table[base + 83] = 0x79787776; ++ tlb_table[base + 84] = 0x8584837A; ++ tlb_table[base + 85] = 0x89888786; ++ tlb_table[base + 86] = 0x9493928A; ++ tlb_table[base + 87] = 0x98979695; ++ tlb_table[base + 88] = 0xA3A29A99; ++ tlb_table[base + 89] = 0xA7A6A5A4; ++ tlb_table[base + 90] = 0xB2AAA9A8; ++ tlb_table[base + 91] = 0xB6B5B4B3; ++ tlb_table[base + 92] = 0xBAB9B8B7; ++ tlb_table[base + 93] = 0xC5C4C3C2; ++ tlb_table[base + 94] = 0xC9C8C7C6; ++ tlb_table[base + 95] = 0xD4D3D2CA; ++ tlb_table[base + 96] = 0xD8D7D6D5; ++ tlb_table[base + 97] = 0xE2E1DAD9; ++ tlb_table[base + 98] = 0xE6E5E4E3; ++ tlb_table[base + 99] = 0xEAE9E8E7; ++ tlb_table[base + 100] = 0xF4F3F2F1; ++ tlb_table[base + 101] = 0xF8F7F6F5; ++ tlb_table[base + 102] = 0xC4FFFAF9; ++ tlb_table[base + 103] = 0x00011F00; ++ tlb_table[base + 104] = 0x01010103; ++ tlb_table[base + 105] = 0x01010101; ++ tlb_table[base + 106] = 0x00000101; ++ tlb_table[base + 107] = 0x00000000; ++ tlb_table[base + 108] = 0x04030201; ++ tlb_table[base + 109] = 0x08070605; ++ tlb_table[base + 110] = 0xFF0B0A09; ++ tlb_table[base + 111] = 0x11B500C4; ++ tlb_table[base + 112] = 0x02010200; ++ tlb_table[base + 113] = 0x04030404; ++ tlb_table[base + 114] = 0x04040507; ++ tlb_table[base + 115] = 0x77020100; ++ tlb_table[base + 116] = 0x03020100; ++ tlb_table[base + 117] = 0x21050411; ++ tlb_table[base + 118] = 0x41120631; ++ tlb_table[base + 119] = 0x71610751; ++ tlb_table[base + 120] = 0x81322213; ++ tlb_table[base + 121] = 0x91421408; ++ tlb_table[base + 122] = 0x09C1B1A1; ++ tlb_table[base + 123] = 0xF0523323; ++ tlb_table[base + 124] = 0xD1726215; ++ tlb_table[base + 125] = 0x3424160A; ++ tlb_table[base + 126] = 0x17F125E1; ++ tlb_table[base + 127] = 0x261A1918; ++ tlb_table[base + 128] = 0x2A292827; ++ tlb_table[base + 129] = 0x38373635; ++ tlb_table[base + 130] = 0x44433A39; ++ tlb_table[base + 131] = 0x48474645; ++ tlb_table[base + 132] = 0x54534A49; ++ tlb_table[base + 133] = 0x58575655; ++ tlb_table[base + 134] = 0x64635A59; ++ tlb_table[base + 135] = 0x68676665; ++ tlb_table[base + 136] = 0x74736A69; ++ tlb_table[base + 137] = 0x78777675; ++ tlb_table[base + 138] = 0x83827A79; ++ tlb_table[base + 139] = 0x87868584; ++ tlb_table[base + 140] = 0x928A8988; ++ tlb_table[base + 141] = 0x96959493; ++ tlb_table[base + 142] = 0x9A999897; ++ tlb_table[base + 143] = 0xA5A4A3A2; ++ tlb_table[base + 144] = 0xA9A8A7A6; ++ tlb_table[base + 145] = 0xB4B3B2AA; ++ tlb_table[base + 146] = 0xB8B7B6B5; ++ tlb_table[base + 147] = 0xC3C2BAB9; ++ tlb_table[base + 148] = 0xC7C6C5C4; ++ tlb_table[base + 149] = 0xD2CAC9C8; ++ tlb_table[base + 150] = 0xD6D5D4D3; ++ tlb_table[base + 151] = 0xDAD9D8D7; ++ tlb_table[base + 152] = 0xE5E4E3E2; ++ tlb_table[base + 153] = 0xE9E8E7E6; ++ tlb_table[base + 154] = 0xF4F3F2EA; ++ tlb_table[base + 155] = 0xF8F7F6F5; ++ tlb_table[base + 156] = 0xDAFFFAF9; ++ tlb_table[base + 157] = 0x01030C00; ++ tlb_table[base + 158] = 0x03110200; ++ tlb_table[base + 159] = 0x003F0011; ++ ++ //Table 0 ++ if (i == 0) { ++ tlb_table[base + 10] = 0x0D140043; ++ tlb_table[base + 11] = 0x0C0F110F; ++ tlb_table[base + 12] = 0x11101114; ++ tlb_table[base + 13] = 0x17141516; ++ tlb_table[base + 14] = 0x1E20321E; ++ tlb_table[base + 15] = 0x3D1E1B1B; ++ tlb_table[base + 16] = 0x32242E2B; ++ tlb_table[base + 17] = 0x4B4C3F48; ++ tlb_table[base + 18] = 0x44463F47; ++ tlb_table[base + 19] = 0x61735A50; ++ tlb_table[base + 20] = 0x566C5550; ++ tlb_table[base + 21] = 0x88644644; ++ tlb_table[base + 22] = 0x7A766C65; ++ tlb_table[base + 23] = 0x4D808280; ++ tlb_table[base + 24] = 0x8C978D60; ++ tlb_table[base + 25] = 0x7E73967D; ++ tlb_table[base + 26] = 0xDBFF7B80; ++ tlb_table[base + 27] = 0x1F014300; ++ tlb_table[base + 28] = 0x272D2121; ++ tlb_table[base + 29] = 0x3030582D; ++ tlb_table[base + 30] = 0x697BB958; ++ tlb_table[base + 31] = 0xB8B9B97B; ++ tlb_table[base + 32] = 0xB9B8A6A6; ++ tlb_table[base + 33] = 0xB9B9B9B9; ++ tlb_table[base + 34] = 0xB9B9B9B9; ++ tlb_table[base + 35] = 0xB9B9B9B9; ++ tlb_table[base + 36] = 0xB9B9B9B9; ++ tlb_table[base + 37] = 0xB9B9B9B9; ++ tlb_table[base + 38] = 0xB9B9B9B9; ++ tlb_table[base + 39] = 0xB9B9B9B9; ++ tlb_table[base + 40] = 0xB9B9B9B9; ++ tlb_table[base + 41] = 0xB9B9B9B9; ++ tlb_table[base + 42] = 0xB9B9B9B9; ++ tlb_table[base + 43] = 0xFFB9B9B9; ++ } ++ //Table 1 ++ if (i == 1) { ++ tlb_table[base + 10] = 0x0C110043; ++ tlb_table[base + 11] = 0x0A0D0F0D; ++ tlb_table[base + 12] = 0x0F0E0F11; ++ tlb_table[base + 13] = 0x14111213; ++ tlb_table[base + 14] = 0x1A1C2B1A; ++ tlb_table[base + 15] = 0x351A1818; ++ tlb_table[base + 16] = 0x2B1F2826; ++ tlb_table[base + 17] = 0x4142373F; ++ tlb_table[base + 18] = 0x3C3D373E; ++ tlb_table[base + 19] = 0x55644E46; ++ tlb_table[base + 20] = 0x4B5F4A46; ++ tlb_table[base + 21] = 0x77573D3C; ++ tlb_table[base + 22] = 0x6B675F58; ++ tlb_table[base + 23] = 0x43707170; ++ tlb_table[base + 24] = 0x7A847B54; ++ tlb_table[base + 25] = 0x6E64836D; ++ tlb_table[base + 26] = 0xDBFF6C70; ++ tlb_table[base + 27] = 0x1B014300; ++ tlb_table[base + 28] = 0x22271D1D; ++ tlb_table[base + 29] = 0x2A2A4C27; ++ tlb_table[base + 30] = 0x5B6BA04C; ++ tlb_table[base + 31] = 0xA0A0A06B; ++ tlb_table[base + 32] = 0xA0A0A0A0; ++ tlb_table[base + 33] = 0xA0A0A0A0; ++ tlb_table[base + 34] = 0xA0A0A0A0; ++ tlb_table[base + 35] = 0xA0A0A0A0; ++ tlb_table[base + 36] = 0xA0A0A0A0; ++ tlb_table[base + 37] = 0xA0A0A0A0; ++ tlb_table[base + 38] = 0xA0A0A0A0; ++ tlb_table[base + 39] = 0xA0A0A0A0; ++ tlb_table[base + 40] = 0xA0A0A0A0; ++ tlb_table[base + 41] = 0xA0A0A0A0; ++ tlb_table[base + 42] = 0xA0A0A0A0; ++ tlb_table[base + 43] = 0xFFA0A0A0; ++ } ++ //Table 2 ++ if (i == 2) { ++ tlb_table[base + 10] = 0x090E0043; ++ tlb_table[base + 11] = 0x090A0C0A; ++ tlb_table[base + 12] = 0x0C0B0C0E; ++ tlb_table[base + 13] = 0x110E0F10; ++ tlb_table[base + 14] = 0x15172415; ++ tlb_table[base + 15] = 0x2C151313; ++ tlb_table[base + 16] = 0x241A211F; ++ tlb_table[base + 17] = 0x36372E34; ++ tlb_table[base + 18] = 0x31322E33; ++ tlb_table[base + 19] = 0x4653413A; ++ tlb_table[base + 20] = 0x3E4E3D3A; ++ tlb_table[base + 21] = 0x62483231; ++ tlb_table[base + 22] = 0x58564E49; ++ tlb_table[base + 23] = 0x385D5E5D; ++ tlb_table[base + 24] = 0x656D6645; ++ tlb_table[base + 25] = 0x5B536C5A; ++ tlb_table[base + 26] = 0xDBFF595D; ++ tlb_table[base + 27] = 0x16014300; ++ tlb_table[base + 28] = 0x1C201818; ++ tlb_table[base + 29] = 0x22223F20; ++ tlb_table[base + 30] = 0x4B58853F; ++ tlb_table[base + 31] = 0x85858558; ++ tlb_table[base + 32] = 0x85858585; ++ tlb_table[base + 33] = 0x85858585; ++ tlb_table[base + 34] = 0x85858585; ++ tlb_table[base + 35] = 0x85858585; ++ tlb_table[base + 36] = 0x85858585; ++ tlb_table[base + 37] = 0x85858585; ++ tlb_table[base + 38] = 0x85858585; ++ tlb_table[base + 39] = 0x85858585; ++ tlb_table[base + 40] = 0x85858585; ++ tlb_table[base + 41] = 0x85858585; ++ tlb_table[base + 42] = 0x85858585; ++ tlb_table[base + 43] = 0xFF858585; ++ } ++ //Table 3 ++ if (i == 3) { ++ tlb_table[base + 10] = 0x070B0043; ++ tlb_table[base + 11] = 0x07080A08; ++ tlb_table[base + 12] = 0x0A090A0B; ++ tlb_table[base + 13] = 0x0D0B0C0C; ++ tlb_table[base + 14] = 0x11121C11; ++ tlb_table[base + 15] = 0x23110F0F; ++ tlb_table[base + 16] = 0x1C141A19; ++ tlb_table[base + 17] = 0x2B2B2429; ++ tlb_table[base + 18] = 0x27282428; ++ tlb_table[base + 19] = 0x3842332E; ++ tlb_table[base + 20] = 0x313E302E; ++ tlb_table[base + 21] = 0x4E392827; ++ tlb_table[base + 22] = 0x46443E3A; ++ tlb_table[base + 23] = 0x2C4A4A4A; ++ tlb_table[base + 24] = 0x50565137; ++ tlb_table[base + 25] = 0x48425647; ++ tlb_table[base + 26] = 0xDBFF474A; ++ tlb_table[base + 27] = 0x12014300; ++ tlb_table[base + 28] = 0x161A1313; ++ tlb_table[base + 29] = 0x1C1C331A; ++ tlb_table[base + 30] = 0x3D486C33; ++ tlb_table[base + 31] = 0x6C6C6C48; ++ tlb_table[base + 32] = 0x6C6C6C6C; ++ tlb_table[base + 33] = 0x6C6C6C6C; ++ tlb_table[base + 34] = 0x6C6C6C6C; ++ tlb_table[base + 35] = 0x6C6C6C6C; ++ tlb_table[base + 36] = 0x6C6C6C6C; ++ tlb_table[base + 37] = 0x6C6C6C6C; ++ tlb_table[base + 38] = 0x6C6C6C6C; ++ tlb_table[base + 39] = 0x6C6C6C6C; ++ tlb_table[base + 40] = 0x6C6C6C6C; ++ tlb_table[base + 41] = 0x6C6C6C6C; ++ tlb_table[base + 42] = 0x6C6C6C6C; ++ tlb_table[base + 43] = 0xFF6C6C6C; ++ } ++ //Table 4 ++ if (i == 4) { ++ tlb_table[base + 10] = 0x06090043; ++ tlb_table[base + 11] = 0x05060706; ++ tlb_table[base + 12] = 0x07070709; ++ tlb_table[base + 13] = 0x0A09090A; ++ tlb_table[base + 14] = 0x0D0E160D; ++ tlb_table[base + 15] = 0x1B0D0C0C; ++ tlb_table[base + 16] = 0x16101413; ++ tlb_table[base + 17] = 0x21221C20; ++ tlb_table[base + 18] = 0x1E1F1C20; ++ tlb_table[base + 19] = 0x2B332824; ++ tlb_table[base + 20] = 0x26302624; ++ tlb_table[base + 21] = 0x3D2D1F1E; ++ tlb_table[base + 22] = 0x3735302D; ++ tlb_table[base + 23] = 0x22393A39; ++ tlb_table[base + 24] = 0x3F443F2B; ++ tlb_table[base + 25] = 0x38334338; ++ tlb_table[base + 26] = 0xDBFF3739; ++ tlb_table[base + 27] = 0x0D014300; ++ tlb_table[base + 28] = 0x11130E0E; ++ tlb_table[base + 29] = 0x15152613; ++ tlb_table[base + 30] = 0x2D355026; ++ tlb_table[base + 31] = 0x50505035; ++ tlb_table[base + 32] = 0x50505050; ++ tlb_table[base + 33] = 0x50505050; ++ tlb_table[base + 34] = 0x50505050; ++ tlb_table[base + 35] = 0x50505050; ++ tlb_table[base + 36] = 0x50505050; ++ tlb_table[base + 37] = 0x50505050; ++ tlb_table[base + 38] = 0x50505050; ++ tlb_table[base + 39] = 0x50505050; ++ tlb_table[base + 40] = 0x50505050; ++ tlb_table[base + 41] = 0x50505050; ++ tlb_table[base + 42] = 0x50505050; ++ tlb_table[base + 43] = 0xFF505050; ++ } ++ //Table 5 ++ if (i == 5) { ++ tlb_table[base + 10] = 0x04060043; ++ tlb_table[base + 11] = 0x03040504; ++ tlb_table[base + 12] = 0x05040506; ++ tlb_table[base + 13] = 0x07060606; ++ tlb_table[base + 14] = 0x09090F09; ++ tlb_table[base + 15] = 0x12090808; ++ tlb_table[base + 16] = 0x0F0A0D0D; ++ tlb_table[base + 17] = 0x16161315; ++ tlb_table[base + 18] = 0x14151315; ++ tlb_table[base + 19] = 0x1D221B18; ++ tlb_table[base + 20] = 0x19201918; ++ tlb_table[base + 21] = 0x281E1514; ++ tlb_table[base + 22] = 0x2423201E; ++ tlb_table[base + 23] = 0x17262726; ++ tlb_table[base + 24] = 0x2A2D2A1C; ++ tlb_table[base + 25] = 0x25222D25; ++ tlb_table[base + 26] = 0xDBFF2526; ++ tlb_table[base + 27] = 0x09014300; ++ tlb_table[base + 28] = 0x0B0D0A0A; ++ tlb_table[base + 29] = 0x0E0E1A0D; ++ tlb_table[base + 30] = 0x1F25371A; ++ tlb_table[base + 31] = 0x37373725; ++ tlb_table[base + 32] = 0x37373737; ++ tlb_table[base + 33] = 0x37373737; ++ tlb_table[base + 34] = 0x37373737; ++ tlb_table[base + 35] = 0x37373737; ++ tlb_table[base + 36] = 0x37373737; ++ tlb_table[base + 37] = 0x37373737; ++ tlb_table[base + 38] = 0x37373737; ++ tlb_table[base + 39] = 0x37373737; ++ tlb_table[base + 40] = 0x37373737; ++ tlb_table[base + 41] = 0x37373737; ++ tlb_table[base + 42] = 0x37373737; ++ tlb_table[base + 43] = 0xFF373737; ++ } ++ //Table 6 ++ if (i == 6) { ++ tlb_table[base + 10] = 0x02030043; ++ tlb_table[base + 11] = 0x01020202; ++ tlb_table[base + 12] = 0x02020203; ++ tlb_table[base + 13] = 0x03030303; ++ tlb_table[base + 14] = 0x04040704; ++ tlb_table[base + 15] = 0x09040404; ++ tlb_table[base + 16] = 0x07050606; ++ tlb_table[base + 17] = 0x0B0B090A; ++ tlb_table[base + 18] = 0x0A0A090A; ++ tlb_table[base + 19] = 0x0E110D0C; ++ tlb_table[base + 20] = 0x0C100C0C; ++ tlb_table[base + 21] = 0x140F0A0A; ++ tlb_table[base + 22] = 0x1211100F; ++ tlb_table[base + 23] = 0x0B131313; ++ tlb_table[base + 24] = 0x1516150E; ++ tlb_table[base + 25] = 0x12111612; ++ tlb_table[base + 26] = 0xDBFF1213; ++ tlb_table[base + 27] = 0x04014300; ++ tlb_table[base + 28] = 0x05060505; ++ tlb_table[base + 29] = 0x07070D06; ++ tlb_table[base + 30] = 0x0F121B0D; ++ tlb_table[base + 31] = 0x1B1B1B12; ++ tlb_table[base + 32] = 0x1B1B1B1B; ++ tlb_table[base + 33] = 0x1B1B1B1B; ++ tlb_table[base + 34] = 0x1B1B1B1B; ++ tlb_table[base + 35] = 0x1B1B1B1B; ++ tlb_table[base + 36] = 0x1B1B1B1B; ++ tlb_table[base + 37] = 0x1B1B1B1B; ++ tlb_table[base + 38] = 0x1B1B1B1B; ++ tlb_table[base + 39] = 0x1B1B1B1B; ++ tlb_table[base + 40] = 0x1B1B1B1B; ++ tlb_table[base + 41] = 0x1B1B1B1B; ++ tlb_table[base + 42] = 0x1B1B1B1B; ++ tlb_table[base + 43] = 0xFF1B1B1B; ++ } ++ //Table 7 ++ if (i == 7) { ++ tlb_table[base + 10] = 0x01020043; ++ tlb_table[base + 11] = 0x01010101; ++ tlb_table[base + 12] = 0x01010102; ++ tlb_table[base + 13] = 0x02020202; ++ tlb_table[base + 14] = 0x03030503; ++ tlb_table[base + 15] = 0x06030202; ++ tlb_table[base + 16] = 0x05030404; ++ tlb_table[base + 17] = 0x07070607; ++ tlb_table[base + 18] = 0x06070607; ++ tlb_table[base + 19] = 0x090B0908; ++ tlb_table[base + 20] = 0x080A0808; ++ tlb_table[base + 21] = 0x0D0A0706; ++ tlb_table[base + 22] = 0x0C0B0A0A; ++ tlb_table[base + 23] = 0x070C0D0C; ++ tlb_table[base + 24] = 0x0E0F0E09; ++ tlb_table[base + 25] = 0x0C0B0F0C; ++ tlb_table[base + 26] = 0xDBFF0C0C; ++ tlb_table[base + 27] = 0x03014300; ++ tlb_table[base + 28] = 0x03040303; ++ tlb_table[base + 29] = 0x04040804; ++ tlb_table[base + 30] = 0x0A0C1208; ++ tlb_table[base + 31] = 0x1212120C; ++ tlb_table[base + 32] = 0x12121212; ++ tlb_table[base + 33] = 0x12121212; ++ tlb_table[base + 34] = 0x12121212; ++ tlb_table[base + 35] = 0x12121212; ++ tlb_table[base + 36] = 0x12121212; ++ tlb_table[base + 37] = 0x12121212; ++ tlb_table[base + 38] = 0x12121212; ++ tlb_table[base + 39] = 0x12121212; ++ tlb_table[base + 40] = 0x12121212; ++ tlb_table[base + 41] = 0x12121212; ++ tlb_table[base + 42] = 0x12121212; ++ tlb_table[base + 43] = 0xFF121212; ++ } ++ //Table 8 ++ if (i == 8) { ++ tlb_table[base + 10] = 0x01020043; ++ tlb_table[base + 11] = 0x01010101; ++ tlb_table[base + 12] = 0x01010102; ++ tlb_table[base + 13] = 0x02020202; ++ tlb_table[base + 14] = 0x03030503; ++ tlb_table[base + 15] = 0x06030202; ++ tlb_table[base + 16] = 0x05030404; ++ tlb_table[base + 17] = 0x07070607; ++ tlb_table[base + 18] = 0x06070607; ++ tlb_table[base + 19] = 0x090B0908; ++ tlb_table[base + 20] = 0x080A0808; ++ tlb_table[base + 21] = 0x0D0A0706; ++ tlb_table[base + 22] = 0x0C0B0A0A; ++ tlb_table[base + 23] = 0x070C0D0C; ++ tlb_table[base + 24] = 0x0E0F0E09; ++ tlb_table[base + 25] = 0x0C0B0F0C; ++ tlb_table[base + 26] = 0xDBFF0C0C; ++ tlb_table[base + 27] = 0x02014300; ++ tlb_table[base + 28] = 0x03030202; ++ tlb_table[base + 29] = 0x04040703; ++ tlb_table[base + 30] = 0x080A0F07; ++ tlb_table[base + 31] = 0x0F0F0F0A; ++ tlb_table[base + 32] = 0x0F0F0F0F; ++ tlb_table[base + 33] = 0x0F0F0F0F; ++ tlb_table[base + 34] = 0x0F0F0F0F; ++ tlb_table[base + 35] = 0x0F0F0F0F; ++ tlb_table[base + 36] = 0x0F0F0F0F; ++ tlb_table[base + 37] = 0x0F0F0F0F; ++ tlb_table[base + 38] = 0x0F0F0F0F; ++ tlb_table[base + 39] = 0x0F0F0F0F; ++ tlb_table[base + 40] = 0x0F0F0F0F; ++ tlb_table[base + 41] = 0x0F0F0F0F; ++ tlb_table[base + 42] = 0x0F0F0F0F; ++ tlb_table[base + 43] = 0xFF0F0F0F; ++ } ++ //Table 9 ++ if (i == 9) { ++ tlb_table[base + 10] = 0x01010043; ++ tlb_table[base + 11] = 0x01010101; ++ tlb_table[base + 12] = 0x01010101; ++ tlb_table[base + 13] = 0x01010101; ++ tlb_table[base + 14] = 0x02020302; ++ tlb_table[base + 15] = 0x04020202; ++ tlb_table[base + 16] = 0x03020303; ++ tlb_table[base + 17] = 0x05050405; ++ tlb_table[base + 18] = 0x05050405; ++ tlb_table[base + 19] = 0x07080606; ++ tlb_table[base + 20] = 0x06080606; ++ tlb_table[base + 21] = 0x0A070505; ++ tlb_table[base + 22] = 0x09080807; ++ tlb_table[base + 23] = 0x05090909; ++ tlb_table[base + 24] = 0x0A0B0A07; ++ tlb_table[base + 25] = 0x09080B09; ++ tlb_table[base + 26] = 0xDBFF0909; ++ tlb_table[base + 27] = 0x02014300; ++ tlb_table[base + 28] = 0x02030202; ++ tlb_table[base + 29] = 0x03030503; ++ tlb_table[base + 30] = 0x07080C05; ++ tlb_table[base + 31] = 0x0C0C0C08; ++ tlb_table[base + 32] = 0x0C0C0C0C; ++ tlb_table[base + 33] = 0x0C0C0C0C; ++ tlb_table[base + 34] = 0x0C0C0C0C; ++ tlb_table[base + 35] = 0x0C0C0C0C; ++ tlb_table[base + 36] = 0x0C0C0C0C; ++ tlb_table[base + 37] = 0x0C0C0C0C; ++ tlb_table[base + 38] = 0x0C0C0C0C; ++ tlb_table[base + 39] = 0x0C0C0C0C; ++ tlb_table[base + 40] = 0x0C0C0C0C; ++ tlb_table[base + 41] = 0x0C0C0C0C; ++ tlb_table[base + 42] = 0x0C0C0C0C; ++ tlb_table[base + 43] = 0xFF0C0C0C; ++ } ++ //Table 10 ++ if (i == 10) { ++ tlb_table[base + 10] = 0x01010043; ++ tlb_table[base + 11] = 0x01010101; ++ tlb_table[base + 12] = 0x01010101; ++ tlb_table[base + 13] = 0x01010101; ++ tlb_table[base + 14] = 0x01010201; ++ tlb_table[base + 15] = 0x03010101; ++ tlb_table[base + 16] = 0x02010202; ++ tlb_table[base + 17] = 0x03030303; ++ tlb_table[base + 18] = 0x03030303; ++ tlb_table[base + 19] = 0x04050404; ++ tlb_table[base + 20] = 0x04050404; ++ tlb_table[base + 21] = 0x06050303; ++ tlb_table[base + 22] = 0x06050505; ++ tlb_table[base + 23] = 0x03060606; ++ tlb_table[base + 24] = 0x07070704; ++ tlb_table[base + 25] = 0x06050706; ++ tlb_table[base + 26] = 0xDBFF0606; ++ tlb_table[base + 27] = 0x01014300; ++ tlb_table[base + 28] = 0x01020101; ++ tlb_table[base + 29] = 0x02020402; ++ tlb_table[base + 30] = 0x05060904; ++ tlb_table[base + 31] = 0x09090906; ++ tlb_table[base + 32] = 0x09090909; ++ tlb_table[base + 33] = 0x09090909; ++ tlb_table[base + 34] = 0x09090909; ++ tlb_table[base + 35] = 0x09090909; ++ tlb_table[base + 36] = 0x09090909; ++ tlb_table[base + 37] = 0x09090909; ++ tlb_table[base + 38] = 0x09090909; ++ tlb_table[base + 39] = 0x09090909; ++ tlb_table[base + 40] = 0x09090909; ++ tlb_table[base + 41] = 0x09090909; ++ tlb_table[base + 42] = 0x09090909; ++ tlb_table[base + 43] = 0xFF090909; ++ } ++ //Table 11 ++ if (i == 11) { ++ tlb_table[base + 10] = 0x01010043; ++ tlb_table[base + 11] = 0x01010101; ++ tlb_table[base + 12] = 0x01010101; ++ tlb_table[base + 13] = 0x01010101; ++ tlb_table[base + 14] = 0x01010101; ++ tlb_table[base + 15] = 0x01010101; ++ tlb_table[base + 16] = 0x01010101; ++ tlb_table[base + 17] = 0x01010101; ++ tlb_table[base + 18] = 0x01010101; ++ tlb_table[base + 19] = 0x02020202; ++ tlb_table[base + 20] = 0x02020202; ++ tlb_table[base + 21] = 0x03020101; ++ tlb_table[base + 22] = 0x03020202; ++ tlb_table[base + 23] = 0x01030303; ++ tlb_table[base + 24] = 0x03030302; ++ tlb_table[base + 25] = 0x03020303; ++ tlb_table[base + 26] = 0xDBFF0403; ++ tlb_table[base + 27] = 0x01014300; ++ tlb_table[base + 28] = 0x01010101; ++ tlb_table[base + 29] = 0x01010201; ++ tlb_table[base + 30] = 0x03040602; ++ tlb_table[base + 31] = 0x06060604; ++ tlb_table[base + 32] = 0x06060606; ++ tlb_table[base + 33] = 0x06060606; ++ tlb_table[base + 34] = 0x06060606; ++ tlb_table[base + 35] = 0x06060606; ++ tlb_table[base + 36] = 0x06060606; ++ tlb_table[base + 37] = 0x06060606; ++ tlb_table[base + 38] = 0x06060606; ++ tlb_table[base + 39] = 0x06060606; ++ tlb_table[base + 40] = 0x06060606; ++ tlb_table[base + 41] = 0x06060606; ++ tlb_table[base + 42] = 0x06060606; ++ tlb_table[base + 43] = 0xFF060606; ++ } ++ } ++} ++ ++static inline void ++video_write(struct AstRVAS *pAstRVAS, u32 val, u32 reg) ++{ ++ VIDEO_ENG_DBG("write offset: %x, val: %x\n", reg, val); ++ //Video is lock after reset, need always unlock ++ //unlock ++ writel(VIDEO_PROTECT_UNLOCK, pAstRVAS->video_reg_base); ++ writel(val, pAstRVAS->video_reg_base + reg); ++} ++ ++static inline u32 ++video_read(struct AstRVAS *pAstRVAS, u32 reg) ++{ ++ u32 val = readl(pAstRVAS->video_reg_base + reg); ++ ++ VIDEO_ENG_DBG("read offset: %x, val: %x\n", reg, val); ++ return val; ++} +diff --git a/drivers/soc/aspeed/rvas/video_engine.h b/drivers/soc/aspeed/rvas/video_engine.h +--- a/drivers/soc/aspeed/rvas/video_engine.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/video_engine.h 2026-09-09 15:51:44.723237996 +0000 +@@ -0,0 +1,270 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * File Name : video_engines.h ++ * Description : AST2600 video engines ++ * ++ * Copyright (C) 2019-2021 ASPEED Technology Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++#ifndef __VIDEO_ENGINE_H__ ++#define __VIDEO_ENGINE_H__ ++ ++#include "video_ioctl.h" ++#include "hardware_engines.h" ++ ++#define VIDEO_STREAM_BUFFER_SIZE (0x400000) //4M ++#define VIDEO_CAPTURE_BUFFER_SIZE (0xA00000) //10M ++#define VIDEO_JPEG_TABLE_SIZE (0x100000) //1M ++ ++#define SCU_VIDEO_ENGINE_BIT BIT(6) ++#define SCU_VIDEO_CAPTURE_STOP_CLOCK_BIT BIT(3) ++#define SCU_VIDEO_ENGINE_STOP_CLOCK_BIT BIT(1) ++/***********************************************************************/ ++/* Register for VIDEO */ ++#define AST_VIDEO_PROTECT 0x000 /* protection key register */ ++#define AST_VIDEO_SEQ_CTRL 0x004 /* Video Sequence Control register */ ++#define AST_VIDEO_PASS_CTRL 0x008 /* Video Pass 1 Control register */ ++ ++//VR008[5]=1 ++#define AST_VIDEO_DIRECT_BASE 0x00C /* Video Direct Frame buffer mode control Register VR008[5]=1 */ ++#define AST_VIDEO_DIRECT_CTRL 0x010 /* Video Direct Frame buffer mode control Register VR008[5]=1 */ ++ ++//VR008[5]=0 ++#define AST_VIDEO_TIMING_H 0x00C /* Video Timing Generation Setting Register */ ++#define AST_VIDEO_TIMING_V 0x010 /* Video Timing Generation Setting Register */ ++#define AST_VIDEO_SCAL_FACTOR 0x014 /* Video Scaling Factor Register */ ++ ++#define AST_VIDEO_SCALING0 0x018 /* Video Scaling Filter Parameter Register #0 */ ++#define AST_VIDEO_SCALING1 0x01C /* Video Scaling Filter Parameter Register #1 */ ++#define AST_VIDEO_SCALING2 0x020 /* Video Scaling Filter Parameter Register #2 */ ++#define AST_VIDEO_SCALING3 0x024 /* Video Scaling Filter Parameter Register #3 */ ++ ++#define AST_VIDEO_BCD_CTRL 0x02C /* Video BCD Control Register */ ++#define AST_VIDEO_CAPTURE_WIN 0x030 /* Video Capturing Window Setting Register */ ++#define AST_VIDEO_COMPRESS_WIN 0x034 /* Video Compression Window Setting Register */ ++ ++#define AST_VIDEO_COMPRESS_PRO 0x038 /* Video Compression Stream Buffer Processing Offset Register */ ++#define AST_VIDEO_COMPRESS_READ 0x03C /* Video Compression Stream Buffer Read Offset Register */ ++ ++#define AST_VIDEO_JPEG_HEADER_BUFF 0x040 /* Video Based Address of JPEG Header Buffer Register */ ++#define AST_VIDEO_SOURCE_BUFF0 0x044 /* Video Based Address of Video Source Buffer #1 Register */ ++#define AST_VIDEO_SOURCE_SCAN_LINE 0x048 /* Video Scan Line Offset of Video Source Buffer Register */ ++#define AST_VIDEO_SOURCE_BUFF1 0x04C /* Video Based Address of Video Source Buffer #2 Register */ ++#define AST_VIDEO_BCD_BUFF 0x050 /* Video Base Address of BCD Flag Buffer Register */ ++#define AST_VIDEO_STREAM_BUFF 0x054 /* Video Base Address of Compressed Video Stream Buffer Register */ ++#define AST_VIDEO_STREAM_SIZE 0x058 /* Video Stream Buffer Size Register */ ++ ++#define AST_VIDEO_COMPRESS_CTRL 0x060 /* Video Compression Control Register */ ++ ++#define AST_VIDEO_COMPRESS_DATA_COUNT 0x070 /* Video Total Size of Compressed Video Stream Read Back Register */ ++#define AST_VIDEO_COMPRESS_BLOCK_COUNT 0x074 /* Video Total Number of Compressed Video Block Read Back Register */ ++#define AST_VIDEO_COMPRESS_FRAME_END 0x078 /* Video Frame-end offset of compressed video stream buffer read back Register */ ++#define AST_VIDEO_COMPRESS_FRAME_COUNT_RB 0x7C ++#define AST_VIDEO_JPEG_SIZE 0x084 ++ ++#define AST_VIDEO_CTRL 0x300 /* Video Control Register */ ++#define AST_VIDEO_INT_EN 0x304 /* Video interrupt Enable */ ++#define AST_VIDEO_INT_STS 0x308 /* Video interrupt status */ ++#define AST_VIDEO_MODE_DETECT 0x30C /* Video Mode Detection Parameter Register */ ++ ++#define AST_VIDEO_CRC1 0x320 /* Primary CRC Parameter Register */ ++#define AST_VIDEO_CRC2 0x324 /* Second CRC Parameter Register */ ++#define AST_VIDEO_DATA_TRUNCA 0x328 /* Video Data Truncation Register */ ++ ++#define AST_VIDEO_E_SCRATCH_34C 0x34C /* Video Scratch Remap Read Back */ ++#define AST_VIDEO_E_SCRATCH_350 0x350 /* Video Scratch Remap Read Back */ ++#define AST_VIDEO_E_SCRATCH_354 0x354 /* Video Scratch Remap Read Back */ ++ ++//multi jpeg ++#define AST_VIDEO_ENCRYPT_SRAM 0x400 /* Video RC4/AES128 Encryption Key Register #0 ~ #63 */ ++#define AST_VIDEO_MULTI_JPEG_SRAM (AST_VIDEO_ENCRYPT_SRAM) /* Multi JPEG registers */ ++ ++#define REG_32_BIT_SZ_IN_BYTES (sizeof(u32)) ++ ++#define SET_FRAME_W_H(w, h) ((((u32)(h)) & 0x1fff) | ((((u32)(w)) & 0x1fff) << 13)) ++#define SET_FRAME_START_ADDR(addr) ((addr) & 0x7fffff80) ++ ++///////////////////////////////////////////////////////////////////////////// ++ ++/* AST_VIDEO_PROTECT: 0x000 - protection key register */ ++#define VIDEO_PROTECT_UNLOCK 0x1A038AA8 ++ ++/* AST_VIDEO_SEQ_CTRL 0x004 Video Sequence Control register */ ++#define VIDEO_HALT_ENG_STS BIT(21) ++#define VIDEO_COMPRESS_BUSY BIT(18) ++#define VIDEO_CAPTURE_BUSY BIT(16) ++#define VIDEO_HALT_ENG_TRIGGER BIT(12) ++#define VIDEO_COMPRESS_FORMAT_MASK BIT(10) ++#define VIDEO_GET_COMPRESS_FORMAT(x) (((x) >> 10) & 0x3) // 0 YUV444 ++#define VIDEO_COMPRESS_FORMAT(x) ((x) << 10) // 0 YUV444 ++#define YUV420 1 ++ ++#define G5_VIDEO_COMPRESS_JPEG_MODE BIT(13) ++#define VIDEO_YUV2RGB_DITHER_EN BIT(8) ++ ++#define VIDEO_COMPRESS_JPEG_MODE BIT(8) ++ ++//if bit 0 : 1 ++#define VIDEO_INPUT_MODE_CHG_WDT BIT(7) ++#define VIDEO_INSERT_FULL_COMPRESS BIT(6) ++#define VIDEO_AUTO_COMPRESS BIT(5) ++#define VIDEO_COMPRESS_TRIGGER BIT(4) ++#define VIDEO_CAPTURE_MULTI_FRAME BIT(3) ++#define VIDEO_COMPRESS_FORCE_IDLE BIT(2) ++#define VIDEO_CAPTURE_TRIGGER BIT(1) ++#define VIDEO_DETECT_TRIGGER BIT(0) ++ ++#define VIDEO_HALT_ENG_RB BIT(21) ++ ++#define VIDEO_ABCD_CHG_EN BIT(1) ++#define VIDEO_BCD_CHG_EN (1) ++ ++/* AST_VIDEO_PASS_CTRL 0x008 Video Pass1 Control register */ ++#define G6_VIDEO_MULTI_JPEG_FLAG_MODE BIT(31) ++#define G6_VIDEO_MULTI_JPEG_MODE BIT(30) ++#define G6_VIDEO_JPEG__COUNT(x) ((x) << 24) ++#define G6_VIDEO_FRAME_CT_MASK (0x3f << 24) ++//x * source frame rate / 60 ++#define VIDEO_FRAME_RATE_CTRL(x) ((x) << 16) ++#define VIDEO_HSYNC_POLARITY_CTRL BIT(15) ++#define VIDEO_INTERLANCE_MODE BIT(14) ++#define VIDEO_DUAL_EDGE_MODE BIT(13) //0 : Single edage ++#define VIDEO_18BIT_SINGLE_EDGE BIT(12) //0: 24bits ++#define VIDEO_DVO_INPUT_DELAY_MASK (7 << 9) ++#define VIDEO_DVO_INPUT_DELAY(x) ((x) << 9) //0 : no delay , 1: 1ns, 2: 2ns, 3:3ns, 4: inversed clock but no delay ++// if bit 5 : 0 ++#define VIDEO_HW_CURSOR_DIS BIT(8) ++// if bit 5 : 1 ++#define VIDEO_AUTO_FETCH BIT(8) // ++#define VIDEO_CAPTURE_FORMATE_MASK (3 << 6) ++ ++#define VIDEO_SET_CAPTURE_FORMAT(x) ((x) << 6) ++#define JPEG_MODE 1 ++#define RGB_MODE 2 ++#define GRAY_MODE 3 ++#define VIDEO_DIRECT_FETCH BIT(5) ++// if bit 5 : 0 ++#define VIDEO_INTERNAL_DE BIT(4) ++#define VIDEO_EXT_ADC_ATTRIBUTE BIT(3) ++ ++/* AST_VIDEO_DIRECT_CTRL 0x010 Video Direct Frame buffer mode control Register VR008[5]=1 */ ++#define VIDEO_FETCH_TIMING(x) ((x) << 16) ++#define VIDEO_FETCH_LINE_OFFSET(x) ((x) & 0xffff) ++ ++/* AST_VIDEO_CAPTURE_WIN 0x030 Video Capturing Window Setting Register */ ++#define VIDEO_CAPTURE_V(x) ((x) & 0x7ff) ++#define VIDEO_CAPTURE_H(x) (((x) & 0x7ff) << 16) ++ ++/* AST_VIDEO_COMPRESS_WIN 0x034 Video Compression Window Setting Register */ ++#define VIDEO_COMPRESS_V(x) ((x) & 0x7ff) ++#define VIDEO_GET_COMPRESS_V(x) ((x) & 0x7ff) ++#define VIDEO_COMPRESS_H(x) (((x) & 0x7ff) << 16) ++#define VIDEO_GET_COMPRESS_H(x) (((x) >> 16) & 0x7ff) ++ ++/* AST_VIDEO_STREAM_SIZE 0x058 Video Stream Buffer Size Register */ ++#define VIDEO_STREAM_PKT_N(x) ((x) << 3) ++#define STREAM_4_PKTS 0 ++#define STREAM_8_PKTS 1 ++#define STREAM_16_PKTS 2 ++#define STREAM_32_PKTS 3 ++#define STREAM_64_PKTS 4 ++#define STREAM_128_PKTS 5 ++ ++#define VIDEO_STREAM_PKT_SIZE(x) (x) ++#define STREAM_1KB 0 ++#define STREAM_2KB 1 ++#define STREAM_4KB 2 ++#define STREAM_8KB 3 ++#define STREAM_16KB 4 ++#define STREAM_32KB 5 ++#define STREAM_64KB 6 ++#define STREAM_128KB 7 ++ ++/* AST_VIDEO_COMPRESS_CTRL 0x060 Video Compression Control Register */ ++#define VIDEO_DCT_CQT_SELECTION (0xf << 6) // bit 6-9, bit 10 for which quantization is referred ++#define VIDEO_DCT_HQ_CQT_SELECTION (0xf << 27) // bit 27-30, bit 31 for which quantization is referred ++ ++#define VIDEO_HQ_DCT_LUM(x) ((x) << 27) ++#define VIDEO_GET_HQ_DCT_LUM(x) (((x) >> 27) & 0x1f) ++#define VIDEO_HQ_DCT_CHROM(x) ((x) << 22) ++#define VIDEO_GET_HQ_DCT_CHROM(x) (((x) >> 22) & 0x1f) ++#define VIDEO_HQ_DCT_MASK (0x3ff << 22) ++#define VIDEO_DCT_HUFFMAN_ENCODE(x) ((x) << 20) ++#define VIDEO_DCT_RESET BIT(17) ++#define VIDEO_HQ_ENABLE BIT(16) ++#define VIDEO_GET_HQ_ENABLE(x) (((x) >> 16) & 0x1) ++#define VIDEO_DCT_LUM(x) ((x) << 11) ++#define VIDEO_GET_DCT_LUM(x) (((x) >> 11) & 0x1f) ++#define VIDEO_DCT_CHROM(x) ((x) << 6) ++#define VIDEO_GET_DCT_CHROM(x) (((x) >> 6) & 0x1f) ++#define VIDEO_DCT_MASK (0x3ff << 6) ++#define VIDEO_ENCRYP_ENABLE BIT(5) ++#define VIDEO_COMPRESS_QUANTIZ_MODE BIT(2) ++#define VIDEO_4COLOR_VQ_ENCODE BIT(1) ++#define VIDEO_DCT_ONLY_ENCODE (1) ++#define VIDEO_DCT_VQ_MASK (0x3) ++ ++#define VIDEO_CTRL_RC4_TEST_MODE BIT(9) ++#define VIDEO_CTRL_RC4_RST BIT(8) ++ ++#define VIDEO_CTRL_ADDRESS_MAP_MULTI_JPEG (0x3 << 30) ++ ++#define VIDEO_CTRL_DWN_SCALING_MASK (0x3 << 4) ++#define VIDEO_CTRL_DWN_SCALING_ENABLE_LINE_BUFFER BIT(4) ++ ++/* AST_VIDEO_INT_EN 0x304 Video interrupt Enable */ ++/* AST_VIDEO_INT_STS 0x308 Video interrupt status */ ++#define VM_COMPRESS_COMPLETE BIT(17) ++#define VM_CAPTURE_COMPLETE BIT(16) ++ ++#define VIDEO_FRAME_COMPLETE BIT(5) ++#define VIDEO_MODE_DETECT_RDY BIT(4) ++#define VIDEO_COMPRESS_COMPLETE BIT(3) ++#define VIDEO_COMPRESS_PKT_COMPLETE BIT(2) ++#define VIDEO_CAPTURE_COMPLETE BIT(1) ++#define VIDEO_MODE_DETECT_WDT BIT(0) ++ ++/***********************************************************************/ ++struct ast_capture_mode { ++ u8 engine_idx; //set 0: engine 0, engine 1 ++ u8 differential; //set 0: full, 1:diff frame ++ u8 mode_change; //get 0: no, 1:change ++}; ++ ++struct ast_compression_mode { ++ u8 engine_idx; //set 0: engine 0, engine 1 ++ u8 mode_change; //get 0: no, 1:change ++ u32 total_size; //get ++ u32 block_count; //get ++}; ++ ++/***********************************************************************/ ++struct INTERNAL_MODE { ++ u16 HorizontalActive; ++ u16 VerticalActive; ++ u16 RefreshRateIndex; ++ u32 PixelClock; ++}; ++ ++// ioctl functions ++void ioctl_get_video_engine_config(struct VideoConfig *pVideoConfig, struct AstRVAS *pAstRVAS); ++void ioctl_set_video_engine_config(struct VideoConfig *pVideoConfig, struct AstRVAS *pAstRVAS); ++void ioctl_get_video_engine_data(struct MultiJpegConfig *pArrayMJConfig, struct AstRVAS *pAstRVAS, phys_addr_t dwPhyStreamAddress); ++void ioctl_get_video_engine_data_2700(struct MultiJpegConfig *pArrayMJConfig, struct AstRVAS *pAstRVAS, dma_addr_t dwPhyStreamAddress); ++ ++//local functions ++irqreturn_t ast_video_isr(int this_irq, void *dev_id); ++int video_engine_reserveMem(struct AstRVAS *pAstRVAS); ++void enable_video_interrupt(struct AstRVAS *pAstRVAS); ++void disable_video_interrupt(struct AstRVAS *pAstRVAS); ++void video_set_Window(struct AstRVAS *pAstRVAS); ++int free_video_engine_memory(struct AstRVAS *pAstRVAS); ++void video_ctrl_init(struct AstRVAS *pAstRVAS); ++void video_engine_rc4Reset(struct AstRVAS *pAstRVAS); ++void set_direct_mode(struct AstRVAS *pAstRVAS); ++ ++#endif // __VIDEO_ENGINE_H__ +diff --git a/drivers/soc/aspeed/rvas/video_ioctl.h b/drivers/soc/aspeed/rvas/video_ioctl.h +--- a/drivers/soc/aspeed/rvas/video_ioctl.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/video_ioctl.h 2026-09-09 15:51:44.723237996 +0000 +@@ -0,0 +1,275 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * This file is part of the ASPEED Linux Device Driver for ASPEED Baseboard Management Controller. ++ * Refer to the README file included with this package for driver version and adapter compatibility. ++ * ++ * Copyright (C) 2019-2021 ASPEED Technology Inc. All rights reserved. ++ * ++ */ ++ ++#ifndef _VIDEO_IOCTL_H ++#define _VIDEO_IOCTL_H ++ ++#include ++ ++#define RVAS_MAGIC ('b') ++#define CMD_IOCTL_TURN_LOCAL_MONITOR_ON _IOR(RVAS_MAGIC, IOCTL_TURN_LOCAL_MONITOR_ON, struct RvasIoctl) ++#define CMD_IOCTL_TURN_LOCAL_MONITOR_OFF _IOR(RVAS_MAGIC, IOCTL_TURN_LOCAL_MONITOR_OFF, struct RvasIoctl) ++#define CMD_IOCTL_IS_LOCAL_MONITOR_ENABLED _IOR(RVAS_MAGIC, IOCTL_IS_LOCAL_MONITOR_ENABLED, struct RvasIoctl) ++#define CMD_IOCTL_GET_VIDEO_GEOMETRY _IOWR(RVAS_MAGIC, IOCTL_GET_VIDEO_GEOMETRY, struct RvasIoctl) ++#define CMD_IOCTL_WAIT_FOR_VIDEO_EVENT _IOWR(RVAS_MAGIC, IOCTL_WAIT_FOR_VIDEO_EVENT, struct RvasIoctl) ++#define CMD_IOCTL_GET_GRC_REGIESTERS _IOWR(RVAS_MAGIC, IOCTL_GET_GRC_REGIESTERS, struct RvasIoctl) ++#define CMD_IOCTL_READ_SNOOP_MAP _IOWR(RVAS_MAGIC, IOCTL_READ_SNOOP_MAP, struct RvasIoctl) ++#define CMD_IOCTL_READ_SNOOP_AGGREGATE _IOWR(RVAS_MAGIC, IOCTL_READ_SNOOP_AGGREGATE, struct RvasIoctl) ++#define CMD_IOCTL_FETCH_VIDEO_TILES _IOWR(RVAS_MAGIC, IOCTL_FETCH_VIDEO_TILES, struct RvasIoctl) ++#define CMD_IOCTL_FETCH_VIDEO_SLICES _IOWR(RVAS_MAGIC, IOCTL_FETCH_VIDEO_SLICES, struct RvasIoctl) ++#define CMD_IOCTL_RUN_LENGTH_ENCODE_DATA _IOWR(RVAS_MAGIC, IOCTL_RUN_LENGTH_ENCODE_DATA, struct RvasIoctl) ++#define CMD_IOCTL_FETCH_TEXT_DATA _IOWR(RVAS_MAGIC, IOCTL_FETCH_TEXT_DATA, struct RvasIoctl) ++#define CMD_IOCTL_FETCH_MODE13_DATA _IOWR(RVAS_MAGIC, IOCTL_FETCH_MODE13_DATA, struct RvasIoctl) ++#define CMD_IOCTL_NEW_CONTEXT _IOWR(RVAS_MAGIC, IOCTL_NEW_CONTEXT, struct RvasIoctl) ++#define CMD_IOCTL_DEL_CONTEXT _IOWR(RVAS_MAGIC, IOCTL_DEL_CONTEXT, struct RvasIoctl) ++#define CMD_IOCTL_ALLOC _IOWR(RVAS_MAGIC, IOCTL_ALLOC, struct RvasIoctl) ++#define CMD_IOCTL_FREE _IOWR(RVAS_MAGIC, IOCTL_FREE, struct RvasIoctl) ++#define CMD_IOCTL_SET_TSE_COUNTER _IOWR(RVAS_MAGIC, IOCTL_SET_TSE_COUNTER, struct RvasIoctl) ++#define CMD_IOCTL_GET_TSE_COUNTER _IOWR(RVAS_MAGIC, IOCTL_GET_TSE_COUNTER, struct RvasIoctl) ++#define CMD_IOCTL_VIDEO_ENGINE_RESET _IOWR(RVAS_MAGIC, IOCTL_VIDEO_ENGINE_RESET, struct RvasIoctl) ++//jpeg ++#define CMD_IOCTL_SET_VIDEO_ENGINE_CONFIG _IOW(RVAS_MAGIC, IOCTL_SET_VIDEO_ENGINE_CONFIG, struct VideoConfig*) ++#define CMD_IOCTL_GET_VIDEO_ENGINE_CONFIG _IOW(RVAS_MAGIC, IOCTL_GET_VIDEO_ENGINE_CONFIG, struct VideoConfig*) ++#define CMD_IOCTL_GET_VIDEO_ENGINE_DATA _IOWR(RVAS_MAGIC, IOCTL_GET_VIDEO_ENGINE_DATA, struct MultiJpegConfig*) ++ ++enum HARD_WARE_ENGINE_IOCTL { ++ IOCTL_TURN_LOCAL_MONITOR_ON = 20, //REMOTE VIDEO GENERAL IOCTL ++ IOCTL_TURN_LOCAL_MONITOR_OFF, ++ IOCTL_IS_LOCAL_MONITOR_ENABLED, ++ ++ IOCTL_GET_VIDEO_GEOMETRY = 40, // REMOTE VIDEO ++ IOCTL_WAIT_FOR_VIDEO_EVENT, ++ IOCTL_GET_GRC_REGIESTERS, ++ IOCTL_READ_SNOOP_MAP, ++ IOCTL_READ_SNOOP_AGGREGATE, ++ IOCTL_FETCH_VIDEO_TILES, ++ IOCTL_FETCH_VIDEO_SLICES, ++ IOCTL_RUN_LENGTH_ENCODE_DATA, ++ IOCTL_FETCH_TEXT_DATA, ++ IOCTL_FETCH_MODE13_DATA, ++ IOCTL_NEW_CONTEXT, ++ IOCTL_DEL_CONTEXT, ++ IOCTL_ALLOC, ++ IOCTL_FREE, ++ IOCTL_SET_TSE_COUNTER, ++ IOCTL_GET_TSE_COUNTER, ++ IOCTL_VIDEO_ENGINE_RESET, ++ IOCTL_SET_VIDEO_ENGINE_CONFIG, ++ IOCTL_GET_VIDEO_ENGINE_CONFIG, ++ IOCTL_GET_VIDEO_ENGINE_DATA, ++}; ++ ++enum GraphicsModeType { ++ InvalidMode = 0, TextMode = 1, VGAGraphicsMode = 2, AGAGraphicsMode = 3 ++}; ++ ++enum RVASStatus { ++ SuccessStatus = 0, ++ GenericError = 1, ++ MemoryAllocError = 2, ++ InvalidMemoryHandle = 3, ++ CannotMapMemory = 4, ++ CannotUnMapMemory = 5, ++ TimedOut = 6, ++ InvalidContextHandle = 7, ++ CaptureTimedOut = 8, ++ CompressionTimedOut = 9, ++ HostSuspended ++}; ++ ++enum SelectedByteMode { ++ AllBytesMode = 0, ++ SkipMode = 1, ++ PlanarToPackedMode, ++ PackedToPackedMode, ++ LowByteMode, ++ MiddleByteMode, ++ TopByteMode ++}; ++ ++enum DataProccessMode { ++ NormalTileMode = 0, ++ FourBitPlanarMode = 1, ++ FourBitPackedMode = 2, ++ AttrMode = 3, ++ AsciiOnlyMode = 4, ++ FontFetchMode = 5, ++ SplitByteMode = 6 ++}; ++ ++enum ResetEngineMode { ++ ResetAll = 0, ++ ResetRvasEngine = 1, ++ ResetVeEngine = 2 ++}; ++ ++struct VideoGeometry { ++ u16 wScreenWidth; ++ u16 wScreenHeight; ++ u16 wStride; ++ u8 byBitsPerPixel; ++ u8 byModeID; ++ enum GraphicsModeType gmt; ++}; ++ ++struct EventMap { ++ u32 bPaletteChanged :1; ++ u32 bATTRChanged :1; ++ u32 bSEQChanged :1; ++ u32 bGCTLChanged :1; ++ u32 bCRTCChanged :1; ++ u32 bCRTCEXTChanged :1; ++ u32 bPLTRAMChanged :1; ++ u32 bXCURCOLChanged :1; ++ u32 bXCURCTLChanged :1; ++ u32 bXCURPOSChanged :1; ++ u32 bDoorbellA :1; ++ u32 bDoorbellB :1; ++ u32 bGeometryChanged :1; ++ u32 bSnoopChanged :1; ++ u32 bTextFontChanged :1; ++ u32 bTextATTRChanged :1; ++ u32 bTextASCIIChanged :1; ++}; ++ ++struct FetchMap { ++ //in parameters ++ bool bEnableRLE; ++ u8 bTextAlignDouble; // 0 - 8 byte, 1 - 16 byte ++ u8 byRLETripletCode; ++ u8 byRLERepeatCode; ++ enum DataProccessMode dpm; ++ //out parameters ++ u32 dwFetchSize; ++ u32 dwFetchRLESize; ++ u32 dwCheckSum; ++ bool bRLEFailed; ++ u8 rsvd[3]; ++}; ++ ++struct SnoopAggregate { ++ u64 qwRow; ++ u64 qwCol; ++}; ++ ++struct FetchRegion { ++ u16 wTopY; ++ u16 wLeftX; ++ u16 wBottomY; ++ u16 wRightX; ++}; ++ ++struct FetchOperation { ++ struct FetchRegion fr; ++ enum SelectedByteMode sbm; ++ u32 dwFetchSize; ++ u32 dwFetchRLESize; ++ u32 dwCheckSum; ++ bool bRLEFailed; ++ bool bEnableRLE; ++ u8 byRLETripletCode; ++ u8 byRLERepeatCode; ++ u8 byVGATextAlignment; //0-8bytes, 1-16bytes. ++ u8 rsvd[3]; ++}; ++ ++struct FetchVideoTilesArg { ++ struct VideoGeometry vg; ++ u32 dwTotalOutputSize; ++ u32 cfo; ++ struct FetchOperation pfo[4]; ++}; ++ ++struct FetchVideoSlicesArg { ++ struct VideoGeometry vg; ++ u32 dwSlicedSize; ++ u32 dwSlicedRLESize; ++ u32 dwCheckSum; ++ bool bEnableRLE; ++ bool bRLEFailed; ++ u8 byRLETripletCode; ++ u8 byRLERepeatCode; ++ u8 cBuckets; ++ u8 rsvd[3]; ++ u8 abyBitIndexes[24]; ++ u32 cfr; ++ struct FetchRegion pfr[4]; ++}; ++ ++struct RVASBuffer { ++ void *pv; ++ size_t cb; ++}; ++ ++struct RvasIoctl { ++ enum RVASStatus rs; ++ void *rc; ++ struct RVASBuffer rvb; ++ void *rmh; ++ void *rmh1; ++ void *rmh2; ++ u32 rmh_mem_size; ++ u32 rmh1_mem_size; ++ u32 rmh2_mem_size; ++ struct VideoGeometry vg; ++ struct EventMap em; ++ struct SnoopAggregate sa; ++ union { ++ u32 tse_counter; ++ u32 req_mem_size; ++ u32 encode; ++ u32 time_out; ++ }; ++ u32 rle_len; // RLE Length ++ u32 rle_checksum; ++ struct FetchMap tfm; ++ u8 flag; ++ u8 lms; ++ u8 resetMode; ++ u8 rsvd; ++}; ++ ++// ++// Video Engine ++// ++ ++#define MAX_MULTI_FRAME_CT (32) ++ ++struct VideoConfig { ++ u8 engine; //0: engine 0 - normal engine, engine 1 - VM legacy engine ++ u8 compression_mode; //0:DCT, 1:DCT_VQ mix VQ-2 color, 2:DCT_VQ mix VQ-4 color 9: ++ u8 compression_format; //0:ASPEED 1:JPEG ++ u8 capture_format; //0:CCIR601-2 YUV, 1:JPEG YUV, 2:RGB for ASPEED mode only, 3:Gray ++ u8 rc4_enable; //0:disable 1:enable ++ u8 YUV420_mode; //0:YUV444, 1:YUV420 ++ u8 Visual_Lossless; ++ u8 Y_JPEGTableSelector; ++ u8 AdvanceTableSelector; ++ u8 AutoMode; ++ u8 rsvd[2]; ++ enum RVASStatus rs; ++}; ++ ++struct MultiJpegFrame { ++ u32 dwSizeInBytes; // Image size in bytes ++ u32 dwOffsetInBytes; // Offset in bytes ++ u16 wXPixels; // In: X coordinate ++ u16 wYPixels; // In: Y coordinate ++ u16 wWidthPixels; // In: Width for Fetch ++ u16 wHeightPixels; // In: Height for Fetch ++}; ++ ++struct MultiJpegConfig { ++ unsigned char multi_jpeg_frames; // frame count ++ struct MultiJpegFrame frame[MAX_MULTI_FRAME_CT]; // The Multi Frames ++ void *aStreamHandle; ++ enum RVASStatus rs; ++}; ++ ++#endif // _VIDEO_IOCTL_H +diff --git a/drivers/soc/aspeed/rvas/video_main.c b/drivers/soc/aspeed/rvas/video_main.c +--- a/drivers/soc/aspeed/rvas/video_main.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/soc/aspeed/rvas/video_main.c 2026-09-09 15:51:44.723237996 +0000 +@@ -0,0 +1,1866 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * File Name : video_main.c ++ * Description : AST2600 RVAS hardware engines ++ * ++ * Copyright (C) ASPEED Technology Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "video_ioctl.h" ++#include "hardware_engines.h" ++#include "video.h" ++#include "video_debug.h" ++#include "video_engine.h" ++ ++#define TEST_GRCE_DETECT_RESOLUTION_CHG ++ ++static long video_ioctl(struct file *file, unsigned int cmd, unsigned long arg); ++static int video_open(struct inode *inode, struct file *file); ++static int video_release(struct inode *inode, struct file *file); ++static irqreturn_t fge_handler(int irq, void *dev_id); ++static void video_off(struct AstRVAS *pAstRVAS); ++static void video_on(struct AstRVAS *pAstRVAS); ++ ++static void video_os_init_sleep_struct(struct Video_OsSleepStruct *Sleep); ++static void video_ss_wakeup_on_timeout(struct Video_OsSleepStruct *Sleep); ++static void enable_rvas_engines(struct AstRVAS *pAstRVAS); ++static void video_engine_init(struct AstRVAS *pAstRVAS); ++static void rvas_init(struct AstRVAS *pAstRVAS); ++static void reset_rvas_engine(struct AstRVAS *pAstRVAS); ++static void reset_video_engine(struct AstRVAS *pAstRVAS); ++static void set_FBInfo_size(struct AstRVAS *pAstRVAS, void __iomem *mcr_base); ++ ++static long video_os_sleep_on_timeout(struct Video_OsSleepStruct *Sleep, u8 *Var, long msecs); ++ ++static struct AstRVAS *file_ast_rvas(struct file *file) ++{ ++ return container_of(file->private_data, struct AstRVAS, rvas_dev); ++} ++ ++static long video_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ int iResult = 0; ++ struct RvasIoctl ri; ++ struct VideoConfig video_config; ++ struct MultiJpegConfig multi_jpeg; ++ u8 bVideoCmd = 0; ++ dma_addr_t dw_phys = 0; ++ struct AstRVAS *pAstRVAS = file_ast_rvas(file); ++ ++ VIDEO_DBG("Start\n"); ++ VIDEO_DBG("pAstRVAS: 0x%p\n", pAstRVAS); ++ memset(&ri, 0, sizeof(ri)); ++ ++ if (cmd != CMD_IOCTL_SET_VIDEO_ENGINE_CONFIG && ++ cmd != CMD_IOCTL_GET_VIDEO_ENGINE_CONFIG && ++ cmd != CMD_IOCTL_GET_VIDEO_ENGINE_DATA) { ++ if (raw_copy_from_user(&ri, (void *)arg, sizeof(struct RvasIoctl))) { ++ dev_err(pAstRVAS->pdev, "Copy from user buffer Failed\n"); ++ return -EINVAL; ++ } ++ ++ ri.rs = SuccessStatus; ++ bVideoCmd = 0; ++ } else { ++ bVideoCmd = 1; ++ } ++ ++ VIDEO_DBG(" Command = 0x%x\n", cmd); ++ ++ switch (cmd) { ++ case CMD_IOCTL_TURN_LOCAL_MONITOR_ON: ++ if (pAstRVAS->config->version == 7) ++ ioctl_update_lms_2700(0x1, pAstRVAS); ++ else ++ ioctl_update_lms(0x1, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_TURN_LOCAL_MONITOR_OFF: ++ if (pAstRVAS->config->version == 7) ++ ioctl_update_lms_2700(0x0, pAstRVAS); ++ else ++ ioctl_update_lms(0x0, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_IS_LOCAL_MONITOR_ENABLED: ++ u32 status; ++ ++ if (pAstRVAS->config->version == 7) ++ status = ioctl_get_lm_status_2700(pAstRVAS); ++ else ++ status = ioctl_get_lm_status(pAstRVAS); ++ ++ if (status) ++ ri.lms = 0x1; ++ else ++ ri.lms = 0x0; ++ break; ++ ++ case CMD_IOCTL_GET_VIDEO_GEOMETRY: ++ VIDEO_DBG(" Command CMD_IOCTL_GET_VIDEO_GEOMETRY\n"); ++ ioctl_get_video_geometry(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_WAIT_FOR_VIDEO_EVENT: ++ VIDEO_DBG(" Command CMD_IOCTL_WAIT_FOR_VIDEO_EVENT\n"); ++ ioctl_wait_for_video_event(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_GET_GRC_REGIESTERS: ++ VIDEO_DBG(" Command CMD_IOCTL_GET_GRC_REGIESTERS\n"); ++ ioctl_get_grc_register(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_READ_SNOOP_MAP: ++ VIDEO_DBG(" Command CMD_IOCTL_READ_SNOOP_MAP\n"); ++ ioctl_read_snoop_map(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_READ_SNOOP_AGGREGATE: ++ VIDEO_DBG(" Command CMD_IOCTL_READ_SNOOP_AGGREGATE\n"); ++ ioctl_read_snoop_aggregate(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_FETCH_VIDEO_TILES: /// ++ VIDEO_DBG("CMD_IOCTL_FETCH_VIDEO_TILES\n"); ++ ioctl_fetch_video_tiles(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_FETCH_VIDEO_SLICES: ++ VIDEO_DBG(" Command CMD_IOCTL_FETCH_VIDEO_SLICES\n"); ++ ioctl_fetch_video_slices(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_RUN_LENGTH_ENCODE_DATA: ++ VIDEO_DBG(" Command CMD_IOCTL_RUN_LENGTH_ENCODE_DATA\n"); ++ ioctl_run_length_encode_data(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_FETCH_TEXT_DATA: ++ VIDEO_DBG(" Command CMD_IOCTL_FETCH_TEXT_DATA\n"); ++ ioctl_fetch_text_data(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_FETCH_MODE13_DATA: ++ VIDEO_DBG(" Command CMD_IOCTL_FETCH_MODE13_DATA\n"); ++ ioctl_fetch_mode_13_data(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_ALLOC: ++ VIDEO_DBG(" Command CMD_IOCTL_ALLOC\n"); ++ ioctl_alloc(file, &ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_FREE: ++ VIDEO_DBG(" Command CMD_IOCTL_FREE\n"); ++ ioctl_free(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_NEW_CONTEXT: ++ VIDEO_DBG(" Command CMD_IOCTL_NEW_CONTEXT\n"); ++ ioctl_new_context(file, &ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_DEL_CONTEXT: ++ VIDEO_DBG(" Command CMD_IOCTL_DEL_CONTEXT\n"); ++ ioctl_delete_context(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_SET_TSE_COUNTER: ++ VIDEO_DBG(" Command CMD_IOCTL_SET_TSE_COUNTER\n"); ++ ioctl_set_tse_tsicr(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_GET_TSE_COUNTER: ++ VIDEO_DBG(" Command CMD_IOCTL_GET_TSE_COUNTER\n"); ++ ioctl_get_tse_tsicr(&ri, pAstRVAS); ++ break; ++ ++ case CMD_IOCTL_VIDEO_ENGINE_RESET: ++ VIDEO_ENG_DBG(" Command CMD_IOCTL_VIDEO_ENGINE_RESET\n"); ++ ioctl_reset_video_engine(&ri, pAstRVAS); ++ break; ++ case CMD_IOCTL_GET_VIDEO_ENGINE_CONFIG: ++ VIDEO_DBG(" Command CMD_IOCTL_GET_VIDEO_ENGINE_CONFIG\n"); ++ ioctl_get_video_engine_config(&video_config, pAstRVAS); ++ ++ iResult = raw_copy_to_user((void *)arg, &video_config, sizeof(video_config)); ++ break; ++ case CMD_IOCTL_SET_VIDEO_ENGINE_CONFIG: ++ VIDEO_DBG(" Command CMD_IOCTL_SET_VIDEO_ENGINE_CONFIG\n"); ++ iResult = raw_copy_from_user(&video_config, (void *)arg, sizeof(video_config)); ++ ++ ioctl_set_video_engine_config(&video_config, pAstRVAS); ++ break; ++ case CMD_IOCTL_GET_VIDEO_ENGINE_DATA: ++ VIDEO_DBG(" Command CMD_IOCTL_GET_VIDEO_ENGINE_DATA\n"); ++ iResult = raw_copy_from_user(&multi_jpeg, (void *)arg, sizeof(multi_jpeg)); ++ dw_phys = get_phys_add_rsvd_mem((u32)multi_jpeg.aStreamHandle, pAstRVAS); ++ VIDEO_DBG("physical stream address: %#llx\n", dw_phys); ++ ++ if (dw_phys == 0) { ++ dev_err(pAstRVAS->pdev, "Error of getting stream buffer address\n"); ++ } else { ++ if (pAstRVAS->config->version == 7) ++ ioctl_get_video_engine_data_2700(&multi_jpeg, pAstRVAS, dw_phys); ++ else ++ ioctl_get_video_engine_data(&multi_jpeg, pAstRVAS, dw_phys); ++ } ++ ++ iResult = raw_copy_to_user((void *)arg, &multi_jpeg, sizeof(multi_jpeg)); ++ break; ++ default: ++ dev_err(pAstRVAS->pdev, "Unknown Ioctl: %#x\n", cmd); ++ iResult = -EINVAL; ++ break; ++ } ++ ++ if (!iResult && !bVideoCmd) ++ if (raw_copy_to_user((void *)arg, &ri, sizeof(struct RvasIoctl))) { ++ dev_err(pAstRVAS->pdev, "Copy to user buffer Failed\n"); ++ iResult = -EINVAL; ++ } ++ ++ return iResult; ++} ++ ++phys_addr_t get_phy_fb_start_address(struct AstRVAS *pAstRVAS) ++{ ++ u32 dw_offset = get_screen_offset(pAstRVAS); ++ ++ pAstRVAS->FBInfo.qwFBPhysStart = (pAstRVAS->config->version == 7) ++ ? DDR_BASE_27 ++ : DDR_BASE; ++ pAstRVAS->FBInfo.qwFBPhysStart += pAstRVAS->FBInfo.qwDRAMSize - pAstRVAS->FBInfo.dwVGASize + dw_offset; ++ if (pAstRVAS->rvas_index == 1) ++ pAstRVAS->FBInfo.qwFBPhysStart -= pAstRVAS->FBInfo.dwVGASize; ++ ++ HW_ENG_DBG("Frame buffer start address: %#llx, dram size: %#x, vga size: %#x\n", ++ pAstRVAS->FBInfo.qwFBPhysStart, ++ pAstRVAS->FBInfo.qwDRAMSize, ++ pAstRVAS->FBInfo.dwVGASize); ++ ++ return pAstRVAS->FBInfo.qwFBPhysStart; ++} ++ ++static int video_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ size_t size; ++ u32 dw_index; ++ u8 found = 0; ++ struct AstRVAS *pAstRVAS = file_ast_rvas(file); ++ ++ struct MemoryMapTable **pmmt = pAstRVAS->ppmmtMemoryTable; ++ ++ size = vma->vm_end - vma->vm_start; ++ vma->vm_private_data = pAstRVAS; ++ VIDEO_DBG("vma->vm_start 0x%lx, vma->vm_end 0x%lx, vma->vm_pgoff=0x%llx\n", ++ vma->vm_start, ++ vma->vm_end, ++ vma->vm_pgoff); ++ VIDEO_DBG("(vma->vm_pgoff << PAGE_SHIFT) = 0x%llx\n", (vma->vm_pgoff << PAGE_SHIFT)); ++ for (dw_index = 0; dw_index < MAX_NUM_MEM_TBL; ++dw_index) { ++ if (pmmt[dw_index]) { ++ VIDEO_DBG("index %d, phys_addr=0x%llx, virt_addr=%p, length=0x%x\n", ++ dw_index, ++ pmmt[dw_index]->mem_phys, ++ pmmt[dw_index]->pvVirtualAddr, ++ pmmt[dw_index]->dwLength); ++ if ((vma->vm_pgoff << PAGE_SHIFT) == pmmt[dw_index]->mem_phys) { ++ found = 1; ++ if (size > pmmt[dw_index]->dwLength) { ++ pr_err("required size exceed alloc size\n"); ++ return -EAGAIN; ++ } ++ break; ++ } ++ } ++ } ++ if (!found) { ++ pr_err("no match mem entry\n"); ++ return -EAGAIN; ++ } ++ ++ vm_flags_set(vma, VM_IO); ++ if (pAstRVAS->config->version == 7) ++ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); ++ else ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ ++ if (io_remap_pfn_range(vma, vma->vm_start, ++ ((u32)vma->vm_pgoff), size, ++ vma->vm_page_prot)) { ++ pr_err("remap_pfn_range fail at %s()\n", __func__); ++ return -EAGAIN; ++ } ++ ++ return 0; ++} ++ ++static int video_open(struct inode *pin, struct file *file) ++{ ++ struct AstRVAS *pAstRVAS = file_ast_rvas(file); ++ ++ VIDEO_DBG("\n"); ++ ++ // make sure the rvas clk is running. ++ // if it's already enabled, clk_enable will just return. ++ clk_enable(pAstRVAS->rvasclk); ++ ++ return 0; ++} ++ ++void free_all_mem_entries(struct AstRVAS *pAstRVAS) ++{ ++ u32 dw_index; ++ struct MemoryMapTable **pmmt = pAstRVAS->ppmmtMemoryTable; ++ void *virt_add; ++ dma_addr_t dw_phys; ++ u32 len; ++ ++ VIDEO_DBG("Removing mem map entries...\n"); ++ for (dw_index = 0; dw_index < MAX_NUM_MEM_TBL; ++dw_index) { ++ if (pmmt[dw_index]) { ++ if (pmmt[dw_index]->mem_phys) { ++ virt_add = get_virt_add_rsvd_mem(dw_index, pAstRVAS); ++ dw_phys = get_phys_add_rsvd_mem(dw_index, pAstRVAS); ++ len = get_len_rsvd_mem(dw_index, pAstRVAS); ++ dma_free_coherent(pAstRVAS->pdev, len, virt_add, dw_phys); ++ } ++ pmmt[dw_index]->pf = NULL; ++ kfree(pmmt[dw_index]); ++ pmmt[dw_index] = NULL; ++ } ++ } ++} ++ ++static int video_release(struct inode *inode, struct file *file) ++{ ++ u32 dw_index; ++ struct AstRVAS *pAstRVAS = file_ast_rvas(file); ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ ++ VIDEO_DBG("Start\n"); ++ ++ free_all_mem_entries(pAstRVAS); ++ ++ VIDEO_DBG("ppctContextTable: 0x%p\n", ppctContextTable); ++ ++ disable_grce_tse_interrupt(pAstRVAS); ++ ++ for (dw_index = 0; dw_index < MAX_NUM_CONTEXT; ++dw_index) { ++ if (ppctContextTable[dw_index]) { ++ VIDEO_DBG("Releasing Context dw_index: %u\n", dw_index); ++ kfree(ppctContextTable[dw_index]); ++ ppctContextTable[dw_index] = NULL; ++ } ++ } ++ enable_grce_tse_interrupt(pAstRVAS); ++ VIDEO_DBG("End\n"); ++ ++ return 0; ++} ++ ++static struct file_operations video_module_ops = { .compat_ioctl = video_ioctl, ++ .unlocked_ioctl = video_ioctl, .open = video_open, .release = ++ video_release, .mmap = video_mmap, .owner = THIS_MODULE, }; ++ ++static struct miscdevice video_misc = { .minor = MISC_DYNAMIC_MINOR, .name = ++ RVAS_DRIVER_NAME, .fops = &video_module_ops, }; ++ ++void ioctl_new_context(struct file *file, struct RvasIoctl *pri, struct AstRVAS *pAstRVAS) ++{ ++ struct ContextTable *pct; ++ ++ VIDEO_DBG("Start\n"); ++ pct = get_new_context_table_entry(pAstRVAS); ++ ++ if (pct) { ++ pct->desc_virt = dma_alloc_coherent(pAstRVAS->pdev, PAGE_SIZE, (dma_addr_t *)&pct->desc_phy, GFP_KERNEL); ++ if (!pct->desc_virt) { ++ pri->rs = MemoryAllocError; ++ return; ++ } ++ pri->rc = pct->rc; ++ } else { ++ pri->rs = MemoryAllocError; ++ } ++ ++ VIDEO_DBG("end: return status: %d\n", pri->rs); ++} ++ ++void ioctl_delete_context(struct RvasIoctl *pri, struct AstRVAS *pAstRVAS) ++{ ++ VIDEO_DBG("Start\n"); ++ ++ VIDEO_DBG("pri->rc: %d\n", pri->rc); ++ if (remove_context_table_entry(pri->rc, pAstRVAS)) { ++ VIDEO_DBG("Success in removing\n"); ++ pri->rs = SuccessStatus; ++ } else { ++ VIDEO_DBG("Failed in removing\n"); ++ pri->rs = InvalidMemoryHandle; ++ } ++} ++ ++int get_mem_entry(struct AstRVAS *pAstRVAS) ++{ ++ int index = 0; ++ u32 dw_size = 0; ++ bool found = false; ++ ++ down(&pAstRVAS->mem_sem); ++ do { ++ if (pAstRVAS->ppmmtMemoryTable[index]) { ++ index++; ++ } else { ++ found = true; ++ break; ++ } ++ ++ } while (!found && (index < MAX_NUM_MEM_TBL)); ++ ++ if (found) { ++ dw_size = sizeof(struct MemoryMapTable); ++ pAstRVAS->ppmmtMemoryTable[index] = kmalloc(dw_size, GFP_KERNEL); ++ if (!pAstRVAS->ppmmtMemoryTable[index]) ++ index = -1; ++ } else { ++ index = -1; ++ } ++ ++ up(&pAstRVAS->mem_sem); ++ return index; ++} ++ ++bool delete_mem_entry(const void *crmh, struct AstRVAS *pAstRVAS) ++{ ++ bool b_ret = false; ++ u32 dw_index = (u32)crmh; ++ ++ VIDEO_DBG("Start, dw_index: %#x\n", dw_index); ++ ++ down(&pAstRVAS->mem_sem); ++ if (dw_index < MAX_NUM_MEM_TBL && pAstRVAS->ppmmtMemoryTable[dw_index]) { ++ VIDEO_DBG("mem: 0x%p\n", pAstRVAS->ppmmtMemoryTable[dw_index]); ++ kfree(pAstRVAS->ppmmtMemoryTable[dw_index]); ++ pAstRVAS->ppmmtMemoryTable[dw_index] = NULL; ++ b_ret = true; ++ } ++ up(&pAstRVAS->mem_sem); ++ VIDEO_DBG("End\n"); ++ return b_ret; ++} ++ ++void *get_virt_add_rsvd_mem(u32 index, struct AstRVAS *pAstRVAS) ++{ ++ if (index < MAX_NUM_MEM_TBL && pAstRVAS->ppmmtMemoryTable[index]) ++ return pAstRVAS->ppmmtMemoryTable[index]->pvVirtualAddr; ++ ++ return 0; ++} ++ ++dma_addr_t get_phys_add_rsvd_mem(u32 index, struct AstRVAS *pAstRVAS) ++{ ++ if (index < MAX_NUM_MEM_TBL && pAstRVAS->ppmmtMemoryTable[index]) ++ return pAstRVAS->ppmmtMemoryTable[index]->mem_phys; ++ ++ return 0; ++} ++ ++u32 get_len_rsvd_mem(u32 index, struct AstRVAS *pAstRVAS) ++{ ++ u32 len = 0; ++ ++ if (index < MAX_NUM_MEM_TBL && pAstRVAS->ppmmtMemoryTable[index]) ++ len = pAstRVAS->ppmmtMemoryTable[index]->dwLength; ++ ++ return len; ++} ++ ++bool virt_is_valid_rsvd_mem(u32 index, u32 size, struct AstRVAS *pAstRVAS) ++{ ++ if (index < MAX_NUM_MEM_TBL && ++ pAstRVAS->ppmmtMemoryTable[index] && ++ pAstRVAS->ppmmtMemoryTable[index]->dwLength) ++ return true; ++ ++ return false; ++} ++ ++void ioctl_alloc(struct file *file, struct RvasIoctl *pri, struct AstRVAS *pAstRVAS) ++{ ++ u32 size; ++ dma_addr_t phys_add = 0; ++ void *virt_add = 0; ++ u32 index = get_mem_entry(pAstRVAS); ++ ++ if (index < 0 || index >= MAX_NUM_MEM_TBL) { ++ pri->rs = MemoryAllocError; ++ return; ++ } ++ if (pri->req_mem_size < PAGE_SIZE) ++ pri->req_mem_size = PAGE_SIZE; ++ ++ size = pri->req_mem_size; ++ ++ VIDEO_DBG("Allocating memory size: 0x%x\n", size); ++ virt_add = dma_alloc_coherent(pAstRVAS->pdev, size, &phys_add, ++ GFP_KERNEL); ++ if (virt_add) { ++ pri->rmh = (void *)index; ++ pri->rvb.pv = (void *)phys_add; ++ pri->rvb.cb = size; ++ pri->rs = SuccessStatus; ++ pAstRVAS->ppmmtMemoryTable[index]->pf = file; ++ pAstRVAS->ppmmtMemoryTable[index]->mem_phys = phys_add; ++ pAstRVAS->ppmmtMemoryTable[index]->pvVirtualAddr = (void *)virt_add; ++ pAstRVAS->ppmmtMemoryTable[index]->dwLength = size; ++ pAstRVAS->ppmmtMemoryTable[index]->byDmaAlloc = 1; ++ } else { ++ if (pAstRVAS->ppmmtMemoryTable[index]) ++ delete_mem_entry((void *)index, pAstRVAS); ++ ++ pr_err("Cannot alloc video destination data buffer\n"); ++ pri->rs = MemoryAllocError; ++ } ++ VIDEO_DBG("Allocated: index: 0x%x phys: %llx cb: 0x%x\n", index, ++ phys_add, pri->rvb.cb); ++} ++ ++void ioctl_free(struct RvasIoctl *pri, struct AstRVAS *pAstRVAS) ++{ ++ void *virt_add = get_virt_add_rsvd_mem((u32)pri->rmh, pAstRVAS); ++ dma_addr_t dw_phys = get_phys_add_rsvd_mem((u32)pri->rmh, pAstRVAS); ++ u32 len = get_len_rsvd_mem((u32)pri->rmh, pAstRVAS); ++ ++ VIDEO_DBG("Start\n"); ++ VIDEO_DBG("Freeing: rmh: 0x%p, phys: 0x%x, size 0x%x virt_add: 0x%p len: %u\n", ++ pri->rmh, dw_phys, pri->rvb.cb, virt_add, len); ++ ++ delete_mem_entry(pri->rmh, pAstRVAS); ++ VIDEO_DBG("After delete_mem_entry\n"); ++ ++ dma_free_coherent(pAstRVAS->pdev, len, ++ virt_add, ++ dw_phys); ++ VIDEO_DBG("After dma_free_coherent\n"); ++} ++ ++//AST2700 has both VGA output and DP out. ++//AST2750 has VGA output for host node 0/VGA0 and DP output for host node 1/VGA1. ++void ioctl_update_lms_2700(u8 lms_on, struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_scu000 = 0; ++ u32 reg_scu448 = 0; ++ u32 reg_scu0C0 = 0; ++ u32 reg_scu0D0 = 0; ++ u32 reg_dptx100 = 0; ++ u32 reg_dptx104 = 0; ++ u32 chip_efuse_option = 0; ++ u32 vga_crt_disbl = 0; ++ u32 vga_pwr_off_vdac = 0; ++ ++ if (pAstRVAS->rvas_index == 0x0) { ++ vga_crt_disbl = VGA0_CRT_DISBL; ++ vga_pwr_off_vdac = VGA0_PWR_OFF_VDAC; ++ } else { ++ vga_crt_disbl = VGA1_CRT_DISBL; ++ vga_pwr_off_vdac = VGA1_PWR_OFF_VDAC; ++ } ++ ++ regmap_read(pAstRVAS->scu, SCU000_Silicon_Revision_ID, ®_scu000); ++ chip_efuse_option = (reg_scu000 & 0xff00) >> 8; ++ regmap_read(pAstRVAS->scu_io, SCU448_Pin_Ctrl, ®_scu448); ++ regmap_read(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, ®_scu0C0); ++ regmap_read(pAstRVAS->scu_io, SCU0D0_Misc3_Ctrl, ®_scu0D0); ++ if ((chip_efuse_option == 0 && pAstRVAS->rvas_index == 0x1) || chip_efuse_option == 1) { ++ if (pAstRVAS->dp_base) { ++ reg_dptx100 = readl(pAstRVAS->dp_base + DPTX_Configuration_Register); ++ reg_dptx104 = readl(pAstRVAS->dp_base + DPTX_PHY_Configuration_Register); ++ } ++ } ++ ++ if (lms_on) { ++ if ((reg_scu448 & VGAVS_ENBL_27) == 0 && (reg_scu448 & VGAHS_ENBL_27) == 0) { ++ reg_scu448 |= (VGAVS_ENBL_27 | VGAHS_ENBL_27); ++ regmap_write(pAstRVAS->scu_io, SCU448_Pin_Ctrl, reg_scu448); ++ } ++ if (reg_scu0C0 & vga_crt_disbl) { ++ reg_scu0C0 &= ~vga_crt_disbl; ++ regmap_write(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, reg_scu0C0); ++ } ++ if (reg_scu0D0 & vga_pwr_off_vdac) { ++ reg_scu0D0 &= ~vga_pwr_off_vdac; ++ regmap_write(pAstRVAS->scu_io, SCU0D0_Misc3_Ctrl, reg_scu0D0); ++ } ++ //dp output ++ if (pAstRVAS->dp_base) { ++ reg_dptx100 |= 1 << AUX_RESETN; ++ writel(reg_dptx100, pAstRVAS->dp_base + DPTX_Configuration_Register); ++ } ++ } else { //turn off ++ if ((reg_scu448 & VGAVS_ENBL_27) == 1 || (reg_scu448 & VGAHS_ENBL_27) == 1) { ++ reg_scu448 &= ~(VGAVS_ENBL_27 | VGAHS_ENBL_27); ++ regmap_write(pAstRVAS->scu_io, SCU448_Pin_Ctrl, reg_scu448); ++ } ++ if (!(reg_scu0C0 & vga_crt_disbl)) { ++ reg_scu0C0 |= vga_crt_disbl; ++ regmap_write(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, reg_scu0C0); ++ } ++ if (!(reg_scu0D0 & vga_pwr_off_vdac)) { ++ reg_scu0D0 |= vga_pwr_off_vdac; ++ regmap_write(pAstRVAS->scu_io, SCU0D0_Misc3_Ctrl, reg_scu0D0); ++ } ++ //dp output ++ if ((chip_efuse_option == 0 && pAstRVAS->rvas_index == 0x1) || chip_efuse_option == 1) { ++ if (pAstRVAS->dp_base) { ++ reg_dptx100 &= ~(1 << AUX_RESETN); ++ writel(reg_dptx100, pAstRVAS->dp_base + DPTX_Configuration_Register); ++ reg_dptx104 &= ~(1 << DP_TX_I_MAIN_ON); ++ writel(reg_dptx104, pAstRVAS->dp_base + DPTX_PHY_Configuration_Register); ++ } ++ } ++ } ++} ++ ++u32 ioctl_get_lm_status_2700(struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_val = 0; ++ ++ regmap_read(pAstRVAS->scu_io, SCU448_Pin_Ctrl, ®_val); ++ if ((reg_val & VGAVS_ENBL_27) == 1 || (reg_val & VGAHS_ENBL_27) == 1) { ++ regmap_read(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, ®_val); ++ if (pAstRVAS->rvas_index == 0x0) { ++ if (!(reg_val & VGA0_CRT_DISBL)) { ++ regmap_read(pAstRVAS->scu_io, SCU0D0_Misc3_Ctrl, ®_val); ++ if (!(reg_val & VGA0_PWR_OFF_VDAC)) ++ return 1; ++ } ++ } else { ++ if (!(reg_val & VGA1_CRT_DISBL)) { ++ regmap_read(pAstRVAS->scu_io, SCU0D0_Misc3_Ctrl, ®_val); ++ if (!(reg_val & VGA1_PWR_OFF_VDAC)) ++ return 1; ++ } ++ } ++ } ++ return 0; ++} ++ ++void ioctl_update_lms(u8 lms_on, struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_scu418 = 0; ++ u32 reg_scu0C0 = 0; ++ u32 reg_scu0D0 = 0; ++ u32 reg_dptx100 = 0; ++ u32 reg_dptx104 = 0; ++ ++ regmap_read(pAstRVAS->scu, SCU418_Pin_Ctrl, ®_scu418); ++ regmap_read(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, ®_scu0C0); ++ regmap_read(pAstRVAS->scu, SCU0D0_Misc3_Ctrl, ®_scu0D0); ++ if (pAstRVAS->dp_base) { ++ reg_dptx100 = readl(pAstRVAS->dp_base + DPTX_Configuration_Register); ++ reg_dptx104 = readl(pAstRVAS->dp_base + DPTX_PHY_Configuration_Register); ++ } ++ ++ if (lms_on) { ++ if (!(reg_scu418 & (VGAVS_ENBL | VGAHS_ENBL))) { ++ reg_scu418 |= (VGAVS_ENBL | VGAHS_ENBL); ++ regmap_write(pAstRVAS->scu, SCU418_Pin_Ctrl, reg_scu418); ++ } ++ if (reg_scu0C0 & VGA_CRT_DISBL) { ++ reg_scu0C0 &= ~VGA_CRT_DISBL; ++ regmap_write(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, reg_scu0C0); ++ } ++ if (reg_scu0D0 & PWR_OFF_VDAC) { ++ reg_scu0D0 &= ~PWR_OFF_VDAC; ++ regmap_write(pAstRVAS->scu, SCU0D0_Misc3_Ctrl, reg_scu0D0); ++ } ++ //dp output ++ if (pAstRVAS->dp_base) { ++ reg_dptx100 |= 1 << AUX_RESETN; ++ writel(reg_dptx100, pAstRVAS->dp_base + DPTX_Configuration_Register); ++ } ++ } else { //turn off ++ if (reg_scu418 & (VGAVS_ENBL | VGAHS_ENBL)) { ++ reg_scu418 &= ~(VGAVS_ENBL | VGAHS_ENBL); ++ regmap_write(pAstRVAS->scu, SCU418_Pin_Ctrl, reg_scu418); ++ } ++ if (!(reg_scu0C0 & VGA_CRT_DISBL)) { ++ reg_scu0C0 |= VGA_CRT_DISBL; ++ regmap_write(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, reg_scu0C0); ++ } ++ if (!(reg_scu0D0 & PWR_OFF_VDAC)) { ++ reg_scu0D0 |= PWR_OFF_VDAC; ++ regmap_write(pAstRVAS->scu, SCU0D0_Misc3_Ctrl, reg_scu0D0); ++ } ++ //dp output ++ if (pAstRVAS->dp_base) { ++ reg_dptx100 &= ~(1 << AUX_RESETN); ++ writel(reg_dptx100, pAstRVAS->dp_base + DPTX_Configuration_Register); ++ reg_dptx104 &= ~(1 << DP_TX_I_MAIN_ON); ++ writel(reg_dptx104, pAstRVAS->dp_base + DPTX_PHY_Configuration_Register); ++ } ++ } ++} ++ ++u32 ioctl_get_lm_status(struct AstRVAS *pAstRVAS) ++{ ++ u32 reg_val = 0; ++ ++ regmap_read(pAstRVAS->scu, SCU418_Pin_Ctrl, ®_val); ++ if (reg_val & (VGAVS_ENBL | VGAHS_ENBL)) { ++ regmap_read(pAstRVAS->scu, SCU0C0_Misc1_Ctrl, ®_val); ++ if (!(reg_val & VGA_CRT_DISBL)) ++ regmap_read(pAstRVAS->scu, SCU0D0_Misc3_Ctrl, ®_val); ++ if (!(reg_val & PWR_OFF_VDAC)) ++ return 1; ++ } ++ return 0; ++} ++ ++void init_osr_es(struct AstRVAS *pAstRVAS) ++{ ++ VIDEO_DBG("Start\n"); ++ sema_init(&pAstRVAS->mem_sem, 1); ++ sema_init(&pAstRVAS->context_sem, 1); ++ ++ video_os_init_sleep_struct(&pAstRVAS->video_wait); ++ ++ memset(&pAstRVAS->tfe_engine, 0x00, sizeof(struct EngineInfo)); ++ memset(&pAstRVAS->bse_engine, 0x00, sizeof(struct EngineInfo)); ++ memset(&pAstRVAS->ldma_engine, 0x00, sizeof(struct EngineInfo)); ++ sema_init(&pAstRVAS->tfe_engine.sem, 1); ++ sema_init(&pAstRVAS->bse_engine.sem, 1); ++ sema_init(&pAstRVAS->ldma_engine.sem, 1); ++ video_os_init_sleep_struct(&pAstRVAS->tfe_engine.wait); ++ video_os_init_sleep_struct(&pAstRVAS->bse_engine.wait); ++ video_os_init_sleep_struct(&pAstRVAS->ldma_engine.wait); ++ ++ memset(pAstRVAS->ppctContextTable, 0x00, MAX_NUM_CONTEXT * sizeof(u32)); ++ pAstRVAS->dwMemoryTableSize = MAX_NUM_MEM_TBL; ++ memset(pAstRVAS->ppmmtMemoryTable, 0x00, MAX_NUM_MEM_TBL * sizeof(u32)); ++ VIDEO_DBG("End\n"); ++} ++ ++void release_osr_es(struct AstRVAS *pAstRVAS) ++{ ++ u32 dw_index; ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ ++ VIDEO_DBG("Removing contexts...\n"); ++ for (dw_index = 0; dw_index < MAX_NUM_CONTEXT; ++dw_index) { ++ //if (ppctContextTable[dw_index]) { ++ kfree(ppctContextTable[dw_index]); ++ ppctContextTable[dw_index] = NULL; ++ //} kfree(NULL) is safe and this check is probably not require ++ } ++ ++ free_all_mem_entries(pAstRVAS); ++} ++ ++//Retrieve a context entry ++struct ContextTable *get_context_entry(const void *crc, struct AstRVAS *pAstRVAS) ++{ ++ struct ContextTable *pct = NULL; ++ u32 dw_index = (u32)crc; ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ ++ if (dw_index < MAX_NUM_CONTEXT && ppctContextTable[dw_index] && ++ ppctContextTable[dw_index]->rc == crc) ++ pct = ppctContextTable[dw_index]; ++ ++ return pct; ++} ++ ++struct ContextTable *get_new_context_table_entry(struct AstRVAS *pAstRVAS) ++{ ++ struct ContextTable *pct = NULL; ++ u32 dw_index = 0; ++ bool b_found = false; ++ u32 dw_size = 0; ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ ++ disable_grce_tse_interrupt(pAstRVAS); ++ down(&pAstRVAS->context_sem); ++ while (!b_found && (dw_index < MAX_NUM_CONTEXT)) { ++ if (!(ppctContextTable[dw_index])) ++ b_found = true; ++ else ++ ++dw_index; ++ } ++ if (b_found) { ++ dw_size = sizeof(struct ContextTable); ++ pct = kmalloc(dw_size, GFP_KERNEL); ++ ++ if (pct) { ++ memset(pct, 0x00, sizeof(struct ContextTable)); ++ pct->rc = (void *)dw_index; ++ memset(&pct->aqwSnoopMap, 0xff, ++ sizeof(pct->aqwSnoopMap)); ++ memset(&pct->sa, 0xff, sizeof(pct->sa)); ++ ppctContextTable[dw_index] = pct; ++ } ++ } ++ up(&pAstRVAS->context_sem); ++ enable_grce_tse_interrupt(pAstRVAS); ++ ++ return pct; ++} ++ ++bool remove_context_table_entry(const void *crc, struct AstRVAS *pAstRVAS) ++{ ++ bool b_ret = false; ++ u32 dw_index = (u32)crc; ++ struct ContextTable *ctx_entry; ++ ++ VIDEO_DBG("Start\n"); ++ ++ VIDEO_DBG("dw_index: %u\n", dw_index); ++ ++ if (dw_index < MAX_NUM_CONTEXT) { ++ ctx_entry = pAstRVAS->ppctContextTable[dw_index]; ++ VIDEO_DBG("ctx_entry: 0x%p\n", ctx_entry); ++ ++ if (ctx_entry) { ++ disable_grce_tse_interrupt(pAstRVAS); ++ if (!ctx_entry->desc_virt) { ++ VIDEO_DBG("Removing memory, virt: 0x%p phys: %#x\n", ++ ctx_entry->desc_virt, ++ ctx_entry->desc_phy); ++ ++ dma_free_coherent(pAstRVAS->pdev, PAGE_SIZE, ctx_entry->desc_virt, ctx_entry->desc_phy); ++ } ++ VIDEO_DBG("Removing memory: 0x%p\n", ctx_entry); ++ pAstRVAS->ppctContextTable[dw_index] = NULL; ++ kfree(ctx_entry); ++ b_ret = true; ++ enable_grce_tse_interrupt(pAstRVAS); ++ } ++ } ++ return b_ret; ++} ++ ++void display_event_map(const struct EventMap *pem) ++{ ++ VIDEO_DBG("EM:\n"); ++ VIDEO_DBG("*************************\n"); ++ VIDEO_DBG(" bATTRChanged= %u\n", pem->bATTRChanged); ++ VIDEO_DBG(" bCRTCChanged= %u\n", pem->bCRTCChanged); ++ VIDEO_DBG(" bCRTCEXTChanged= %u\n", pem->bCRTCEXTChanged); ++ VIDEO_DBG(" bDoorbellA= %u\n", pem->bDoorbellA); ++ VIDEO_DBG(" bDoorbellB= %u\n", pem->bDoorbellB); ++ VIDEO_DBG(" bGCTLChanged= %u\n", pem->bGCTLChanged); ++ VIDEO_DBG(" bGeometryChanged= %u\n", pem->bGeometryChanged); ++ VIDEO_DBG(" bPLTRAMChanged= %u\n", pem->bPLTRAMChanged); ++ VIDEO_DBG(" bPaletteChanged= %u\n", pem->bPaletteChanged); ++ VIDEO_DBG(" bSEQChanged= %u\n", pem->bSEQChanged); ++ VIDEO_DBG(" bSnoopChanged= %u\n", pem->bSnoopChanged); ++ VIDEO_DBG(" bTextASCIIChanged= %u\n", pem->bTextASCIIChanged); ++ VIDEO_DBG(" bTextATTRChanged= %u\n", pem->bTextATTRChanged); ++ VIDEO_DBG(" bTextFontChanged= %u\n", pem->bTextFontChanged); ++ VIDEO_DBG(" bXCURCOLChanged= %u\n", pem->bXCURCOLChanged); ++ VIDEO_DBG(" bXCURCTLChanged= %u\n", pem->bXCURCTLChanged); ++ VIDEO_DBG(" bXCURPOSChanged= %u\n", pem->bXCURPOSChanged); ++ VIDEO_DBG("*************************\n"); ++} ++ ++void ioctl_wait_for_video_event(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ union EmDwordUnion eduRequested; ++ union EmDwordUnion eduReturned; ++ union EmDwordUnion eduChanged; ++ struct EventMap anEm; ++ u32 result = 1; ++ int iTimerRemaining = ri->time_out; ++ unsigned long ulTimeStart, ulTimeEnd, ulElapsedTime; ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ ++ memset(&anEm, 0x0, sizeof(struct EventMap)); ++ ++ VIDEO_DBG("Calling VideoSleepOnTimeout\n"); ++ ++ eduRequested.em = ri->em; ++ VIDEO_DBG("eduRequested.em:\n"); ++ //display_event_map(&eduRequested.em); ++ eduChanged.em = ppctContextTable[(int)ri->rc]->emEventReceived; ++ VIDEO_DBG("eduChanged.em:\n"); ++ //display_event_map(&eduChanged.em); ++ ++ // While event has not occurred and there is still time remaining for wait ++ while (!(eduChanged.dw & eduRequested.dw) && (iTimerRemaining > 0) && ++ result) { ++ pAstRVAS->video_intr_occurred = 0; ++ ulTimeStart = jiffies_to_msecs(jiffies); ++ result = video_os_sleep_on_timeout(&pAstRVAS->video_wait, ++ &pAstRVAS->video_intr_occurred, ++ iTimerRemaining); ++ ulTimeEnd = jiffies_to_msecs(jiffies); ++ ulElapsedTime = (ulTimeEnd - ulTimeStart); ++ iTimerRemaining -= (int)ulElapsedTime; ++ eduChanged.em = ppctContextTable[(int)ri->rc]->emEventReceived; ++// VIDEO_DBG("Elapsedtime [%u], timestart[%u], timeend[%u]\n", dwElapsedTime, dwTimeStart, dwTimeEnd); ++ ++ VIDEO_DBG("ulElapsedTime [%lu], ulTimeStart[%lu], ulTimeEnd[%lu]\n", ++ ulElapsedTime, ulTimeStart, ulTimeEnd); ++ VIDEO_DBG("HZ [%ul]\n", HZ); ++ VIDEO_DBG("result [%u], iTimerRemaining [%d]\n", result, ++ iTimerRemaining); ++ } ++ ++ if (result == 0 && ri->time_out != 0) { ++ VIDEO_DBG("IOCTL Timedout\n"); ++ ri->rs = TimedOut; ++ memset(&ri->em, 0x0, sizeof(struct EventMap)); ++ } else { ++ eduChanged.em = ppctContextTable[(int)ri->rc]->emEventReceived; ++ VIDEO_DBG("Event Received[%X]\n", eduChanged.dw); ++ // Mask out the changes we are waiting on ++ eduReturned.dw = eduChanged.dw & eduRequested.dw; ++ ++ // Reset flags of changes that have been returned ++ eduChanged.dw &= ~(eduReturned.dw); ++ VIDEO_DBG("Event Reset[%X]\n", eduChanged.dw); ++ ppctContextTable[(int)ri->rc]->emEventReceived = eduChanged.em; ++ ++ // Copy changes back to ri ++ ri->em = eduReturned.em; ++ VIDEO_DBG("ri->em:\n"); ++ //display_event_map(&ri->em); ++ ri->rs = SuccessStatus; ++ VIDEO_DBG("Success [%x]\n", ++ eduReturned.dw); ++ } ++} ++ ++static void update_context_events(struct AstRVAS *pAstRVAS, ++ union EmDwordUnion eduFge_status) ++{ ++ union EmDwordUnion eduEmReceived; ++ u32 dwIter = 0; ++ struct ContextTable **ppctContextTable = pAstRVAS->ppctContextTable; ++ // VIDEO_DBG("Setting up context\n"); ++ for (dwIter = 0; dwIter < MAX_NUM_CONTEXT; ++dwIter) { ++ if (ppctContextTable[dwIter]) { ++ // VIDEO_DBG ("Copying EventMap to RVAS Context\n"); ++ memcpy((void *)&eduEmReceived, ++ (void *)&ppctContextTable[dwIter]->emEventReceived, ++ sizeof(union EmDwordUnion)); ++ eduEmReceived.dw |= eduFge_status.dw; ++ memcpy((void *)&ppctContextTable[dwIter]->emEventReceived, ++ (void *)&eduEmReceived, ++ sizeof(union EmDwordUnion)); ++ } ++ } ++ pAstRVAS->video_intr_occurred = 1; ++ video_ss_wakeup_on_timeout(&pAstRVAS->video_wait); ++} ++ ++static irqreturn_t fge_handler(int irq, void *dev_id) ++{ ++ union EmDwordUnion eduFge_status; ++ u32 tse_sts = 0; ++ u32 dwGRCEStatus = 0; ++ bool bFgeItr = false; ++ bool bTfeItr = false; ++ bool bBSEItr = false; ++ bool bLdmaItr = false; ++ bool vg_changed = false; ++ u32 dw_screen_offset = 0; ++ struct AstRVAS *pAstRVAS = (struct AstRVAS *)dev_id; ++ struct VideoGeometry *cur_vg = NULL; ++ ++ memset(&eduFge_status, 0x0, sizeof(union EmDwordUnion)); ++ bFgeItr = false; ++ ++ // Checking for GRC status changes ++ dwGRCEStatus = readl(pAstRVAS->grce_reg_base + GRCE_STATUS_REGISTER); ++ if (dwGRCEStatus & GRC_INT_STS_MASK) { ++ VIDEO_DBG("GRC Status Changed: %#x\n", dwGRCEStatus); ++ eduFge_status.dw |= dwGRCEStatus & GRC_INT_STS_MASK; ++ bFgeItr = true; ++ ++ if (dwGRCEStatus & 0x30) { ++ dw_screen_offset = get_screen_offset(pAstRVAS); ++ ++ if (pAstRVAS->dwScreenOffset != dw_screen_offset) { ++ pAstRVAS->dwScreenOffset = dw_screen_offset; ++ vg_changed = true; ++ } ++ } ++ } ++ vg_changed |= video_geometry_change(pAstRVAS, dwGRCEStatus); ++ if (vg_changed) { ++ eduFge_status.em.bGeometryChanged = true; ++ bFgeItr = true; ++ set_snoop_engine(vg_changed, pAstRVAS); ++ video_set_Window(pAstRVAS); ++ VIDEO_DBG("Geometry has changed\n"); ++ VIDEO_DBG("Reconfigure TSE\n"); ++ } ++ // Checking and clear TSE Intr Status ++ tse_sts = clear_tse_interrupt(pAstRVAS); ++ ++ if (tse_sts & TSSTS_ALL) { ++ bFgeItr = true; ++ if (tse_sts & (TSSTS_TC_SCREEN0 | TSSTS_TC_SCREEN1)) { ++ eduFge_status.em.bSnoopChanged = 1; ++ cur_vg = &pAstRVAS->current_vg; ++ ++ if (cur_vg->gmt == TextMode) { ++ eduFge_status.em.bTextASCIIChanged = 1; ++ eduFge_status.em.bTextATTRChanged = 1; ++ eduFge_status.em.bTextFontChanged = 1; ++ } ++ } ++ if (tse_sts & TSSTS_ASCII) { ++ //VIDEO_DBG("Text Ascii Changed\n"); ++ eduFge_status.em.bTextASCIIChanged = 1; ++ } ++ ++ if (tse_sts & TSSTS_ATTR) { ++ //VIDEO_DBG("Text Attr Changed\n"); ++ eduFge_status.em.bTextATTRChanged = 1; ++ } ++ ++ if (tse_sts & TSSTS_FONT) { ++ //VIDEO_DBG("Text Font Changed\n"); ++ eduFge_status.em.bTextFontChanged = 1; ++ } ++ } ++ ++ if (clear_ldma_interrupt(pAstRVAS)) { ++ bLdmaItr = true; ++ pAstRVAS->ldma_engine.finished = 1; ++ video_ss_wakeup_on_timeout(&pAstRVAS->ldma_engine.wait); ++ } ++ ++ if (clear_tfe_interrupt(pAstRVAS)) { ++ bTfeItr = true; ++ pAstRVAS->tfe_engine.finished = 1; ++ video_ss_wakeup_on_timeout(&pAstRVAS->tfe_engine.wait); ++ } ++ ++ if (clear_bse_interrupt(pAstRVAS)) { ++ bBSEItr = true; ++ pAstRVAS->bse_engine.finished = 1; ++ video_ss_wakeup_on_timeout(&pAstRVAS->bse_engine.wait); ++ } ++ ++ if (!bFgeItr && !bTfeItr && !bBSEItr && !bLdmaItr) { ++ //VIDEO_DBG(" Unknown Interrupt\n"); ++// VIDEO_DBG("TFE CRT [%#x].", *fge_intr); ++ return IRQ_NONE; ++ } ++ ++ if (bFgeItr) { ++ update_context_events(pAstRVAS, eduFge_status); ++ pAstRVAS->video_intr_occurred = 1; ++ video_ss_wakeup_on_timeout(&pAstRVAS->video_wait); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++/*Sleep and Wakeup Functions*/ ++ ++void video_os_init_sleep_struct(struct Video_OsSleepStruct *Sleep) ++{ ++ init_waitqueue_head(&Sleep->queue); ++ Sleep->Timeout = 0; ++} ++ ++void video_ss_wakeup_on_timeout(struct Video_OsSleepStruct *Sleep) ++{ ++ /* Wakeup Process and Kill timeout handler */ ++ wake_up(&Sleep->queue); ++} ++ ++long video_os_sleep_on_timeout(struct Video_OsSleepStruct *Sleep, u8 *Var, long msecs) ++{ ++ long timeout; /* In jiffies */ ++ u8 *Condition = Var; ++ /* Sleep on the Condition for a wakeup */ ++ timeout = wait_event_interruptible_timeout(Sleep->queue, ++ (*Condition == 1), ++ msecs_to_jiffies(msecs)); ++ ++ return timeout; ++} ++ ++void disable_video_engines(struct AstRVAS *pAstRVAS) ++{ ++ clk_disable(pAstRVAS->eclk); ++ clk_disable(pAstRVAS->vclk); ++} ++ ++void enable_video_engines(struct AstRVAS *pAstRVAS) ++{ ++ clk_enable(pAstRVAS->eclk); ++ clk_enable(pAstRVAS->vclk); ++} ++ ++void disable_rvas_engines(struct AstRVAS *pAstRVAS) ++{ ++ clk_disable(pAstRVAS->rvasclk); ++} ++ ++void enable_rvas_engines(struct AstRVAS *pAstRVAS) ++{ ++ // ast2600 clk enable does ++ // reset engine reset at SCU040 ++ // delay 100 us ++ // enable clock at SCU080 ++ // delay 10ms ++ // disable engine reset at SCU040 ++ ++ // ast2700 clk enable only enable clock at SCU240 ++ clk_enable(pAstRVAS->rvasclk); ++} ++ ++static void reset_rvas_engine(struct AstRVAS *pAstRVAS) ++{ ++ disable_rvas_engines(pAstRVAS); ++ mdelay(200); ++ if (pAstRVAS->config->version == 7) ++ reset_control_deassert(pAstRVAS->rvas_reset); ++ mdelay(200); ++ enable_rvas_engines(pAstRVAS); ++ ++ rvas_init(pAstRVAS); ++} ++ ++static void video_on(struct AstRVAS *pAstRVAS) ++{ ++ if (pAstRVAS->config->version == 7) { ++ // enable clk ++ regmap_write(pAstRVAS->scu, 0x200, 0x40); ++ mdelay(200); ++ regmap_write(pAstRVAS->scu, 0x244, 0x2); ++ regmap_write(pAstRVAS->scu, 0x244, 0x8); ++ mdelay(100); ++ regmap_write(pAstRVAS->scu, 0x204, 0x40); ++ } else { ++ video_engine_init(pAstRVAS); ++ } ++} ++ ++static void video_off(struct AstRVAS *pAstRVAS) ++{ ++ if (pAstRVAS->config->version == 7) { ++ disable_video_interrupt(pAstRVAS); ++ // stop clock ++ regmap_write(pAstRVAS->scu, 0x240, 0x2); ++ regmap_write(pAstRVAS->scu, 0x240, 0x8); ++ mdelay(100); ++ } else { ++ disable_video_engines(pAstRVAS); ++ enable_video_engines(pAstRVAS); ++ } ++} ++ ++static void reset_video_engine(struct AstRVAS *pAstRVAS) ++{ ++ video_off(pAstRVAS); ++ video_on(pAstRVAS); ++} ++ ++void ioctl_reset_video_engine(struct RvasIoctl *ri, struct AstRVAS *pAstRVAS) ++{ ++ enum ResetEngineMode resetMode = ri->resetMode; ++ ++ switch (resetMode) { ++ case ResetAll: ++ VIDEO_DBG("reset all engine\n"); ++ reset_rvas_engine(pAstRVAS); ++ reset_video_engine(pAstRVAS); ++ break; ++ case ResetRvasEngine: ++ VIDEO_DBG("reset rvas engine\n"); ++ reset_rvas_engine(pAstRVAS); ++ break; ++ case ResetVeEngine: ++ VIDEO_DBG("reset video engine\n"); ++ reset_video_engine(pAstRVAS); ++ break; ++ default: ++ dev_err(pAstRVAS->pdev, "Error resetting: no such mode: %d\n", resetMode); ++ break; ++ } ++ ++ if (ri) ++ ri->rs = SuccessStatus; ++} ++ ++static ssize_t rvas_reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct AstRVAS *pAstRVAS = dev_get_drvdata(dev); ++ u32 val = kstrtoul(buf, 10, NULL); ++ ++ if (val) ++ ioctl_reset_video_engine(NULL, pAstRVAS); ++ ++ return count; ++} ++ ++static DEVICE_ATTR_WO(rvas_reset); ++ ++static struct attribute *ast_rvas_attributes[] = { ++ &dev_attr_rvas_reset.attr, ++ NULL ++}; ++ ++static const struct attribute_group rvas_attribute_group = { ++ .attrs = ast_rvas_attributes ++}; ++ ++bool sleep_on_tfe_busy(struct AstRVAS *pAstRVAS, phys_addr_t desc_addr_phys, ++ u32 dwTFEControlR, u32 dwTFERleLimitor, ++ u32 *pdwRLESize, u32 *pdwCheckSum) ++{ ++ void __iomem *addrTFEDTBR = pAstRVAS->fg_reg_base + TFE_Descriptor_Table_Offset; ++ void __iomem *addrTFECR = pAstRVAS->fg_reg_base + TFE_Descriptor_Control_Resgister; ++ void __iomem *addrTFERleL = pAstRVAS->fg_reg_base + TFE_RLE_LIMITOR; ++ void __iomem *addrTFERSTS = pAstRVAS->fg_reg_base + TFE_Status_Register; ++ bool bResult = true; ++ ++ down(&pAstRVAS->tfe_engine.sem); ++ VIDEO_DBG("In Busy Semaphore......\n"); ++ ++ VIDEO_DBG("Before change, TFECR: %#x\n", readl(addrTFECR)); ++ writel(dwTFEControlR, addrTFECR); ++ VIDEO_DBG("After change, TFECR: %#x\n", readl(addrTFECR)); ++ writel(dwTFERleLimitor, addrTFERleL); ++ VIDEO_DBG("dwTFEControlR: %#x\n", dwTFEControlR); ++ VIDEO_DBG("dwTFERleLimitor: %#x\n", dwTFERleLimitor); ++ VIDEO_DBG("desc_addr_phys: %#x\n", desc_addr_phys); ++ // put descriptor add to TBR and Fetch start ++ writel((u32)desc_addr_phys, addrTFEDTBR); ++ //wTFETiles = 1; ++ pAstRVAS->tfe_engine.finished = 0; ++ video_os_sleep_on_timeout(&pAstRVAS->tfe_engine.wait, ++ &pAstRVAS->tfe_engine.finished, ++ TFE_TIMEOUT_IN_MS); ++ ++ if (!pAstRVAS->tfe_engine.finished) { ++ dev_err(pAstRVAS->pdev, "Video TFE failed\n"); ++ writel(0x00, addrTFERSTS); ++ pAstRVAS->tfe_engine.finished = 1; ++ bResult = false; ++ } ++ ++ writel((readl(addrTFECR) & (~0x3)), addrTFECR); // Disable IRQ and Turn off TFE when done ++ *pdwRLESize = readl(pAstRVAS->fg_reg_base + TFE_RLE_Byte_Count); ++ *pdwCheckSum = readl(pAstRVAS->fg_reg_base + TFE_RLE_CheckSum); ++ ++ up(&pAstRVAS->tfe_engine.sem); ++ VIDEO_DBG("Done Busy: bResult: %d\n", bResult); ++ ++ return bResult; ++} ++ ++bool sleep_on_tfe_text_busy(struct AstRVAS *pAstRVAS, phys_addr_t desc_addr_phys, ++ u32 dwTFEControlR, u32 dwTFERleLimitor, u32 *pdwRLESize, ++ u32 *pdwCheckSum) ++{ ++ void __iomem *addrTFEDTBR = pAstRVAS->fg_reg_base + TFE_Descriptor_Table_Offset; ++ void __iomem *addrTFECR = pAstRVAS->fg_reg_base + TFE_Descriptor_Control_Resgister; ++ void __iomem *addrTFERleL = pAstRVAS->fg_reg_base + TFE_RLE_LIMITOR; ++ void __iomem *addrTFERSTS = pAstRVAS->fg_reg_base + TFE_Status_Register; ++ bool bResult = true; ++ ++ down(&pAstRVAS->tfe_engine.sem); ++ VIDEO_DBG("In Busy Semaphore......\n"); ++ ++ VIDEO_DBG("Before change, TFECR: %#x\n", readl(addrTFECR)); ++ writel(dwTFEControlR, addrTFECR); ++ VIDEO_DBG("After change, TFECR: %#x\n", readl(addrTFECR)); ++ writel(dwTFERleLimitor, addrTFERleL); ++ VIDEO_DBG("dwTFEControlR: %#x\n", dwTFEControlR); ++ VIDEO_DBG("dwTFERleLimitor: %#x\n", dwTFERleLimitor); ++ VIDEO_DBG("desc_addr_phys: %#x\n", desc_addr_phys); ++ // put descriptor add to TBR and Fetch start ++ writel((u32)desc_addr_phys, addrTFEDTBR); ++ //wTFETiles = 1; ++ pAstRVAS->tfe_engine.finished = 0; ++ video_os_sleep_on_timeout(&pAstRVAS->tfe_engine.wait, ++ &pAstRVAS->tfe_engine.finished, TFE_TIMEOUT_IN_MS); ++ ++ if (!pAstRVAS->tfe_engine.finished) { ++ dev_err(pAstRVAS->pdev, "Video TFE failed\n"); ++ writel(0x00, addrTFERSTS); ++ pAstRVAS->tfe_engine.finished = 1; ++ bResult = false; ++ } ++ ++ writel((readl(addrTFECR) & (~0x3)), addrTFECR);// Disable IRQ and Turn off TFE when done ++ writel((readl(addrTFERSTS) | 0x2), addrTFERSTS); // clear status bit ++ *pdwRLESize = readl(pAstRVAS->fg_reg_base + TFE_RLE_Byte_Count); ++ *pdwCheckSum = readl(pAstRVAS->fg_reg_base + TFE_RLE_CheckSum); ++ ++ up(&pAstRVAS->tfe_engine.sem); ++ VIDEO_DBG("Done Busy: bResult: %d\n", bResult); ++ ++ return bResult; ++} ++ ++bool sleep_on_bse_busy(struct AstRVAS *pAstRVAS, phys_addr_t desc_addr_phys, ++ struct BSEAggregateRegister aBSEAR, u32 size) ++{ ++ void __iomem *addrBSEDTBR = pAstRVAS->fg_reg_base + BSE_Descriptor_Table_Base_Register; ++ void __iomem *addrBSCR = pAstRVAS->fg_reg_base + BSE_Command_Register; ++ void __iomem *addrBSDBS = pAstRVAS->fg_reg_base + BSE_Destination_Buket_Size_Resgister; ++ void __iomem *addrBSBPS0 = pAstRVAS->fg_reg_base + BSE_Bit_Position_Register_0; ++ void __iomem *addrBSBPS1 = pAstRVAS->fg_reg_base + BSE_Bit_Position_Register_1; ++ void __iomem *addrBSBPS2 = pAstRVAS->fg_reg_base + BSE_Bit_Position_Register_2; ++ void __iomem *addrBSESSTS = pAstRVAS->fg_reg_base + BSE_Status_Register; ++ u8 byCounter = 0; ++ bool bResult = true; ++ ++ down(&pAstRVAS->bse_engine.sem); ++ pAstRVAS->bse_engine.finished = 0; ++ ++ // Set BSE Temp buffer address, and clear lower u16 ++ writel(BSE_LMEM_Temp_Buffer_Offset << 16, addrBSCR); ++ writel(readl(addrBSCR) | (aBSEAR.dwBSCR & 0X00000FFF), addrBSCR); ++ writel(aBSEAR.dwBSDBS, addrBSDBS); ++ writel(aBSEAR.adwBSBPS[0], addrBSBPS0); ++ writel(aBSEAR.adwBSBPS[1], addrBSBPS1); ++ writel(aBSEAR.adwBSBPS[2], addrBSBPS2); ++ ++ writel((u32)desc_addr_phys, addrBSEDTBR); ++ ++ while (!pAstRVAS->bse_engine.finished) { ++ VIDEO_DBG("BSE Sleeping...\n"); ++ video_os_sleep_on_timeout(&pAstRVAS->bse_engine.wait, ++ &pAstRVAS->bse_engine.finished, ++ 1000); // loop if bse timedout ++ byCounter++; ++ VIDEO_DBG("Back from BSE Sleeping, finished: %u\n", ++ pAstRVAS->bse_engine.finished); ++ ++ if (byCounter == ENGINE_TIMEOUT_IN_SECONDS) { ++ writel(0x00, addrBSESSTS); ++ pAstRVAS->bse_engine.finished = 1; ++ dev_err(pAstRVAS->pdev, "TIMEOUT::Waiting BSE\n"); ++ bResult = false; ++ } ++ } ++ ++ VIDEO_DBG("*pdwBSESSTS = %#x\n", readl(addrBSESSTS)); ++ writel(readl(addrBSCR) & (~0x3), addrBSCR); ++ ++ up(&pAstRVAS->bse_engine.sem); ++ ++ return bResult; ++} ++ ++void sleep_on_ldma_busy(struct AstRVAS *pAstRVAS, phys_addr_t desc_addr_phys) ++{ ++ void __iomem *addrLDMADTBR = pAstRVAS->fg_reg_base + LDMA_Descriptor_Table_Base_Register; ++ void __iomem *addrLDMAControlR = pAstRVAS->fg_reg_base + LDMA_Control_Register; ++ ++ VIDEO_DBG("In sleepONldma busy\n"); ++ ++ down(&pAstRVAS->ldma_engine.sem); ++ ++ pAstRVAS->ldma_engine.finished = 0; ++ ++ writel(0x83, addrLDMAControlR);// descriptor can only in LMEM FOR LDMA ++ writel((u32)desc_addr_phys, addrLDMADTBR); ++ VIDEO_DBG("LDMA: control [%#x]\n", readl(addrLDMAControlR)); ++ VIDEO_DBG("LDMA: DTBR [%#x]\n", readl(addrLDMADTBR)); ++ ++ while (!pAstRVAS->ldma_engine.finished) ++ video_os_sleep_on_timeout(&pAstRVAS->ldma_engine.wait, (u8 *)&pAstRVAS->ldma_engine.finished, 1000); // loop if bse timedout ++ ++ VIDEO_DBG("LDMA wake up\n"); ++ writel(readl(addrLDMAControlR) & (~0x3), addrLDMAControlR); ++ up(&pAstRVAS->ldma_engine.sem); ++} ++ ++static int video_drv_get_resources(struct platform_device *pdev) ++{ ++ int result = 0; ++ struct resource *io_fg; ++ struct resource *io_grc; ++ struct resource *io_video; ++ struct AstRVAS *pAstRVAS = platform_get_drvdata(pdev); ++ ++ //get resources from platform ++ io_fg = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ VIDEO_DBG("io_fg: 0x%p\n", io_fg); ++ ++ if (!io_fg) { ++ dev_err(&pdev->dev, "No Frame Grabber IORESOURCE_MEM entry\n"); ++ return -ENOENT; ++ } ++ io_grc = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ VIDEO_DBG("io_grc: 0x%p\n", io_grc); ++ if (!io_grc) { ++ dev_err(&pdev->dev, "No GRCE IORESOURCE_MEM entry\n"); ++ return -ENOENT; ++ } ++ io_video = platform_get_resource(pdev, IORESOURCE_MEM, 2); ++ VIDEO_DBG("io_video: 0x%p\n", io_video); ++ if (!io_video) { ++ dev_err(&pdev->dev, "No video compression IORESOURCE_MEM entry\n"); ++ return -ENOENT; ++ } ++ ++ //map resource by device ++ pAstRVAS->fg_reg_base = devm_ioremap_resource(&pdev->dev, io_fg); ++ VIDEO_DBG("fg_reg_base: %p\n", pAstRVAS->fg_reg_base); ++ if (IS_ERR((void *)pAstRVAS->fg_reg_base)) { ++ result = PTR_ERR((void *)pAstRVAS->fg_reg_base); ++ dev_err(&pdev->dev, "Cannot map FG registers\n"); ++ pAstRVAS->fg_reg_base = 0; ++ return result; ++ } ++ pAstRVAS->grce_reg_base = devm_ioremap_resource(&pdev->dev, io_grc); ++ VIDEO_DBG("grce_reg_base: %p\n", pAstRVAS->grce_reg_base); ++ if (IS_ERR((void *)pAstRVAS->grce_reg_base)) { ++ result = PTR_ERR((void *)pAstRVAS->grce_reg_base); ++ dev_err(&pdev->dev, "Cannot map GRC registers\n"); ++ pAstRVAS->grce_reg_base = 0; ++ return result; ++ } ++ pAstRVAS->video_reg_base = devm_ioremap_resource(&pdev->dev, io_video); ++ VIDEO_DBG("video_reg_base: %p\n", pAstRVAS->video_reg_base); ++ if (IS_ERR((void *)pAstRVAS->video_reg_base)) { ++ result = PTR_ERR((void *)pAstRVAS->video_reg_base); ++ dev_err(&pdev->dev, "Cannot map video registers\n"); ++ pAstRVAS->video_reg_base = 0; ++ return result; ++ } ++ ++ pAstRVAS->config = of_device_get_match_data(&pdev->dev); ++ if (!pAstRVAS->config) ++ return -ENODEV; ++ return 0; ++} ++ ++static int video_drv_get_irqs(struct platform_device *pdev) ++{ ++ struct AstRVAS *pAstRVAS = platform_get_drvdata(pdev); ++ ++ pAstRVAS->irq_fge = platform_get_irq(pdev, 0); ++ VIDEO_DBG("irq_fge: %#x\n", pAstRVAS->irq_fge); ++ if (pAstRVAS->irq_fge < 0) { ++ dev_err(&pdev->dev, "NO FGE irq entry\n"); ++ return -ENOENT; ++ } ++ pAstRVAS->irq_vga = platform_get_irq(pdev, 1); ++ VIDEO_DBG("irq_vga: %#x\n", pAstRVAS->irq_vga); ++ if (pAstRVAS->irq_vga < 0) { ++ dev_err(&pdev->dev, "NO VGA irq entry\n"); ++ return -ENOENT; ++ } ++ pAstRVAS->irq_video = platform_get_irq(pdev, 2); ++ VIDEO_DBG("irq_video: %#x\n", pAstRVAS->irq_video); ++ if (pAstRVAS->irq_video < 0) { ++ dev_err(&pdev->dev, "NO video compression entry\n"); ++ return -ENOENT; ++ } ++ return 0; ++} ++ ++static int video_drv_get_clock(struct platform_device *pdev) ++{ ++ struct AstRVAS *pAstRVAS = platform_get_drvdata(pdev); ++ ++ pAstRVAS->eclk = devm_clk_get(&pdev->dev, "eclk"); ++ if (IS_ERR(pAstRVAS->eclk)) { ++ dev_err(&pdev->dev, "no eclk clock defined\n"); ++ return PTR_ERR(pAstRVAS->eclk); ++ } ++ ++ clk_prepare_enable(pAstRVAS->eclk); ++ ++ pAstRVAS->vclk = devm_clk_get(&pdev->dev, "vclk"); ++ if (IS_ERR(pAstRVAS->vclk)) { ++ dev_err(&pdev->dev, "no vclk clock defined\n"); ++ return PTR_ERR(pAstRVAS->vclk); ++ } ++ ++ clk_prepare_enable(pAstRVAS->vclk); ++ ++ if (pAstRVAS->config->version == 7) { ++ pAstRVAS->rvasclk = devm_clk_get(&pdev->dev, "rvasclk"); ++ if (IS_ERR(pAstRVAS->rvasclk)) { ++ pAstRVAS->rvasclk = devm_clk_get(&pdev->dev, "rvas2clk"); ++ if (IS_ERR(pAstRVAS->rvasclk)) { ++ dev_err(&pdev->dev, "no rvasclk or rvas2clk clock defined\n"); ++ return PTR_ERR(pAstRVAS->rvasclk); ++ } ++ } ++ } else { ++ pAstRVAS->rvasclk = devm_clk_get(&pdev->dev, "rvasclk-gate"); ++ if (IS_ERR(pAstRVAS->rvasclk)) { ++ dev_err(&pdev->dev, "no rvasclk clock defined\n"); ++ return PTR_ERR(pAstRVAS->rvasclk); ++ } ++ } ++ clk_prepare_enable(pAstRVAS->rvasclk); ++ return 0; ++} ++ ++static int video_drv_map_irqs(struct platform_device *pdev) ++{ ++ int result = 0; ++ struct AstRVAS *pAstRVAS = platform_get_drvdata(pdev); ++ //Map IRQS to handler ++ VIDEO_DBG("Requesting IRQs, irq_fge: %d, irq_vga: %d, irq_video: %d\n", ++ pAstRVAS->irq_fge, pAstRVAS->irq_vga, pAstRVAS->irq_video); ++ ++ result = devm_request_irq(&pdev->dev, pAstRVAS->irq_fge, fge_handler, 0, ++ dev_name(&pdev->dev), pAstRVAS); ++ if (result) { ++ pr_err("Error in requesting IRQ\n"); ++ pr_err("RVAS: Failed request FGE irq %d\n", pAstRVAS->irq_fge); ++ misc_deregister(&pAstRVAS->rvas_dev); ++ return result; ++ } ++ ++ result = devm_request_irq(&pdev->dev, pAstRVAS->irq_vga, fge_handler, 0, ++ dev_name(&pdev->dev), pAstRVAS); ++ if (result) { ++ pr_err("Error in requesting IRQ\n"); ++ pr_err("RVAS: Failed request vga irq %d\n", pAstRVAS->irq_vga); ++ misc_deregister(&pAstRVAS->rvas_dev); ++ return result; ++ } ++ ++ result = devm_request_irq(&pdev->dev, pAstRVAS->irq_video, ast_video_isr, 0, ++ dev_name(&pdev->dev), pAstRVAS); ++ if (result) { ++ pr_err("Error in requesting IRQ\n"); ++ pr_err("RVAS: Failed request video irq %d\n", pAstRVAS->irq_video); ++ misc_deregister(&pAstRVAS->rvas_dev); ++ return result; ++ } ++ ++ return result; ++} ++ ++// ++// ++// ++static int video_drv_probe(struct platform_device *pdev) ++{ ++ int result = 0; ++ struct AstRVAS *pAstRVAS; ++ struct regmap *sdram_scu; ++ struct device_node *dp_node; ++ struct device_node *edac_node; ++ void __iomem *mcr_base; ++ ++ pr_info("RVAS driver probe\n"); ++ pAstRVAS = devm_kzalloc(&pdev->dev, sizeof(struct AstRVAS), GFP_KERNEL); ++ VIDEO_DBG("pAstRVAS: 0x%llx\n", pAstRVAS); ++ ++ if (!pAstRVAS) { ++ dev_err(pAstRVAS->pdev, "Cannot allocate device structure\n"); ++ return -ENOMEM; ++ } ++ dev_set_drvdata(&pdev->dev, pAstRVAS); ++ pAstRVAS->pdev = (void *)&pdev->dev; ++ ++ // Get resources ++ result = video_drv_get_resources(pdev); ++ if (result < 0) { ++ dev_err(pAstRVAS->pdev, "video_probe: Error getting resources\n"); ++ return result; ++ } ++ ++ if (pAstRVAS->config->version == 7) ++ result = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); ++ else ++ result = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); ++ ++ if (result) { ++ dev_err(&pdev->dev, "Failed to set DMA mask\n"); ++ of_reserved_mem_device_release(&pdev->dev); ++ } ++ ++ //get irqs ++ result = video_drv_get_irqs(pdev); ++ if (result < 0) { ++ dev_err(pAstRVAS->pdev, "video_probe: Error getting irqs\n"); ++ return result; ++ } ++ ++ pAstRVAS->rvas_reset = devm_reset_control_get_by_index(&pdev->dev, 0); ++ if (IS_ERR(pAstRVAS->rvas_reset)) { ++ dev_err(&pdev->dev, "can't get rvas reset\n"); ++ return -ENOENT; ++ } ++ if (pAstRVAS->config->version == 7) ++ reset_control_deassert(pAstRVAS->rvas_reset); ++ pAstRVAS->video_engine_reset = devm_reset_control_get_shared_by_index(&pdev->dev, 1); ++ if (IS_ERR(pAstRVAS->video_engine_reset)) { ++ dev_err(&pdev->dev, "can't get video engine reset\n"); ++ return -ENOENT; ++ } ++ ++ //prepare video engine clock ++ result = video_drv_get_clock(pdev); ++ if (result < 0) { ++ dev_err(pAstRVAS->pdev, "video_probe: Error getting clocks\n"); ++ return result; ++ } ++ ++ dp_node = of_find_compatible_node(NULL, NULL, "aspeed,ast2600-displayport"); ++ if (!dp_node) { ++ dev_err(&pdev->dev, "cannot find dp node\n"); ++ } else { ++ pAstRVAS->dp_base = of_iomap(dp_node, 0); ++ if (!pAstRVAS->dp_base) ++ dev_err(&pdev->dev, "failed to iomem of display port\n"); ++ } ++ ++ if (pAstRVAS->config->version == 7) ++ edac_node = of_find_compatible_node(NULL, NULL, "aspeed,ast2700-sdram-edac"); ++ else ++ edac_node = of_find_compatible_node(NULL, NULL, "aspeed,ast2600-sdram-edac"); ++ ++ if (!edac_node) { ++ dev_err(&pdev->dev, "cannot find edac node\n"); ++ } else { ++ mcr_base = of_iomap(edac_node, 0); ++ if (!mcr_base) ++ dev_err(&pdev->dev, "failed to iomem of MCR\n"); ++ } ++ ++ set_FBInfo_size(pAstRVAS, mcr_base); ++ ++ //scu ++ if (pAstRVAS->config->version == 7) { ++ sdram_scu = syscon_regmap_lookup_by_compatible("aspeed,ast2700-scu0"); ++ VIDEO_DBG("sdram_scu: 0x%llx\n", sdram_scu); ++ if (IS_ERR(sdram_scu)) { ++ dev_err(&pdev->dev, "failed to find ast2700-scu0 regmap\n"); ++ return PTR_ERR(sdram_scu); ++ } ++ pAstRVAS->scu = sdram_scu; ++ ++ sdram_scu = syscon_regmap_lookup_by_compatible("aspeed,ast2700-scu1"); ++ VIDEO_DBG("sdram_scu: 0x%llx\n", sdram_scu); ++ if (IS_ERR(sdram_scu)) { ++ dev_err(&pdev->dev, "failed to find ast2700-scu0 regmap\n"); ++ return PTR_ERR(sdram_scu); ++ } ++ pAstRVAS->scu_io = sdram_scu; ++ } else { ++ sdram_scu = syscon_regmap_lookup_by_compatible("aspeed,ast2600-scu"); ++ VIDEO_DBG("sdram_scu: 0x%llx\n", sdram_scu); ++ if (IS_ERR(sdram_scu)) { ++ dev_err(&pdev->dev, "failed to find ast2600-scu regmap\n"); ++ return PTR_ERR(sdram_scu); ++ } ++ pAstRVAS->scu = sdram_scu; ++ } ++ pAstRVAS->rvas_dev = video_misc; ++ if (pAstRVAS->config->version == 7) { ++ if (of_alias_get_id(pdev->dev.of_node, "rvas") == 1) { ++ pAstRVAS->rvas_index = 1; ++ pAstRVAS->rvas_dev.name = "rvas1"; ++ } else { ++ pAstRVAS->rvas_index = 0; ++ } ++ } ++ pAstRVAS->rvas_dev.parent = &pdev->dev; ++ result = misc_register(&pAstRVAS->rvas_dev); ++ if (result) { ++ pr_err("Failed in rvas misc device register (err: %d)\n", result); ++ return result; ++ } ++ pr_info("Video misc minor %d\n", pAstRVAS->rvas_dev.minor); ++ VIDEO_DBG("pdev: 0x%llx dev: 0x%llx pAstRVAS: 0x%llx rvas_dev: 0x%llx\n", pdev, ++ &pdev->dev, pAstRVAS, pAstRVAS->rvas_dev); ++ ++ if (sysfs_create_group(&pdev->dev.kobj, &rvas_attribute_group)) { ++ pr_err("Failed in creating group\n"); ++ return -1; ++ } ++ ++ VIDEO_DBG("Disabling interrupts...\n"); ++ disable_grce_tse_interrupt(pAstRVAS); ++ ++ //reserve memory ++ of_reserved_mem_device_init(&pdev->dev); ++ ++ // map irqs to irq_handlers ++ result = video_drv_map_irqs(pdev); ++ if (result < 0) { ++ dev_err(pAstRVAS->pdev, "video_probe: Error mapping irqs\n"); ++ return result; ++ } ++ VIDEO_DBG("After IRQ registration\n"); ++ ++ init_osr_es(pAstRVAS); ++ rvas_init(pAstRVAS); ++ video_engine_reserveMem(pAstRVAS); ++ video_on(pAstRVAS); ++ ++ pr_info("RVAS: driver successfully loaded.\n"); ++ return result; ++} ++ ++static void rvas_init(struct AstRVAS *pAstRVAS) ++{ ++ VIDEO_ENG_DBG("\n"); ++ ++ reset_snoop_engine(pAstRVAS); ++ update_video_geometry(pAstRVAS); ++ ++ set_snoop_engine(true, pAstRVAS); ++ enable_grce_tse_interrupt(pAstRVAS); ++} ++ ++static void video_engine_init(struct AstRVAS *pAstRVAS) ++{ ++ VIDEO_ENG_DBG("\n"); ++ // video engine ++ video_ctrl_init(pAstRVAS); ++ video_engine_rc4Reset(pAstRVAS); ++ set_direct_mode(pAstRVAS); ++ video_set_Window(pAstRVAS); ++ enable_video_interrupt(pAstRVAS); ++} ++ ++static void video_drv_remove(struct platform_device *pdev) ++{ ++ struct AstRVAS *pAstRVAS = NULL; ++ ++ VIDEO_DBG("\n"); ++ pAstRVAS = platform_get_drvdata(pdev); ++ video_off(pAstRVAS); ++ VIDEO_DBG("disable_grce_tse_interrupt...\n"); ++ disable_grce_tse_interrupt(pAstRVAS); ++ disable_video_interrupt(pAstRVAS); ++ ++ sysfs_remove_group(&pdev->dev.kobj, &rvas_attribute_group); ++ ++ VIDEO_DBG("misc_deregister...\n"); ++ misc_deregister(&pAstRVAS->rvas_dev); ++ ++ VIDEO_DBG("Releasing OSRes...\n"); ++ release_osr_es(pAstRVAS); ++ ++ free_video_engine_memory(pAstRVAS); ++ pr_info("RVAS: driver successfully unloaded.\n"); ++} ++ ++static const u64 ast2400_dram_table[] = { ++ 0x04000000, //64MB ++ 0x08000000, //128MB ++ 0x10000000, //256MB ++ 0x20000000, //512MB ++}; ++ ++static const u64 ast2500_dram_table[] = { ++ 0x08000000, //128MB ++ 0x10000000, //256MB ++ 0x20000000, //512MB ++ 0x40000000, //1024MB ++}; ++ ++static const u64 ast2600_dram_table[] = { ++ 0x10000000, //256MB ++ 0x20000000, //512MB ++ 0x40000000, //1024MB ++ 0x80000000, //2048MB ++}; ++ ++static const u64 ast2700_dram_table[] = { ++ 0x10000000, //256MB ++ 0x20000000, //512MB ++ 0x40000000, //1024MB ++ 0x80000000, //2048MB ++ 0x100000000, //4GB ++ 0x200000000, //8GB ++}; ++ ++static const u64 aspeed_vga_table[] = { ++ 0x800000, //8MB ++ 0x1000000, //16MB ++ 0x2000000, //32MB ++ 0x4000000, //64MB ++}; ++ ++static const struct aspeed_rvas_config ast2600_config = { ++ .version = 6, ++ .dram_table = ast2600_dram_table, ++}; ++ ++static const struct aspeed_rvas_config ast2700_config = { ++ .version = 7, ++ .dram_table = ast2700_dram_table, ++}; ++ ++static void set_FBInfo_size(struct AstRVAS *pAstRVAS, void __iomem *mcr_base) ++{ ++ if (pAstRVAS->config->version == 7) { ++ u32 reg_mcr010 = readl(mcr_base + MCR_CONF_AST27XX); ++ u32 reg_mcr100 = readl(mcr_base + MCR_GRAPHIC_MEM_CONF_AST27XX); ++ ++ pAstRVAS->FBInfo.qwDRAMSize = pAstRVAS->config->dram_table[((reg_mcr010 & 0x1c) >> 2)]; ++ pAstRVAS->FBInfo.dwVGASize = (reg_mcr100 & 0x1) ? 0x4000000 : 0x2000000; ++ } else { ++ u32 reg_mcr004 = readl(mcr_base + MCR_CONF); ++ ++ pAstRVAS->FBInfo.qwDRAMSize = pAstRVAS->config->dram_table[reg_mcr004 & 0x3]; ++ pAstRVAS->FBInfo.dwVGASize = aspeed_vga_table[((reg_mcr004 & 0xC) >> 2)]; ++ } ++} ++ ++static const struct of_device_id ast_rvas_match[] = { ++ { .compatible = "aspeed,ast2700-rvas", .data = &ast2700_config }, ++ { .compatible = "aspeed,ast2600-rvas", .data = &ast2600_config }, ++ { }, ++}; ++ ++MODULE_DEVICE_TABLE(of, ast_rvas_match); ++ ++static struct platform_driver video_driver = { ++ .probe = video_drv_probe, ++ .remove = video_drv_remove, ++ .driver = { .of_match_table = of_match_ptr(ast_rvas_match), .name = ++ RVAS_DRIVER_NAME, .owner = THIS_MODULE, }, }; ++ ++module_platform_driver(video_driver); ++ ++MODULE_AUTHOR("ASPEED Technology"); ++MODULE_DESCRIPTION("RVAS video driver module for AST2600"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c +--- a/drivers/spi/spi-aspeed-smc.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/spi/spi-aspeed-smc.c 2026-09-09 15:51:44.654239039 +0000 +@@ -7,10 +7,16 @@ + */ + + #include ++#include ++#include ++#include ++#include + #include ++#include + #include + #include + #include ++#include + #include + #include + +@@ -23,12 +29,21 @@ + /* CE Control Register */ + #define CE_CTRL_REG 0x4 + ++#define INTR_CTRL_STATUS_REG 0x08 ++#define SPI_DMA_STATUS BIT(11) ++#define SPI_DMA_IRQ_STS BIT(19) ++ ++#define CMD_CTRL_REG 0xc ++ + /* CEx Control Register */ + #define CE0_CTRL_REG 0x10 + #define CTRL_IO_MODE_MASK GENMASK(30, 28) + #define CTRL_IO_SINGLE_DATA 0x0 + #define CTRL_IO_DUAL_DATA BIT(29) ++#define CTRL_IO_DUAL_ADDR_DATA GENMASK(29, 28) + #define CTRL_IO_QUAD_DATA BIT(30) ++#define CTRL_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28)) ++#define CTRL_IO_QUAD_IO BIT(31) + #define CTRL_COMMAND_SHIFT 16 + #define CTRL_IO_ADDRESS_4B BIT(13) /* AST2400 SPI only */ + #define CTRL_IO_DUMMY_SET(dummy) \ +@@ -42,14 +57,43 @@ + #define CTRL_IO_MODE_WRITE 0x2 + #define CTRL_IO_MODE_USER 0x3 + +-#define CTRL_IO_CMD_MASK 0xf0ff40c3 ++#define CTRL_IO_CMD_MASK 0xf0ff40c7 + + /* CEx Address Decoding Range Register */ + #define CE0_SEGMENT_ADDR_REG 0x30 + ++#define MISC_CTRL_REG 0x54 ++#define SPI_USER_CMD_MODE BIT(27) ++#define SPI_CS_TO_DIS BIT(26) ++#define SPI_UNALGNED_ACCESS BIT(24) ++#define SPI_CS_CONTINUOUS BIT(16) ++#define DUMMY_OUTPUT_DATA GENMASK(7, 0) ++ ++#define HOST_DIRECT_ACCESS_CMD_CTRL4 0x6c ++#define HOST_DIRECT_ACCESS_CMD_CTRL2 0x74 ++ ++#define DMA_HI_ADDR_REG 0x7c ++#define DMA_DRAM_HI_ADDR (0x4) ++ ++#define DMA_CTRL_REG 0x80 ++#define SPI_DMA_ENABLE BIT(0) ++#define SPI_DMA_IRQ_EN BIT(3) ++#define SPI_DAM_GRANT BIT(30) ++#define SPI_DAM_REQUEST BIT(31) ++#define DMA_GET_REQ_MAGIC 0xaeed0000 ++#define DMA_DISCARD_REQ_MAGIC 0xdeea0000 ++ ++#define DMA_FLASH_ADDR_REG 0x84 ++#define DMA_RAM_ADDR_REG 0x88 ++#define DMA_LEN_REG 0x8c ++ + /* CEx Read timing compensation register */ + #define CE0_TIMING_COMPENSATION_REG 0x94 + ++#define ASPEED_SPI_OP_BUF_LEN 0x10000 ++ ++static spinlock_t g_lock; ++ + enum aspeed_spi_ctl_reg_value { + ASPEED_SPI_BASE, + ASPEED_SPI_READ, +@@ -57,6 +101,13 @@ + ASPEED_SPI_MAX, + }; + ++enum aspeed_spi_op_field { ++ SPI_OP_CMD = 1, ++ SPI_OP_ADDR, ++ SPI_OP_DATA, ++ SPI_OP_ALL, ++}; ++ + struct aspeed_spi; + + struct aspeed_spi_chip { +@@ -64,7 +115,8 @@ + u32 cs; + void __iomem *ctl; + void __iomem *ahb_base; +- u32 ahb_window_size; ++ phys_addr_t ahb_base_phy; ++ size_t ahb_window_sz; + u32 ctl_val[ASPEED_SPI_MAX]; + u32 clk_freq; + }; +@@ -78,43 +130,78 @@ + u32 timing; + u32 hclk_mask; + u32 hdiv_max; ++ size_t min_window_sz; ++ u32 ver; + +- u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); +- u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); +- u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); ++ int (*adjust_window)(struct aspeed_spi *aspi); ++ u64 (*segment_start)(struct aspeed_spi *aspi, u32 reg); ++ u64 (*segment_end)(struct aspeed_spi *aspi, u32 reg); ++ u32 (*segment_reg)(struct aspeed_spi *aspi, u64 start, u64 end); ++ u32 (*get_clk_div)(struct aspeed_spi_chip *chip, u32 hz); + int (*calibrate)(struct aspeed_spi_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf); ++ void (*safs_init)(struct aspeed_spi *aspi, struct spi_mem_op *op); ++ void (*safs_start)(struct aspeed_spi *aspi); ++ void (*safs_stop)(struct aspeed_spi *aspi); + }; + + #define ASPEED_SPI_MAX_NUM_CS 5 + ++#define ASPEED_SPI_NORMAL_MODE 0x00000001 ++#define ASPEED_SPI_DMA_WRITE_MODE 0x00000002 ++#define ASPEED_SPI_FIXED_LOW_W_CLK 0x00000004 ++#define ASPEED_SPI_MIN_WINDOW 0x00000008 ++#define ASPEED_SPI_DMA_MODE 0x00000010 ++#define ASPEED_SPI_PURE_USER_MODE 0x00000020 ++#define ASPEED_SPI_TIMING_CLB_DISABLED 0x00000040 ++#define ASPEED_SPI_LTPI_SUPPORT 0x00000080 ++#define ASPEED_SPI_QUAD_ADDR_SUPPORT 0x00000100 ++ + struct aspeed_spi { + const struct aspeed_spi_data *data; + + void __iomem *regs; +- void __iomem *ahb_base; +- u32 ahb_base_phy; +- u32 ahb_window_size; ++ phys_addr_t ahb_base_phy; ++ u64 ltpi_base_phy; ++ size_t ahb_window_sz; ++ u32 num_cs; + struct device *dev; + + struct clk *clk; + u32 clk_freq; + + struct aspeed_spi_chip chips[ASPEED_SPI_MAX_NUM_CS]; ++ ++ int irq; ++ struct completion dma_done; ++ dma_addr_t dma_addr_phy; ++ void __iomem *op_buf; ++ u32 flag; + }; + +-static u32 aspeed_spi_get_io_mode(const struct spi_mem_op *op) ++static u32 aspeed_spi_get_io_mode(const struct spi_mem_op *op, ++ enum aspeed_spi_op_field field) + { +- switch (op->data.buswidth) { +- case 1: +- return CTRL_IO_SINGLE_DATA; +- case 2: +- return CTRL_IO_DUAL_DATA; +- case 4: +- return CTRL_IO_QUAD_DATA; +- default: +- return CTRL_IO_SINGLE_DATA; ++ if (field == SPI_OP_ALL || field == SPI_OP_CMD) { ++ if (op->cmd.buswidth == 4) ++ return CTRL_IO_QUAD_IO; + } ++ ++ if (field == SPI_OP_ALL || field == SPI_OP_ADDR) { ++ if (op->addr.buswidth == 4) ++ return CTRL_IO_QUAD_ADDR_DATA; ++ else if (op->addr.buswidth == 2) ++ return CTRL_IO_DUAL_ADDR_DATA; ++ } ++ ++ if (field == SPI_OP_ALL || field == SPI_OP_DATA) { ++ if (op->data.buswidth == 4) ++ return CTRL_IO_QUAD_DATA; ++ else if (op->data.buswidth == 2) ++ return CTRL_IO_DUAL_DATA; ++ } ++ ++ return CTRL_IO_SINGLE_DATA; + } + + static void aspeed_spi_set_io_mode(struct aspeed_spi_chip *chip, u32 io_mode) +@@ -137,11 +224,12 @@ + + ctl &= ~CTRL_CE_STOP_ACTIVE; + writel(ctl, chip->ctl); ++ readl(chip->ctl); + } + + static void aspeed_spi_stop_user(struct aspeed_spi_chip *chip) + { +- u32 ctl = chip->ctl_val[ASPEED_SPI_READ] | ++ u32 ctl = chip->ctl_val[ASPEED_SPI_BASE] | + CTRL_IO_MODE_USER | CTRL_CE_STOP_ACTIVE; + + writel(ctl, chip->ctl); +@@ -178,8 +266,13 @@ + return 0; + } + +-static int aspeed_spi_send_cmd_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, +- u64 offset, u32 opcode) ++static void aspeed_spi_send_cmd(struct aspeed_spi_chip *chip, u8 opcode) ++{ ++ aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); ++} ++ ++static int aspeed_spi_send_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, ++ u64 offset) + { + __be32 temp; + u32 cmdaddr; +@@ -187,20 +280,18 @@ + switch (addr_nbytes) { + case 3: + cmdaddr = offset & 0xFFFFFF; +- cmdaddr |= opcode << 24; +- +- temp = cpu_to_be32(cmdaddr); +- aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); ++ temp = cpu_to_be32(cmdaddr) >> 8; ++ aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 3); + break; + case 4: + temp = cpu_to_be32(offset); +- aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); + break; + default: + WARN_ONCE(1, "Unexpected address width %u", addr_nbytes); + return -EOPNOTSUPP; + } ++ + return 0; + } + +@@ -230,27 +321,36 @@ + const struct spi_mem_op *op, + u64 offset, size_t len, void *buf) + { +- int io_mode = aspeed_spi_get_io_mode(op); ++ int io_mode; + u8 dummy = 0xFF; + int i; + int ret; + + aspeed_spi_start_user(chip); + +- ret = aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, offset, op->cmd.opcode); ++ io_mode = aspeed_spi_get_io_mode(op, SPI_OP_CMD); ++ aspeed_spi_set_io_mode(chip, io_mode); ++ aspeed_spi_send_cmd(chip, op->cmd.opcode); ++ ++ io_mode = aspeed_spi_get_io_mode(op, SPI_OP_ADDR); ++ aspeed_spi_set_io_mode(chip, io_mode); ++ ret = aspeed_spi_send_addr(chip, op->addr.nbytes, op->addr.val); + if (ret < 0) + goto stop_user; + + if (op->dummy.buswidth && op->dummy.nbytes) { +- for (i = 0; i < op->dummy.nbytes / op->dummy.buswidth; i++) +- aspeed_spi_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); ++ for (i = 0; i < op->dummy.nbytes; i++) ++ aspeed_spi_write_to_ahb(chip->ahb_base, ++ &dummy, sizeof(dummy)); + } + ++ io_mode = aspeed_spi_get_io_mode(op, SPI_OP_DATA); + aspeed_spi_set_io_mode(chip, io_mode); +- + aspeed_spi_read_from_ahb(buf, chip->ahb_base, len); ++ + stop_user: + aspeed_spi_stop_user(chip); ++ + return ret; + } + +@@ -258,32 +358,51 @@ + const struct spi_mem_op *op) + { + int ret; ++ int io_mode; + + aspeed_spi_start_user(chip); +- ret = aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, op->addr.val, op->cmd.opcode); ++ ++ io_mode = aspeed_spi_get_io_mode(op, SPI_OP_CMD); ++ aspeed_spi_set_io_mode(chip, io_mode); ++ aspeed_spi_send_cmd(chip, op->cmd.opcode); ++ ++ io_mode = aspeed_spi_get_io_mode(op, SPI_OP_ADDR); ++ aspeed_spi_set_io_mode(chip, io_mode); ++ ret = aspeed_spi_send_addr(chip, op->addr.nbytes, op->addr.val); + if (ret < 0) + goto stop_user; ++ ++ io_mode = aspeed_spi_get_io_mode(op, SPI_OP_DATA); ++ aspeed_spi_set_io_mode(chip, io_mode); + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, op->data.nbytes); ++ + stop_user: + aspeed_spi_stop_user(chip); ++ + return ret; + } + + /* support for 1-1-1, 1-1-2 or 1-1-4 */ + static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) + { ++ struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->controller); ++ + if (op->cmd.buswidth > 1) + return false; + + if (op->addr.nbytes != 0) { +- if (op->addr.buswidth > 1) ++ if (op->addr.buswidth > 1 && ++ !(aspi->flag & ASPEED_SPI_QUAD_ADDR_SUPPORT)) + return false; + if (op->addr.nbytes < 3 || op->addr.nbytes > 4) + return false; + } + + if (op->dummy.nbytes != 0) { +- if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) ++ if (op->dummy.buswidth > 1 && ++ !(aspi->flag & ASPEED_SPI_QUAD_ADDR_SUPPORT)) ++ return false; ++ if (op->dummy.nbytes > 7) + return false; + } + +@@ -294,13 +413,12 @@ + } + + static const struct aspeed_spi_data ast2400_spi_data; ++static const struct aspeed_spi_data ast2500_fmc_data; + + static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) + { + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->controller); + struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(mem->spi, 0)]; +- u32 addr_mode, addr_mode_backup; +- u32 ctl_val; + int ret = 0; + + dev_dbg(aspi->dev, +@@ -310,39 +428,14 @@ + op->dummy.buswidth, op->data.buswidth, + op->addr.nbytes, op->dummy.nbytes, op->data.nbytes); + +- addr_mode = readl(aspi->regs + CE_CTRL_REG); +- addr_mode_backup = addr_mode; +- +- ctl_val = chip->ctl_val[ASPEED_SPI_BASE]; +- ctl_val &= ~CTRL_IO_CMD_MASK; +- +- ctl_val |= op->cmd.opcode << CTRL_COMMAND_SHIFT; +- +- /* 4BYTE address mode */ +- if (op->addr.nbytes) { +- if (op->addr.nbytes == 4) +- addr_mode |= (0x11 << chip->cs); +- else +- addr_mode &= ~(0x11 << chip->cs); +- +- if (op->addr.nbytes == 4 && chip->aspi->data == &ast2400_spi_data) +- ctl_val |= CTRL_IO_ADDRESS_4B; +- } +- +- if (op->dummy.nbytes) +- ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); +- +- if (op->data.nbytes) +- ctl_val |= aspeed_spi_get_io_mode(op); +- +- if (op->data.dir == SPI_MEM_DATA_OUT) +- ctl_val |= CTRL_IO_MODE_WRITE; +- else +- ctl_val |= CTRL_IO_MODE_READ; ++ /* no operation for AST2500 when SW reset is executed */ ++ if (aspi->data == &ast2500_fmc_data && ++ (op->cmd.opcode == SPINOR_OP_SRSTEN || ++ op->cmd.opcode == SPINOR_OP_SRST)) ++ return ret; + +- if (addr_mode != addr_mode_backup) +- writel(addr_mode, aspi->regs + CE_CTRL_REG); +- writel(ctl_val, chip->ctl); ++ if (aspi->data->safs_stop) ++ aspi->data->safs_stop(aspi); + + if (op->data.dir == SPI_MEM_DATA_IN) { + if (!op->addr.nbytes) +@@ -357,13 +450,524 @@ + ret = aspeed_spi_write_user(chip, op); + } + ++ if (aspi->data->safs_start) ++ aspi->data->safs_start(aspi); ++ + /* Restore defaults */ +- if (addr_mode != addr_mode_backup) +- writel(addr_mode_backup, aspi->regs + CE_CTRL_REG); + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); + return ret; + } + ++/* ++ * If the slave device is SPI NOR flash, there are two types ++ * of command mode for ASPEED SPI memory controller used to ++ * transfer data. The first one is user mode and the other is ++ * normal read/write mode. With user mode, SPI NOR flash ++ * command, address and data processes are all handled by CPU. ++ * With normal read/write mode, we can easily read/write data ++ * to flash by reading or writing related remapped address, ++ * then, SPI NOR flash command and address will be transferred ++ * to flash by controller automatically. Besides, ASPEED SPI ++ * memory controller can also block address or data bytes by ++ * configure FMC0C/SPIR0C address and data mask register in ++ * order to satisfy the following SPI flash operation sequences: ++ * (command) only, (command and address) only or ++ * (coommand and data) only. ++ */ ++static int aspeed_spi_exec_op_normal_mode(struct spi_mem *mem, ++ const struct spi_mem_op *op) ++{ ++ struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->controller); ++ struct device *dev = aspi->dev; ++ struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(mem->spi, 0)]; ++ u32 cs = spi_get_chipselect(mem->spi, 0); ++ u32 ctrl_val; ++ u32 addr_mode_reg, addr_mode_reg_backup; ++ u32 addr_data_mask = 0; ++ void __iomem *op_addr; ++ const void *data_buf; ++ u32 data_byte = 0; ++ u32 dummy_data = 0; ++ unsigned long flags; ++ ++ dev_dbg(dev, "cs:%d, cmd:%x(%d),addr:%llx(%d),dummy:%d(%d),data_len:%x(%d)\n", ++ cs, op->cmd.opcode, op->cmd.buswidth, op->addr.val, ++ op->addr.buswidth, op->dummy.nbytes, op->dummy.buswidth, ++ op->data.nbytes, op->data.buswidth); ++ ++ addr_mode_reg = readl(aspi->regs + CE_CTRL_REG); ++ addr_mode_reg_backup = addr_mode_reg; ++ addr_data_mask = readl(aspi->regs + CMD_CTRL_REG); ++ ++ ctrl_val = chip->ctl_val[ASPEED_SPI_BASE]; ++ ctrl_val &= ~CTRL_IO_CMD_MASK; ++ ctrl_val |= aspeed_spi_get_io_mode(op, SPI_OP_ALL); ++ ++ /* configure opcode */ ++ ctrl_val |= op->cmd.opcode << 16; ++ ++ /* configure operation address, address length and address mask */ ++ if (op->addr.nbytes != 0) { ++ if (op->addr.nbytes == 3) ++ addr_mode_reg &= ~(0x11 << cs); ++ else ++ addr_mode_reg |= (0x11 << cs); ++ ++ addr_data_mask &= 0x0f; ++ op_addr = chip->ahb_base + op->addr.val; ++ } else { ++ addr_data_mask |= 0xf0; ++ op_addr = chip->ahb_base; ++ } ++ ++ if (op->dummy.nbytes != 0) { ++ ctrl_val |= ((op->dummy.nbytes & 0x3) << 6 | ++ ((op->dummy.nbytes & 0x4) >> 2) << 14); ++ } ++ ++ /* configure data io mode and data mask */ ++ if (op->data.nbytes != 0) { ++ addr_data_mask &= 0xF0; ++ data_byte = op->data.nbytes; ++ if (op->data.dir == SPI_MEM_DATA_OUT) { ++ if (data_byte % 4 != 0) { ++ memset(aspi->op_buf, 0xff, ++ (((data_byte + 3) / 4) * 4)); ++ memcpy(aspi->op_buf, op->data.buf.out, data_byte); ++ data_buf = aspi->op_buf; ++ data_byte = (((data_byte + 3) / 4) * 4); ++ } else { ++ data_buf = op->data.buf.out; ++ } ++ } else { ++ data_buf = op->data.buf.in; ++ } ++ } else { ++ addr_data_mask |= 0x0f; ++ data_byte = 1; ++ data_buf = &dummy_data; ++ } ++ ++ /* configure command mode */ ++ if (op->data.dir == SPI_MEM_DATA_OUT) ++ ctrl_val |= CTRL_IO_MODE_WRITE; ++ else ++ ctrl_val |= CTRL_IO_MODE_READ; ++ ++ /* set controller registers */ ++ writel(ctrl_val, chip->ctl); ++ writel(addr_mode_reg, aspi->regs + CE_CTRL_REG); ++ writel(addr_data_mask, aspi->regs + CMD_CTRL_REG); ++ ++ dev_dbg(dev, "ctrl: 0x%08x, addr_mode: 0x%x, mask: 0x%x, addr:0x%p\n", ++ ctrl_val, addr_mode_reg, addr_data_mask, op_addr); ++ ++ /* trigger spi transmission or reception sequence */ ++ spin_lock_irqsave(&g_lock, flags); ++ ++ if (op->data.dir == SPI_MEM_DATA_OUT) ++ memcpy_toio(op_addr, data_buf, data_byte); ++ else ++ memcpy_fromio((void *)data_buf, op_addr, data_byte); ++ ++ spin_unlock_irqrestore(&g_lock, flags); ++ ++ /* restore controller setting */ ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ writel(addr_mode_reg_backup, aspi->regs + CE_CTRL_REG); ++ writel(0x0, aspi->regs + CMD_CTRL_REG); ++ ++ return 0; ++} ++ ++#define MAX_READ_SZ_ONCE 0x3000 /* 12KB */ ++ ++/* ++ * When DMA memory mode is enabled, there is a limitation for AST2600, ++ * both DMA source and destination address should be 4-byte aligned. ++ * Thus, a 4-byte aligned buffer should be allocated previously and ++ * CPU needs to copy data from it after DMA done. ++ */ ++static ssize_t aspeed_2600_spi_dirmap_dma_read(struct spi_mem_dirmap_desc *desc, ++ u64 offs, size_t len, void *buf) ++{ ++ int ret = 0; ++ u32 timeout = 0; ++ struct aspeed_spi *aspi = spi_controller_get_devdata(desc->mem->spi->controller); ++ struct device *dev = aspi->dev; ++ struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; ++ struct spi_mem_op op_tmpl = desc->info.op_tmpl; ++ u32 reg_val; ++ u32 extra; ++ u32 tb_read_len = len; ++ u32 read_len; ++ u32 buf_offs = 0; ++ u32 flash_offs = (u32)offs; ++ ++ if (chip->ahb_window_sz < offs + len) { ++ dev_err(dev, "read range exceeds flash remapping size\n"); ++ return 0; ++ } ++ ++ dev_dbg(dev, "read op:0x%x, addr:0x%llx, len:0x%zx\n", ++ op_tmpl.cmd.opcode, offs, len); ++ ++ while (tb_read_len > 0) { ++ /* read max 10KB bytes once */ ++ read_len = MAX_READ_SZ_ONCE - (flash_offs % MAX_READ_SZ_ONCE); ++ if (tb_read_len < read_len) ++ read_len = tb_read_len; ++ ++ /* For AST2600 SPI DMA, flash offset should be 4 byte aligned */ ++ extra = flash_offs % 4; ++ if (extra != 0) { ++ flash_offs = (flash_offs / 4) * 4; ++ read_len += extra; ++ } ++ ++ writel(DMA_GET_REQ_MAGIC, aspi->regs + DMA_CTRL_REG); ++ if (readl(aspi->regs + DMA_CTRL_REG) & SPI_DAM_REQUEST) { ++ while (!(readl(aspi->regs + DMA_CTRL_REG) & ++ SPI_DAM_GRANT)) ++ ; ++ } ++ ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ ++ /* ++ * don't use dma_map_single here, since we cannot make sure the buf's ++ * start address is 4-byte-aligned. ++ */ ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ writel(aspi->dma_addr_phy, aspi->regs + DMA_RAM_ADDR_REG); ++ writel(chip->ahb_base_phy + flash_offs, aspi->regs + DMA_FLASH_ADDR_REG); ++ writel(read_len - 1, aspi->regs + DMA_LEN_REG); ++ ++ /* enable DMA irq */ ++ reg_val = readl(aspi->regs + INTR_CTRL_STATUS_REG); ++ reg_val |= SPI_DMA_IRQ_EN; ++ writel(reg_val, aspi->regs + INTR_CTRL_STATUS_REG); ++ ++ reinit_completion(&aspi->dma_done); ++ ++ /* enable read DMA */ ++ writel(0x1, aspi->regs + DMA_CTRL_REG); ++ timeout = wait_for_completion_timeout(&aspi->dma_done, ++ msecs_to_jiffies(2000)); ++ if (timeout == 0) { ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ writel(DMA_DISCARD_REQ_MAGIC, aspi->regs + DMA_CTRL_REG); ++ dev_err(dev, "read data timeout %d\n", ret); ++ ret = -1; ++ goto end; ++ } else { ++ memcpy(buf + buf_offs, aspi->op_buf + extra, read_len - extra); ++ } ++ ++ read_len -= extra; ++ ++ buf_offs += read_len; ++ flash_offs += read_len; ++ tb_read_len -= read_len; ++ } ++ ++end: ++ return ret ? 0 : len; ++} ++ ++static ssize_t aspeed_2600_spi_dirmap_dma_write(struct spi_mem_dirmap_desc *desc, ++ u64 offs, size_t len, ++ const void *buf) ++{ ++ int ret = 0; ++ struct aspeed_spi *aspi = spi_controller_get_devdata(desc->mem->spi->controller); ++ struct device *dev = aspi->dev; ++ struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; ++ u32 timeout = 0; ++ u32 reg_val; ++ struct spi_mem_op op_tmpl = desc->info.op_tmpl; ++ ++ if (chip->ahb_window_sz < offs + len) { ++ dev_info(dev, "write range exceeds flash remapping size\n"); ++ return 0; ++ } ++ ++ if (len < 1) ++ return 0; ++ ++ if (len > ASPEED_SPI_OP_BUF_LEN) { ++ dev_info(dev, ++ "written length exceeds expected value (0x%zx)\n", ++ len); ++ return 0; ++ } ++ ++ dev_dbg(dev, "write op:0x%x, addr:0x%llx, len:0x%zx\n", ++ op_tmpl.cmd.opcode, offs, len); ++ ++ writel(DMA_GET_REQ_MAGIC, aspi->regs + DMA_CTRL_REG); ++ if (readl(aspi->regs + DMA_CTRL_REG) & SPI_DAM_REQUEST) { ++ while (!(readl(aspi->regs + DMA_CTRL_REG) & ++ SPI_DAM_GRANT)) ++ ; ++ } ++ ++ writel(chip->ctl_val[ASPEED_SPI_WRITE], chip->ctl); ++ ++ /* ++ * don't use dma_map_single here, since we cannot make sure the buf's ++ * start address is 4-byte-aligned. ++ */ ++ memcpy(aspi->op_buf, buf, len); ++ ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ writel(aspi->dma_addr_phy, aspi->regs + DMA_RAM_ADDR_REG); ++ writel(chip->ahb_base_phy + offs, aspi->regs + DMA_FLASH_ADDR_REG); ++ writel(len - 1, aspi->regs + DMA_LEN_REG); ++ ++ /* enable DMA irq */ ++ reg_val = readl(aspi->regs + INTR_CTRL_STATUS_REG); ++ reg_val |= SPI_DMA_IRQ_EN; ++ writel(reg_val, aspi->regs + INTR_CTRL_STATUS_REG); ++ ++ reinit_completion(&aspi->dma_done); ++ ++ /* enable write DMA */ ++ writel(0x3, aspi->regs + DMA_CTRL_REG); ++ timeout = wait_for_completion_timeout(&aspi->dma_done, msecs_to_jiffies(2000)); ++ if (timeout == 0) { ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ writel(DMA_DISCARD_REQ_MAGIC, aspi->regs + DMA_CTRL_REG); ++ dev_err(dev, "write data timeout %d\n", ret); ++ ret = -1; ++ } ++ ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ ++ return ret ? 0 : len; ++} ++ ++/* ++ * When DMA memory mode is enabled, there is a limitation for AST2700, ++ * both DMA source and destination address should be 4-byte aligned. ++ * Thus, a 4-byte aligned buffer should be allocated previously and ++ * CPU needs to copy data from it after DMA done. ++ */ ++static ssize_t aspeed_2700_spi_dirmap_dma_read(struct spi_mem_dirmap_desc *desc, ++ u64 offs, size_t len, void *buf) ++{ ++ int ret = 0; ++ u32 timeout = 0; ++ struct aspeed_spi *aspi = spi_controller_get_devdata(desc->mem->spi->controller); ++ struct device *dev = aspi->dev; ++ struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; ++ struct spi_mem_op op_tmpl = desc->info.op_tmpl; ++ u32 reg_val; ++ u32 extra; ++ u32 tb_read_len = len; ++ u32 read_len; ++ u32 buf_offs = 0; ++ u32 flash_offs = (u32)offs; ++ u32 flash_addr; ++ ++ if (chip->ahb_window_sz < offs + len) { ++ dev_err(dev, "read range exceeds flash remapping size\n"); ++ return 0; ++ } ++ ++ dev_dbg(dev, "read op:0x%x, addr:0x%llx, len:0x%zx\n", ++ op_tmpl.cmd.opcode, offs, len); ++ ++ while (tb_read_len > 0) { ++ /* read max 10KB bytes once */ ++ read_len = MAX_READ_SZ_ONCE - (flash_offs % MAX_READ_SZ_ONCE); ++ if (tb_read_len < read_len) ++ read_len = tb_read_len; ++ ++ /* For AST2600 SPI DMA, flash offset should be 4 byte aligned */ ++ extra = flash_offs % 4; ++ if (extra != 0) { ++ flash_offs = (flash_offs / 4) * 4; ++ read_len += extra; ++ } ++ ++ reg_val = readl(aspi->regs + MISC_CTRL_REG); ++ reg_val |= (SPI_CS_CONTINUOUS | SPI_CS_TO_DIS); ++ writel(reg_val, aspi->regs + MISC_CTRL_REG); ++ ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ ++ /* ++ * - Don't use dma_map_single here, since we cannot make sure the buf's ++ * start address is 4-byte-aligned. ++ * - Only support from SPI flash to DRAM. ++ */ ++ flash_addr = (chip->ahb_base_phy + flash_offs) & 0x7fffffff; ++ ++ writel(DMA_DRAM_HI_ADDR, aspi->regs + DMA_HI_ADDR_REG); ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ writel((u32)aspi->dma_addr_phy, aspi->regs + DMA_RAM_ADDR_REG); ++ writel(flash_addr, aspi->regs + DMA_FLASH_ADDR_REG); ++ writel(read_len - 1, aspi->regs + DMA_LEN_REG); ++ ++ /* enable DMA irq */ ++ reg_val = readl(aspi->regs + INTR_CTRL_STATUS_REG); ++ reg_val |= SPI_DMA_IRQ_EN; ++ writel(reg_val, aspi->regs + INTR_CTRL_STATUS_REG); ++ ++ reinit_completion(&aspi->dma_done); ++ ++ /* enable read DMA */ ++ writel(0x1, aspi->regs + DMA_CTRL_REG); ++ timeout = wait_for_completion_timeout(&aspi->dma_done, ++ msecs_to_jiffies(2000)); ++ ++ writel(chip->ctl_val[ASPEED_SPI_READ] | CTRL_CE_STOP_ACTIVE, ++ chip->ctl); ++ ++ if (timeout == 0) { ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ dev_err(dev, "dma read data timeout %d\n", ret); ++ ret = -1; ++ goto end; ++ } else { ++ memcpy_fromio(buf + buf_offs, aspi->op_buf + extra, read_len - extra); ++ } ++ ++ read_len -= extra; ++ ++ buf_offs += read_len; ++ flash_offs += read_len; ++ tb_read_len -= read_len; ++ } ++ ++end: ++ writel(0x0, aspi->regs + MISC_CTRL_REG); ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ ++ return ret ? 0 : len; ++} ++ ++static ssize_t aspeed_2700_spi_dirmap_dma_write(struct spi_mem_dirmap_desc *desc, ++ u64 offs, size_t len, const void *buf) ++{ ++ int ret = 0; ++ struct aspeed_spi *aspi = spi_controller_get_devdata(desc->mem->spi->controller); ++ struct device *dev = aspi->dev; ++ struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; ++ u32 timeout = 0; ++ u32 reg_val; ++ struct spi_mem_op op_tmpl = desc->info.op_tmpl; ++ u32 flash_addr; ++ ++ if (chip->ahb_window_sz < offs + len) { ++ dev_info(dev, "write range exceeds flash remapping size\n"); ++ return 0; ++ } ++ ++ if (len < 1) ++ return 0; ++ ++ if (len > ASPEED_SPI_OP_BUF_LEN) { ++ dev_info(dev, ++ "written length exceeds expected value (0x%zx)\n", ++ len); ++ return 0; ++ } ++ ++ dev_dbg(dev, "write op:0x%x, addr:0x%08llx, len:0x%08zx\n", ++ op_tmpl.cmd.opcode, offs, len); ++ ++ reg_val = readl(aspi->regs + MISC_CTRL_REG); ++ reg_val |= (SPI_CS_TO_DIS); ++ writel(reg_val, aspi->regs + MISC_CTRL_REG); ++ ++ writel(chip->ctl_val[ASPEED_SPI_WRITE], chip->ctl); ++ ++ /* ++ * - Don't use dma_map_single here, since we cannot make sure the buf's ++ * start address is 4-byte-aligned. ++ * - Only support from SPI flash to DRAM. ++ */ ++ flash_addr = (chip->ahb_base_phy + offs) & 0x7fffffff; ++ memcpy_toio(aspi->op_buf, buf, len); ++ writel(DMA_DRAM_HI_ADDR, aspi->regs + DMA_HI_ADDR_REG); ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ writel((u32)aspi->dma_addr_phy, aspi->regs + DMA_RAM_ADDR_REG); ++ writel(flash_addr, aspi->regs + DMA_FLASH_ADDR_REG); ++ writel(len - 1, aspi->regs + DMA_LEN_REG); ++ ++ /* enable DMA irq */ ++ reg_val = readl(aspi->regs + INTR_CTRL_STATUS_REG); ++ reg_val |= SPI_DMA_IRQ_EN; ++ writel(reg_val, aspi->regs + INTR_CTRL_STATUS_REG); ++ ++ reinit_completion(&aspi->dma_done); ++ ++ /* enable write DMA */ ++ writel(0x3, aspi->regs + DMA_CTRL_REG); ++ timeout = wait_for_completion_timeout(&aspi->dma_done, msecs_to_jiffies(2000)); ++ ++ if (timeout == 0) { ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ dev_err(dev, "write data timeout %d\n", ret); ++ ret = -1; ++ } ++ ++ writel(chip->ctl_val[ASPEED_SPI_WRITE] | CTRL_CE_STOP_ACTIVE, ++ chip->ctl); ++ ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ writel(0x0, aspi->regs + MISC_CTRL_REG); ++ ++ return ret ? 0 : len; ++} ++ ++static irqreturn_t aspeed_2600_spi_dma_isr(int irq, void *dev_id) ++{ ++ struct aspeed_spi *aspi = (struct aspeed_spi *)dev_id; ++ u32 reg_val; ++ ++ if (!(readl(aspi->regs + INTR_CTRL_STATUS_REG) & SPI_DMA_STATUS)) ++ return IRQ_NONE; ++ ++ reg_val = readl(aspi->regs + INTR_CTRL_STATUS_REG); ++ reg_val &= ~SPI_DMA_IRQ_EN; ++ writel(reg_val, aspi->regs + INTR_CTRL_STATUS_REG); ++ ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ writel(DMA_DISCARD_REQ_MAGIC, aspi->regs + DMA_CTRL_REG); ++ ++ complete(&aspi->dma_done); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t aspeed_2700_spi_dma_isr(int irq, void *dev_id) ++{ ++ struct aspeed_spi *aspi = (struct aspeed_spi *)dev_id; ++ u32 reg_val; ++ ++ if (!(readl(aspi->regs + INTR_CTRL_STATUS_REG) & SPI_DMA_STATUS) || ++ !(readl(aspi->regs + INTR_CTRL_STATUS_REG) & SPI_DMA_IRQ_STS)) { ++ return IRQ_NONE; ++ } ++ ++ reg_val = readl(aspi->regs + INTR_CTRL_STATUS_REG); ++ reg_val &= ~SPI_DMA_IRQ_EN; ++ writel(reg_val, aspi->regs + INTR_CTRL_STATUS_REG); ++ ++ reg_val = readl(aspi->regs + INTR_CTRL_STATUS_REG); ++ reg_val |= SPI_DMA_IRQ_STS; ++ writel(reg_val, aspi->regs + INTR_CTRL_STATUS_REG); ++ ++ writel(0x0, aspi->regs + DMA_CTRL_REG); ++ ++ complete(&aspi->dma_done); ++ ++ return IRQ_HANDLED; ++} ++ + static int aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) + { + int ret; +@@ -383,171 +987,310 @@ + spi_get_chipselect(mem->spi, 0)); + } + +-struct aspeed_spi_window { +- u32 cs; +- u32 offset; +- u32 size; +-}; ++static const struct aspeed_spi_data ast2500_fmc_data; ++static const struct aspeed_spi_data ast2500_spi_data; ++static const struct aspeed_spi_data ast2600_spi_data; ++static const struct aspeed_spi_data ast2600_fmc_data; + +-static void aspeed_spi_get_windows(struct aspeed_spi *aspi, +- struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS]) ++static int aspeed_spi_set_window(struct aspeed_spi *aspi) + { +- const struct aspeed_spi_data *data = aspi->data; +- u32 reg_val; ++ struct device *dev = aspi->dev; ++ off_t offset = 0; ++ phys_addr_t start; ++ phys_addr_t ltpi_start; ++ phys_addr_t end; ++ void __iomem *seg_reg_base = aspi->regs + CE0_SEGMENT_ADDR_REG; ++ void __iomem *seg_reg; ++ u32 seg_val; + u32 cs; ++ size_t win_sz; + + for (cs = 0; cs < aspi->data->max_cs; cs++) { +- reg_val = readl(aspi->regs + CE0_SEGMENT_ADDR_REG + cs * 4); +- windows[cs].cs = cs; +- windows[cs].size = data->segment_end(aspi, reg_val) - +- data->segment_start(aspi, reg_val); +- windows[cs].offset = data->segment_start(aspi, reg_val) - aspi->ahb_base_phy; +- dev_vdbg(aspi->dev, "CE%d offset=0x%.8x size=0x%x\n", cs, +- windows[cs].offset, windows[cs].size); ++ if (aspi->chips[cs].ahb_base) ++ devm_iounmap(dev, aspi->chips[cs].ahb_base); + } ++ ++ for (cs = 0; cs < aspi->data->max_cs; cs++) { ++ seg_reg = seg_reg_base + cs * 4; ++ start = aspi->ahb_base_phy + offset; ++ ltpi_start = aspi->ltpi_base_phy + offset; ++ win_sz = aspi->chips[cs].ahb_window_sz; ++ end = start + win_sz; ++ ++ seg_val = aspi->data->segment_reg(aspi, start, end); ++ if (win_sz == 0) ++ seg_val = 0; ++ ++ writel(seg_val, seg_reg); ++ ++ if (seg_val != readl(seg_reg)) { ++ dev_warn(dev, "CE%d expected window [ 0x%.9llx - 0x%.9llx ] %zdMB", ++ cs, (u64)start, (u64)end - 1, win_sz >> 20); ++ dev_warn(dev, "seg_val = 0x%x, readl(seg_reg) = 0x%x\n", ++ seg_val, readl(seg_reg)); ++ ++ seg_val = readl(seg_reg); ++ dev_warn(dev, "restore to 0x%x\n", seg_val); ++ ++ win_sz = aspi->data->segment_end(aspi, seg_val) - ++ aspi->data->segment_start(aspi, seg_val); ++ ++ if (win_sz < 0) ++ return -ERANGE; ++ ++ end = start + win_sz; ++ } ++ ++ if (win_sz != 0) ++ dev_dbg(dev, "CE%d new window [ 0x%.9llx - 0x%.9llx ] %zdMB", ++ cs, (u64)start, (u64)end - 1, win_sz >> 20); ++ else ++ dev_dbg(dev, "CE%d window closed", cs); ++ ++ aspi->chips[cs].ahb_base_phy = start; ++ offset += win_sz; ++ ++ if (offset > aspi->ahb_window_sz) { ++ dev_err(dev, "offset value 0x%llx is too large.\n", (u64)offset); ++ return -ENOSPC; ++ } ++ ++ if (win_sz == 0) ++ continue; ++ ++ if ((aspi->flag & ASPEED_SPI_MIN_WINDOW) != 0) { ++ aspi->chips[cs].ahb_base = devm_ioremap(dev, ++ start, ++ aspi->data->min_window_sz); ++ } else { ++ if ((aspi->flag & ASPEED_SPI_LTPI_SUPPORT) != 0) ++ aspi->chips[cs].ahb_base = devm_ioremap(dev, ++ ltpi_start, ++ win_sz); ++ else ++ aspi->chips[cs].ahb_base = devm_ioremap(dev, ++ start, ++ win_sz); ++ } ++ ++ if (!aspi->chips[cs].ahb_base) { ++ dev_err(dev, "fail to remap window [0x%.9llx - 0x%.9llx]\n", ++ (u64)start, (u64)end - 1); ++ return -ENOMEM; ++ } ++ } ++ ++ return 0; + } + + /* +- * On the AST2600, some CE windows are closed by default at reset but +- * U-Boot should open all. ++ * Usually, the decoding address is not configured at the u-boot stage. ++ * Or, the existing decoding address configuration is wrong. ++ * Thus, force to assign a default decoding address during driver probe. + */ +-static int aspeed_spi_chip_set_default_window(struct aspeed_spi_chip *chip) ++static int aspeed_spi_chip_set_default_window(struct aspeed_spi *aspi) + { +- struct aspeed_spi *aspi = chip->aspi; +- struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; +- struct aspeed_spi_window *win = &windows[chip->cs]; ++ u32 cs; + + /* No segment registers for the AST2400 SPI controller */ + if (aspi->data == &ast2400_spi_data) { +- win->offset = 0; +- win->size = aspi->ahb_window_size; +- } else { +- aspeed_spi_get_windows(aspi, windows); ++ aspi->chips[0].ahb_window_sz = aspi->ahb_window_sz; ++ return aspeed_spi_set_window(aspi); + } + +- chip->ahb_base = aspi->ahb_base + win->offset; +- chip->ahb_window_size = win->size; ++ for (cs = 0; cs < aspi->num_cs; cs++) ++ aspi->chips[cs].ahb_window_sz = aspi->data->min_window_sz; ++ ++ /* Close unused CS */ ++ for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) ++ aspi->chips[cs].ahb_window_sz = 0; + +- dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ] %dMB", +- chip->cs, aspi->ahb_base_phy + win->offset, +- aspi->ahb_base_phy + win->offset + win->size - 1, +- win->size >> 20); ++ if (aspi->data->adjust_window) ++ aspi->data->adjust_window(aspi); + +- return chip->ahb_window_size ? 0 : -1; ++ return aspeed_spi_set_window(aspi); + } + +-static int aspeed_spi_set_window(struct aspeed_spi *aspi, +- const struct aspeed_spi_window *win) ++/* ++ * As the flash size grows up, we need to trim some decoding ++ * size if needed for the sake of conforming the maximum ++ * decoding size. We trim the decoding size from the largest ++ * CS in order to avoid affecting the default boot up sequence ++ * from CS0 where command mode or normal mode is used. ++ * Notice, if a CS decoding size is trimmed, command mode may ++ * not work perfectly on that CS. ++ */ ++static int aspeed_spi_trim_window_size(struct aspeed_spi *aspi) + { +- u32 start = aspi->ahb_base_phy + win->offset; +- u32 end = start + win->size; +- void __iomem *seg_reg = aspi->regs + CE0_SEGMENT_ADDR_REG + win->cs * 4; +- u32 seg_val_backup = readl(seg_reg); +- u32 seg_val = aspi->data->segment_reg(aspi, start, end); ++ struct aspeed_spi_chip *chips = aspi->chips; ++ size_t total_sz; ++ int cs = aspi->data->max_cs - 1; ++ u32 i; ++ bool trimed = false; ++ ++ do { ++ total_sz = 0; ++ for (i = 0; i < aspi->data->max_cs; i++) ++ total_sz += chips[i].ahb_window_sz; + +- if (seg_val == seg_val_backup) +- return 0; ++ if (cs < 0) ++ return -ENOMEM; ++ ++ if (chips[cs].ahb_window_sz <= aspi->data->min_window_sz) { ++ cs--; ++ continue; ++ } ++ ++ if (total_sz > aspi->ahb_window_sz) { ++ chips[cs].ahb_window_sz -= aspi->data->min_window_sz; ++ total_sz -= aspi->data->min_window_sz; ++ trimed = true; ++ } ++ } while (total_sz > aspi->ahb_window_sz); ++ ++ if (trimed) { ++ dev_warn(aspi->dev, "trimed window size:\n"); ++ for (cs = 0; cs < aspi->data->max_cs; cs++) { ++ dev_warn(aspi->dev, "CE%d: 0x%08zx\n", ++ cs, chips[cs].ahb_window_sz); ++ } ++ } ++ ++ return 0; ++} ++ ++static int aspeed_adjust_window_ast2400(struct aspeed_spi *aspi) ++{ ++ int ret; ++ int cs; ++ struct aspeed_spi_chip *chips = aspi->chips; ++ ++ /* Close unused CS. */ ++ for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) ++ chips[cs].ahb_window_sz = 0; ++ ++ ret = aspeed_spi_trim_window_size(aspi); ++ if (ret != 0) ++ return ret; + +- writel(seg_val, seg_reg); ++ return 0; ++} ++ ++/* ++ * For AST2500, the minimum address decoding size for each CS ++ * is 8MB instead of zero. This address decoding size is ++ * mandatory for each CS no matter whether it will be used. ++ * This is a HW limitation. ++ */ ++static int aspeed_adjust_window_ast2500(struct aspeed_spi *aspi) ++{ ++ int ret; ++ int i; ++ int cs; ++ size_t pre_sz; ++ size_t extra_sz; ++ struct aspeed_spi_chip *chips = aspi->chips; ++ ++ /* Assign min_window_sz to unused CS. */ ++ for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) { ++ if (chips[cs].ahb_window_sz < aspi->data->min_window_sz) ++ chips[cs].ahb_window_sz = aspi->data->min_window_sz; ++ } + + /* +- * Restore initial value if something goes wrong else we could +- * loose access to the chip. ++ * If commnad mode or normal mode is used, the start address of a ++ * decoding range should be multiple of its related flash size. ++ * Namely, the total decoding size from flash 0 to flash N should ++ * be multiple of the size of flash (N + 1). + */ +- if (seg_val != readl(seg_reg)) { +- dev_err(aspi->dev, "CE%d invalid window [ 0x%.8x - 0x%.8x ] %dMB", +- win->cs, start, end - 1, win->size >> 20); +- writel(seg_val_backup, seg_reg); +- return -EIO; ++ for (cs = aspi->num_cs - 1; cs >= 0; cs--) { ++ pre_sz = 0; ++ for (i = 0; i < cs; i++) ++ pre_sz += chips[i].ahb_window_sz; ++ ++ if (chips[cs].ahb_window_sz != 0 && ++ (pre_sz % chips[cs].ahb_window_sz) != 0) { ++ extra_sz = chips[cs].ahb_window_sz - ++ (pre_sz % chips[cs].ahb_window_sz); ++ chips[0].ahb_window_sz += extra_sz; ++ } + } + +- if (win->size) +- dev_dbg(aspi->dev, "CE%d new window [ 0x%.8x - 0x%.8x ] %dMB", +- win->cs, start, end - 1, win->size >> 20); +- else +- dev_dbg(aspi->dev, "CE%d window closed", win->cs); ++ ret = aspeed_spi_trim_window_size(aspi); ++ if (ret != 0) ++ return ret; ++ ++ if (aspi->data == &ast2500_spi_data) ++ chips[1].ahb_window_sz = 0x08000000 - chips[0].ahb_window_sz; ++ ++ return 0; ++} ++ ++static int aspeed_adjust_window_ast2600(struct aspeed_spi *aspi) ++{ ++ int ret; ++ int i; ++ int cs; ++ size_t pre_sz; ++ size_t extra_sz; ++ struct aspeed_spi_chip *chips = aspi->chips; ++ ++ /* Close unused CS. */ ++ for (cs = aspi->num_cs; cs < aspi->data->max_cs; cs++) ++ chips[cs].ahb_window_sz = 0; ++ ++ /* ++ * If commnad mode or normal mode is used, the start address of a ++ * decoding range should be multiple of its related flash size. ++ * Namely, the total decoding size from flash 0 to flash N should ++ * be multiple of the size of flash (N + 1). ++ */ ++ for (cs = aspi->num_cs - 1; cs >= 0; cs--) { ++ pre_sz = 0; ++ for (i = 0; i < cs; i++) ++ pre_sz += chips[i].ahb_window_sz; ++ ++ if (chips[cs].ahb_window_sz != 0 && ++ (pre_sz % chips[cs].ahb_window_sz) != 0) { ++ extra_sz = chips[cs].ahb_window_sz - ++ (pre_sz % chips[cs].ahb_window_sz); ++ chips[0].ahb_window_sz += extra_sz; ++ } ++ } ++ ++ ret = aspeed_spi_trim_window_size(aspi); ++ if (ret != 0) ++ return ret; + + return 0; + } + + /* + * Yet to be done when possible : +- * - Align mappings on flash size (we don't have the info) + * - ioremap each window, not strictly necessary since the overall window + * is correct. + */ +-static const struct aspeed_spi_data ast2500_spi_data; +-static const struct aspeed_spi_data ast2600_spi_data; +-static const struct aspeed_spi_data ast2600_fmc_data; + + static int aspeed_spi_chip_adjust_window(struct aspeed_spi_chip *chip, +- u32 local_offset, u32 size) ++ size_t size) + { + struct aspeed_spi *aspi = chip->aspi; +- struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; +- struct aspeed_spi_window *win = &windows[chip->cs]; + int ret; + + /* No segment registers for the AST2400 SPI controller */ + if (aspi->data == &ast2400_spi_data) + return 0; + +- /* +- * Due to an HW issue on the AST2500 SPI controller, the CE0 +- * window size should be smaller than the maximum 128MB. +- */ +- if (aspi->data == &ast2500_spi_data && chip->cs == 0 && size == SZ_128M) { +- size = 120 << 20; +- dev_info(aspi->dev, "CE%d window resized to %dMB (AST2500 HW quirk)", +- chip->cs, size >> 20); +- } +- +- /* +- * The decoding size of AST2600 SPI controller should set at +- * least 2MB. +- */ +- if ((aspi->data == &ast2600_spi_data || aspi->data == &ast2600_fmc_data) && +- size < SZ_2M) { +- size = SZ_2M; +- dev_info(aspi->dev, "CE%d window resized to %dMB (AST2600 Decoding)", +- chip->cs, size >> 20); +- } +- +- aspeed_spi_get_windows(aspi, windows); +- + /* Adjust this chip window */ +- win->offset += local_offset; +- win->size = size; ++ aspi->chips[chip->cs].ahb_window_sz = size; + +- if (win->offset + win->size > aspi->ahb_window_size) { +- win->size = aspi->ahb_window_size - win->offset; +- dev_warn(aspi->dev, "CE%d window resized to %dMB", chip->cs, win->size >> 20); +- } ++ if (aspi->data->adjust_window) ++ aspi->data->adjust_window(aspi); + +- ret = aspeed_spi_set_window(aspi, win); ++ ret = aspeed_spi_set_window(aspi); + if (ret) + return ret; + +- /* Update chip mapping info */ +- chip->ahb_base = aspi->ahb_base + win->offset; +- chip->ahb_window_size = win->size; +- +- /* +- * Also adjust next chip window to make sure that it does not +- * overlap with the current window. +- */ +- if (chip->cs < aspi->data->max_cs - 1) { +- struct aspeed_spi_window *next = &windows[chip->cs + 1]; +- +- /* Change offset and size to keep the same end address */ +- if ((next->offset + next->size) > (win->offset + win->size)) +- next->size = (next->offset + next->size) - (win->offset + win->size); +- else +- next->size = 0; +- next->offset = win->offset + win->size; +- +- aspeed_spi_set_window(aspi, next); +- } + return 0; + } + +@@ -559,6 +1302,9 @@ + struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; + struct spi_mem_op *op = &desc->info.op_tmpl; + u32 ctl_val; ++ u32 reg_val; ++ u32 div = 0; ++ int i; + int ret = 0; + + dev_dbg(aspi->dev, +@@ -571,50 +1317,108 @@ + + chip->clk_freq = desc->mem->spi->max_speed_hz; + ++ if (aspi->data->safs_init) ++ aspi->data->safs_init(aspi, op); ++ + /* Only for reads */ +- if (op->data.dir != SPI_MEM_DATA_IN) +- return -EOPNOTSUPP; ++ if (op->data.dir == SPI_MEM_DATA_IN) { ++ ret = aspeed_spi_chip_adjust_window(chip, desc->info.length); ++ if (ret) ++ return ret; + +- aspeed_spi_chip_adjust_window(chip, desc->info.offset, desc->info.length); ++ if (desc->info.length > chip->ahb_window_sz) ++ dev_warn(aspi->dev, "CE%d window (%zdMB) too small for mapping", ++ chip->cs, chip->ahb_window_sz >> 20); ++ ++ /* Define the default IO read settings */ ++ ctl_val = chip->ctl_val[ASPEED_SPI_BASE] & ~CTRL_IO_CMD_MASK; ++ ctl_val |= aspeed_spi_get_io_mode(op, SPI_OP_ALL) | ++ op->cmd.opcode << CTRL_COMMAND_SHIFT | ++ CTRL_IO_MODE_READ; ++ ++ if (op->dummy.nbytes) ++ ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes); ++ ++ if (op->addr.buswidth == 4) { ++ ctl_val |= BIT(15); ++ reg_val = readl(aspi->regs + MISC_CTRL_REG); ++ reg_val |= DUMMY_OUTPUT_DATA; ++ writel(reg_val, aspi->regs + MISC_CTRL_REG); ++ } + +- if (desc->info.length > chip->ahb_window_size) +- dev_warn(aspi->dev, "CE%d window (%dMB) too small for mapping", +- chip->cs, chip->ahb_window_size >> 20); +- +- /* Define the default IO read settings */ +- ctl_val = readl(chip->ctl) & ~CTRL_IO_CMD_MASK; +- ctl_val |= aspeed_spi_get_io_mode(op) | +- op->cmd.opcode << CTRL_COMMAND_SHIFT | +- CTRL_IO_MODE_READ; +- +- if (op->dummy.nbytes) +- ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); +- +- /* Tune 4BYTE address mode */ +- if (op->addr.nbytes) { +- u32 addr_mode = readl(aspi->regs + CE_CTRL_REG); ++ /* Tune 4BYTE address mode */ ++ if (op->addr.nbytes) { ++ u32 addr_mode = readl(aspi->regs + CE_CTRL_REG); ++ ++ if (op->addr.nbytes == 4) ++ addr_mode |= (0x11 << chip->cs); ++ else ++ addr_mode &= ~(0x11 << chip->cs); ++ writel(addr_mode, aspi->regs + CE_CTRL_REG); + +- if (op->addr.nbytes == 4) +- addr_mode |= (0x11 << chip->cs); +- else +- addr_mode &= ~(0x11 << chip->cs); +- writel(addr_mode, aspi->regs + CE_CTRL_REG); ++ /* AST2400 SPI controller sets 4BYTE address mode in ++ * CE0 Control Register ++ */ ++ if (op->addr.nbytes == 4 && chip->aspi->data == &ast2400_spi_data) ++ ctl_val |= CTRL_IO_ADDRESS_4B; ++ } ++ ++ /* READ mode is the controller default setting */ ++ chip->ctl_val[ASPEED_SPI_READ] = ctl_val; ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ ++ /* assign SPI clock frequency division */ ++ if (chip->clk_freq < aspi->clk_freq / 5) { ++ if (aspi->data->get_clk_div) ++ div = aspi->data->get_clk_div(chip, chip->clk_freq); ++ ++ for (i = 0; i < ASPEED_SPI_MAX; i++) ++ chip->ctl_val[i] = (chip->ctl_val[i] & ++ aspi->data->hclk_mask) | ++ div; ++ ++ writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ } else { ++ ret = aspeed_spi_do_calibration(chip); ++ } + +- /* AST2400 SPI controller sets 4BYTE address mode in +- * CE0 Control Register ++ dev_info(aspi->dev, "CE%d read buswidth: %d [0x%08x]\n", ++ chip->cs, op->data.buswidth, chip->ctl_val[ASPEED_SPI_READ]); ++ dev_dbg(aspi->dev, "spi clock frequency: %dMHz\n", ++ chip->clk_freq / 1000000); ++ ++ /* ++ * aspeed_spi_dirmap_read is not created in ++ * current spi_controller_mem_ops. + */ +- if (op->addr.nbytes == 4 && chip->aspi->data == &ast2400_spi_data) +- ctl_val |= CTRL_IO_ADDRESS_4B; +- } ++ if (!desc->mem->spi->controller->mem_ops->dirmap_read) ++ return -EOPNOTSUPP; + +- /* READ mode is the controller default setting */ +- chip->ctl_val[ASPEED_SPI_READ] = ctl_val; +- writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ } else if (op->data.dir == SPI_MEM_DATA_OUT) { ++ /* record some information for normal mode. */ ++ ctl_val = chip->ctl_val[ASPEED_SPI_BASE] & (~CTRL_IO_CMD_MASK); ++ ctl_val |= aspeed_spi_get_io_mode(op, SPI_OP_ALL) | ++ op->cmd.opcode << 16 | CTRL_IO_MODE_WRITE; ++ ++ if ((aspi->flag & ASPEED_SPI_FIXED_LOW_W_CLK) != 0) { ++ /* adjust spi clk for write */ ++ ctl_val = (ctl_val & (~0x0f000f00)) | 0x03000000; ++ } + +- ret = aspeed_spi_do_calibration(chip); ++ chip->ctl_val[ASPEED_SPI_WRITE] = ctl_val; + +- dev_info(aspi->dev, "CE%d read buswidth:%d [0x%08x]\n", +- chip->cs, op->data.buswidth, chip->ctl_val[ASPEED_SPI_READ]); ++ dev_info(aspi->dev, "CE%d write buswidth: %d [0x%08x]\n", ++ chip->cs, op->data.buswidth, chip->ctl_val[ASPEED_SPI_WRITE]); ++ ++ /* ++ * aspeed_spi_dirmap_write is not created in ++ * current spi_controller_mem_ops. ++ */ ++ if (!desc->mem->spi->controller->mem_ops->dirmap_write) ++ return -EOPNOTSUPP; ++ } else { ++ return -EOPNOTSUPP; ++ } + + return ret; + } +@@ -626,7 +1430,7 @@ + struct aspeed_spi_chip *chip = &aspi->chips[spi_get_chipselect(desc->mem->spi, 0)]; + + /* Switch to USER command mode if mapping window is too small */ +- if (chip->ahb_window_size < offset + len) { ++ if (chip->ahb_window_sz < offset + len) { + int ret; + + ret = aspeed_spi_read_user(chip, &desc->info.op_tmpl, offset, len, buf); +@@ -647,6 +1451,48 @@ + .dirmap_read = aspeed_spi_dirmap_read, + }; + ++static const struct spi_controller_mem_ops aspeed_spi_mem_ops_pure_user = { ++ .supports_op = aspeed_spi_supports_op, ++ .exec_op = aspeed_spi_exec_op, ++ .get_name = aspeed_spi_get_name, ++ .dirmap_create = aspeed_spi_dirmap_create, ++}; ++ ++static const struct spi_controller_mem_ops aspeed_spi_mem_ops_normal_mode = { ++ .supports_op = aspeed_spi_supports_op, ++ .exec_op = aspeed_spi_exec_op_normal_mode, ++ .get_name = aspeed_spi_get_name, ++ .dirmap_create = aspeed_spi_dirmap_create, ++ .dirmap_read = aspeed_spi_dirmap_read, ++}; ++ ++static const struct spi_controller_mem_ops aspeed_spi_ops_normal_read_dma_write = { ++ .get_name = aspeed_spi_get_name, ++ .exec_op = aspeed_spi_exec_op_normal_mode, ++ .supports_op = aspeed_spi_supports_op, ++ .dirmap_create = aspeed_spi_dirmap_create, ++ .dirmap_read = aspeed_spi_dirmap_read, ++ .dirmap_write = aspeed_2600_spi_dirmap_dma_write, ++}; ++ ++static const struct spi_controller_mem_ops aspeed_2600_spi_ops_dma_mode = { ++ .get_name = aspeed_spi_get_name, ++ .exec_op = aspeed_spi_exec_op, ++ .supports_op = aspeed_spi_supports_op, ++ .dirmap_create = aspeed_spi_dirmap_create, ++ .dirmap_read = aspeed_2600_spi_dirmap_dma_read, ++ .dirmap_write = aspeed_2600_spi_dirmap_dma_write, ++}; ++ ++static const struct spi_controller_mem_ops aspeed_2700_spi_ops_dma_mode = { ++ .get_name = aspeed_spi_get_name, ++ .exec_op = aspeed_spi_exec_op, ++ .supports_op = aspeed_spi_supports_op, ++ .dirmap_create = aspeed_spi_dirmap_create, ++ .dirmap_read = aspeed_2700_spi_dirmap_dma_read, ++ .dirmap_write = aspeed_2700_spi_dirmap_dma_write, ++}; ++ + static void aspeed_spi_chip_set_type(struct aspeed_spi *aspi, unsigned int cs, int type) + { + u32 reg; +@@ -675,6 +1521,8 @@ + const struct aspeed_spi_data *data = aspi->data; + unsigned int cs = spi_get_chipselect(spi, 0); + struct aspeed_spi_chip *chip = &aspi->chips[cs]; ++ u32 clk_div = 0; ++ u32 i; + + chip->aspi = aspi; + chip->cs = cs; +@@ -684,15 +1532,20 @@ + if (data->hastype) + aspeed_spi_chip_set_type(aspi, cs, CONFIG_TYPE_SPI); + +- if (aspeed_spi_chip_set_default_window(chip) < 0) { +- dev_warn(aspi->dev, "CE%d window invalid", cs); +- return -EINVAL; +- } +- + aspeed_spi_chip_enable(aspi, cs, true); + + chip->ctl_val[ASPEED_SPI_BASE] = CTRL_CE_STOP_ACTIVE | CTRL_IO_MODE_USER; + ++ if ((aspi->flag & ASPEED_SPI_TIMING_CLB_DISABLED) != 0) { ++ if (aspi->data->get_clk_div) ++ clk_div = aspi->data->get_clk_div(chip, spi->max_speed_hz); ++ ++ for (i = 0; i < ASPEED_SPI_MAX; i++) { ++ chip->ctl_val[i] = (chip->ctl_val[i] & aspi->data->hclk_mask) | ++ clk_div; ++ } ++ } ++ + dev_dbg(aspi->dev, "CE%d setup done\n", cs); + return 0; + } +@@ -722,7 +1575,8 @@ + struct spi_controller *ctlr; + struct aspeed_spi *aspi; + struct resource *res; +- int ret; ++ struct reset_control *reset; ++ int ret = 0; + + data = of_device_get_match_data(&pdev->dev); + if (!data) +@@ -737,17 +1591,13 @@ + aspi->data = data; + aspi->dev = dev; + +- aspi->regs = devm_platform_ioremap_resource(pdev, 0); ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ aspi->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(aspi->regs)) + return PTR_ERR(aspi->regs); + +- aspi->ahb_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res); +- if (IS_ERR(aspi->ahb_base)) { +- dev_err(dev, "missing AHB mapping window\n"); +- return PTR_ERR(aspi->ahb_base); +- } +- +- aspi->ahb_window_size = resource_size(res); ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ aspi->ahb_window_sz = resource_size(res); + aspi->ahb_base_phy = res->start; + + aspi->clk = devm_clk_get_enabled(&pdev->dev, NULL); +@@ -762,20 +1612,140 @@ + return -EINVAL; + } + ++ reset = devm_reset_control_get_exclusive(dev, NULL); ++ if (!IS_ERR(reset)) ++ reset_control_deassert(reset); ++ ++ aspi->flag = 0; ++ if (of_property_read_bool(dev->of_node, "fmc-spi-normal-mode")) ++ aspi->flag |= ASPEED_SPI_NORMAL_MODE; ++ else if (of_property_read_bool(dev->of_node, "fmc-spi-dma-write")) ++ aspi->flag |= ASPEED_SPI_DMA_WRITE_MODE; ++ else if (of_property_read_bool(dev->of_node, "fmc-spi-dma-mode")) ++ aspi->flag |= ASPEED_SPI_DMA_MODE; ++ else if (of_property_read_bool(dev->of_node, "pure-spi-mode-only")) ++ aspi->flag |= ASPEED_SPI_PURE_USER_MODE; ++ ++ if (of_property_read_bool(dev->of_node, "spi-quad-address")) ++ aspi->flag |= ASPEED_SPI_QUAD_ADDR_SUPPORT; ++ ++ if (of_property_read_bool(dev->of_node, "timing-calibration-disabled")) ++ aspi->flag |= ASPEED_SPI_TIMING_CLB_DISABLED; ++ ++ /* Should be set on AST2600-A1/A2 for errata 65 */ ++ if (of_property_read_bool(dev->of_node, "low-spi-clk-write")) { ++ dev_info(dev, "adopt low spi clk for write\n"); ++ aspi->flag |= ASPEED_SPI_FIXED_LOW_W_CLK; ++ } ++ ++ if (!of_property_read_u64(dev->of_node, "ltpi-base", &aspi->ltpi_base_phy)) { ++ dev_info(dev, "ltpi support\n"); ++ aspi->flag |= ASPEED_SPI_LTPI_SUPPORT; ++ } ++ ++ if ((aspi->flag & ASPEED_SPI_NORMAL_MODE) != 0 || ++ (aspi->flag & ASPEED_SPI_DMA_WRITE_MODE) != 0 || ++ (aspi->flag & ASPEED_SPI_DMA_MODE) != 0) { ++ if (data->ver == 2700) { ++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (ret) { ++ dev_err(dev, "cannot set 64-bits DMA mask\n"); ++ return ret; ++ } ++ } ++ ++ aspi->op_buf = dma_alloc_coherent(dev, ++ ASPEED_SPI_OP_BUF_LEN, ++ &aspi->dma_addr_phy, ++ GFP_DMA | GFP_KERNEL); ++ if (!aspi->op_buf) { ++ dev_err(dev, "fail to alloc dma buffer\n"); ++ ret = -ENOMEM; ++ goto end; ++ } ++ } ++ ++ if ((aspi->flag & ASPEED_SPI_DMA_WRITE_MODE) != 0 || ++ (aspi->flag & ASPEED_SPI_DMA_MODE) != 0) { ++ aspi->irq = platform_get_irq(pdev, 0); ++ if (aspi->irq < 0) { ++ dev_err(dev, "fail to get irq (%d)\n", aspi->irq); ++ return aspi->irq; ++ } ++ ++ if (data->ver == 2600) { ++ ret = devm_request_irq(dev, aspi->irq, ++ aspeed_2600_spi_dma_isr, ++ IRQF_SHARED, dev_name(dev), ++ aspi); ++ } else if (data->ver == 2700) { ++ ret = devm_request_irq(dev, aspi->irq, ++ aspeed_2700_spi_dma_isr, ++ IRQF_SHARED, dev_name(dev), ++ aspi); ++ } ++ ++ if (ret < 0) { ++ dev_err(dev, "fail to request irq (%d)\n", ret); ++ goto end; ++ } ++ ++ init_completion(&aspi->dma_done); ++ } ++ ++ if ((aspi->flag & ASPEED_SPI_DMA_MODE) != 0 || ++ (aspi->flag & ASPEED_SPI_PURE_USER_MODE) != 0) ++ aspi->flag |= ASPEED_SPI_MIN_WINDOW; ++ + /* IRQ is for DMA, which the driver doesn't support yet */ + + ctlr->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | data->mode_bits; + ctlr->bus_num = pdev->id; +- ctlr->mem_ops = &aspeed_spi_mem_ops; + ctlr->setup = aspeed_spi_setup; + ctlr->cleanup = aspeed_spi_cleanup; +- ctlr->num_chipselect = data->max_cs; ++ ctlr->num_chipselect = of_get_available_child_count(dev->of_node); + ctlr->dev.of_node = dev->of_node; + ++ if ((aspi->flag & ASPEED_SPI_NORMAL_MODE) != 0) { ++ dev_info(&pdev->dev, "normal mode is used\n"); ++ ctlr->mem_ops = &aspeed_spi_mem_ops_normal_mode; ++ } else if ((aspi->flag & ASPEED_SPI_DMA_WRITE_MODE) != 0) { ++ dev_info(&pdev->dev, "normal read and dma write mode are used\n"); ++ ctlr->mem_ops = &aspeed_spi_ops_normal_read_dma_write; ++ } else if ((aspi->flag & ASPEED_SPI_DMA_MODE) != 0) { ++ dev_info(&pdev->dev, "dma mode is used\n"); ++ if (data->ver == 2600) ++ ctlr->mem_ops = &aspeed_2600_spi_ops_dma_mode; ++ else if (data->ver == 2700) ++ ctlr->mem_ops = &aspeed_2700_spi_ops_dma_mode; ++ } else if ((aspi->flag & ASPEED_SPI_PURE_USER_MODE) != 0) { ++ dev_info(&pdev->dev, "user mode is used\n"); ++ ctlr->mem_ops = &aspeed_spi_mem_ops_pure_user; ++ } else { ++ dev_info(&pdev->dev, "user mode and normal read are used\n"); ++ ctlr->mem_ops = &aspeed_spi_mem_ops; ++ } ++ ++ if (ctlr->num_chipselect == 0) { ++ dev_warn(&pdev->dev, "Force num_chipselect to 1\n"); ++ ctlr->num_chipselect = 1; ++ } ++ ++ aspi->num_cs = ctlr->num_chipselect; ++ ++ ret = aspeed_spi_chip_set_default_window(aspi); ++ if (ret) { ++ dev_err(&pdev->dev, "fail to set default window\n"); ++ goto end; ++ } ++ + ret = spi_register_controller(ctlr); +- if (ret) ++ if (ret) { + dev_err(&pdev->dev, "spi_register_controller failed\n"); ++ goto end; ++ } + ++end: + return ret; + } + +@@ -786,6 +1756,13 @@ + + spi_unregister_controller(ctlr); + ++ if (aspi->op_buf) { ++ dma_free_coherent(aspi->dev, ++ ASPEED_SPI_OP_BUF_LEN, ++ aspi->op_buf, ++ aspi->dma_addr_phy); ++ } ++ + aspeed_spi_enable(aspi, false); + } + +@@ -798,19 +1775,20 @@ + * The address range is encoded with absolute addresses in the overall + * mapping window. + */ +-static u32 aspeed_spi_segment_start(struct aspeed_spi *aspi, u32 reg) ++static u64 aspeed_spi_segment_start(struct aspeed_spi *aspi, u32 reg) + { + return ((reg >> 16) & 0xFF) << 23; + } + +-static u32 aspeed_spi_segment_end(struct aspeed_spi *aspi, u32 reg) ++static u64 aspeed_spi_segment_end(struct aspeed_spi *aspi, u32 reg) + { + return ((reg >> 24) & 0xFF) << 23; + } + +-static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, u32 start, u32 end) ++static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, u64 start, u64 end) + { +- return (((start >> 23) & 0xFF) << 16) | (((end >> 23) & 0xFF) << 24); ++ return (u32)((((start >> 23) & 0xFF) << 16) | ++ (((end >> 23) & 0xFF) << 24)); + } + + /* +@@ -820,18 +1798,18 @@ + + #define AST2600_SEG_ADDR_MASK 0x0ff00000 + +-static u32 aspeed_spi_segment_ast2600_start(struct aspeed_spi *aspi, ++static u64 aspeed_spi_segment_ast2600_start(struct aspeed_spi *aspi, + u32 reg) + { +- u32 start_offset = (reg << 16) & AST2600_SEG_ADDR_MASK; ++ u64 start_offset = (reg << 16) & AST2600_SEG_ADDR_MASK; + + return aspi->ahb_base_phy + start_offset; + } + +-static u32 aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, ++static u64 aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, + u32 reg) + { +- u32 end_offset = reg & AST2600_SEG_ADDR_MASK; ++ u64 end_offset = reg & AST2600_SEG_ADDR_MASK; + + /* segment is disabled */ + if (!end_offset) +@@ -841,28 +1819,193 @@ + } + + static u32 aspeed_spi_segment_ast2600_reg(struct aspeed_spi *aspi, +- u32 start, u32 end) ++ u64 start, u64 end) + { + /* disable zero size segments */ + if (start == end) + return 0; + +- return ((start & AST2600_SEG_ADDR_MASK) >> 16) | +- ((end - 1) & AST2600_SEG_ADDR_MASK); ++ return (u32)((start & AST2600_SEG_ADDR_MASK) >> 16) | ++ ((end - 1) & AST2600_SEG_ADDR_MASK); ++} ++ ++static u64 aspeed_spi_segment_ast2700_start(struct aspeed_spi *aspi, ++ u32 reg) ++{ ++ u64 start_offset = (((reg) & 0x0000ffff) << 16); ++ ++ if (start_offset == 0) ++ return aspi->ahb_base_phy; ++ ++ return aspi->ahb_base_phy + start_offset; ++} ++ ++static u64 aspeed_spi_segment_ast2700_end(struct aspeed_spi *aspi, ++ u32 reg) ++{ ++ u64 end_offset = reg & 0xffff0000; ++ ++ /* Meaningless end_offset, set to physical ahb base. */ ++ if (end_offset == 0) ++ return aspi->ahb_base_phy; ++ ++ return aspi->ahb_base_phy + end_offset; ++} ++ ++static u32 aspeed_spi_segment_ast2700_reg(struct aspeed_spi *aspi, ++ u64 start, u64 end) ++{ ++ if (start == end) ++ return 0; ++ ++ return (u32)((((start) >> 16) & 0x7fff) | ++ ((end + 1) & 0x7fff0000)); ++} ++ ++static const u32 aspeed_spi_hclk_divs[] = { ++ /* HCLK, HCLK/2, HCLK/3, HCLK/4, HCLK/5, ..., HCLK/16 */ ++ 0xf, 0x7, 0xe, 0x6, 0xd, ++ 0x5, 0xc, 0x4, 0xb, 0x3, ++ 0xa, 0x2, 0x9, 0x1, 0x8, ++ 0x0 ++}; ++ ++#define ASPEED_SPI_HCLK_DIV(i) \ ++ (aspeed_spi_hclk_divs[(i)] << CTRL_FREQ_SEL_SHIFT) ++ ++/* Transfer maximum clock frequency to register setting */ ++static u32 apseed_get_clk_div_ast2400(struct aspeed_spi_chip *chip, ++ u32 max_hz) ++{ ++ struct device *dev = chip->aspi->dev; ++ u32 hclk_clk = chip->aspi->clk_freq; ++ u32 hclk_div = 0; ++ u32 i; ++ bool found = false; ++ ++ /* FMC/SPIR10[11:8] */ ++ for (i = 0; i < ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { ++ if (hclk_clk / (i + 1) <= max_hz) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) { ++ hclk_div = ASPEED_SPI_HCLK_DIV(i); ++ chip->clk_freq = hclk_clk / (i + 1); ++ } ++ ++ dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", ++ found ? "yes" : "no", hclk_clk, max_hz); ++ ++ if (found) { ++ dev_dbg(dev, "h_div: %d (mask 0x%08x), speed: %d\n", ++ i + 1, hclk_div, chip->clk_freq); ++ } ++ ++ return hclk_div; ++} ++ ++static u32 apseed_get_clk_div_ast2500(struct aspeed_spi_chip *chip, ++ u32 max_hz) ++{ ++ struct device *dev = chip->aspi->dev; ++ u32 hclk_clk = chip->aspi->clk_freq; ++ u32 hclk_div = 0; ++ u32 i; ++ bool found = false; ++ ++ /* FMC/SPIR10[11:8] */ ++ for (i = 0; i < ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { ++ if (hclk_clk / (i + 1) <= max_hz) { ++ found = true; ++ chip->clk_freq = hclk_clk / (i + 1); ++ break; ++ } ++ } ++ ++ if (found) { ++ hclk_div = ASPEED_SPI_HCLK_DIV(i); ++ goto end; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { ++ if (hclk_clk / ((i + 1) * 4) <= max_hz) { ++ found = true; ++ chip->clk_freq = hclk_clk / ((i + 1) * 4); ++ break; ++ } ++ } ++ ++ if (found) ++ hclk_div = BIT(13) | ASPEED_SPI_HCLK_DIV(i); ++ ++end: ++ dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", ++ found ? "yes" : "no", hclk_clk, max_hz); ++ ++ if (found) { ++ dev_dbg(dev, "h_div: %d (mask %x), speed: %d\n", ++ i + 1, hclk_div, chip->clk_freq); ++ } ++ ++ return hclk_div; ++} ++ ++static u32 apseed_get_clk_div_ast2600(struct aspeed_spi_chip *chip, ++ u32 max_hz) ++{ ++ struct device *dev = chip->aspi->dev; ++ u32 hclk_clk = chip->aspi->clk_freq; ++ u32 hclk_div = 0; ++ u32 i, j; ++ bool found = false; ++ ++ /* FMC/SPIR10[27:24] */ ++ for (j = 0; j < 16; j++) { ++ /* FMC/SPIR10[11:8] */ ++ for (i = 0; i < ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { ++ if (i == 0 && j == 0) ++ continue; ++ ++ if (hclk_clk / (i + 1 + (j * 16)) <= max_hz) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) { ++ hclk_div = ((j << 24) | ASPEED_SPI_HCLK_DIV(i)); ++ chip->clk_freq = hclk_clk / (i + 1 + j * 16); ++ break; ++ } ++ } ++ ++ dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", ++ found ? "yes" : "no", hclk_clk, max_hz); ++ ++ if (found) { ++ dev_dbg(dev, "base_clk: %d, h_div: %d (mask %x), speed: %d\n", ++ j, i + 1, hclk_div, chip->clk_freq); ++ } ++ ++ return hclk_div; + } + + /* + * Read timing compensation sequences + */ + +-#define CALIBRATE_BUF_SIZE SZ_16K ++#define CALIBRATE_BUF_SIZE SZ_4K ++#define CALIBRATE_REPEAT_COUNT 1 + + static bool aspeed_spi_check_reads(struct aspeed_spi_chip *chip, + const u8 *golden_buf, u8 *test_buf) + { + int i; + +- for (i = 0; i < 10; i++) { ++ for (i = 0; i < CALIBRATE_REPEAT_COUNT; i++) { + memcpy_fromio(test_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + if (memcmp(test_buf, golden_buf, CALIBRATE_BUF_SIZE) != 0) { + #if defined(VERBOSE_DEBUG) +@@ -875,7 +2018,10 @@ + return true; + } + +-#define FREAD_TPASS(i) (((i) / 2) | (((i) & 1) ? 0 : 8)) ++static inline u32 FREAD_TPASS(int i) ++{ ++ return (((i) / 2) | (((i) & 1) ? 8 : 0)); ++} + + /* + * The timing register is shared by all devices. Only update for CE0. +@@ -905,7 +2051,7 @@ + pass = aspeed_spi_check_reads(chip, golden_buf, test_buf); + dev_dbg(aspi->dev, + " * [%08x] %d HCLK delay, %dns DI delay : %s", +- fread_timing_val, i / 2, (i & 1) ? 0 : 4, ++ fread_timing_val, i / 2, (i & 1) ? 4 : 0, + pass ? "PASS" : "FAIL"); + if (pass) { + pass_count++; +@@ -951,17 +2097,6 @@ + return cnt >= 64; + } + +-static const u32 aspeed_spi_hclk_divs[] = { +- 0xf, /* HCLK */ +- 0x7, /* HCLK/2 */ +- 0xe, /* HCLK/3 */ +- 0x6, /* HCLK/4 */ +- 0xd, /* HCLK/5 */ +-}; +- +-#define ASPEED_SPI_HCLK_DIV(i) \ +- (aspeed_spi_hclk_divs[(i) - 1] << CTRL_FREQ_SEL_SHIFT) +- + static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip) + { + struct aspeed_spi *aspi = chip->aspi; +@@ -972,10 +2107,17 @@ + u8 *golden_buf = NULL; + u8 *test_buf = NULL; + int i, rc, best_div = -1; ++ u32 clk_div_config = 0; ++ u32 freq = 0; + + dev_dbg(aspi->dev, "calculate timing compensation - AHB freq: %d MHz", + ahb_freq / 1000000); + ++ if ((aspi->flag & ASPEED_SPI_TIMING_CLB_DISABLED) != 0) { ++ dev_info(aspi->dev, "timing calibration is disabled\n"); ++ return 0; ++ } ++ + /* + * use the related low frequency to get check calibration data + * and get golden data. +@@ -991,7 +2133,7 @@ + + memcpy_fromio(golden_buf, chip->ahb_base, CALIBRATE_BUF_SIZE); + if (!aspeed_spi_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) { +- dev_info(aspi->dev, "Calibration area too uniform, using low speed"); ++ dev_info(aspi->dev, "Calibration area too uniform\n"); + goto no_calib; + } + +@@ -1001,42 +2143,81 @@ + #endif + + /* Now we iterate the HCLK dividers until we find our breaking point */ +- for (i = ARRAY_SIZE(aspeed_spi_hclk_divs); i > data->hdiv_max - 1; i--) { +- u32 tv, freq; ++ for (i = data->hdiv_max; i <= 5; i++) { ++ u32 tv; + + freq = ahb_freq / i; + if (freq > max_freq) + continue; + + /* Set the timing */ +- tv = chip->ctl_val[ASPEED_SPI_READ] | ASPEED_SPI_HCLK_DIV(i); ++ tv = chip->ctl_val[ASPEED_SPI_READ] & data->hclk_mask; ++ tv |= ASPEED_SPI_HCLK_DIV(i - 1); + writel(tv, chip->ctl); + dev_dbg(aspi->dev, "Trying HCLK/%d [%08x] ...", i, tv); + rc = data->calibrate(chip, i, golden_buf, test_buf); +- if (rc == 0) ++ if (rc == 0) { + best_div = i; ++ break; ++ } + } + ++no_calib: ++ + /* Nothing found ? */ + if (best_div < 0) { +- dev_warn(aspi->dev, "No good frequency, using dumb slow"); ++ if (data->get_clk_div) ++ clk_div_config = data->get_clk_div(chip, max_freq); + } else { + dev_dbg(aspi->dev, "Found good read timings at HCLK/%d", best_div); +- +- /* Record the freq */ +- for (i = 0; i < ASPEED_SPI_MAX; i++) +- chip->ctl_val[i] = (chip->ctl_val[i] & data->hclk_mask) | +- ASPEED_SPI_HCLK_DIV(best_div); ++ chip->clk_freq = freq; ++ clk_div_config = ASPEED_SPI_HCLK_DIV(best_div - 1); + } + +-no_calib: ++ /* Record the freq */ ++ for (i = 0; i < ASPEED_SPI_MAX; i++) ++ chip->ctl_val[i] = (chip->ctl_val[i] & data->hclk_mask) | ++ clk_div_config; ++ + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); ++ + kfree(test_buf); ++ + return 0; + } + ++static int get_mid_point_of_longest_one(u8 *buf, u32 len) ++{ ++ int i; ++ int start = 0, mid_point = 0; ++ int max_cnt = 0, cnt = 0; ++ ++ for (i = 0; i < len; i++) { ++ if (buf[i] == 1) { ++ cnt++; ++ } else { ++ cnt = 0; ++ start = i; ++ } ++ ++ if (max_cnt < cnt) { ++ max_cnt = cnt; ++ mid_point = start + (cnt / 2); ++ } ++ } ++ ++ /* ++ * In order to get a stable SPI read timing, ++ * abandon the result if the length of longest ++ * consecutive good points is too short. ++ */ ++ if (max_cnt < 4) ++ return -1; ++ ++ return mid_point; ++} ++ + #define TIMING_DELAY_DI BIT(3) +-#define TIMING_DELAY_HCYCLE_MAX 5 + #define TIMING_REG_AST2600(chip) \ + ((chip)->aspi->regs + (chip)->aspi->data->timing + \ + (chip)->cs * 4) +@@ -1044,60 +2225,136 @@ + static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf) + { ++ struct device *dev = chip->aspi->dev; + struct aspeed_spi *aspi = chip->aspi; + int hcycle; + u32 shift = (hdiv - 2) << 3; +- u32 mask = ~(0xfu << shift); ++ u32 mask = ~(0xffu << shift); + u32 fread_timing_val = 0; ++ u8 *calib_res = NULL; ++ int calib_point; ++ u32 final_delay; ++ int delay_ns; ++ bool pass; + +- for (hcycle = 0; hcycle <= TIMING_DELAY_HCYCLE_MAX; hcycle++) { +- int delay_ns; +- bool pass = false; +- +- fread_timing_val &= mask; +- fread_timing_val |= hcycle << shift; +- +- /* no DI input delay first */ +- writel(fread_timing_val, TIMING_REG_AST2600(chip)); +- pass = aspeed_spi_check_reads(chip, golden_buf, test_buf); +- dev_dbg(aspi->dev, +- " * [%08x] %d HCLK delay, DI delay none : %s", +- fread_timing_val, hcycle, pass ? "PASS" : "FAIL"); +- if (pass) +- return 0; ++ calib_res = kzalloc(6 * 17, GFP_KERNEL); ++ if (!calib_res) ++ return -ENOMEM; + +- /* Add DI input delays */ ++ for (hcycle = 0; hcycle <= 5; hcycle++) { + fread_timing_val &= mask; + fread_timing_val |= (TIMING_DELAY_DI | hcycle) << shift; + +- for (delay_ns = 0; delay_ns < 0x10; delay_ns++) { +- fread_timing_val &= ~(0xf << (4 + shift)); ++ for (delay_ns = 0; delay_ns < 16; delay_ns++) { ++ fread_timing_val &= ~(0xfu << (4 + shift)); + fread_timing_val |= delay_ns << (4 + shift); + + writel(fread_timing_val, TIMING_REG_AST2600(chip)); + pass = aspeed_spi_check_reads(chip, golden_buf, test_buf); + dev_dbg(aspi->dev, + " * [%08x] %d HCLK delay, DI delay %d.%dns : %s", +- fread_timing_val, hcycle, (delay_ns + 1) / 2, +- (delay_ns + 1) & 1 ? 5 : 5, pass ? "PASS" : "FAIL"); +- /* +- * TODO: This is optimistic. We should look +- * for a working interval and save the middle +- * value in the read timing register. +- */ +- if (pass) +- return 0; ++ fread_timing_val, hcycle, delay_ns / 2, ++ (delay_ns & 1) ? 5 : 0, pass ? "PASS" : "FAIL"); ++ ++ calib_res[hcycle * 17 + delay_ns] = pass; + } + } + +- /* No good setting for this frequency */ +- return -1; ++ calib_point = get_mid_point_of_longest_one(calib_res, 6 * 17); ++ ++ if (calib_point < 0) { ++ dev_info(dev, "[HCLK/%d] cannot get good calibration point.\n", ++ hdiv); ++ kfree(calib_res); ++ ++ return -1; ++ } ++ ++ hcycle = calib_point / 17; ++ delay_ns = calib_point % 17; ++ ++ dev_dbg(dev, "final hcycle: %d, delay_ns: %d\n", hcycle, ++ delay_ns); ++ ++ final_delay = (TIMING_DELAY_DI | hcycle | (delay_ns << 4)) << shift; ++ writel(final_delay, TIMING_REG_AST2600(chip)); ++ ++ kfree(calib_res); ++ ++ return 0; ++} ++ ++static void aspeed_spi_ast2600_fill_safs_cmd(struct aspeed_spi *aspi, ++ struct spi_mem_op *op) ++{ ++ u32 tmp_val; ++ ++ if (op->data.dir == SPI_MEM_DATA_IN) { ++ tmp_val = readl(aspi->regs + HOST_DIRECT_ACCESS_CMD_CTRL4); ++ if (op->addr.nbytes == 4) ++ tmp_val = (tmp_val & 0xffff00ff) | (op->cmd.opcode << 8); ++ else ++ tmp_val = (tmp_val & 0xffffff00) | op->cmd.opcode; ++ ++ tmp_val = (tmp_val & 0x0fffffff) | ++ aspeed_spi_get_io_mode(op, SPI_OP_ALL); ++ ++ writel(tmp_val, aspi->regs + HOST_DIRECT_ACCESS_CMD_CTRL4); ++ ++ } else if (op->data.dir == SPI_MEM_DATA_OUT) { ++ tmp_val = readl(aspi->regs + HOST_DIRECT_ACCESS_CMD_CTRL4); ++ tmp_val = (tmp_val & 0xf0ffffff) | ++ (aspeed_spi_get_io_mode(op, SPI_OP_ALL) >> 4); ++ ++ writel(tmp_val, aspi->regs + HOST_DIRECT_ACCESS_CMD_CTRL4); ++ ++ tmp_val = readl(aspi->regs + HOST_DIRECT_ACCESS_CMD_CTRL2); ++ if (op->addr.nbytes == 4) ++ tmp_val = (tmp_val & 0xffff00ff) | (op->cmd.opcode << 8); ++ else ++ tmp_val = (tmp_val & 0xffffff00) | op->cmd.opcode; ++ ++ writel(tmp_val, aspi->regs + HOST_DIRECT_ACCESS_CMD_CTRL2); ++ } ++} ++ ++static void aspeed_spi_ast2700_safs_init(struct aspeed_spi *aspi, ++ struct spi_mem_op *op) ++{ ++ u32 val; ++ ++ (void)op; ++ ++ val = readl(aspi->regs + MISC_CTRL_REG); ++ val |= SPI_UNALGNED_ACCESS | SPI_USER_CMD_MODE; ++ val &= ~SPI_CS_CONTINUOUS; ++ writel(val, aspi->regs + MISC_CTRL_REG); ++} ++ ++static void aspeed_spi_ast2700_safs_start(struct aspeed_spi *aspi) ++{ ++ u32 val; ++ ++ val = readl(aspi->regs + MISC_CTRL_REG); ++ val |= SPI_UNALGNED_ACCESS | SPI_USER_CMD_MODE; ++ val &= ~SPI_CS_CONTINUOUS; ++ writel(val, aspi->regs + MISC_CTRL_REG); ++} ++ ++static void aspeed_spi_ast2700_safs_stop(struct aspeed_spi *aspi) ++{ ++ u32 val; ++ ++ val = readl(aspi->regs + MISC_CTRL_REG); ++ val &= ~GENMASK(27, 24); ++ writel(val, aspi->regs + MISC_CTRL_REG); + } + + /* + * Platform definitions + */ + static const struct aspeed_spi_data ast2400_fmc_data = { ++ .ver = 2400, + .max_cs = 5, + .hastype = true, + .we0 = 16, +@@ -1105,13 +2362,17 @@ + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, ++ .min_window_sz = 0x800000, ++ .get_clk_div = apseed_get_clk_div_ast2400, + .calibrate = aspeed_spi_calibrate, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, ++ .adjust_window = aspeed_adjust_window_ast2400, + }; + + static const struct aspeed_spi_data ast2400_spi_data = { ++ .ver = 2400, + .max_cs = 1, + .hastype = false, + .we0 = 0, +@@ -1119,11 +2380,13 @@ + .timing = 0x14, + .hclk_mask = 0xfffff0ff, + .hdiv_max = 1, ++ .get_clk_div = apseed_get_clk_div_ast2400, + .calibrate = aspeed_spi_calibrate, + /* No segment registers */ + }; + + static const struct aspeed_spi_data ast2500_fmc_data = { ++ .ver = 2500, + .max_cs = 3, + .hastype = true, + .we0 = 16, +@@ -1131,13 +2394,17 @@ + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xffffd0ff, + .hdiv_max = 1, ++ .min_window_sz = 0x800000, ++ .get_clk_div = apseed_get_clk_div_ast2500, + .calibrate = aspeed_spi_calibrate, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, ++ .adjust_window = aspeed_adjust_window_ast2500, + }; + + static const struct aspeed_spi_data ast2500_spi_data = { ++ .ver = 2500, + .max_cs = 2, + .hastype = false, + .we0 = 16, +@@ -1145,13 +2412,17 @@ + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xffffd0ff, + .hdiv_max = 1, ++ .min_window_sz = 0x800000, ++ .get_clk_div = apseed_get_clk_div_ast2500, + .calibrate = aspeed_spi_calibrate, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, ++ .adjust_window = aspeed_adjust_window_ast2500, + }; + + static const struct aspeed_spi_data ast2600_fmc_data = { ++ .ver = 2600, + .max_cs = 3, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_TX_QUAD, +@@ -1160,13 +2431,17 @@ + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, ++ .min_window_sz = 0x200000, ++ .get_clk_div = apseed_get_clk_div_ast2600, + .calibrate = aspeed_spi_ast2600_calibrate, + .segment_start = aspeed_spi_segment_ast2600_start, + .segment_end = aspeed_spi_segment_ast2600_end, + .segment_reg = aspeed_spi_segment_ast2600_reg, ++ .adjust_window = aspeed_adjust_window_ast2600, + }; + + static const struct aspeed_spi_data ast2600_spi_data = { ++ .ver = 2600, + .max_cs = 2, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_TX_QUAD, +@@ -1175,10 +2450,53 @@ + .timing = CE0_TIMING_COMPENSATION_REG, + .hclk_mask = 0xf0fff0ff, + .hdiv_max = 2, ++ .min_window_sz = 0x200000, ++ .get_clk_div = apseed_get_clk_div_ast2600, + .calibrate = aspeed_spi_ast2600_calibrate, + .segment_start = aspeed_spi_segment_ast2600_start, + .segment_end = aspeed_spi_segment_ast2600_end, + .segment_reg = aspeed_spi_segment_ast2600_reg, ++ .adjust_window = aspeed_adjust_window_ast2600, ++ .safs_init = aspeed_spi_ast2600_fill_safs_cmd, ++}; ++ ++static const struct aspeed_spi_data ast2700_fmc_data = { ++ .ver = 2700, ++ .max_cs = 3, ++ .hastype = false, ++ .mode_bits = SPI_RX_QUAD | SPI_TX_QUAD, ++ .we0 = 16, ++ .ctl0 = CE0_CTRL_REG, ++ .timing = CE0_TIMING_COMPENSATION_REG, ++ .hclk_mask = 0xf0fff0ff, ++ .hdiv_max = 2, ++ .min_window_sz = 0x10000, ++ .get_clk_div = apseed_get_clk_div_ast2600, ++ .calibrate = aspeed_spi_ast2600_calibrate, ++ .segment_start = aspeed_spi_segment_ast2700_start, ++ .segment_end = aspeed_spi_segment_ast2700_end, ++ .segment_reg = aspeed_spi_segment_ast2700_reg, ++}; ++ ++static const struct aspeed_spi_data ast2700_spi_data = { ++ .ver = 2700, ++ .max_cs = 2, ++ .hastype = false, ++ .mode_bits = SPI_RX_QUAD | SPI_TX_QUAD, ++ .we0 = 16, ++ .ctl0 = CE0_CTRL_REG, ++ .timing = CE0_TIMING_COMPENSATION_REG, ++ .hclk_mask = 0xf0fff0ff, ++ .hdiv_max = 2, ++ .min_window_sz = 0x10000, ++ .get_clk_div = apseed_get_clk_div_ast2600, ++ .calibrate = aspeed_spi_ast2600_calibrate, ++ .segment_start = aspeed_spi_segment_ast2700_start, ++ .segment_end = aspeed_spi_segment_ast2700_end, ++ .segment_reg = aspeed_spi_segment_ast2700_reg, ++ .safs_init = aspeed_spi_ast2700_safs_init, ++ .safs_start = aspeed_spi_ast2700_safs_start, ++ .safs_stop = aspeed_spi_ast2700_safs_stop, + }; + + static const struct of_device_id aspeed_spi_matches[] = { +@@ -1188,6 +2506,8 @@ + { .compatible = "aspeed,ast2500-spi", .data = &ast2500_spi_data }, + { .compatible = "aspeed,ast2600-fmc", .data = &ast2600_fmc_data }, + { .compatible = "aspeed,ast2600-spi", .data = &ast2600_spi_data }, ++ { .compatible = "aspeed,ast2700-fmc", .data = &ast2700_fmc_data }, ++ { .compatible = "aspeed,ast2700-spi", .data = &ast2700_spi_data }, + { } + }; + MODULE_DEVICE_TABLE(of, aspeed_spi_matches); +diff --git a/drivers/spi/spi-aspeed-txrx.c b/drivers/spi/spi-aspeed-txrx.c +--- a/drivers/spi/spi-aspeed-txrx.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/spi/spi-aspeed-txrx.c 2026-09-09 15:51:44.647239144 +0000 +@@ -0,0 +1,671 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) ASPEED Technology Inc. ++ * Ryan Chen ++ * Chin-Ting Kuo ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SPI_CONFIG 0x00 ++#define SPI_CTRL 0x04 ++#define SPI_CE0_CTRL 0x10 ++#define SPI_DECODE_ADDR_REG 0x30 ++#define SPI_MISC_CTRL 0x54 ++ ++#define SPI_FULL_DUPLEX_RX_REG 0x1e4 ++ ++#define SPI_IO_MASK (0xf0000000) ++#define SPI_DUAL_IO_MODE (0x3 << 28) ++#define SPI_QUAD_IO_MODE (0x5 << 28) ++ ++#define SPI_LSB_FIRST_CTRL BIT(5) ++#define SPI_CE_INACTIVE BIT(2) ++#define SPI_CMD_USER_MODE (0x3) ++ ++struct aspeed_spi_host { ++ phys_addr_t ahb_base_phy; ++ size_t ahb_window_sz; ++ void __iomem *ctrl_reg; ++ struct spi_controller *ctrl; ++ struct spi_device *spi_dev; ++ struct device *dev; ++ struct clk *clk; ++ u32 ahb_clk; ++ u32 ctrl_val[5]; ++ void __iomem *chip_ahb_base[5]; ++ struct spi_device *cs_change_spi; ++ const struct aspeed_spi_info *info; ++ u32 saved_host_cmd_ctrl; ++}; ++ ++struct aspeed_spi_info { ++ u32 max_cs; ++ size_t min_window_sz; ++ u32 hclk_mask; ++ u32 (*get_clk_div)(struct aspeed_spi_host *host, u32 hz); ++ void (*set_segment)(struct aspeed_spi_host *host); ++}; ++ ++static inline void spi_aspeed_dump_buf(const u8 *buf, u32 len) ++{ ++ u32 i; ++ ++ if (len > 10) { ++ for (i = 0; i < 10; i++) ++ pr_info("%02x ", buf[i]); ++ } else { ++ for (i = 0; i < len; i++) ++ pr_info("%02x ", buf[i]); ++ } ++ pr_info("\n"); ++} ++ ++#define G5_SEGMENT_ADDR_VALUE(start, end) \ ++ (((((start) >> 23) & 0xFF) << 16) | ((((end) >> 23) & 0xFF) << 24)) ++ ++static void aspeed_spi_set_segment_addr_ast2500(struct aspeed_spi_host *host) ++{ ++ const struct aspeed_spi_info *info = host->info; ++ u32 cs; ++ phys_addr_t start = host->ahb_base_phy; ++ phys_addr_t end; ++ u32 reg_val; ++ ++ for (cs = 0; cs < info->max_cs; cs++) { ++ end = start + info->min_window_sz; ++ if (cs == info->max_cs - 1) ++ end = host->ahb_base_phy + host->ahb_window_sz; ++ ++ reg_val = G5_SEGMENT_ADDR_VALUE(start, end); ++ writel(reg_val, host->ctrl_reg + SPI_CE0_CTRL + cs * 4); ++ /* always mapping min_window_sz due to user mode is used */ ++ host->chip_ahb_base[cs] = devm_ioremap(host->dev, ++ start, ++ info->min_window_sz); ++ start = end; ++ } ++} ++ ++#define G6_SEGMENT_ADDR_VALUE(start, end) \ ++ ((((start) & 0x0ff00000) >> 16) | (((end) - 1) & 0x0ff00000)) ++ ++static void aspeed_spi_set_segment_addr_ast2600(struct aspeed_spi_host *host) ++{ ++ const struct aspeed_spi_info *info = host->info; ++ u32 cs; ++ phys_addr_t start = host->ahb_base_phy; ++ phys_addr_t end; ++ u32 reg_val; ++ ++ for (cs = 0; cs < info->max_cs; cs++) { ++ end = start + info->min_window_sz; ++ reg_val = G6_SEGMENT_ADDR_VALUE(start, end); ++ writel(reg_val, host->ctrl_reg + SPI_DECODE_ADDR_REG + cs * 4); ++ host->chip_ahb_base[cs] = devm_ioremap(host->dev, ++ start, ++ info->min_window_sz); ++ start = end; ++ } ++} ++ ++#define G7_SEGMENT_ADDR_VALUE(start, end) \ ++ ((((start) >> 16) & 0x7fff) | (((end) + 1) & 0x7fff0000)) ++ ++static void aspeed_spi_set_segment_addr_ast2700(struct aspeed_spi_host *host) ++{ ++ const struct aspeed_spi_info *info = host->info; ++ u32 cs; ++ phys_addr_t start = host->ahb_base_phy; ++ phys_addr_t end; ++ u32 reg_val; ++ ++ for (cs = 0; cs < info->max_cs; cs++) { ++ end = start + info->min_window_sz; ++ reg_val = G7_SEGMENT_ADDR_VALUE(start - host->ahb_base_phy, ++ end - host->ahb_base_phy); ++ writel(reg_val, host->ctrl_reg + SPI_DECODE_ADDR_REG + cs * 4); ++ host->chip_ahb_base[cs] = devm_ioremap(host->dev, ++ start, ++ info->min_window_sz); ++ start = end; ++ } ++} ++ ++static const u32 aspeed_spi_hclk_divs[] = { ++ /* HCLK, HCLK/2, HCLK/3, HCLK/4, HCLK/5, ..., HCLK/16 */ ++ 0xf, 0x7, 0xe, 0x6, 0xd, ++ 0x5, 0xc, 0x4, 0xb, 0x3, ++ 0xa, 0x2, 0x9, 0x1, 0x8, ++ 0x0 ++}; ++ ++#define ASPEED_SPI_HCLK_DIV(i) (aspeed_spi_hclk_divs[(i)] << 8) ++ ++static u32 apseed_get_clk_div_ast2500(struct aspeed_spi_host *host, ++ u32 max_hz) ++{ ++ struct device *dev = host->dev; ++ u32 hclk_clk = host->ahb_clk; ++ u32 hclk_div = 0; ++ u32 i; ++ bool found = false; ++ ++ /* FMC/SPIR10[11:8] */ ++ for (i = 0; i < ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { ++ if (hclk_clk / (i + 1) <= max_hz) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) { ++ hclk_div = ASPEED_SPI_HCLK_DIV(i); ++ goto end; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { ++ if (hclk_clk / ((i + 1) * 4) <= max_hz) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) ++ hclk_div = BIT(13) | ASPEED_SPI_HCLK_DIV(i); ++ ++end: ++ dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", ++ found ? "yes" : "no", hclk_clk, max_hz); ++ ++ if (found) { ++ dev_dbg(dev, "h_div: %d (mask %x)\n", ++ i + 1, hclk_div); ++ } ++ ++ return hclk_div; ++} ++ ++static u32 apseed_get_clk_div_ast2600(struct aspeed_spi_host *host, ++ u32 max_hz) ++{ ++ struct device *dev = host->dev; ++ u32 hclk_clk = host->ahb_clk; ++ u32 hclk_div = 0; ++ u32 i, j; ++ bool found = false; ++ ++ /* FMC/SPIR10[27:24] */ ++ for (j = 0; j < 16; j++) { ++ /* FMC/SPIR10[11:8] */ ++ for (i = 0; i < ARRAY_SIZE(aspeed_spi_hclk_divs); i++) { ++ if (i == 0 && j == 0) ++ continue; ++ ++ if (hclk_clk / (i + 1 + (j * 16)) <= max_hz) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) { ++ hclk_div = ((j << 24) | ASPEED_SPI_HCLK_DIV(i)); ++ break; ++ } ++ } ++ ++ dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", ++ found ? "yes" : "no", hclk_clk, max_hz); ++ ++ if (found) { ++ dev_dbg(dev, "base_clk: %d, h_div: %d (mask %x)\n", ++ j, i + 1, hclk_div); ++ } ++ ++ return hclk_div; ++} ++ ++static void aspeed_spi_chip_set_type(struct aspeed_spi_host *host) ++{ ++ u32 reg; ++ u32 cs; ++ ++ for (cs = 0; cs < host->info->max_cs; cs++) { ++ reg = readl(host->ctrl_reg + SPI_CONFIG); ++ reg &= ~(0x3 << (cs * 2)); ++ reg |= 0x2 << (cs * 2); ++ writel(reg, host->ctrl_reg + SPI_CONFIG); ++ } ++} ++ ++static void aspeed_spi_enable(struct aspeed_spi_host *host, bool enable) ++{ ++ u32 cs; ++ u32 reg; ++ u32 we_bit; ++ ++ for (cs = 0; cs < host->info->max_cs; cs++) { ++ reg = readl(host->ctrl_reg + SPI_CONFIG); ++ we_bit = (0x1 << cs) << 16; ++ ++ if (enable) ++ reg |= we_bit; ++ else ++ reg &= ~we_bit; ++ ++ writel(reg, host->ctrl_reg + SPI_CONFIG); ++ } ++} ++ ++static int aspeed_spi_setup(struct spi_device *spi) ++{ ++ struct aspeed_spi_host *host = ++ (struct aspeed_spi_host *)spi_controller_get_devdata(spi->controller); ++ struct device *dev = host->dev; ++ u32 clk_div; ++ u8 cs = spi_get_chipselect(spi, 0); ++ void __iomem *ctrl_reg = host->ctrl_reg + SPI_CE0_CTRL + cs * 4; ++ u32 unsupport_mode = (u32)(~(SPI_MODE_0 | SPI_RX_DUAL | SPI_TX_DUAL | ++ SPI_RX_QUAD | SPI_TX_QUAD | SPI_LSB_FIRST)); ++ ++ dev_dbg(dev, "cs: %d, mode: %d, max_speed: %d, bits_per_word: %d\n", ++ cs, spi->mode, spi->max_speed_hz, spi->bits_per_word); ++ ++ if (spi->mode & unsupport_mode) { ++ dev_dbg(dev, "unsupported mode bits: %x\n", spi->mode); ++ return -EINVAL; ++ } ++ ++ host->ctrl_val[cs] = SPI_CE_INACTIVE | SPI_CMD_USER_MODE; ++ ++ if (spi->max_speed_hz) { ++ clk_div = host->info->get_clk_div(host, spi->max_speed_hz); ++ } else { ++ /* speed zero means "as slow as possible" */ ++ clk_div = ~(host->info->hclk_mask); ++ } ++ ++ host->ctrl_val[cs] |= clk_div; ++ ++ if (spi->mode & SPI_LSB_FIRST) ++ host->ctrl_val[cs] |= SPI_LSB_FIRST_CTRL; ++ ++ writel(host->ctrl_val[cs], ctrl_reg); ++ ++ dev_info(dev, "cs: %d, ctrl_val: 0x%08x\n", cs, host->ctrl_val[cs]); ++ ++ return 0; ++} ++ ++static void aspeed_spi_start_user(struct spi_device *spi) ++{ ++ struct aspeed_spi_host *host = ++ (struct aspeed_spi_host *)spi_controller_get_devdata(spi->controller); ++ u8 cs = spi_get_chipselect(spi, 0); ++ u32 ctrl_val = host->ctrl_val[cs]; ++ void __iomem *ctrl_reg = host->ctrl_reg + SPI_CE0_CTRL + cs * 4; ++ ++ ctrl_val |= SPI_CE_INACTIVE; ++ writel(ctrl_val, ctrl_reg); ++ ++ ctrl_val &= ~SPI_CE_INACTIVE; ++ writel(ctrl_val, ctrl_reg); ++} ++ ++static void aspeed_spi_stop_user(struct spi_device *spi) ++{ ++ struct aspeed_spi_host *host = ++ (struct aspeed_spi_host *)spi_controller_get_devdata(spi->controller); ++ u8 cs = spi_get_chipselect(spi, 0); ++ u32 ctrl_val = host->ctrl_val[cs] | SPI_CE_INACTIVE; ++ void __iomem *ctrl_reg = host->ctrl_reg + SPI_CE0_CTRL + cs * 4; ++ ++ writel(ctrl_val, ctrl_reg); ++} ++ ++static void aspeed_spi_restore_host_cmd_ctrl(struct aspeed_spi_host *host) ++{ ++ writel(host->saved_host_cmd_ctrl, host->ctrl_reg + SPI_MISC_CTRL); ++} ++ ++static void aspeed_spi_transfer_tx(struct aspeed_spi_host *host, const u8 *tx_buf, ++ u8 *rx_buf, void *dst, u32 len) ++{ ++ u32 i; ++ ++ for (i = 0; i < len; i++) { ++ writeb(tx_buf[i], dst); ++ ++ if (rx_buf && tx_buf == rx_buf) ++ rx_buf[i] = readb(host->ctrl_reg + SPI_FULL_DUPLEX_RX_REG); ++ } ++} ++ ++/* ++ * aspeed_spi_transfer - transfer_one_message callback ++ * ++ * SPI_MISC_CTRL holds the host command configuration and must be cleared ++ * before entering user mode; it is saved here and restored only when CS ++ * is finally deasserted. ++ * ++ * cs_change on the last transfer keeps CS active across messages ++ * (cs_change_spi tracks the owning device). cs_change on any earlier ++ * transfer toggles CS in place. A stale CS held by another device is ++ * force-released before the new message begins. ++ */ ++static int aspeed_spi_transfer(struct spi_controller *ctlr, ++ struct spi_message *msg) ++{ ++ struct aspeed_spi_host *host = ++ (struct aspeed_spi_host *)spi_controller_get_devdata(ctlr); ++ struct device *dev = host->dev; ++ struct spi_device *spi = msg->spi; ++ struct spi_transfer *xfer; ++ const u8 *tx_buf; ++ u8 *rx_buf; ++ u32 cs; ++ u32 j = 0; ++ u32 ctrl_val; ++ void __iomem *ctrl_reg; ++ bool is_last; ++ bool keep_cs = false; ++ ++ /* Drop a CS held by another device before starting this message. */ ++ if (host->cs_change_spi && host->cs_change_spi != spi) { ++ dev_warn(dev, "CS was kept active for another SPI device, releasing it\n"); ++ ++ aspeed_spi_stop_user(host->cs_change_spi); ++ aspeed_spi_restore_host_cmd_ctrl(host); ++ ++ host->cs_change_spi = NULL; ++ } ++ ++ /* Backup host command mode information from MISC_CTRL register before activating CS */ ++ if (!host->cs_change_spi) { ++ host->saved_host_cmd_ctrl = readl(host->ctrl_reg + SPI_MISC_CTRL); ++ ++ writel(0x0, host->ctrl_reg + SPI_MISC_CTRL); ++ aspeed_spi_start_user(spi); ++ } ++ ++ cs = spi_get_chipselect(spi, 0); ++ ctrl_reg = host->ctrl_reg + SPI_CE0_CTRL + cs * 4; ++ ctrl_val = readl(ctrl_reg); ++ ++ dev_dbg(dev, "cs: %d\n", cs); ++ ++ list_for_each_entry(xfer, &msg->transfers, transfer_list) { ++ is_last = list_is_last(&xfer->transfer_list, &msg->transfers); ++ ++ dev_dbg(dev, ++ "xfer[%d]: width %d, len %u, tx %p, rx %p\n", ++ j, ++ xfer->bits_per_word, xfer->len, ++ xfer->tx_buf, xfer->rx_buf); ++ ++ tx_buf = xfer->tx_buf; ++ rx_buf = xfer->rx_buf; ++ ++ if (tx_buf) { ++ ctrl_val &= ~SPI_IO_MASK; ++ if (xfer->tx_nbits == SPI_NBITS_DUAL) ++ ctrl_val |= SPI_DUAL_IO_MODE; ++ else if (xfer->tx_nbits == SPI_NBITS_QUAD) ++ ctrl_val |= SPI_QUAD_IO_MODE; ++ writel(ctrl_val, ctrl_reg); ++ ++#if defined(SPI_ASPEED_TXRX_DBG) ++ pr_info("tx : "); ++ spi_aspeed_dump_buf(tx_buf, xfer->len); ++#endif ++ ++ aspeed_spi_transfer_tx(host, tx_buf, rx_buf, ++ (void *)host->chip_ahb_base[cs], ++ xfer->len); ++ } ++ ++ if (rx_buf && rx_buf != tx_buf) { ++ ctrl_val &= ~SPI_IO_MASK; ++ if (xfer->rx_nbits == SPI_NBITS_DUAL) ++ ctrl_val |= SPI_DUAL_IO_MODE; ++ else if (xfer->rx_nbits == SPI_NBITS_QUAD) ++ ctrl_val |= SPI_QUAD_IO_MODE; ++ writel(ctrl_val, ctrl_reg); ++ ++ ioread8_rep(host->chip_ahb_base[cs], rx_buf, xfer->len); ++ ++#if defined(SPI_ASPEED_TXRX_DBG) ++ pr_info("rx : "); ++ spi_aspeed_dump_buf(rx_buf, xfer->len); ++#endif ++ } ++ ++ msg->actual_length += xfer->len; ++ ++ /* Non-final cs_change toggles CS; final cs_change keeps it active. */ ++ if (xfer->cs_change) { ++ if (is_last) { ++ keep_cs = true; ++ } else { ++ aspeed_spi_stop_user(spi); ++ /* CS deassert-to-reassert delay; uses xfer->cs_change_delay if set, else 10us */ ++ spi_transfer_cs_change_delay_exec(msg, xfer); ++ aspeed_spi_start_user(spi); ++ } ++ } ++ ++ j++; ++ } ++ ++ if (!keep_cs) { ++ aspeed_spi_stop_user(spi); ++ aspeed_spi_restore_host_cmd_ctrl(host); ++ ++ host->cs_change_spi = NULL; ++ } else { ++ host->cs_change_spi = spi; ++ } ++ ++ msg->status = 0; ++ ++ spi_finalize_current_message(ctlr); ++ ++ return 0; ++} ++ ++static int aspeed_spi_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ struct aspeed_spi_host *host; ++ struct spi_controller *ctrl; ++ struct reset_control *rst; ++ int err = 0; ++ ++ ctrl = devm_spi_alloc_master(dev, sizeof(struct aspeed_spi_host)); ++ if (!ctrl) { ++ dev_err(dev, "No memory for spi controller\n"); ++ return -ENOMEM; ++ } ++ ++ ctrl->mode_bits = SPI_MODE_0 | SPI_RX_DUAL | SPI_TX_DUAL | ++ SPI_RX_QUAD | SPI_TX_QUAD | SPI_LSB_FIRST; ++ ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ++ ctrl->dev.of_node = pdev->dev.of_node; ++ ctrl->bus_num = pdev->id; ++ ++ host = spi_controller_get_devdata(ctrl); ++ platform_set_drvdata(pdev, host); ++ ++ memset(host, 0, sizeof(struct aspeed_spi_host)); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(dev, "cannot get IORESOURCE_MEM 0\n"); ++ return -ENXIO; ++ } ++ ++ host->ctrl_reg = devm_ioremap_resource(dev, res); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ if (!res) { ++ dev_err(dev, "cannot get IORESOURCE_MEM 1\n"); ++ return -ENXIO; ++ } ++ ++ host->ahb_base_phy = res->start; ++ host->ahb_window_sz = resource_size(res); ++ ++ host->ctrl = spi_controller_get(ctrl); ++ host->dev = &pdev->dev; ++ host->info = of_device_get_match_data(&pdev->dev); ++ if (!host->info) ++ return -ENODEV; ++ ++ rst = devm_reset_control_get_optional(&pdev->dev, NULL); ++ if (rst) { ++ err = reset_control_deassert(rst); ++ if (err) { ++ dev_err(dev, "fail to deassert reset control\n"); ++ return -EBUSY; ++ } ++ } ++ ++ host->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(host->clk)) { ++ dev_err(dev, "missing clock\n"); ++ return PTR_ERR(host->clk); ++ } ++ ++ host->ahb_clk = clk_get_rate(host->clk); ++ if (!host->ahb_clk) { ++ dev_err(dev, "invalid clock\n"); ++ return -EINVAL; ++ } ++ ++ err = clk_prepare_enable(host->clk); ++ if (err) { ++ dev_err(dev, "can not enable the clock\n"); ++ return err; ++ } ++ ++ host->ctrl->setup = aspeed_spi_setup; ++ host->ctrl->transfer_one_message = aspeed_spi_transfer; ++ host->ctrl->num_chipselect = host->info->max_cs; ++ ++ /* configure minimum segment window */ ++ host->info->set_segment(host); ++ aspeed_spi_enable(host, true); ++ aspeed_spi_chip_set_type(host); ++ ++ err = devm_spi_register_controller(dev, host->ctrl); ++ if (err) { ++ dev_err(dev, "failed to register SPI controller\n"); ++ goto disable_clk; ++ } ++ ++ return 0; ++ ++disable_clk: ++ clk_disable_unprepare(host->clk); ++ ++ return err; ++} ++ ++static void aspeed_spi_remove(struct platform_device *pdev) ++{ ++ struct aspeed_spi_host *host = platform_get_drvdata(pdev); ++ ++ aspeed_spi_enable(host, false); ++ clk_disable_unprepare(host->clk); ++} ++ ++struct aspeed_spi_info ast2500_fmc_info = { ++ .max_cs = 3, ++ .min_window_sz = 0x800000, ++ .hclk_mask = 0xffffd0ff, ++ .get_clk_div = apseed_get_clk_div_ast2500, ++ .set_segment = aspeed_spi_set_segment_addr_ast2500, ++}; ++ ++struct aspeed_spi_info ast2500_spi_info = { ++ .max_cs = 2, ++ .min_window_sz = 0x800000, ++ .hclk_mask = 0xffffd0ff, ++ .get_clk_div = apseed_get_clk_div_ast2500, ++ .set_segment = aspeed_spi_set_segment_addr_ast2500, ++}; ++ ++struct aspeed_spi_info ast2600_fmc_info = { ++ .max_cs = 3, ++ .min_window_sz = 0x200000, ++ .hclk_mask = 0xf0fff0ff, ++ .get_clk_div = apseed_get_clk_div_ast2600, ++ .set_segment = aspeed_spi_set_segment_addr_ast2600, ++}; ++ ++struct aspeed_spi_info ast2600_spi_info = { ++ .max_cs = 2, ++ .min_window_sz = 0x200000, ++ .hclk_mask = 0xf0fff0ff, ++ .get_clk_div = apseed_get_clk_div_ast2600, ++ .set_segment = aspeed_spi_set_segment_addr_ast2600, ++}; ++ ++struct aspeed_spi_info ast2700_fmc_info = { ++ .max_cs = 3, ++ .min_window_sz = 0x10000, ++ .hclk_mask = 0xf0fff0ff, ++ .get_clk_div = apseed_get_clk_div_ast2600, ++ .set_segment = aspeed_spi_set_segment_addr_ast2700, ++}; ++ ++struct aspeed_spi_info ast2700_spi_info = { ++ .max_cs = 2, ++ .min_window_sz = 0x10000, ++ .hclk_mask = 0xf0fff0ff, ++ .get_clk_div = apseed_get_clk_div_ast2600, ++ .set_segment = aspeed_spi_set_segment_addr_ast2700, ++}; ++ ++static const struct of_device_id aspeed_spi_of_match[] = { ++ { .compatible = "aspeed,ast2500-fmc-txrx", .data = &ast2500_fmc_info}, ++ { .compatible = "aspeed,ast2500-spi-txrx", .data = &ast2500_spi_info}, ++ { .compatible = "aspeed,ast2600-fmc-txrx", .data = &ast2600_fmc_info}, ++ { .compatible = "aspeed,ast2600-spi-txrx", .data = &ast2600_spi_info}, ++ { .compatible = "aspeed,ast2700-fmc-txrx", .data = &ast2700_fmc_info}, ++ { .compatible = "aspeed,ast2700-spi-txrx", .data = &ast2700_spi_info}, ++ { }, ++}; ++ ++static struct platform_driver aspeed_spi_driver = { ++ .probe = aspeed_spi_probe, ++ .remove = aspeed_spi_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_spi_of_match, ++ }, ++}; ++ ++module_platform_driver(aspeed_spi_driver); ++ ++MODULE_DESCRIPTION("ASPEED Pure SPI Driver"); ++MODULE_AUTHOR("Ryan Chen"); ++MODULE_AUTHOR("Chin-Ting Kuo"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/tty/serial/8250/8250_aspeed.c b/drivers/tty/serial/8250/8250_aspeed.c +--- a/drivers/tty/serial/8250/8250_aspeed.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/tty/serial/8250/8250_aspeed.c 2026-09-09 15:51:44.527240958 +0000 +@@ -0,0 +1,545 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) ASPEED Technology Inc. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "8250.h" ++ ++#define DEVICE_NAME "aspeed-uart" ++#define UNKNOWN 0 ++#define AST2500_PLAT 1 ++#define AST2600_PLAT 2 ++#define AST2700_PLAT 3 ++ ++/* offsets for the aspeed virtual uart registers */ ++#define VUART_GCRA 0x20 ++#define VUART_GCRA_VUART_EN BIT(0) ++#define VUART_GCRA_SIRQ_POLARITY BIT(1) ++#define VUART_GCRA_CHARACTER_TIMEOUT_TIME_MASK GENMASK(3, 2) ++#define VUART_GCRA_DISABLE_HOST_TX_DISCARD BIT(5) ++#define VUART_GCRB 0x24 ++#define VUART_GCRB_HOST_SIRQ_MASK GENMASK(7, 4) ++#define VUART_GCRB_HOST_SIRQ_SHIFT 4 ++#define VUART_ADDRL 0x28 ++#define VUART_ADDRH 0x2c ++#define VUART_GCRG 0x38 ++#define VUART_GCRG_CHARACTER_TIMEOUT_TIME_CONTROL BIT(1) ++ ++#define DMA_RX_BUFSZ (64 * 1024) ++ ++struct uart_ops ast8250_pops; ++ ++struct ast8250_vuart { ++ u32 port; ++ u32 sirq; ++ u32 sirq_pol; ++ bool character_timeout_time_en; ++}; ++ ++struct ast8250_udma { ++ u32 ch; ++ ++ u32 tx_rbsz; ++ u32 rx_rbsz; ++ ++ dma_addr_t tx_addr; ++ dma_addr_t rx_addr; ++ ++ struct tty_port *tport; ++ struct circ_buf *rx_rb; ++ ++ bool tx_tmout_dis; ++ bool rx_tmout_dis; ++}; ++ ++struct ast8250_data { ++ int line; ++ ++ struct resource *res; ++ u8 __iomem *regs; ++ ++ bool is_vuart; ++ bool use_dma; ++ ++ struct reset_control *rst; ++ struct clk *clk; ++ ++ struct ast8250_vuart vuart; ++ struct ast8250_udma dma; ++}; ++ ++static void ast8250_dma_tx_complete(int tx_rb_rptr, void *id) ++{ ++ unsigned long flags; ++ struct uart_port *port = id; ++ struct ast8250_data *data = port->private_data; ++ unsigned int count, tail; ++ ++ uart_port_lock_irqsave(port, &flags); ++ ++ count = kfifo_out_linear(&data->dma.tport->xmit_fifo, &tail, data->dma.tx_rbsz); ++ count = CIRC_CNT(tx_rb_rptr, tail, data->dma.tx_rbsz); ++ if (!count) ++ count = data->dma.tx_rbsz; ++ kfifo_dma_out_finish(&data->dma.tport->xmit_fifo, count); ++ port->icount.tx += count; ++ ++ if (kfifo_len(&data->dma.tport->xmit_fifo) < WAKEUP_CHARS) ++ uart_write_wakeup(port); ++ ++ uart_port_unlock_irqrestore(port, flags); ++} ++ ++static void ast8250_dma_rx_complete(int rx_rb_wptr, void *id) ++{ ++ unsigned long flags; ++ struct uart_port *up = (struct uart_port*)id; ++ struct tty_port *tp = &up->state->port; ++ struct ast8250_data *data = up->private_data; ++ struct ast8250_udma *dma = &data->dma; ++ struct circ_buf *rx_rb = dma->rx_rb; ++ u32 rx_rbsz = dma->rx_rbsz; ++ u32 count = 0; ++ ++ uart_port_lock_irqsave(up, &flags); ++ ++ rx_rb->head = rx_rb_wptr; ++ ++ dma_sync_single_for_cpu(up->dev, ++ dma->rx_addr, dma->rx_rbsz, DMA_FROM_DEVICE); ++ ++ while (CIRC_CNT(rx_rb->head, rx_rb->tail, rx_rbsz)) { ++ count = CIRC_CNT_TO_END(rx_rb->head, rx_rb->tail, rx_rbsz); ++ ++ tty_insert_flip_string(tp, rx_rb->buf + rx_rb->tail, count); ++ ++ rx_rb->tail += count; ++ rx_rb->tail %= rx_rbsz; ++ ++ up->icount.rx += count; ++ } ++ ++ if (count) { ++ aspeed_udma_set_rx_rptr(data->dma.ch, rx_rb->tail); ++ tty_flip_buffer_push(tp); ++ } ++ ++ uart_port_unlock_irqrestore(up, flags); ++} ++ ++static void ast8250_dma_start_tx(struct uart_port *port) ++{ ++ struct ast8250_data *data = port->private_data; ++ struct ast8250_udma *dma = &data->dma; ++ typeof(&dma->tport->xmit_fifo) tmp = &dma->tport->xmit_fifo; ++ struct __kfifo *tx_rb = &tmp->kfifo; ++ ++ dma_sync_single_for_device(port->dev, ++ dma->tx_addr, dma->tx_rbsz, DMA_TO_DEVICE); ++ ++ aspeed_udma_set_tx_wptr(dma->ch, tx_rb->in); ++} ++ ++static void ast8250_dma_pops_hook(struct uart_port *port) ++{ ++ static int first = 1; ++ ++ if (first) { ++ ast8250_pops = *port->ops; ++ ast8250_pops.start_tx = ast8250_dma_start_tx; ++ } ++ ++ first = 0; ++ port->ops = &ast8250_pops; ++} ++ ++static void ast8250_vuart_init(struct ast8250_data *data) ++{ ++ u8 reg; ++ struct ast8250_vuart *vuart = &data->vuart; ++ ++ /* IO port address */ ++ writeb((u8)(vuart->port >> 0), data->regs + VUART_ADDRL); ++ writeb((u8)(vuart->port >> 8), data->regs + VUART_ADDRH); ++ ++ /* SIRQ number */ ++ reg = readb(data->regs + VUART_GCRB); ++ reg &= ~VUART_GCRB_HOST_SIRQ_MASK; ++ reg |= ((vuart->sirq << VUART_GCRB_HOST_SIRQ_SHIFT) & VUART_GCRB_HOST_SIRQ_MASK); ++ writeb(reg, data->regs + VUART_GCRB); ++ ++ /* SIRQ polarity */ ++ reg = readb(data->regs + VUART_GCRA); ++ if (vuart->sirq_pol) ++ reg |= VUART_GCRA_SIRQ_POLARITY; ++ else ++ reg &= ~VUART_GCRA_SIRQ_POLARITY; ++ writeb(reg, data->regs + VUART_GCRA); ++ ++ if (vuart->character_timeout_time_en) { ++ /* Character timeout time */ ++ reg = readb(data->regs + VUART_GCRA); ++ reg |= VUART_GCRA_CHARACTER_TIMEOUT_TIME_MASK; ++ writeb(reg, data->regs + VUART_GCRA); ++ ++ /* Character timeout time by LCLK control bit */ ++ reg = readb(data->regs + VUART_GCRG); ++ reg |= VUART_GCRG_CHARACTER_TIMEOUT_TIME_CONTROL; ++ writeb(reg, data->regs + VUART_GCRG); ++ } ++} ++ ++static void ast8250_vuart_set_host_tx_discard(struct ast8250_data *data, bool discard) ++{ ++ u8 reg; ++ ++ reg = readb(data->regs + VUART_GCRA); ++ if (discard) ++ reg &= ~VUART_GCRA_DISABLE_HOST_TX_DISCARD; ++ else ++ reg |= VUART_GCRA_DISABLE_HOST_TX_DISCARD; ++ writeb(reg, data->regs + VUART_GCRA); ++} ++ ++static void ast8250_vuart_set_enable(struct ast8250_data *data, bool enable) ++{ ++ u8 reg; ++ ++ reg = readb(data->regs + VUART_GCRA); ++ if (enable) ++ reg |= VUART_GCRA_VUART_EN; ++ else ++ reg &= ~VUART_GCRA_VUART_EN; ++ writeb(reg, data->regs + VUART_GCRA); ++} ++ ++static int ast8250_handle_irq(struct uart_port *port) ++{ ++ u32 iir = port->serial_in(port, UART_IIR); ++ return serial8250_handle_irq(port, iir); ++} ++ ++static int ast8250_startup(struct uart_port *port) ++{ ++ int rc = 0; ++ struct ast8250_data *data = port->private_data; ++ struct ast8250_udma *dma; ++ ++ if (data->is_vuart) ++ ast8250_vuart_set_host_tx_discard(data, false); ++ ++ if (data->use_dma) { ++ dma = &data->dma; ++ ++ dma->tx_rbsz = UART_XMIT_SIZE; ++ dma->rx_rbsz = DMA_RX_BUFSZ; ++ ++ /* ++ * We take the xmit buffer passed from upper layers as ++ * the DMA TX buffer and allocate a new buffer for the ++ * RX use. ++ * ++ * To keep the TX/RX operation consistency, we use the ++ * streaming DMA interface instead of the coherent one ++ */ ++ dma->tport = &port->state->port; ++ dma->rx_rb->buf = kzalloc(data->dma.rx_rbsz, GFP_KERNEL); ++ if (IS_ERR_OR_NULL(dma->rx_rb->buf)) { ++ dev_err(port->dev, "failed to allcoate RX DMA buffer\n"); ++ rc = -ENOMEM; ++ goto out; ++ } ++ ++ dma->tx_addr = dma_map_single(port->dev, dma->tport->xmit_buf, ++ dma->tx_rbsz, DMA_TO_DEVICE); ++ if (dma_mapping_error(port->dev, dma->tx_addr)) { ++ dev_err(port->dev, "failed to map streaming TX DMA region\n"); ++ rc = -ENOMEM; ++ goto free_dma_n_out; ++ } ++ ++ dma->rx_addr = dma_map_single(port->dev, dma->rx_rb->buf, ++ dma->rx_rbsz, DMA_FROM_DEVICE); ++ if (dma_mapping_error(port->dev, dma->rx_addr)) { ++ dev_err(port->dev, "failed to map streaming RX DMA region\n"); ++ rc = -ENOMEM; ++ goto free_dma_n_out; ++ } ++ ++ rc = aspeed_udma_request_tx_chan(dma->ch, dma->tx_addr, ++ dma->tx_rbsz, ast8250_dma_tx_complete, port, dma->tx_tmout_dis); ++ if (rc) { ++ dev_err(port->dev, "failed to request DMA TX channel\n"); ++ goto free_dma_n_out; ++ } ++ ++ rc = aspeed_udma_request_rx_chan(dma->ch, dma->rx_addr, ++ dma->rx_rbsz, ast8250_dma_rx_complete, port, dma->rx_tmout_dis); ++ if (rc) { ++ dev_err(port->dev, "failed to request DMA RX channel\n"); ++ goto free_dma_n_out; ++ } ++ ++ ast8250_dma_pops_hook(port); ++ ++ aspeed_udma_tx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_ENABLE); ++ aspeed_udma_rx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_ENABLE); ++ } ++ ++ memset(&port->icount, 0, sizeof(port->icount)); ++ return serial8250_do_startup(port); ++ ++free_dma_n_out: ++ kfree(dma->rx_rb->buf); ++out: ++ return rc; ++} ++ ++static void ast8250_shutdown(struct uart_port *port) ++{ ++ int rc; ++ struct ast8250_data *data = port->private_data; ++ struct ast8250_udma *dma; ++ ++ if (data->use_dma) { ++ dma = &data->dma; ++ ++ aspeed_udma_tx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_RESET); ++ aspeed_udma_rx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_RESET); ++ ++ aspeed_udma_tx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_DISABLE); ++ aspeed_udma_rx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_DISABLE); ++ ++ rc = aspeed_udma_free_tx_chan(dma->ch); ++ if (rc) ++ dev_err(port->dev, "failed to free DMA TX channel, rc=%d\n", rc); ++ ++ rc = aspeed_udma_free_rx_chan(dma->ch); ++ if (rc) ++ dev_err(port->dev, "failed to free DMA TX channel, rc=%d\n", rc); ++ ++ dma_unmap_single(port->dev, dma->tx_addr, ++ dma->tx_rbsz, DMA_TO_DEVICE); ++ dma_unmap_single(port->dev, dma->rx_addr, ++ dma->rx_rbsz, DMA_FROM_DEVICE); ++ ++ if (dma->rx_rb->buf) ++ kfree(dma->rx_rb->buf); ++ } ++ ++ if (data->is_vuart) ++ ast8250_vuart_set_host_tx_discard(data, true); ++ ++ serial8250_do_shutdown(port); ++} ++ ++static int __maybe_unused ast8250_suspend(struct device *dev) ++{ ++ struct ast8250_data *data = dev_get_drvdata(dev); ++ serial8250_suspend_port(data->line); ++ return 0; ++} ++ ++static int __maybe_unused ast8250_resume(struct device *dev) ++{ ++ struct ast8250_data *data = dev_get_drvdata(dev); ++ serial8250_resume_port(data->line); ++ return 0; ++} ++ ++static int ast8250_probe_of(struct platform_device *pdev, struct uart_port *p, ++ struct ast8250_data *data) ++{ ++ struct device *dev = &pdev->dev; ++ int rc; ++ ++ data->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!data->res) { ++ dev_err(dev, "failed to get register base\n"); ++ return -ENODEV; ++ } ++ ++ data->regs = devm_ioremap(dev, data->res->start, resource_size(data->res)); ++ if (IS_ERR(data->regs)) { ++ dev_err(dev, "failed to map registers\n"); ++ return PTR_ERR(data->regs); ++ } ++ ++ data->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(data->clk)) { ++ dev_err(dev, "failed to get clocks\n"); ++ return -ENODEV; ++ } ++ ++ data->rst = devm_reset_control_get_optional_exclusive(dev, NULL); ++ ++ data->is_vuart = of_property_read_bool(dev->of_node, "virtual"); ++ if (data->is_vuart) { ++ u32 plat = (unsigned long)of_device_get_match_data(dev); ++ ++ rc = of_property_read_u32(dev->of_node, "port", &data->vuart.port); ++ if (rc) { ++ dev_err(dev, "failed to get VUART port address\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "sirq", &data->vuart.sirq); ++ if (rc) { ++ dev_err(dev, "failed to get VUART SIRQ number\n"); ++ return -ENODEV; ++ } ++ ++ rc = of_property_read_u32(dev->of_node, "sirq-polarity", &data->vuart.sirq_pol); ++ if (rc) { ++ dev_err(dev, "failed to get VUART SIRQ polarity\n"); ++ return -ENODEV; ++ } ++ ++ if (plat == AST2700_PLAT) ++ data->vuart.character_timeout_time_en = true; ++ else ++ data->vuart.character_timeout_time_en = false; ++ } ++ ++ data->use_dma = of_property_read_bool(dev->of_node, "dma-mode"); ++ if (data->use_dma) { ++ rc = of_property_read_u32(dev->of_node, "dma-channel", &data->dma.ch); ++ if (rc) { ++ dev_err(dev, "failed to get DMA channel\n"); ++ return -ENODEV; ++ } ++ ++ data->dma.tx_tmout_dis = of_property_read_bool(dev->of_node, "dma-tx-timeout-disable"); ++ data->dma.rx_tmout_dis = of_property_read_bool(dev->of_node, "dma-rx-timeout-disable"); ++ } ++ ++ return 0; ++} ++ ++static int ast8250_probe(struct platform_device *pdev) ++{ ++ int rc; ++ struct uart_8250_port uart = {}; ++ struct uart_port *port = &uart.port; ++ struct device *dev = &pdev->dev; ++ struct ast8250_data *data; ++ ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (rc) { ++ dev_err(dev, "cannot set 64-bits DMA mask\n"); ++ return rc; ++ } ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->dma.rx_rb = devm_kzalloc(dev, sizeof(data->dma.rx_rb), GFP_KERNEL); ++ if (!data->dma.rx_rb) ++ return -ENOMEM; ++ ++ rc = ast8250_probe_of(pdev, port, data); ++ if (rc) ++ return rc; ++ ++ rc = clk_prepare_enable(data->clk); ++ if (rc) { ++ dev_err(dev, "failed to enable clock\n"); ++ return rc; ++ } ++ ++ if (!IS_ERR(data->rst)) ++ reset_control_deassert(data->rst); ++ ++ if (data->is_vuart) { ++ ast8250_vuart_init(data); ++ ast8250_vuart_set_host_tx_discard(data, true); ++ ast8250_vuart_set_enable(data, true); ++ } ++ ++ spin_lock_init(&port->lock); ++ port->dev = dev; ++ port->mapbase = data->res->start; ++ port->mapsize = resource_size(data->res); ++ port->membase = data->regs; ++ port->flags = UPF_FIXED_TYPE | UPF_FIXED_PORT | UPF_SHARE_IRQ; ++ port->startup = ast8250_startup; ++ port->shutdown = ast8250_shutdown; ++ port->private_data = data; ++ uart.bugs |= UART_BUG_TXRACE; ++ ++ rc = uart_read_port_properties(port); ++ if (rc) ++ return rc; ++ ++ port->type = (data->is_vuart) ? PORT_ASPEED_VUART : PORT_16550A; ++ port->handle_irq = ast8250_handle_irq; ++ port->uartclk = clk_get_rate(data->clk); ++ ++ uart.capabilities = UART_CAP_FIFO | UART_CAP_AFE; ++ ++ data->line = serial8250_register_8250_port(&uart); ++ if (data->line < 0) { ++ dev_err(dev, "failed to register 8250 port\n"); ++ return data->line; ++ } ++ ++ pm_runtime_set_active(&pdev->dev); ++ pm_runtime_enable(&pdev->dev); ++ ++ platform_set_drvdata(pdev, data); ++ return 0; ++} ++ ++static void ast8250_remove(struct platform_device *pdev) ++{ ++ struct ast8250_data *data = platform_get_drvdata(pdev); ++ ++ if (data->is_vuart) ++ ast8250_vuart_set_enable(data, false); ++ ++ serial8250_unregister_port(data->line); ++} ++ ++static const struct dev_pm_ops ast8250_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(ast8250_suspend, ast8250_resume) ++}; ++ ++static const struct of_device_id ast8250_of_match[] = { ++ { .compatible = "aspeed,ast2500-uart", .data = (void *)AST2500_PLAT}, ++ { .compatible = "aspeed,ast2600-uart", .data = (void *)AST2600_PLAT}, ++ { .compatible = "aspeed,ast2700-uart", .data = (void *)AST2700_PLAT}, ++ { }, ++}; ++ ++static struct platform_driver ast8250_platform_driver = { ++ .driver = { ++ .name = DEVICE_NAME, ++ .pm = &ast8250_pm_ops, ++ .of_match_table = ast8250_of_match, ++ }, ++ .probe = ast8250_probe, ++ .remove = ast8250_remove, ++}; ++ ++module_platform_driver(ast8250_platform_driver); ++ ++MODULE_AUTHOR("Chia-Wei Wang "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Aspeed UART Driver"); +diff --git a/drivers/ufs/host/ufs-aspeed.c b/drivers/ufs/host/ufs-aspeed.c +--- a/drivers/ufs/host/ufs-aspeed.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/ufs/host/ufs-aspeed.c 2026-09-09 15:51:44.608239734 +0000 +@@ -0,0 +1,425 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* Copyright (C) 2019 ASPEED Technology Inc. */ ++/* Copyright (C) 2019 IBM Corp. */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ufshcd-pltfrm.h" ++ ++#define UFS_MPHY_RST_REG 0x0 ++#define UFS_MPHY_RST_N BIT(0) ++#define UFS_MPHY_RST_N_PCS BIT(4) ++ ++#define UFS_MPHY_VCONTROL 0x98 ++#define UFS_MPHY_CALI_IN_1 0x90 ++#define UFS_MPHY_CALI_IN_0 0x8c ++ ++#define ASPEED_UFS_REG_HCLKDIV 0xFC ++ ++struct aspeed_ufscnr { ++ struct clk *clk; ++ struct resource *res; ++ struct reset_control *rst; ++ struct device *dev; ++ ++ void __iomem *regs; ++}; ++ ++struct aspeed_ufshc { ++ u32 temp; ++}; ++ ++static int aspeed_ufscnr_probe(struct platform_device *pdev) ++{ ++ struct device_node *parent, *child; ++ struct aspeed_ufscnr *cnr; ++ u32 reg; ++ int ret; ++ ++ cnr = devm_kzalloc(&pdev->dev, sizeof(struct aspeed_ufscnr), GFP_KERNEL); ++ if (!cnr) ++ return -ENOMEM; ++ ++ cnr->dev = &pdev->dev; ++ ++ cnr->rst = devm_reset_control_get(cnr->dev, NULL); ++ if (IS_ERR(cnr->rst)) { ++ dev_err(&pdev->dev, "Unable to get reset\n"); ++ return PTR_ERR(cnr->rst); ++ } ++ ++ ret = reset_control_assert(cnr->rst); ++ mdelay(1); ++ ret = reset_control_deassert(cnr->rst); ++ mdelay(1); ++ ++ cnr->clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(cnr->clk)) { ++ dev_err(&pdev->dev, "Unable to get clock\n"); ++ return PTR_ERR(cnr->clk); ++ } ++ ++ ret = clk_prepare_enable(cnr->clk); ++ if (ret) { ++ dev_err(&pdev->dev, "Unable to enable UFS CLK\n"); ++ return ret; ++ } ++ ++ cnr->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ cnr->regs = devm_ioremap_resource(&pdev->dev, cnr->res); ++ if (IS_ERR(cnr->regs)) { ++ ret = PTR_ERR(cnr->regs); ++ goto err_clk; ++ } ++ ++ /* given rext, rx_r100, tx calibration value */ ++ writel(0x000d0707, cnr->regs + UFS_MPHY_CALI_IN_1); ++ writel(0xbbffff00, cnr->regs + UFS_MPHY_CALI_IN_0); ++ ++ /* mphy reset deassert */ ++ reg = readl(cnr->regs + UFS_MPHY_RST_REG); ++ reg &= ~(UFS_MPHY_RST_N | UFS_MPHY_RST_N_PCS); ++ ++ writel(0, cnr->regs + UFS_MPHY_RST_REG); ++ mdelay(1); ++ writel(reg | UFS_MPHY_RST_N | UFS_MPHY_RST_N_PCS, cnr->regs + UFS_MPHY_RST_REG); ++ mdelay(1); ++ ++ dev_set_drvdata(&pdev->dev, cnr); ++ ++ parent = pdev->dev.of_node; ++ for_each_available_child_of_node(parent, child) { ++ struct platform_device *cpdev; ++ ++ cpdev = of_platform_device_create(child, NULL, &pdev->dev); ++ if (!cpdev) { ++ of_node_put(child); ++ ret = -ENODEV; ++ goto err_clk; ++ } ++ } ++ ++ return 0; ++ ++err_clk: ++ clk_disable_unprepare(cnr->clk); ++ return ret; ++} ++ ++/** ++ * aspeed_ufscnr_remove - removes the ufscnr driver ++ * @pdev: pointer to platform device handle ++ * ++ * Always returns 0 ++ */ ++static void aspeed_ufscnr_remove(struct platform_device *pdev) ++{ ++ struct aspeed_ufscnr *cnr = dev_get_drvdata(&pdev->dev); ++ ++ clk_disable_unprepare(cnr->clk); ++} ++ ++/** ++ * aspeed_ufshc_init - performs additional ufs initialization ++ * @hba: host controller instance ++ * ++ * Returns status of initialization ++ */ ++static int aspeed_ufshc_init(struct ufs_hba *hba) ++{ ++ int status = 0; ++ struct aspeed_ufshc *ufshc; ++ struct device *dev = hba->dev; ++ ++ ufshc = devm_kzalloc(dev, sizeof(*ufshc), GFP_KERNEL); ++ ++ if (!ufshc) ++ return -ENOMEM; ++ ++ ufshcd_set_variant(hba, ufshc); ++ ++ status = ufshcd_vops_phy_initialization(hba); ++ ++ hba->quirks |= UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE | UFSHCD_QUIRK_HIBERN_FASTAUTO; ++ ++ return status; ++} ++ ++static int aspeed_ufshc_set_hclkdiv(struct ufs_hba *hba) ++{ ++ struct ufs_clk_info *clki; ++ struct list_head *head = &hba->clk_list_head; ++ unsigned long core_clk_rate = 0; ++ u32 core_clk_div = 0; ++ ++ if (list_empty(head)) ++ return 0; ++ ++ list_for_each_entry(clki, head, list) { ++ if (IS_ERR_OR_NULL(clki->clk)) ++ continue; ++ if (!strcmp(clki->name, "core_clk")) ++ core_clk_rate = clk_get_rate(clki->clk); ++ } ++ ++ if (!core_clk_rate) { ++ dev_err(hba->dev, "%s: unable to find core_clk rate\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ core_clk_div = core_clk_rate / USEC_PER_SEC; ++ ++ ufshcd_writel(hba, core_clk_div, ASPEED_UFS_REG_HCLKDIV); ++ /** ++ * Make sure the register was updated, ++ * UniPro layer will not work with an incorrect value. ++ */ ++ mb(); ++ ++ return 0; ++} ++ ++static int aspeed_ufshc_hce_enable_notify(struct ufs_hba *hba, ++ enum ufs_notify_change_status status) ++{ ++ if (status != PRE_CHANGE) ++ return 0; ++ ++ return aspeed_ufshc_set_hclkdiv(hba); ++} ++ ++/** ++ * aspeed_ufshc_link_startup_notify() ++ * Called before and after Link startup is carried out. ++ * @hba: host controller instance ++ * @status: notify stage (pre, post change) ++ * ++ * Return zero for success and non-zero for failure ++ */ ++static int aspeed_ufshc_link_startup_notify(struct ufs_hba *hba, ++ enum ufs_notify_change_status status) ++{ ++ struct aspeed_ufscnr *cnr; ++ ++ if (status != PRE_CHANGE) { ++ cnr = dev_get_drvdata(hba->dev->parent); ++ writel(0x170707, cnr->regs + UFS_MPHY_CALI_IN_1); ++ return 0; ++ } ++ ++ /* ++ * Some UFS devices have issues if LCC is enabled. ++ * So we are setting PA_Local_TX_LCC_Enable to 0 ++ * before link startup which will make sure that both host ++ * and device TX LCC are disabled once link startup is ++ * completed. ++ */ ++ ufshcd_disable_host_tx_lcc(hba); ++ ++ /* ++ * Disabling Autohibern8 feature. ++ */ ++ hba->ahit = 0; ++ ++ return 0; ++} ++ ++static int aspeed_ufshc_pre_pwr_change(struct ufs_hba *hba, ++ struct ufs_pa_layer_attr *dev_max_params, ++ struct ufs_pa_layer_attr *dev_req_params) ++{ ++ struct ufs_host_params host_cap; ++ int ret; ++ ++ ufshcd_init_host_params(&host_cap); ++ host_cap.hs_rx_gear = UFS_HS_G3; ++ host_cap.hs_tx_gear = UFS_HS_G3; ++ host_cap.rx_pwr_hs = FASTAUTO_MODE; ++ host_cap.tx_pwr_hs = FASTAUTO_MODE; ++ ++ ret = ufshcd_negotiate_pwr_params(&host_cap, ++ dev_max_params, ++ dev_req_params); ++ if (ret) { ++ pr_info("%s: failed to determine capabilities\n", ++ __func__); ++ } ++ ++ /* change 2 lane before high speed due to old unipro v1.6 */ ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXTERMINATION), false); ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXGEAR), UFS_PWM_G1); ++ ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXTERMINATION), false); ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXGEAR), UFS_PWM_G1); ++ ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVETXDATALANES), ++ dev_req_params->lane_tx); ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVERXDATALANES), ++ dev_req_params->lane_rx); ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HSSERIES), ++ dev_req_params->hs_rate); ++ ++ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE), ++ PA_NO_ADAPT); ++ ++ ret = ufshcd_uic_change_pwr_mode(hba, ++ SLOWAUTO_MODE << 4 | SLOWAUTO_MODE); ++ ++ if (ret) { ++ dev_err(hba->dev, "%s: PWMG1B SLOWAUTO failed ret=%d\n", ++ __func__, ret); ++ } ++ ++ return ret; ++} ++ ++static int aspeed_ufshc_pwr_change_notify(struct ufs_hba *hba, ++ enum ufs_notify_change_status stage, ++ struct ufs_pa_layer_attr *dev_max_params, ++ struct ufs_pa_layer_attr *dev_req_params) ++{ ++ int ret = 0; ++ ++ switch (stage) { ++ case PRE_CHANGE: ++ ret = aspeed_ufshc_pre_pwr_change(hba, dev_max_params, ++ dev_req_params); ++ break; ++ case POST_CHANGE: ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static const struct ufs_hba_variant_ops aspeed_ufshc_hba_vops = { ++ .name = "aspeed-ufshc", ++ .init = aspeed_ufshc_init, ++ .hce_enable_notify = aspeed_ufshc_hce_enable_notify, ++ .link_startup_notify = aspeed_ufshc_link_startup_notify, ++ .pwr_change_notify = aspeed_ufshc_pwr_change_notify, ++}; ++ ++static const struct of_device_id aspeed_ufshc_of_match[] = { ++ { ++ .compatible = "aspeed,ast2700-ufshc", ++ .data = &aspeed_ufshc_hba_vops, ++ }, ++ { }, ++}; ++ ++MODULE_DEVICE_TABLE(of, aspeed_ufshc_of_match); ++ ++/** ++ * aspeed_ufshc_probe - probe routine of the driver ++ * @pdev: pointer to platform device handle ++ * ++ * Return zero for success and non-zero for failure ++ */ ++static int aspeed_ufshc_probe(struct platform_device *pdev) ++{ ++ int err; ++ const struct of_device_id *of_id; ++ struct ufs_hba_variant_ops *vops; ++ struct device *dev = &pdev->dev; ++ ++ of_id = of_match_node(aspeed_ufshc_of_match, dev->of_node); ++ vops = (struct ufs_hba_variant_ops *)of_id->data; ++ ++ /* Perform generic probe */ ++ err = ufshcd_pltfrm_init(pdev, vops); ++ if (err) ++ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err); ++ ++ return err; ++} ++ ++/** ++ * aspeed_ufshc_remove - removes the ufs driver ++ * @pdev: pointer to platform device handle ++ * ++ * Always returns 0 ++ */ ++static void aspeed_ufshc_remove(struct platform_device *pdev) ++{ ++ struct ufs_hba *hba = platform_get_drvdata(pdev); ++ ++ ufshcd_remove(hba); ++} ++ ++static const struct dev_pm_ops aspeed_ufshc_dev_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume) ++ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL) ++ .prepare = ufshcd_suspend_prepare, ++ .complete = ufshcd_resume_complete, ++}; ++ ++static struct platform_driver aspeed_ufshc_driver = { ++ .probe = aspeed_ufshc_probe, ++ .remove = aspeed_ufshc_remove, ++ .driver = { ++ .name = "aspeed-ufshcd", ++ .pm = &aspeed_ufshc_dev_pm_ops, ++ .of_match_table = aspeed_ufshc_of_match, ++ }, ++}; ++ ++static const struct of_device_id aspeed_ufscnr_of_match[] = { ++ { .compatible = "aspeed,ast2700-ufscnr", }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(of, aspeed_ufscnr_of_match); ++ ++static struct platform_driver aspeed_ufscnr_driver = { ++ .driver = { ++ .name = "aspeed-ufscnr", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .of_match_table = aspeed_ufscnr_of_match, ++ }, ++ .probe = aspeed_ufscnr_probe, ++ .remove = aspeed_ufscnr_remove, ++}; ++ ++static int __init aspeed_ufs_init(void) ++{ ++ int rc; ++ ++ rc = platform_driver_register(&aspeed_ufscnr_driver); ++ if (rc < 0) ++ return rc; ++ ++ rc = platform_driver_register(&aspeed_ufshc_driver); ++ if (rc < 0) ++ platform_driver_unregister(&aspeed_ufscnr_driver); ++ ++ return rc; ++} ++module_init(aspeed_ufs_init); ++ ++static void __exit aspeed_ufs_exit(void) ++{ ++ platform_driver_unregister(&aspeed_ufscnr_driver); ++ platform_driver_unregister(&aspeed_ufshc_driver); ++} ++module_exit(aspeed_ufs_exit); ++ ++MODULE_AUTHOR("Cool Lee "); ++MODULE_DESCRIPTION("Aspeed UFS host controller platform driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/Kconfig b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig +--- a/drivers/usb/gadget/udc/aspeed-vhub/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig 2026-09-09 15:51:44.747237633 +0000 +@@ -4,5 +4,6 @@ + depends on ARCH_ASPEED || COMPILE_TEST + depends on USB_LIBCOMPOSITE + help +- USB peripheral controller for the Aspeed AST2400, AST2500 and +- AST2600 family SoCs supporting the "vHub" functionality and USB2.0 ++ USB peripheral controller for the Aspeed AST2400, AST2500, AST2600, ++ and AST2700 family SoCs supporting the "vHub" functionality and ++ USB2.0 +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/core.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c 2026-09-09 15:51:44.747237633 +0000 +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + + #include "vhub.h" + +@@ -239,9 +240,24 @@ + if (vhub->force_usb1) + ctrl |= VHUB_CTRL_FULL_SPEED_ONLY; + ++ /* Enlarge FIFO for ast2700 soc0 vhub1 */ ++ if (vhub->enlarge_fifo) ++ ctrl |= VHUB_CTRL_ENLARGE_FIFO; ++ ++ ctrl |= VHUB_CTRL_AUTO_REMOTE_WAKEUP; + ctrl |= VHUB_CTRL_UPSTREAM_CONNECT; + writel(ctrl, vhub->regs + AST_VHUB_CTRL); + ++ /* AST2700 SoC0 vhub0 only: force retry via CRC error injection ++ * when DRAM access is delayed due to bus contention. ++ */ ++ if (vhub->txfifo_retry_quirk) { ++ u32 val = readl(vhub->regs + AST_VHUB_PHY_00); ++ ++ val |= AST_VHUB0_FIFO_FORCE_RETRY; ++ writel(val, vhub->regs + AST_VHUB_PHY_00); ++ } ++ + /* Enable some interrupts */ + writel(VHUB_IRQ_HUB_EP0_IN_ACK_STALL | + VHUB_IRQ_HUB_EP0_OUT_ACK_STALL | +@@ -280,6 +296,8 @@ + if (vhub->clk) + clk_disable_unprepare(vhub->clk); + ++ reset_control_assert(vhub->rst); ++ + spin_unlock_irqrestore(&vhub->lock, flags); + + if (vhub->ep0_bufs) +@@ -291,9 +309,65 @@ + vhub->ep0_bufs = NULL; + } + ++static int ast_vhub_init_uart(struct device *dev, struct ast_vhub *vhub) ++{ ++ const struct device_node *np = dev->of_node; ++ void __iomem *regs = vhub->regs + 0x800; ++ int i, rc = 0; ++ int num_ports; ++ u32 ports[AST_VHUB_NUM_UART_PORTS], port; ++ u32 mode_sel = 0, dev_en = 0; ++ ++ num_ports = of_property_count_u32_elems(np, "aspeed,uart-ports"); ++ if (num_ports == -EINVAL) { ++ /* Property not found */ ++ return 0; ++ } ++ if (num_ports < 0) { ++ dev_err(dev, "Failed to read uart-ports property\n"); ++ return num_ports; ++ } ++ if (num_ports > AST_VHUB_NUM_UART_PORTS) { ++ dev_warn(dev, "Too many UART ports (%d), max is %d\n", ++ num_ports, AST_VHUB_NUM_UART_PORTS); ++ num_ports = AST_VHUB_NUM_UART_PORTS; ++ } ++ ++ rc = of_property_read_u32_array(np, "aspeed,uart-ports", ++ ports, num_ports); ++ if (rc) ++ return rc; ++ ++ dev_en = readl(regs + AST_VHUB_COM_EN_CTRL); ++ ++ for (i = 0; i < num_ports; i++) { ++ // io-die uart only ++ if (ports[i] == 4 || ports[i] > AST_VHUB_NUM_UART_PORTS) { ++ dev_warn(dev, "Ignoring invalid UART port %d\n", ++ ports[i]); ++ continue; ++ } ++ ++ if (ports[i] < 4) ++ port = ports[i]; ++ else ++ port = ports[i] - 1; ++ ++ mode_sel |= (0x2 << (port * 2)); ++ dev_en |= BIT(port + 16); ++ } ++ ++ dev_info(dev, "Enabled UART ports\n"); ++ ++ writel(mode_sel, regs + AST_VHUB_COM_MODE_SEL); ++ writel(dev_en, regs + AST_VHUB_COM_EN_CTRL); ++ return 0; ++} ++ + static int ast_vhub_probe(struct platform_device *pdev) + { + enum usb_device_speed max_speed; ++ const u64 *dma_mask_ptr; + struct ast_vhub *vhub; + struct resource *res; + int i, rc = 0; +@@ -323,6 +397,10 @@ + if (!vhub->epns) + return -ENOMEM; + ++ vhub->enlarge_fifo = of_property_read_bool(np, "aspeed,enlarge-fifo"); ++ vhub->txfifo_retry_quirk = ++ of_property_read_bool(np, "aspeed,txfifo-retry-quirk"); ++ + spin_lock_init(&vhub->lock); + vhub->pdev = pdev; + vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, +@@ -348,6 +426,21 @@ + goto err; + } + ++ vhub->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL); ++ if (IS_ERR(vhub->rst)) { ++ rc = PTR_ERR(vhub->rst); ++ goto err; ++ } ++ ++ if (vhub->rst) { ++ mdelay(10); ++ rc = reset_control_deassert(vhub->rst); ++ if (rc) ++ goto err; ++ } ++ ++ ast_vhub_init_uart(&pdev->dev, vhub); ++ + /* Check if we need to limit the HW to USB1 */ + max_speed = usb_get_maximum_speed(&pdev->dev); + if (max_speed != USB_SPEED_UNKNOWN && max_speed < USB_SPEED_HIGH) +@@ -370,6 +463,13 @@ + goto err; + } + ++ dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev); ++ if (dma_mask_ptr) { ++ rc = dma_coerce_mask_and_coherent(&pdev->dev, *dma_mask_ptr); ++ if (rc) ++ goto err; ++ } ++ + /* + * Allocate DMA buffers for all EP0s in one chunk, + * one per port and one for the vHub itself +@@ -412,15 +512,25 @@ + return rc; + } + ++static const u64 dma_mask_32 = DMA_BIT_MASK(32); ++static const u64 dma_mask_64 = DMA_BIT_MASK(64); ++ + static const struct of_device_id ast_vhub_dt_ids[] = { + { + .compatible = "aspeed,ast2400-usb-vhub", ++ .data = &dma_mask_32, + }, + { + .compatible = "aspeed,ast2500-usb-vhub", ++ .data = &dma_mask_32, + }, + { + .compatible = "aspeed,ast2600-usb-vhub", ++ .data = &dma_mask_32, ++ }, ++ { ++ .compatible = "aspeed,ast2700-usb-vhub", ++ .data = &dma_mask_64, + }, + { } + }; +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c 2026-09-09 15:51:44.747237633 +0000 +@@ -116,10 +116,14 @@ + + if (wValue == USB_DEVICE_REMOTE_WAKEUP) { + d->wakeup_en = is_set; ++ val = readl(d->vhub->regs + AST_VHUB_CTRL); ++ if (is_set) ++ writel(val | VHUB_CTRL_AUTO_REMOTE_WAKEUP, ++ d->vhub->regs + AST_VHUB_CTRL); ++ + return std_req_complete; +- } + +- if (wValue == USB_DEVICE_TEST_MODE) { ++ } else if (wValue == USB_DEVICE_TEST_MODE) { + val = readl(d->vhub->regs + AST_VHUB_CTRL); + val &= ~GENMASK(10, 8); + val |= VHUB_CTRL_SET_TEST_MODE((wIndex >> 8) & 0x7); +@@ -239,7 +243,7 @@ + d->gadget.speed = ep->vhub->speed; + if (d->gadget.speed > d->driver->max_speed) + d->gadget.speed = d->driver->max_speed; +- DDBG(d, "fist packet, captured speed %d\n", ++ DDBG(d, "first packet, captured speed %d\n", + d->gadget.speed); + } + +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/epn.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c 2026-09-09 15:51:44.747237633 +0000 +@@ -340,7 +340,9 @@ + struct ast_vhub *vhub = ep->vhub; + unsigned long flags; + bool empty; +- int rc; ++ int rc = 0; ++ ++ spin_lock_irqsave(&vhub->lock, flags); + + /* Paranoid checks */ + if (!u_req || !u_req->complete || !u_req->buf) { +@@ -349,14 +351,16 @@ + dev_warn(&vhub->pdev->dev, "complete=%p internal=%d\n", + u_req->complete, req->internal); + } +- return -EINVAL; ++ rc = -EINVAL; ++ goto out; + } + + /* Endpoint enabled ? */ + if (!ep->epn.enabled || !u_ep->desc || !ep->dev || !ep->d_idx || + !ep->dev->enabled) { + EPDBG(ep, "Enqueuing request on wrong or disabled EP\n"); +- return -ESHUTDOWN; ++ rc = -ESHUTDOWN; ++ goto out; + } + + /* Map request for DMA if possible. For now, the rule for DMA is +@@ -383,11 +387,16 @@ + if (rc) { + dev_warn(&vhub->pdev->dev, + "Request mapping failure %d\n", rc); +- return rc; ++ goto out; + } + } else + u_req->dma = 0; + ++ if (ep->dev->wakeup_en) { ++ EPVDBG(ep, "Wakeup host first\n"); ++ ast_vhub_hub_wake_all(vhub); ++ } ++ + EPVDBG(ep, "enqueue req @%p\n", req); + EPVDBG(ep, " l=%d dma=0x%x zero=%d noshort=%d noirq=%d is_in=%d\n", + u_req->length, (u32)u_req->dma, u_req->zero, +@@ -400,9 +409,8 @@ + req->act_count = 0; + req->active = false; + req->last_desc = -1; +- spin_lock_irqsave(&vhub->lock, flags); +- empty = list_empty(&ep->queue); + ++ empty = list_empty(&ep->queue); + /* Add request to list and kick processing if empty */ + list_add_tail(&req->queue, &ep->queue); + if (empty) { +@@ -411,9 +419,10 @@ + else + ast_vhub_epn_kick(ep, req); + } ++out: + spin_unlock_irqrestore(&vhub->lock, flags); + +- return 0; ++ return rc; + } + + static void ast_vhub_stop_active_req(struct ast_vhub_ep *ep, +@@ -786,6 +795,20 @@ + ep->dev = NULL; + } + ++static void ast_vhub_epn_flush(struct usb_ep *u_ep) ++{ ++ struct ast_vhub_ep *ep = to_ast_ep(u_ep); ++ struct ast_vhub *vhub = ep->vhub; ++ unsigned long flags; ++ ++ EPDBG(ep, "flushing !\n"); ++ ++ spin_lock_irqsave(&vhub->lock, flags); ++ /* This will clear out all the request of the endpoint and send requests done messages. */ ++ ast_vhub_nuke(ep, -EINVAL); ++ spin_unlock_irqrestore(&vhub->lock, flags); ++} ++ + static const struct usb_ep_ops ast_vhub_epn_ops = { + .enable = ast_vhub_epn_enable, + .disable = ast_vhub_epn_disable, +@@ -796,6 +819,7 @@ + .set_wedge = ast_vhub_epn_set_wedge, + .alloc_request = ast_vhub_alloc_request, + .free_request = ast_vhub_free_request, ++ .fifo_flush = ast_vhub_epn_flush, + }; + + struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr) +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c 2026-09-09 15:51:44.747237633 +0000 +@@ -221,9 +221,8 @@ + EPDBG(ep, "Hub remote wakeup %s\n", + is_set ? "enabled" : "disabled"); + return std_req_complete; +- } + +- if (wValue == USB_DEVICE_TEST_MODE) { ++ } else if (wValue == USB_DEVICE_TEST_MODE) { + val = readl(ep->vhub->regs + AST_VHUB_CTRL); + val &= ~GENMASK(10, 8); + val |= VHUB_CTRL_SET_TEST_MODE((wIndex >> 8) & 0x7); +@@ -445,10 +444,9 @@ + + /* GET/SET_CONFIGURATION */ + case DeviceRequest | USB_REQ_GET_CONFIGURATION: +- return ast_vhub_simple_reply(ep, 1); ++ return ast_vhub_simple_reply(ep, vhub->current_config); + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: +- if (wValue != 1) +- return std_req_stall; ++ vhub->current_config = wValue; + return std_req_complete; + + /* GET_DESCRIPTOR */ +@@ -673,6 +671,9 @@ + ast_vhub_port_reset(vhub, port); + return std_req_complete; + case USB_PORT_FEAT_POWER: ++ ast_vhub_change_port_stat(vhub, port, ++ 0, USB_PORT_STAT_POWER, ++ false); + /* + * On Power-on, we mark the connected flag changed, + * if there's a connected device, some hosts will +@@ -750,9 +751,6 @@ + stat = vhub->ports[port].status; + chg = vhub->ports[port].change; + +- /* We always have power */ +- stat |= USB_PORT_STAT_POWER; +- + EPDBG(ep, " port status=%04x change=%04x\n", stat, chg); + + return ast_vhub_simple_reply(ep, +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +--- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h 2026-09-09 15:51:44.747237633 +0000 +@@ -29,12 +29,14 @@ + #define AST_VHUB_EP1_STS_CHG 0x3C /* Endpoint 1 Status Change Bitmap Data */ + #define AST_VHUB_SETUP0 0x80 /* Root Device Setup Data Buffer0 */ + #define AST_VHUB_SETUP1 0x84 /* Root Device Setup Data Buffer1 */ ++#define AST_VHUB_PHY_00 0x800 /* USB PHY Control/Status #1 */ + + /* Main control reg */ + #define VHUB_CTRL_PHY_CLK (1 << 31) + #define VHUB_CTRL_PHY_LOOP_TEST (1 << 25) + #define VHUB_CTRL_DN_PWN (1 << 24) + #define VHUB_CTRL_DP_PWN (1 << 23) ++#define VHUB_CTRL_ENLARGE_FIFO (1 << 21) + #define VHUB_CTRL_LONG_DESC (1 << 18) + #define VHUB_CTRL_ISO_RSP_CTRL (1 << 17) + #define VHUB_CTRL_SPLIT_IN (1 << 16) +@@ -184,6 +186,13 @@ + #define VHUB_EP_DMA_SET_CPU_WPTR(x) (x) + #define VHUB_EP_DMA_SINGLE_KICK (1 << 0) /* WPTR = 1 for single mode */ + ++/******************************************* ++ * * ++ * PHY Control/Status register definitions * ++ * * ++ *******************************************/ ++#define AST_VHUB0_FIFO_FORCE_RETRY BIT(13) ++ + /******************************* + * * + * DMA descriptors definitions * +@@ -199,6 +208,14 @@ + #define VHUB_DSC1_IN_SET_LEN(x) ((x) & 0xfff) + #define VHUB_DSC1_IN_LEN(x) ((x) & 0xfff) + ++/*********************************** ++ * * ++ * USB2COM register definitions * ++ * * ++ ***********************************/ ++#define AST_VHUB_COM_MODE_SEL 0x10 ++#define AST_VHUB_COM_EN_CTRL 0x1C ++ + /**************************************** + * * + * Data structures and misc definitions * +@@ -218,6 +235,8 @@ + * values are 256 and 32) + */ + ++#define AST_VHUB_NUM_UART_PORTS 15 /* USB2COM ports */ ++ + struct ast_vhub; + struct ast_vhub_dev; + +@@ -388,6 +407,7 @@ + spinlock_t lock; + struct work_struct wake_work; + struct clk *clk; ++ struct reset_control *rst; + + /* EP0 DMA buffers allocated in one chunk */ + void *ep0_bufs; +@@ -417,8 +437,15 @@ + /* Force full speed only */ + bool force_usb1 : 1; + ++ /* Enlarge FIFO for ast2700 soc0 vhub1 */ ++ bool enlarge_fifo : 1; ++ ++ /* Force TXFIFO retry when is DRAM busy for ast2700 soc0 vhub0 */ ++ bool txfifo_retry_quirk : 1; ++ + /* Upstream bus speed captured at bus reset */ + unsigned int speed; ++ u8 current_config; + + /* Standard USB Descriptors of the vhub. */ + struct usb_device_descriptor vhub_dev_desc; +diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c +--- a/drivers/usb/gadget/udc/aspeed_udc.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/usb/gadget/udc/aspeed_udc.c 2026-09-09 15:51:44.703238298 +0000 +@@ -156,7 +156,7 @@ + #define AST_EP_DMA_DESC_PID_DATA1 (2 << 14) + #define AST_EP_DMA_DESC_PID_MDATA (3 << 14) + #define EP_DESC1_IN_LEN(x) ((x) & 0x1fff) +-#define AST_EP_DMA_DESC_MAX_LEN (7680) /* Max packet length for trasmit in 1 desc */ ++#define AST_EP_DMA_DESC_MAX_LEN (4096) /* Max packet length for trasmit in 1 desc */ + + struct ast_udc_request { + struct usb_request req; +@@ -278,6 +278,17 @@ + + /*-------------------------------------------------------------------------*/ + ++static inline void ast_udc_dma_workaround(void *addr) ++{ ++ /* ++ * The workaround consists of using a dummy read of the memory before ++ * doing the MMIO writes. This will ensure that the previous writes ++ * have been "pushed out". ++ */ ++ mb(); ++ (void)__raw_readl((void __iomem *)addr); ++} ++ + static void ast_udc_done(struct ast_udc_ep *ep, struct ast_udc_request *req, + int status) + { +@@ -368,7 +379,6 @@ + ep->desc = desc; + ep->stopped = 0; + ep->ep.maxpacket = maxpacket; +- ep->chunk_max = AST_EP_DMA_DESC_MAX_LEN; + + if (maxpacket < AST_UDC_EPn_MAX_PACKET) + ep_conf = EP_SET_MAX_PKT(maxpacket); +@@ -381,7 +391,18 @@ + if (!ep->dir_in) + ep_conf |= EP_DIR_OUT; + +- EP_DBG(ep, "type %d, dir_in %d\n", type, dir_in); ++ /* ++ * Large send function can send up to 8 packets from ++ * one descriptor with a limit of 4096 bytes. ++ */ ++ ep->chunk_max = ep->ep.maxpacket; ++ if (ep->dir_in) { ++ ep->chunk_max <<= 3; ++ while (ep->chunk_max > AST_EP_DMA_DESC_MAX_LEN) ++ ep->chunk_max -= ep->ep.maxpacket; ++ } ++ ++ EP_DBG(ep, "type %d, dir_in %d, chunk_max %d\n", type, dir_in, ep->chunk_max); + switch (type) { + case USB_ENDPOINT_XFER_ISOC: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_ISO); +@@ -478,7 +499,7 @@ + struct device *dev = &udc->pdev->dev; + bool last = false; + int chunk, count; +- u32 offset; ++ u32 offset, size; + + if (!ep->descs) { + dev_warn(dev, "%s: Empty DMA descs list failure\n", +@@ -489,9 +510,9 @@ + chunk = tx_len; + offset = count = 0; + +- EP_DBG(ep, "req @%p, %s:%d, %s:0x%x, %s:0x%x\n", req, ++ EP_DBG(ep, "req @%p, %s:%d, %s:0x%x, %s:0x%x zero=%d\n", req, + "wptr", ep->descs_wptr, "dma_buf", dma_buf, +- "tx_len", tx_len); ++ "tx_len", tx_len, req->req.zero); + + /* Create Descriptor Lists */ + while (chunk >= 0 && !last && count < AST_UDC_DESCS_COUNT) { +@@ -499,13 +520,23 @@ + ep->descs[ep->descs_wptr].des_0 = dma_buf + offset; + + if (chunk > ep->chunk_max) { +- ep->descs[ep->descs_wptr].des_1 = ep->chunk_max; ++ size = ep->chunk_max; + } else { +- ep->descs[ep->descs_wptr].des_1 = chunk; +- last = true; ++ size = chunk; ++ /* ++ * Check if this is the last packet? ++ * May go the loop again for the zero length packet ++ */ ++ if (!chunk || !req->req.zero || (chunk % ep->ep.maxpacket) != 0) ++ last = true; + } + +- chunk -= ep->chunk_max; ++ ep->descs[ep->descs_wptr].des_1 = size; ++ chunk -= size; ++ offset += size; ++ ++ if (last) ++ ast_udc_dma_workaround(&ep->descs[ep->descs_wptr]); + + EP_DBG(ep, "descs[%d]: 0x%x 0x%x\n", + ep->descs_wptr, +@@ -520,8 +551,6 @@ + + if (ep->descs_wptr >= AST_UDC_DESCS_COUNT) + ep->descs_wptr = 0; +- +- offset = ep->chunk_max * count; + } + + return 0; +@@ -538,6 +567,9 @@ + EP_DBG(ep, "kick req @%p, len:%d, dir:%d\n", + req, tx_len, ep->dir_in); + ++ if (ep->dir_in) ++ ast_udc_dma_workaround(req->req.buf + req->req.actual); ++ + ast_ep_write(ep, req->req.dma + req->req.actual, AST_UDC_EP_DMA_BUFF); + + /* Start DMA */ +@@ -549,11 +581,13 @@ + static void ast_udc_epn_kick_desc(struct ast_udc_ep *ep, + struct ast_udc_request *req) + { ++ u32 count; + u32 descs_max_size; + u32 tx_len; + u32 last; + +- descs_max_size = AST_EP_DMA_DESC_MAX_LEN * AST_UDC_DESCS_COUNT; ++ count = req->req.zero ? AST_UDC_DESCS_COUNT - 1 : AST_UDC_DESCS_COUNT; ++ descs_max_size = AST_EP_DMA_DESC_MAX_LEN * count; + + last = req->req.length - req->req.actual; + tx_len = last > descs_max_size ? descs_max_size : last; +@@ -1134,21 +1168,6 @@ + /* Ack interrupts */ + ast_udc_write(udc, isr, AST_UDC_ISR); + +- if (isr & UDC_IRQ_BUS_RESET) { +- ISR_DBG(udc, "UDC_IRQ_BUS_RESET\n"); +- udc->gadget.speed = USB_SPEED_UNKNOWN; +- +- ep = &udc->ep[1]; +- EP_DBG(ep, "dctrl:0x%x\n", +- ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); +- +- if (udc->driver && udc->driver->reset) { +- spin_unlock(&udc->lock); +- udc->driver->reset(&udc->gadget); +- spin_lock(&udc->lock); +- } +- } +- + if (isr & UDC_IRQ_BUS_SUSPEND) { + ISR_DBG(udc, "UDC_IRQ_BUS_SUSPEND\n"); + udc->suspended_from = udc->gadget.state; +@@ -1172,6 +1191,21 @@ + } + } + ++ if (isr & UDC_IRQ_BUS_RESET) { ++ ISR_DBG(udc, "UDC_IRQ_BUS_RESET\n"); ++ udc->gadget.speed = USB_SPEED_UNKNOWN; ++ ++ ep = &udc->ep[1]; ++ EP_DBG(ep, "dctrl:0x%x\n", ++ ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); ++ ++ if (udc->driver && udc->driver->reset) { ++ spin_unlock(&udc->lock); ++ udc->driver->reset(&udc->gadget); ++ spin_lock(&udc->lock); ++ } ++ } ++ + if (isr & UDC_IRQ_EP0_IN_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP0_IN_ACK_STALL\n"); + ast_udc_ep0_in(udc); +@@ -1303,6 +1337,7 @@ + UDC_DBG(udc, "\n"); + udc->driver = driver; + udc->gadget.dev.of_node = udc->pdev->dev.of_node; ++ udc->gadget.dev.of_node_reused = true; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; +diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c +--- a/drivers/watchdog/aspeed_wdt.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/watchdog/aspeed_wdt.c 2026-09-09 15:51:44.660238948 +0000 +@@ -35,6 +35,8 @@ + u32 irq_shift; + u32 irq_mask; + struct aspeed_wdt_scu scu; ++ u32 num_reset_masks; ++ void (*wdt_writel)(u32 val, void __iomem *addr); + }; + + struct aspeed_wdt { +@@ -44,6 +46,9 @@ + const struct aspeed_wdt_config *cfg; + }; + ++static void wdt_writel_normal(u32 val, void __iomem *addr); ++static void wdt_writel_delay(u32 val, void __iomem *addr); ++ + static const struct aspeed_wdt_config ast2400_config = { + .ext_pulse_width_mask = 0xff, + .irq_shift = 0, +@@ -54,6 +59,7 @@ + .wdt_reset_mask = 0x1, + .wdt_reset_mask_shift = 1, + }, ++ .wdt_writel = wdt_writel_normal, + }; + + static const struct aspeed_wdt_config ast2500_config = { +@@ -66,6 +72,8 @@ + .wdt_reset_mask = 0x1, + .wdt_reset_mask_shift = 2, + }, ++ .num_reset_masks = 1, ++ .wdt_writel = wdt_writel_normal, + }; + + static const struct aspeed_wdt_config ast2600_config = { +@@ -78,12 +86,29 @@ + .wdt_reset_mask = 0xf, + .wdt_reset_mask_shift = 16, + }, ++ .num_reset_masks = 2, ++ .wdt_writel = wdt_writel_normal, ++}; ++ ++static const struct aspeed_wdt_config ast2700_config = { ++ .ext_pulse_width_mask = 0xfffff, ++ .irq_shift = 0, ++ .irq_mask = GENMASK(31, 10), ++ .scu = { ++ .compatible = "aspeed,ast2700-scu0", ++ .reset_status_reg = 0x70, ++ .wdt_reset_mask = 0xf, ++ .wdt_reset_mask_shift = 0, ++ }, ++ .num_reset_masks = 5, ++ .wdt_writel = wdt_writel_delay, + }; + + static const struct of_device_id aspeed_wdt_of_table[] = { + { .compatible = "aspeed,ast2400-wdt", .data = &ast2400_config }, + { .compatible = "aspeed,ast2500-wdt", .data = &ast2500_config }, + { .compatible = "aspeed,ast2600-wdt", .data = &ast2600_config }, ++ { .compatible = "aspeed,ast2700-wdt", .data = &ast2700_config }, + { }, + }; + MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table); +@@ -96,7 +121,8 @@ + #define WDT_CTRL_RESET_MODE_SOC (0x00 << 5) + #define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5) + #define WDT_CTRL_RESET_MODE_ARM_CPU (0x10 << 5) +-#define WDT_CTRL_1MHZ_CLK BIT(4) ++#define WDT_CTRL_RST_SOC BIT(4) ++#define WDT_CTRL_1MHZ_CLK BIT(4) /* AST2400 only */ + #define WDT_CTRL_WDT_EXT BIT(3) + #define WDT_CTRL_WDT_INTR BIT(2) + #define WDT_CTRL_RESET_SYSTEM BIT(1) +@@ -148,6 +174,17 @@ + #define WDT_DEFAULT_TIMEOUT 30 + #define WDT_RATE_1MHZ 1000000 + ++static void wdt_writel_normal(u32 val, void __iomem *addr) ++{ ++ writel(val, addr); ++} ++ ++static void wdt_writel_delay(u32 val, void __iomem *addr) ++{ ++ writel(val, addr); ++ udelay(5); ++} ++ + static struct aspeed_wdt *to_aspeed_wdt(struct watchdog_device *wdd) + { + return container_of(wdd, struct aspeed_wdt, wdd); +@@ -157,10 +194,10 @@ + { + wdt->ctrl |= WDT_CTRL_ENABLE; + +- writel(0, wdt->base + WDT_CTRL); +- writel(count, wdt->base + WDT_RELOAD_VALUE); +- writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); +- writel(wdt->ctrl, wdt->base + WDT_CTRL); ++ wdt->cfg->wdt_writel(0, wdt->base + WDT_CTRL); ++ wdt->cfg->wdt_writel(count, wdt->base + WDT_RELOAD_VALUE); ++ wdt->cfg->wdt_writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); ++ wdt->cfg->wdt_writel(wdt->ctrl, wdt->base + WDT_CTRL); + } + + static int aspeed_wdt_start(struct watchdog_device *wdd) +@@ -177,7 +214,7 @@ + struct aspeed_wdt *wdt = to_aspeed_wdt(wdd); + + wdt->ctrl &= ~WDT_CTRL_ENABLE; +- writel(wdt->ctrl, wdt->base + WDT_CTRL); ++ wdt->cfg->wdt_writel(wdt->ctrl, wdt->base + WDT_CTRL); + + return 0; + } +@@ -186,7 +223,7 @@ + { + struct aspeed_wdt *wdt = to_aspeed_wdt(wdd); + +- writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); ++ wdt->cfg->wdt_writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); + + return 0; + } +@@ -201,8 +238,8 @@ + + actual = min(timeout, wdd->max_hw_heartbeat_ms / 1000); + +- writel(actual * WDT_RATE_1MHZ, wdt->base + WDT_RELOAD_VALUE); +- writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); ++ wdt->cfg->wdt_writel(actual * WDT_RATE_1MHZ, wdt->base + WDT_RELOAD_VALUE); ++ wdt->cfg->wdt_writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); + + return 0; + } +@@ -222,7 +259,7 @@ + else + wdt->ctrl &= ~WDT_CTRL_WDT_INTR; + +- writel(wdt->ctrl, wdt->base + WDT_CTRL); ++ wdt->cfg->wdt_writel(wdt->ctrl, wdt->base + WDT_CTRL); + + return 0; + } +@@ -375,8 +412,10 @@ + struct aspeed_wdt *wdt = to_aspeed_wdt(wdd); + u32 status = readl(wdt->base + WDT_TIMEOUT_STATUS); + +- if (status & WDT_TIMEOUT_STATUS_IRQ) ++ if (status & WDT_TIMEOUT_STATUS_IRQ) { + watchdog_notify_pretimeout(wdd); ++ wdt->cfg->wdt_writel(0x1, wdt->base + WDT_CLEAR_TIMEOUT_STATUS); ++ } + + return IRQ_HANDLED; + } +@@ -450,13 +489,16 @@ + ret = of_property_read_string(np, "aspeed,reset-type", &reset_type); + if (ret) { + wdt->ctrl |= WDT_CTRL_RESET_MODE_SOC | WDT_CTRL_RESET_SYSTEM; ++ if (!of_device_is_compatible(np, "aspeed,ast2400-wdt")) ++ wdt->ctrl |= WDT_CTRL_RST_SOC; + } else { + if (!strcmp(reset_type, "cpu")) + wdt->ctrl |= WDT_CTRL_RESET_MODE_ARM_CPU | + WDT_CTRL_RESET_SYSTEM; + else if (!strcmp(reset_type, "soc")) + wdt->ctrl |= WDT_CTRL_RESET_MODE_SOC | +- WDT_CTRL_RESET_SYSTEM; ++ WDT_CTRL_RESET_SYSTEM | ++ WDT_CTRL_RST_SOC; + else if (!strcmp(reset_type, "system")) + wdt->ctrl |= WDT_CTRL_RESET_MODE_FULL_CHIP | + WDT_CTRL_RESET_SYSTEM; +@@ -479,11 +521,11 @@ + set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); + } + +- if ((of_device_is_compatible(np, "aspeed,ast2500-wdt")) || +- (of_device_is_compatible(np, "aspeed,ast2600-wdt"))) { +- u32 reset_mask[2]; +- size_t nrstmask = of_device_is_compatible(np, "aspeed,ast2600-wdt") ? 2 : 1; ++ if (!of_device_is_compatible(np, "aspeed,ast2400-wdt")) { ++ u32 reset_mask[5]; ++ size_t nrstmask = wdt->cfg->num_reset_masks; + u32 reg = readl(wdt->base + WDT_RESET_WIDTH); ++ int i; + + reg &= wdt->cfg->ext_pulse_width_mask; + if (of_property_read_bool(np, "aspeed,ext-active-high")) +@@ -491,7 +533,7 @@ + else + reg |= WDT_ACTIVE_LOW_MAGIC; + +- writel(reg, wdt->base + WDT_RESET_WIDTH); ++ wdt->cfg->wdt_writel(reg, wdt->base + WDT_RESET_WIDTH); + + reg &= wdt->cfg->ext_pulse_width_mask; + if (of_property_read_bool(np, "aspeed,ext-push-pull")) +@@ -499,13 +541,14 @@ + else + reg |= WDT_OPEN_DRAIN_MAGIC; + +- writel(reg, wdt->base + WDT_RESET_WIDTH); ++ wdt->cfg->wdt_writel(reg, wdt->base + WDT_RESET_WIDTH); + + ret = of_property_read_u32_array(np, "aspeed,reset-mask", reset_mask, nrstmask); + if (!ret) { +- writel(reset_mask[0], wdt->base + WDT_RESET_MASK1); +- if (nrstmask > 1) +- writel(reset_mask[1], wdt->base + WDT_RESET_MASK2); ++ for (i = 0; i < nrstmask; i++) { ++ wdt->cfg->wdt_writel(reset_mask[i], ++ wdt->base + WDT_RESET_MASK1 + i * 4); ++ } + } + } + +@@ -532,7 +575,7 @@ + * + * This implies a value of 0 gives a 1us pulse. + */ +- writel(duration - 1, wdt->base + WDT_RESET_WIDTH); ++ wdt->cfg->wdt_writel(duration - 1, wdt->base + WDT_RESET_WIDTH); + } + + aspeed_wdt_update_bootstatus(pdev, wdt); +diff --git a/include/dt-bindings/clock/aspeed,ast1700-clk.h b/include/dt-bindings/clock/aspeed,ast1700-clk.h +--- a/include/dt-bindings/clock/aspeed,ast1700-clk.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/dt-bindings/clock/aspeed,ast1700-clk.h 2026-09-09 15:51:44.868235805 +0000 +@@ -0,0 +1,97 @@ ++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ ++/* ++ * Device Tree binding constants for AST2700 clock controller. ++ * ++ * Copyright (c) 2023 Aspeed Technology Inc. ++ */ ++ ++#ifndef __DT_BINDINGS_CLOCK_AST1700_H ++#define __DT_BINDINGS_CLOCK_AST1700_H ++ ++/* io die clk gate */ ++#define AST1700_CLK_GATE_LCLK0 (0) ++#define AST1700_CLK_GATE_LCLK1 (1) ++#define AST1700_CLK_GATE_ESPI0CLK (2) ++#define AST1700_CLK_GATE_ESPI1CLK (3) ++#define AST1700_CLK_GATE_SDCLK (4) ++#define AST1700_CLK_GATE_REFCLK (5) ++#define AST1700_CLK_GATE_RSV5CLK (6) ++#define AST1700_CLK_GATE_LPCHCLK (7) ++#define AST1700_CLK_GATE_MAC0CLK (8) ++#define AST1700_CLK_GATE_MAC1CLK (9) ++#define AST1700_CLK_GATE_MAC2CLK (10) ++#define AST1700_CLK_GATE_UART0CLK (11) ++#define AST1700_CLK_GATE_UART1CLK (12) ++#define AST1700_CLK_GATE_UART2CLK (13) ++#define AST1700_CLK_GATE_UART3CLK (14) ++/* reserved bit 15*/ ++#define AST1700_CLK_GATE_I3C0CLK (16) ++#define AST1700_CLK_GATE_I3C1CLK (17) ++#define AST1700_CLK_GATE_I3C2CLK (18) ++#define AST1700_CLK_GATE_I3C3CLK (19) ++#define AST1700_CLK_GATE_I3C4CLK (20) ++#define AST1700_CLK_GATE_I3C5CLK (21) ++#define AST1700_CLK_GATE_I3C6CLK (22) ++#define AST1700_CLK_GATE_I3C7CLK (23) ++#define AST1700_CLK_GATE_I3C8CLK (24) ++#define AST1700_CLK_GATE_I3C9CLK (25) ++#define AST1700_CLK_GATE_I3C10CLK (26) ++#define AST1700_CLK_GATE_I3C11CLK (27) ++#define AST1700_CLK_GATE_I3C12CLK (28) ++#define AST1700_CLK_GATE_I3C13CLK (29) ++#define AST1700_CLK_GATE_I3C14CLK (30) ++#define AST1700_CLK_GATE_I3C15CLK (31) ++ ++#define AST1700_CLK_GATE_UART5CLK (32 + 0) ++#define AST1700_CLK_GATE_UART6CLK (32 + 1) ++#define AST1700_CLK_GATE_UART7CLK (32 + 2) ++#define AST1700_CLK_GATE_UART8CLK (32 + 3) ++#define AST1700_CLK_GATE_UART9CLK (32 + 4) ++#define AST1700_CLK_GATE_UART10CLK (32 + 5) ++#define AST1700_CLK_GATE_UART11CLK (32 + 6) ++#define AST1700_CLK_GATE_UART12CLK (32 + 7) ++#define AST1700_CLK_GATE_FSICLK (32 + 8) ++#define AST1700_CLK_GATE_LTPIPHYCLK (32 + 9) ++#define AST1700_CLK_GATE_LTPICLK (32 + 10) ++#define AST1700_CLK_GATE_VGALCLK (32 + 11) ++#define AST1700_CLK_GATE_USBUARTCLK (32 + 12) ++#define AST1700_CLK_GATE_CANCLK (32 + 13) ++#define AST1700_CLK_GATE_PCICLK (32 + 14) ++#define AST1700_CLK_GATE_SLICLK (32 + 15) ++ ++#define AST1700_CLK_GATE_NUM (AST1700_CLK_GATE_SLICLK + 1) ++ ++/* io die clk */ ++#define AST1700_CLKIN (AST1700_CLK_GATE_NUM + 0) ++#define AST1700_CLK_HPLL (AST1700_CLK_GATE_NUM + 1) ++#define AST1700_CLK_APLL (AST1700_CLK_GATE_NUM + 2) ++#define AST1700_CLK_APLL_DIV2 (AST1700_CLK_GATE_NUM + 3) ++#define AST1700_CLK_APLL_DIV4 (AST1700_CLK_GATE_NUM + 4) ++#define AST1700_CLK_DPLL (AST1700_CLK_GATE_NUM + 5) ++#define AST1700_CLK_UXCLK (AST1700_CLK_GATE_NUM + 6) ++#define AST1700_CLK_HUXCLK (AST1700_CLK_GATE_NUM + 7) ++#define AST1700_CLK_UARTX (AST1700_CLK_GATE_NUM + 8) ++#define AST1700_CLK_HUARTX (AST1700_CLK_GATE_NUM + 9) ++#define AST1700_CLK_AHB (AST1700_CLK_GATE_NUM + 10) ++#define AST1700_CLK_APB (AST1700_CLK_GATE_NUM + 11) ++#define AST1700_CLK_UART0 (AST1700_CLK_GATE_NUM + 12) ++#define AST1700_CLK_UART1 (AST1700_CLK_GATE_NUM + 13) ++#define AST1700_CLK_UART2 (AST1700_CLK_GATE_NUM + 14) ++#define AST1700_CLK_UART3 (AST1700_CLK_GATE_NUM + 15) ++#define AST1700_CLK_UART5 (AST1700_CLK_GATE_NUM + 16) ++#define AST1700_CLK_UART6 (AST1700_CLK_GATE_NUM + 17) ++#define AST1700_CLK_UART7 (AST1700_CLK_GATE_NUM + 18) ++#define AST1700_CLK_UART8 (AST1700_CLK_GATE_NUM + 19) ++#define AST1700_CLK_UART9 (AST1700_CLK_GATE_NUM + 20) ++#define AST1700_CLK_UART10 (AST1700_CLK_GATE_NUM + 21) ++#define AST1700_CLK_UART11 (AST1700_CLK_GATE_NUM + 22) ++#define AST1700_CLK_UART12 (AST1700_CLK_GATE_NUM + 23) ++#define AST1700_CLK_HPLL_DIVN (AST1700_CLK_GATE_NUM + 24) ++#define AST1700_CLK_APLL_DIVN (AST1700_CLK_GATE_NUM + 25) ++#define AST1700_CLK_SDCLK (AST1700_CLK_GATE_NUM + 26) ++#define AST1700_CLK_RMII (AST1700_CLK_GATE_NUM + 27) ++#define AST1700_CLK_RGMII (AST1700_CLK_GATE_NUM + 28) ++#define AST1700_CLK_MACHCLK (AST1700_CLK_GATE_NUM + 29) ++ ++#define AST1700_NUM_CLKS (AST1700_CLK_MACHCLK + 1) ++#endif +diff --git a/include/dt-bindings/clock/aspeed,ast1800-clk.h b/include/dt-bindings/clock/aspeed,ast1800-clk.h +--- a/include/dt-bindings/clock/aspeed,ast1800-clk.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/dt-bindings/clock/aspeed,ast1800-clk.h 2026-09-09 15:51:44.878235654 +0000 +@@ -0,0 +1,104 @@ ++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ ++/* ++ * Device Tree binding constants for AST2700 clock controller. ++ * ++ * Copyright (c) 2023 Aspeed Technology Inc. ++ */ ++ ++#ifndef __DT_BINDINGS_CLOCK_AST1800_H ++#define __DT_BINDINGS_CLOCK_AST1800_H ++ ++#define AST1800_CLK_GATE_MCU (0) ++#define AST1800_CLK_GATE_CONFIG (1) ++#define AST1800_CLK_GATE_MEM (2) ++#define AST1800_CLK_GATE_LTPI (3) ++#define AST1800_CLK_GATE_SPI_SLAVE (4) ++#define AST1800_CLK_GATE_I2C_SLAVE (5) ++#define AST1800_CLK_GATE_SRAM (6) ++#define AST1800_CLK_GATE_UART_DBG (7) ++/* reserved bit 8 ~ 31 */ ++#define AST1800_CLK_GATE_I3C0 (32) ++#define AST1800_CLK_GATE_I3C1 (33) ++#define AST1800_CLK_GATE_I3C2 (34) ++#define AST1800_CLK_GATE_I3C3 (35) ++#define AST1800_CLK_GATE_I3C4 (36) ++#define AST1800_CLK_GATE_I3C5 (37) ++#define AST1800_CLK_GATE_I3C6 (38) ++#define AST1800_CLK_GATE_I3C7 (39) ++#define AST1800_CLK_GATE_I3C8 (40) ++#define AST1800_CLK_GATE_I3C9 (41) ++#define AST1800_CLK_GATE_I3C10 (42) ++#define AST1800_CLK_GATE_I3C11 (43) ++#define AST1800_CLK_GATE_I3C12 (44) ++#define AST1800_CLK_GATE_I3C13 (45) ++#define AST1800_CLK_GATE_I3C14 (46) ++#define AST1800_CLK_GATE_I3C15 (47) ++/* reserved bit 16 ~ 31 */ ++#define AST1800_CLK_GATE_EFPGA0 (64) ++#define AST1800_CLK_GATE_EFPGA1 (65) ++#define AST1800_CLK_GATE_EFPGA2 (66) ++#define AST1800_CLK_GATE_EFPGA3 (67) ++#define AST1800_CLK_GATE_EFPGA4 (68) ++#define AST1800_CLK_GATE_EFPGA5 (69) ++#define AST1800_CLK_GATE_EFPGA6 (70) ++#define AST1800_CLK_GATE_EFPGA7 (71) ++#define AST1800_CLK_GATE_EFPGA_DBG (72) ++ ++#define AST1800_CLKIN (AST1800_CLK_GATE_EFPGA_DBG + 0) ++#define AST1800_CLK_HPLL (AST1800_CLK_GATE_EFPGA_DBG + 1) ++#define AST1800_CLK_HPLL_DIV2 (AST1800_CLK_GATE_EFPGA_DBG + 2) ++#define AST1800_CLK_HPLL_DIV4 (AST1800_CLK_GATE_EFPGA_DBG + 3) ++#define AST1800_CLK_HPLL_DIV5 (AST1800_CLK_GATE_EFPGA_DBG + 4) ++#define AST1800_CLK_HPLL_DIV8 (AST1800_CLK_GATE_EFPGA_DBG + 5) ++#define AST1800_CLK_HPLL_DIV10 (AST1800_CLK_GATE_EFPGA_DBG + 6) ++#define AST1800_CLK_HPLL_DIV20 (AST1800_CLK_GATE_EFPGA_DBG + 7) ++#define AST1800_CLK_HPLL_DIV25 (AST1800_CLK_GATE_EFPGA_DBG + 8) ++#define AST1800_CLK_HPLL_DIV50 (AST1800_CLK_GATE_EFPGA_DBG + 9) ++#define AST1800_CLK_HPLL_DIV100 (AST1800_CLK_GATE_EFPGA_DBG + 10) ++#define AST1800_CLK_HPLL_DIV200 (AST1800_CLK_GATE_EFPGA_DBG + 11) ++#define AST1800_CLK_HPLL_DIV1000 (AST1800_CLK_GATE_EFPGA_DBG + 12) ++#define AST1800_CLK_HPLL_EFPGA0 (AST1800_CLK_GATE_EFPGA_DBG + 13) ++#define AST1800_CLK_HPLL_EFPGA1 (AST1800_CLK_GATE_EFPGA_DBG + 14) ++#define AST1800_CLK_HPLL_EFPGA2 (AST1800_CLK_GATE_EFPGA_DBG + 15) ++#define AST1800_CLK_HPLL_EFPGA3 (AST1800_CLK_GATE_EFPGA_DBG + 16) ++#define AST1800_CLK_HPLL_EFPGA4 (AST1800_CLK_GATE_EFPGA_DBG + 17) ++#define AST1800_CLK_HPLL_EFPGA5 (AST1800_CLK_GATE_EFPGA_DBG + 18) ++#define AST1800_CLK_HPLL_EFPGA6 (AST1800_CLK_GATE_EFPGA_DBG + 19) ++#define AST1800_CLK_HPLL_EFPGA7 (AST1800_CLK_GATE_EFPGA_DBG + 20) ++#define AST1800_CLK_EPLL (AST1800_CLK_GATE_EFPGA_DBG + 21) ++#define AST1800_CLK_EPLL_DIV2 (AST1800_CLK_GATE_EFPGA_DBG + 22) ++#define AST1800_CLK_EPLL_DIV4 (AST1800_CLK_GATE_EFPGA_DBG + 23) ++#define AST1800_CLK_EPLL_DIV8 (AST1800_CLK_GATE_EFPGA_DBG + 24) ++#define AST1800_CLK_EPLL_DIV12 (AST1800_CLK_GATE_EFPGA_DBG + 25) ++#define AST1800_CLK_EPLL_EFPGA0 (AST1800_CLK_GATE_EFPGA_DBG + 26) ++#define AST1800_CLK_EPLL_EFPGA1 (AST1800_CLK_GATE_EFPGA_DBG + 27) ++#define AST1800_CLK_EPLL_EFPGA2 (AST1800_CLK_GATE_EFPGA_DBG + 28) ++#define AST1800_CLK_EPLL_EFPGA3 (AST1800_CLK_GATE_EFPGA_DBG + 29) ++#define AST1800_CLK_EPLL_EFPGA4 (AST1800_CLK_GATE_EFPGA_DBG + 30) ++#define AST1800_CLK_EPLL_EFPGA5 (AST1800_CLK_GATE_EFPGA_DBG + 31) ++#define AST1800_CLK_EPLL_EFPGA6 (AST1800_CLK_GATE_EFPGA_DBG + 32) ++#define AST1800_CLK_EPLL_EFPGA7 (AST1800_CLK_GATE_EFPGA_DBG + 33) ++#define AST1800_CLK_EFPGA0 (AST1800_CLK_GATE_EFPGA_DBG + 34) ++#define AST1800_CLK_EFPGA1 (AST1800_CLK_GATE_EFPGA_DBG + 35) ++#define AST1800_CLK_EFPGA2 (AST1800_CLK_GATE_EFPGA_DBG + 36) ++#define AST1800_CLK_EFPGA3 (AST1800_CLK_GATE_EFPGA_DBG + 37) ++#define AST1800_CLK_EFPGA4 (AST1800_CLK_GATE_EFPGA_DBG + 38) ++#define AST1800_CLK_EFPGA5 (AST1800_CLK_GATE_EFPGA_DBG + 39) ++#define AST1800_CLK_EFPGA6 (AST1800_CLK_GATE_EFPGA_DBG + 40) ++#define AST1800_CLK_EFPGA7 (AST1800_CLK_GATE_EFPGA_DBG + 41) ++#define AST1800_CLK_LPLL (AST1800_CLK_GATE_EFPGA_DBG + 42) ++#define AST1800_CLK_LPLL_DIV2 (AST1800_CLK_GATE_EFPGA_DBG + 43) ++#define AST1800_CLK_LPLL_DIV4 (AST1800_CLK_GATE_EFPGA_DBG + 44) ++#define AST1800_CLK_LPLL_DIV8 (AST1800_CLK_GATE_EFPGA_DBG + 45) ++#define AST1800_CLK_UXCLK (AST1800_CLK_GATE_EFPGA_DBG + 46) ++#define AST1800_CLK_HUXCLK (AST1800_CLK_GATE_EFPGA_DBG + 47) ++#define AST1800_CLK_UARTX (AST1800_CLK_GATE_EFPGA_DBG + 48) ++#define AST1800_CLK_HUARTX (AST1800_CLK_GATE_EFPGA_DBG + 49) ++#define AST1800_CLK_AHB (AST1800_CLK_GATE_EFPGA_DBG + 50) ++#define AST1800_CLK_APB (AST1800_CLK_GATE_EFPGA_DBG + 51) ++#define AST1800_CLK_UART (AST1800_CLK_GATE_EFPGA_DBG + 52) ++#define AST1800_CLK_I3C (AST1800_CLK_GATE_EFPGA_DBG + 53) ++ ++#define AST1800_NUM_CLKS (AST1800_CLK_I3C + 1) ++ ++#endif +diff --git a/include/dt-bindings/clock/aspeed,ast2700-scu.h b/include/dt-bindings/clock/aspeed,ast2700-scu.h +--- a/include/dt-bindings/clock/aspeed,ast2700-scu.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/dt-bindings/clock/aspeed,ast2700-scu.h 2026-09-09 15:51:44.883235578 +0000 +@@ -0,0 +1,169 @@ ++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ ++/* ++ * Device Tree binding constants for AST2700 clock controller. ++ * ++ * Copyright (c) 2024 Aspeed Technology Inc. ++ */ ++ ++#ifndef __DT_BINDINGS_CLOCK_AST2700_H ++#define __DT_BINDINGS_CLOCK_AST2700_H ++ ++/* SOC0 clk */ ++#define SCU0_CLKIN 0 ++#define SCU0_CLK_24M 1 ++#define SCU0_CLK_192M 2 ++#define SCU0_CLK_UART 3 ++#define SCU0_CLK_UART_DIV13 3 ++#define SCU0_CLK_PSP 4 ++#define SCU0_CLK_HPLL 5 ++#define SCU0_CLK_HPLL_DIV2 6 ++#define SCU0_CLK_HPLL_DIV4 7 ++#define SCU0_CLK_HPLL_DIV_AHB 8 ++#define SCU0_CLK_DPLL 9 ++#define SCU0_CLK_MPLL 10 ++#define SCU0_CLK_MPLL_DIV2 11 ++#define SCU0_CLK_MPLL_DIV4 12 ++#define SCU0_CLK_MPLL_DIV8 13 ++#define SCU0_CLK_MPLL_DIV_AHB 14 ++#define SCU0_CLK_D0 15 ++#define SCU0_CLK_D1 16 ++#define SCU0_CLK_CRT0 17 ++#define SCU0_CLK_CRT1 18 ++#define SCU0_CLK_MPHY 19 ++#define SCU0_CLK_AXI0 20 ++#define SCU0_CLK_AXI1 21 ++#define SCU0_CLK_AHB 22 ++#define SCU0_CLK_APB 23 ++#define SCU0_CLK_UART4 24 ++#define SCU0_CLK_EMMCMUX 25 ++#define SCU0_CLK_EMMC 26 ++#define SCU0_CLK_U2PHY_CLK12M 27 ++#define SCU0_CLK_U2PHY_REFCLK 28 ++ ++/* SOC0 clk-gate */ ++#define SCU0_CLK_GATE_MCLK 29 ++#define SCU0_CLK_GATE_ECLK 30 ++#define SCU0_CLK_GATE_2DCLK 31 ++#define SCU0_CLK_GATE_VCLK 32 ++#define SCU0_CLK_GATE_BCLK 33 ++#define SCU0_CLK_GATE_VGA0CLK 34 ++#define SCU0_CLK_GATE_REFCLK 35 ++#define SCU0_CLK_GATE_PORTBUSB2CLK 36 ++#define SCU0_CLK_GATE_UHCICLK 37 ++#define SCU0_CLK_GATE_VGA1CLK 38 ++#define SCU0_CLK_GATE_DDRPHYCLK 39 ++#define SCU0_CLK_GATE_E2M0CLK 40 ++#define SCU0_CLK_GATE_HACCLK 41 ++#define SCU0_CLK_GATE_PORTAUSB2CLK 42 ++#define SCU0_CLK_GATE_UART4CLK 43 ++#define SCU0_CLK_GATE_SLICLK 44 ++#define SCU0_CLK_GATE_DACCLK 45 ++#define SCU0_CLK_GATE_DP 46 ++#define SCU0_CLK_GATE_E2M1CLK 47 ++#define SCU0_CLK_GATE_CRT0CLK 48 ++#define SCU0_CLK_GATE_CRT1CLK 49 ++#define SCU0_CLK_GATE_ECDSACLK 50 ++#define SCU0_CLK_GATE_RSACLK 51 ++#define SCU0_CLK_GATE_RVAS0CLK 52 ++#define SCU0_CLK_GATE_UFSCLK 53 ++#define SCU0_CLK_GATE_EMMCCLK 54 ++#define SCU0_CLK_GATE_RVAS1CLK 55 ++#define SCU0_CLK_U2PHY_REFCLKSRC 56 ++#define SCU0_CLK_AHBMUX 57 ++#define SCU0_CLK_MPHYSRC 58 ++ ++/* SOC1 clk */ ++#define SCU1_CLKIN 0 ++#define SCU1_CLK_HPLL 1 ++#define SCU1_CLK_APLL 2 ++#define SCU1_CLK_APLL_DIV2 3 ++#define SCU1_CLK_APLL_DIV4 4 ++#define SCU1_CLK_DPLL 5 ++#define SCU1_CLK_UXCLK 6 ++#define SCU1_CLK_HUXCLK 7 ++#define SCU1_CLK_UARTX 8 ++#define SCU1_CLK_HUARTX 9 ++#define SCU1_CLK_AHB 10 ++#define SCU1_CLK_APB 11 ++#define SCU1_CLK_UART0 12 ++#define SCU1_CLK_UART1 13 ++#define SCU1_CLK_UART2 14 ++#define SCU1_CLK_UART3 15 ++#define SCU1_CLK_UART5 16 ++#define SCU1_CLK_UART6 17 ++#define SCU1_CLK_UART7 18 ++#define SCU1_CLK_UART8 19 ++#define SCU1_CLK_UART9 20 ++#define SCU1_CLK_UART10 21 ++#define SCU1_CLK_UART11 22 ++#define SCU1_CLK_UART12 23 ++#define SCU1_CLK_UART13 24 ++#define SCU1_CLK_UART14 25 ++#define SCU1_CLK_APLL_DIVN 26 ++#define SCU1_CLK_SDMUX 27 ++#define SCU1_CLK_SDCLK 28 ++#define SCU1_CLK_RMII 29 ++#define SCU1_CLK_RGMII 30 ++#define SCU1_CLK_MACHCLK 31 ++#define SCU1_CLK_MAC0RCLK 32 ++#define SCU1_CLK_MAC1RCLK 33 ++#define SCU1_CLK_CAN 34 ++ ++/* SOC1 clk gate */ ++#define SCU1_CLK_GATE_LCLK0 35 ++#define SCU1_CLK_GATE_LCLK1 36 ++#define SCU1_CLK_GATE_ESPI0CLK 37 ++#define SCU1_CLK_GATE_ESPI1CLK 38 ++#define SCU1_CLK_GATE_SDCLK 39 ++#define SCU1_CLK_GATE_IPEREFCLK 40 ++#define SCU1_CLK_GATE_REFCLK 41 ++#define SCU1_CLK_GATE_LPCHCLK 42 ++#define SCU1_CLK_GATE_MAC0CLK 43 ++#define SCU1_CLK_GATE_MAC1CLK 44 ++#define SCU1_CLK_GATE_MAC2CLK 45 ++#define SCU1_CLK_GATE_UART0CLK 46 ++#define SCU1_CLK_GATE_UART1CLK 47 ++#define SCU1_CLK_GATE_UART2CLK 48 ++#define SCU1_CLK_GATE_UART3CLK 49 ++#define SCU1_CLK_GATE_I2CCLK 50 ++#define SCU1_CLK_GATE_I3C0CLK 51 ++#define SCU1_CLK_GATE_I3C1CLK 52 ++#define SCU1_CLK_GATE_I3C2CLK 53 ++#define SCU1_CLK_GATE_I3C3CLK 54 ++#define SCU1_CLK_GATE_I3C4CLK 55 ++#define SCU1_CLK_GATE_I3C5CLK 56 ++#define SCU1_CLK_GATE_I3C6CLK 57 ++#define SCU1_CLK_GATE_I3C7CLK 58 ++#define SCU1_CLK_GATE_I3C8CLK 59 ++#define SCU1_CLK_GATE_I3C9CLK 60 ++#define SCU1_CLK_GATE_I3C10CLK 61 ++#define SCU1_CLK_GATE_I3C11CLK 62 ++#define SCU1_CLK_GATE_I3C12CLK 63 ++#define SCU1_CLK_GATE_I3C13CLK 64 ++#define SCU1_CLK_GATE_I3C14CLK 65 ++#define SCU1_CLK_GATE_I3C15CLK 66 ++#define SCU1_CLK_GATE_UART5CLK 67 ++#define SCU1_CLK_GATE_UART6CLK 68 ++#define SCU1_CLK_GATE_UART7CLK 69 ++#define SCU1_CLK_GATE_UART8CLK 70 ++#define SCU1_CLK_GATE_UART9CLK 71 ++#define SCU1_CLK_GATE_UART10CLK 72 ++#define SCU1_CLK_GATE_UART11CLK 73 ++#define SCU1_CLK_GATE_UART12CLK 74 ++#define SCU1_CLK_GATE_FSICLK 75 ++#define SCU1_CLK_GATE_LTPIPHYCLK 76 ++#define SCU1_CLK_GATE_LTPICLK 77 ++#define SCU1_CLK_GATE_VGALCLK 78 ++#define SCU1_CLK_GATE_UHCICLK 79 ++#define SCU1_CLK_GATE_CANCLK 80 ++#define SCU1_CLK_GATE_PCICLK 81 ++#define SCU1_CLK_GATE_SLICLK 82 ++#define SCU1_CLK_GATE_E2MCLK 83 ++#define SCU1_CLK_GATE_PORTCUSB2CLK 84 ++#define SCU1_CLK_GATE_PORTDUSB2CLK 85 ++#define SCU1_CLK_GATE_LTPI1TXCLK 86 ++#define SCU1_CLK_I3C 87 ++#define SCU1_CLK_HPLL_DIV4 88 ++#define SCU1_CLK_PECI 89 ++ ++#endif +diff --git a/include/dt-bindings/clock/aspeed-clock.h b/include/dt-bindings/clock/aspeed-clock.h +--- a/include/dt-bindings/clock/aspeed-clock.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/dt-bindings/clock/aspeed-clock.h 2026-09-09 15:51:44.873235729 +0000 +@@ -53,5 +53,6 @@ + #define ASPEED_RESET_AHB 8 + #define ASPEED_RESET_CRT1 9 + #define ASPEED_RESET_HACE 10 ++#define ASPEED_RESET_VIDEO 21 + + #endif +diff --git a/include/dt-bindings/clock/ast2600-clock.h b/include/dt-bindings/clock/ast2600-clock.h +--- a/include/dt-bindings/clock/ast2600-clock.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/dt-bindings/clock/ast2600-clock.h 2026-09-09 15:51:44.888235503 +0000 +@@ -72,7 +72,7 @@ + #define ASPEED_CLK_D1CLK 55 + #define ASPEED_CLK_VCLK 56 + #define ASPEED_CLK_LHCLK 57 +-#define ASPEED_CLK_UART 58 ++#define ASPEED_CLK_UART5 58 + #define ASPEED_CLK_UARTX 59 + #define ASPEED_CLK_SDIO 60 + #define ASPEED_CLK_EMMC 61 +@@ -87,8 +87,12 @@ + #define ASPEED_CLK_MAC4RCLK 70 + #define ASPEED_CLK_I3C 71 + #define ASPEED_CLK_FSI 72 ++#define ASPEED_CLK_HUARTX 73 ++#define ASPEED_CLK_UXCLK 74 ++#define ASPEED_CLK_HUXCLK 75 + + /* Only list resets here that are not part of a clock gate + reset pair */ ++#define ASPEED_RESET_ESPI 57 + #define ASPEED_RESET_ADC 55 + #define ASPEED_RESET_JTAG_MASTER2 54 + +@@ -118,10 +122,16 @@ + #define ASPEED_RESET_DEV_MCTP 24 + #define ASPEED_RESET_RC_MCTP 23 + #define ASPEED_RESET_JTAG_MASTER 22 +-#define ASPEED_RESET_PCIE_DEV_O 21 +-#define ASPEED_RESET_PCIE_DEV_OEN 20 +-#define ASPEED_RESET_PCIE_RC_O 19 +-#define ASPEED_RESET_PCIE_RC_OEN 18 ++#define ASPEED_RESET_PCIE_DEV_OE 21 ++#define ASPEED_RESET_PCIE_DEV_O 20 ++#define ASPEED_RESET_PCIE_RC_OE 19 ++#define ASPEED_RESET_PCIE_RC_O 18 ++#define ASPEED_RESET_EMMC 16 ++#define ASPEED_RESET_CRT 13 ++#define ASPEED_RESET_MAC2 12 ++#define ASPEED_RESET_MAC1 11 ++#define ASPEED_RESET_RVAS 9 ++#define ASPEED_RESET_VIDEO 6 + #define ASPEED_RESET_PCI_DP 5 + #define ASPEED_RESET_HACE 4 + #define ASPEED_RESET_AHB 1 +diff --git a/include/dt-bindings/interrupt-controller/aspeed-e2m-ic.h b/include/dt-bindings/interrupt-controller/aspeed-e2m-ic.h +--- a/include/dt-bindings/interrupt-controller/aspeed-e2m-ic.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/dt-bindings/interrupt-controller/aspeed-e2m-ic.h 2026-09-09 15:51:44.893235427 +0000 +@@ -0,0 +1,21 @@ ++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ ++/* ++ * Device Tree binding constants for AST2700 E2M interrupt ++ * controller. ++ * ++ * Copyright (c) 2024 Aspeed Technology Inc. ++ */ ++ ++#ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_E2M_IC_H_ ++#define _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_E2M_IC_H_ ++ ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT0 0 ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT1 1 ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT2 2 ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT3 3 ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT4 4 ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT5 5 ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT6 6 ++#define ASPEED_AST2700_E2M_MMBI_H2B_INT7 7 ++ ++#endif /* _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_E2M_IC_H_ */ +diff --git a/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h b/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h +--- a/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h 2026-09-09 15:51:44.898235352 +0000 +@@ -20,4 +20,18 @@ + #define ASPEED_AST2600_SCU_IC1_LPC_RESET_LO_TO_HI 0 + #define ASPEED_AST2600_SCU_IC1_LPC_RESET_HI_TO_LO 1 + ++#define ASPEED_AST2700_SCU_IC0_PCIE_PERST_LO_TO_HI 3 ++#define ASPEED_AST2700_SCU_IC0_PCIE_PERST_HI_TO_LO 2 ++ ++#define ASPEED_AST2700_SCU_IC1_PCIE_RCRST_LO_TO_HI 3 ++#define ASPEED_AST2700_SCU_IC1_PCIE_RCRST_HI_TO_LO 2 ++ ++#define ASPEED_AST2700_SCU_IC2_PCIE_PERST_LO_TO_HI 3 ++#define ASPEED_AST2700_SCU_IC2_PCIE_PERST_HI_TO_LO 2 ++#define ASPEED_AST2700_SCU_IC2_LPC_RESET_LO_TO_HI 1 ++#define ASPEED_AST2700_SCU_IC2_LPC_RESET_HI_TO_LO 0 ++ ++#define ASPEED_AST2700_SCU_IC3_LPC_RESET_LO_TO_HI 1 ++#define ASPEED_AST2700_SCU_IC3_LPC_RESET_HI_TO_LO 0 ++ + #endif /* _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_SCU_IC_H_ */ +diff --git a/include/dt-bindings/reset/aspeed,ast1700-reset.h b/include/dt-bindings/reset/aspeed,ast1700-reset.h +--- a/include/dt-bindings/reset/aspeed,ast1700-reset.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/dt-bindings/reset/aspeed,ast1700-reset.h 2026-09-09 15:51:44.913235125 +0000 +@@ -0,0 +1,69 @@ ++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ ++/* ++ * Device Tree binding constants for AST2700 reset controller. ++ * ++ * Copyright (c) 2023 Aspeed Technology Inc. ++ */ ++ ++#ifndef _MACH_ASPEED_AST1700_RESET_H_ ++#define _MACH_ASPEED_AST1700_RESET_H_ ++ ++#define AST1700_RESET_LPC0 (0) ++#define AST1700_RESET_LPC1 (1) ++#define AST1700_RESET_MII (2) ++#define AST1700_RESET_PECI (3) ++#define AST1700_RESET_PWM (4) ++#define AST1700_RESET_MAC0 (5) ++#define AST1700_RESET_MAC1 (6) ++#define AST1700_RESET_MAC2 (7) ++#define AST1700_RESET_ADC (8) ++#define AST1700_RESET_SD (9) ++#define AST1700_RESET_ESPI0 (10) ++#define AST1700_RESET_ESPI1 (11) ++#define AST1700_RESET_JTAG1 (12) ++#define AST1700_RESET_SPI0 (13) ++#define AST1700_RESET_SPI1 (14) ++#define AST1700_RESET_SPI2 (15) ++#define AST1700_RESET_I3C0 (16) ++#define AST1700_RESET_I3C1 (17) ++#define AST1700_RESET_I3C2 (18) ++#define AST1700_RESET_I3C3 (19) ++#define AST1700_RESET_I3C4 (20) ++#define AST1700_RESET_I3C5 (21) ++#define AST1700_RESET_I3C6 (22) ++#define AST1700_RESET_I3C7 (23) ++#define AST1700_RESET_I3C8 (24) ++#define AST1700_RESET_I3C9 (25) ++#define AST1700_RESET_I3C10 (26) ++#define AST1700_RESET_I3C11 (27) ++#define AST1700_RESET_I3C12 (28) ++#define AST1700_RESET_I3C13 (29) ++#define AST1700_RESET_I3C14 (30) ++#define AST1700_RESET_I3C15 (31) ++/* reserved 32 */ ++#define AST1700_RESET_IOMCU (33) ++#define AST1700_RESET_H2A_SPI1 (34) ++#define AST1700_RESET_H2A_SPI2 (35) ++#define AST1700_RESET_UART0 (36) ++#define AST1700_RESET_UART1 (37) ++#define AST1700_RESET_UART2 (38) ++#define AST1700_RESET_UART3 (39) ++#define AST1700_RESET_I2C_FILTER (40) ++#define AST1700_RESET_CALIPTRA (41) ++/* reserved 42:43 */ ++#define AST1700_RESET_FSI (44) ++#define AST1700_RESET_CAN (45) ++#define AST1700_RESET_MCTP (46) ++#define AST1700_RESET_I2C (47) ++#define AST1700_RESET_UART6 (48) ++#define AST1700_RESET_UART7 (49) ++#define AST1700_RESET_UART8 (50) ++#define AST1700_RESET_UART9 (51) ++#define AST1700_RESET_LTPI (52) ++#define AST1700_RESET_VGAL (53) ++/* reserved 54:62 */ ++#define AST1700_RESET_I3CDMA (63) ++ ++#define AST1700_RESET_NUMS (AST1700_RESET_I3CDMA + 1) ++ ++#endif /* _MACH_ASPEED_AST1700_RESET_H_ */ +diff --git a/include/dt-bindings/reset/aspeed,ast1800-reset.h b/include/dt-bindings/reset/aspeed,ast1800-reset.h +--- a/include/dt-bindings/reset/aspeed,ast1800-reset.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/dt-bindings/reset/aspeed,ast1800-reset.h 2026-09-09 15:51:44.923234974 +0000 +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ ++/* ++ * Device Tree binding constants for AST1800 reset controller. ++ * ++ * Copyright (c) 2025 Aspeed Technology Inc. ++ */ ++ ++#ifndef _MACH_ASPEED_AST1800_RESET_H_ ++#define _MACH_ASPEED_AST1800_RESET_H_ ++ ++#define AST1800_RESET_CONFIG (0) ++#define AST1800_RESET_MEM (1) ++#define AST1800_RESET_LTPI (2) ++#define AST1800_RESET_EFPGA_DBG (3) ++#define AST1800_RESET_SPI_SLAVE (4) ++#define AST1800_RESET_I2C_SLAVE (5) ++/* reserved 6 7 */ ++#define AST1800_RESET_I2C_MASTER (8) ++#define AST1800_RESET_I3C_DMA (9) ++#define AST1800_RESET_ADC (10) ++#define AST1800_RESET_GPIO (11) ++#define AST1800_RESET_JTAG (12) ++#define AST1800_RESET_PWM (13) ++#define AST1800_RESET_UART_DBG (14) ++/* reserved 15~31 */ ++#define AST1800_RESET_I3C0 (32) ++#define AST1800_RESET_I3C1 (33) ++#define AST1800_RESET_I3C2 (34) ++#define AST1800_RESET_I3C3 (35) ++#define AST1800_RESET_I3C4 (36) ++#define AST1800_RESET_I3C5 (37) ++#define AST1800_RESET_I3C6 (38) ++#define AST1800_RESET_I3C7 (39) ++#define AST1800_RESET_I3C8 (40) ++#define AST1800_RESET_I3C9 (41) ++#define AST1800_RESET_I3C10 (42) ++#define AST1800_RESET_I3C11 (43) ++#define AST1800_RESET_I3C12 (44) ++#define AST1800_RESET_I3C13 (45) ++#define AST1800_RESET_I3C14 (46) ++#define AST1800_RESET_I3C15 (47) ++#define AST1800_RESET_I2C0 (48) ++#define AST1800_RESET_I2C1 (49) ++#define AST1800_RESET_I2C2 (50) ++#define AST1800_RESET_I2C3 (51) ++#define AST1800_RESET_I2C4 (52) ++#define AST1800_RESET_I2C5 (53) ++#define AST1800_RESET_I2C6 (54) ++#define AST1800_RESET_I2C7 (55) ++#define AST1800_RESET_I2C8 (56) ++#define AST1800_RESET_I2C9 (57) ++#define AST1800_RESET_I2C10 (58) ++#define AST1800_RESET_I2C11 (59) ++#define AST1800_RESET_I2C12 (60) ++#define AST1800_RESET_I2C13 (61) ++#define AST1800_RESET_I2C14 (62) ++#define AST1800_RESET_I2C15 (63) ++ ++#define AST1800_RESET_NUMS (AST1800_RESET_I2C15 + 1) ++ ++#endif /* _MACH_ASPEED_AST1800_RESET_H_ */ +diff --git a/include/dt-bindings/reset/aspeed,ast2700-scu.h b/include/dt-bindings/reset/aspeed,ast2700-scu.h +--- a/include/dt-bindings/reset/aspeed,ast2700-scu.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/dt-bindings/reset/aspeed,ast2700-scu.h 2026-09-09 15:51:44.918235049 +0000 +@@ -0,0 +1,125 @@ ++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ ++/* ++ * Device Tree binding constants for AST2700 reset controller. ++ * ++ * Copyright (c) 2024 Aspeed Technology Inc. ++ */ ++ ++#ifndef _MACH_ASPEED_AST2700_RESET_H_ ++#define _MACH_ASPEED_AST2700_RESET_H_ ++ ++/* SOC0 */ ++#define SCU0_RESET_SDRAM 0 ++#define SCU0_RESET_DDRPHY 1 ++#define SCU0_RESET_RSA 2 ++#define SCU0_RESET_SHA3 3 ++#define SCU0_RESET_HACE 4 ++#define SCU0_RESET_SOC 5 ++#define SCU0_RESET_VIDEO 6 ++#define SCU0_RESET_2D 7 ++#define SCU0_RESET_PCIS 8 ++#define SCU0_RESET_RVAS0 9 ++#define SCU0_RESET_RVAS1 10 ++#define SCU0_RESET_SM3 11 ++#define SCU0_RESET_SM4 12 ++#define SCU0_RESET_CRT0 13 ++#define SCU0_RESET_ECC 14 ++#define SCU0_RESET_DP_PCI 15 ++#define SCU0_RESET_UFS 16 ++#define SCU0_RESET_EMMC 17 ++#define SCU0_RESET_PCIE1RST 18 ++#define SCU0_RESET_PCIE1RSTOE 19 ++#define SCU0_RESET_PCIE0RST 20 ++#define SCU0_RESET_PCIE0RSTOE 21 ++#define SCU0_RESET_JTAG 22 ++#define SCU0_RESET_MCTP0 23 ++#define SCU0_RESET_MCTP1 24 ++#define SCU0_RESET_XDMA0 25 ++#define SCU0_RESET_XDMA1 26 ++#define SCU0_RESET_H2X1 27 ++#define SCU0_RESET_DP 28 ++#define SCU0_RESET_DP_MCU 29 ++#define SCU0_RESET_SSP 30 ++#define SCU0_RESET_H2X0 31 ++#define SCU0_RESET_PORTA_VHUB 32 ++#define SCU0_RESET_PORTA_PHY3 33 ++#define SCU0_RESET_PORTA_XHCI 34 ++#define SCU0_RESET_PORTB_VHUB 35 ++#define SCU0_RESET_PORTB_PHY3 36 ++#define SCU0_RESET_PORTB_XHCI 37 ++#define SCU0_RESET_PORTA_VHUB_EHCI 38 ++#define SCU0_RESET_PORTB_VHUB_EHCI 39 ++#define SCU0_RESET_UHCI 40 ++#define SCU0_RESET_TSP 41 ++#define SCU0_RESET_E2M0 42 ++#define SCU0_RESET_E2M1 43 ++#define SCU0_RESET_VLINK 44 ++ ++/* SOC1 */ ++#define SCU1_RESET_LPC0 0 ++#define SCU1_RESET_LPC1 1 ++#define SCU1_RESET_MII 2 ++#define SCU1_RESET_PECI 3 ++#define SCU1_RESET_PWM 4 ++#define SCU1_RESET_MAC0 5 ++#define SCU1_RESET_MAC1 6 ++#define SCU1_RESET_MAC2 7 ++#define SCU1_RESET_ADC 8 ++#define SCU1_RESET_SD 9 ++#define SCU1_RESET_ESPI0 10 ++#define SCU1_RESET_ESPI1 11 ++#define SCU1_RESET_JTAG1 12 ++#define SCU1_RESET_SPI0 13 ++#define SCU1_RESET_SPI1 14 ++#define SCU1_RESET_SPI2 15 ++#define SCU1_RESET_I3C0 16 ++#define SCU1_RESET_I3C1 17 ++#define SCU1_RESET_I3C2 18 ++#define SCU1_RESET_I3C3 19 ++#define SCU1_RESET_I3C4 20 ++#define SCU1_RESET_I3C5 21 ++#define SCU1_RESET_I3C6 22 ++#define SCU1_RESET_I3C7 23 ++#define SCU1_RESET_I3C8 24 ++#define SCU1_RESET_I3C9 25 ++#define SCU1_RESET_I3C10 26 ++#define SCU1_RESET_I3C11 27 ++#define SCU1_RESET_I3C12 28 ++#define SCU1_RESET_I3C13 29 ++#define SCU1_RESET_I3C14 30 ++#define SCU1_RESET_I3C15 31 ++#define SCU1_RESET_MCU0 32 ++#define SCU1_RESET_MCU1 33 ++#define SCU1_RESET_H2A_SPI1 34 ++#define SCU1_RESET_H2A_SPI2 35 ++#define SCU1_RESET_UART0 36 ++#define SCU1_RESET_UART1 37 ++#define SCU1_RESET_UART2 38 ++#define SCU1_RESET_UART3 39 ++#define SCU1_RESET_I2C_FILTER 40 ++#define SCU1_RESET_CALIPTRA 41 ++#define SCU1_RESET_XDMA 42 ++#define SCU1_RESET_FSI 43 ++#define SCU1_RESET_CAN 44 ++#define SCU1_RESET_MCTP 45 ++#define SCU1_RESET_I2C 46 ++#define SCU1_RESET_UART6 47 ++#define SCU1_RESET_UART7 48 ++#define SCU1_RESET_UART8 49 ++#define SCU1_RESET_UART9 50 ++#define SCU1_RESET_LTPI0 51 ++#define SCU1_RESET_VGAL 52 ++#define SCU1_RESET_LTPI1 53 ++#define SCU1_RESET_ACE 54 ++#define SCU1_RESET_E2M 55 ++#define SCU1_RESET_UHCI 56 ++#define SCU1_RESET_PORTC_USB2UART 57 ++#define SCU1_RESET_PORTC_VHUB_EHCI 58 ++#define SCU1_RESET_PORTD_USB2UART 59 ++#define SCU1_RESET_PORTD_VHUB_EHCI 60 ++#define SCU1_RESET_H2X 61 ++#define SCU1_RESET_I3CDMA 62 ++#define SCU1_RESET_PCIE2RST 63 ++#define SCU1_RESET_XPCS 64 /* for AST2755 */ ++ ++#endif /* _MACH_ASPEED_AST2700_RESET_H_ */ +diff --git a/include/dt-bindings/watchdog/aspeed-wdt.h b/include/dt-bindings/watchdog/aspeed-wdt.h +--- a/include/dt-bindings/watchdog/aspeed-wdt.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/dt-bindings/watchdog/aspeed-wdt.h 2026-09-09 15:51:44.908235200 +0000 +@@ -89,4 +89,142 @@ + + #define AST2600_WDT_RESET2_DEFAULT 0x03fffff1 + ++#define AST2700_WDT_RESET1_CPU (1 << 0) ++#define AST2700_WDT_RESET1_DRAM (1 << 1) ++#define AST2700_WDT_RESET1_SLI0 (1 << 2) ++#define AST2700_WDT_RESET1_EHCI (1 << 3) ++#define AST2700_WDT_RESET1_HACE (1 << 4) ++#define AST2700_WDT_RESET1_SOC_MISC0 (1 << 5) ++#define AST2700_WDT_RESET1_VIDEO (1 << 6) ++#define AST2700_WDT_RESET1_2D_GRAPHIC (1 << 7) ++#define AST2700_WDT_RESET1_RAVS0 (1 << 8) ++#define AST2700_WDT_RESET1_RAVS1 (1 << 9) ++#define AST2700_WDT_RESET1_GPIO0 (1 << 10) ++#define AST2700_WDT_RESET1_SSP (1 << 11) ++#define AST2700_WDT_RESET1_TSP (1 << 12) ++#define AST2700_WDT_RESET1_CRT (1 << 13) ++#define AST2700_WDT_RESET1_USB20_HOST (1 << 14) ++#define AST2700_WDT_RESET1_USB11_HOST (1 << 15) ++#define AST2700_WDT_RESET1_UFS (1 << 16) ++#define AST2700_WDT_RESET1_EMMC (1 << 17) ++#define AST2700_WDT_RESET1_AHB_TO_PCIE1 (1 << 18) ++#define AST2700_WDT_RESET1_XDMA0 (1 << 22) ++#define AST2700_WDT_RESET1_MCTP1 (1 << 23) ++#define AST2700_WDT_RESET1_MCTP0 (1 << 24) ++#define AST2700_WDT_RESET1_JTAG0 (1 << 25) ++#define AST2700_WDT_RESET1_ECC (1 << 26) ++#define AST2700_WDT_RESET1_XDMA1 (1 << 27) ++#define AST2700_WDT_RESET1_DP (1 << 28) ++#define AST2700_WDT_RESET1_DP_MCU (1 << 29) ++#define AST2700_WDT_RESET1_AHB_TO_PCIE0 (1 << 31) ++ ++#define AST2700_WDT_RESET1_DEFAULT 0x8207ff71 ++ ++#define AST2700_WDT_RESET2_USB3_A_HOST (1 << 0) ++#define AST2700_WDT_RESET2_USB3_A_VHUB3 (1 << 1) ++#define AST2700_WDT_RESET2_USB3_A_VHUB2 (1 << 2) ++#define AST2700_WDT_RESET2_USB3_B_HOST (1 << 3) ++#define AST2700_WDT_RESET2_USB3_B_VHUB3 (1 << 4) ++#define AST2700_WDT_RESET2_USB3_B_VHUB2 (1 << 5) ++#define AST2700_WDT_RESET2_SM3 (1 << 6) ++#define AST2700_WDT_RESET2_SM4 (1 << 7) ++#define AST2700_WDT_RESET2_SHA3 (1 << 8) ++#define AST2700_WDT_RESET2_RSA (1 << 9) ++ ++#define AST2700_WDT_RESET2_DEFAULT 0x000003f6 ++ ++#define AST2700_WDT_RESET3_LPC0 (1 << 0) ++#define AST2700_WDT_RESET3_LPC1 (1 << 1) ++#define AST2700_WDT_RESET3_MDIO (1 << 2) ++#define AST2700_WDT_RESET3_PECI (1 << 3) ++#define AST2700_WDT_RESET3_PWM (1 << 4) ++#define AST2700_WDT_RESET3_MAC0 (1 << 5) ++#define AST2700_WDT_RESET3_MAC1 (1 << 6) ++#define AST2700_WDT_RESET3_MAC2 (1 << 7) ++#define AST2700_WDT_RESET3_ADC (1 << 8) ++#define AST2700_WDT_RESET3_SDC (1 << 9) ++#define AST2700_WDT_RESET3_ESPI0 (1 << 10) ++#define AST2700_WDT_RESET3_ESPI1 (1 << 11) ++#define AST2700_WDT_RESET3_JTAG1 (1 << 12) ++#define AST2700_WDT_RESET3_SPI0 (1 << 13) ++#define AST2700_WDT_RESET3_SPI1 (1 << 14) ++#define AST2700_WDT_RESET3_SPI2 (1 << 15) ++#define AST2700_WDT_RESET3_I3C0 (1 << 16) ++#define AST2700_WDT_RESET3_I3C1 (1 << 17) ++#define AST2700_WDT_RESET3_I3C2 (1 << 18) ++#define AST2700_WDT_RESET3_I3C3 (1 << 19) ++#define AST2700_WDT_RESET3_I3C4 (1 << 20) ++#define AST2700_WDT_RESET3_I3C5 (1 << 21) ++#define AST2700_WDT_RESET3_I3C6 (1 << 22) ++#define AST2700_WDT_RESET3_I3C7 (1 << 23) ++#define AST2700_WDT_RESET3_I3C8 (1 << 24) ++#define AST2700_WDT_RESET3_I3C9 (1 << 25) ++#define AST2700_WDT_RESET3_I3C10 (1 << 26) ++#define AST2700_WDT_RESET3_I3C11 (1 << 27) ++#define AST2700_WDT_RESET3_I3C12 (1 << 28) ++#define AST2700_WDT_RESET3_I3C13 (1 << 29) ++#define AST2700_WDT_RESET3_I3C14 (1 << 30) ++#define AST2700_WDT_RESET3_I3C15 (1 << 31) ++ ++#define AST2700_WDT_RESET3_DEFAULT 0x000093ec ++ ++#define AST2700_WDT_RESET4_FMC (1 << 0) ++#define AST2700_WDT_RESET4_SOC_MISC1 (1 << 1) ++#define AST2700_WDT_RESET4_AHB (1 << 2) ++#define AST2700_WDT_RESET4_SLI1 (1 << 3) ++#define AST2700_WDT_RESET4_UART0 (1 << 4) ++#define AST2700_WDT_RESET4_UART1 (1 << 5) ++#define AST2700_WDT_RESET4_UART2 (1 << 6) ++#define AST2700_WDT_RESET4_UART3 (1 << 7) ++#define AST2700_WDT_RESET4_I2C_MONITOR (1 << 8) ++#define AST2700_WDT_RESET4_HOST_TO_SPI1 (1 << 9) ++#define AST2700_WDT_RESET4_HOST_TO_SPI2 (1 << 10) ++#define AST2700_WDT_RESET4_GPIO1 (1 << 11) ++#define AST2700_WDT_RESET4_FSI (1 << 12) ++#define AST2700_WDT_RESET4_CANBUS (1 << 13) ++#define AST2700_WDT_RESET4_MCTP (1 << 14) ++#define AST2700_WDT_RESET4_XDMA (1 << 15) ++#define AST2700_WDT_RESET4_UART5 (1 << 16) ++#define AST2700_WDT_RESET4_UART6 (1 << 17) ++#define AST2700_WDT_RESET4_UART7 (1 << 18) ++#define AST2700_WDT_RESET4_UART8 (1 << 19) ++#define AST2700_WDT_RESET4_BOOT_MCU (1 << 20) ++#define AST2700_WDT_RESET4_IO_MCU (1 << 21) ++#define AST2700_WDT_RESET4_LTPI0 (1 << 22) ++#define AST2700_WDT_RESET4_VGA_LINK (1 << 23) ++#define AST2700_WDT_RESET4_LTPI1 (1 << 24) ++#define AST2700_WDT_RESET4_LTPI_PHY (1 << 25) ++#define AST2700_WDT_RESET4_ACE (1 << 26) ++#define AST2700_WDT_RESET4_LTPI_GPIO0 (1 << 28) ++#define AST2700_WDT_RESET4_LTPI_GPIO1 (1 << 29) ++#define AST2700_WDT_RESET4_AHB_TO_PCIE1 (1 << 30) ++#define AST2700_WDT_RESET4_I3C_DMA (1 << 31) ++ ++#define AST2700_WDT_RESET4_DEFAULT 0x40303803 ++ ++#define AST2700_WDT_RESET5_I2C_GLOBAL (1 << 0) ++#define AST2700_WDT_RESET5_I2C0 (1 << 1) ++#define AST2700_WDT_RESET5_I2C1 (1 << 2) ++#define AST2700_WDT_RESET5_I2C2 (1 << 3) ++#define AST2700_WDT_RESET5_I2C3 (1 << 4) ++#define AST2700_WDT_RESET5_I2C4 (1 << 5) ++#define AST2700_WDT_RESET5_I2C5 (1 << 6) ++#define AST2700_WDT_RESET5_I2C6 (1 << 7) ++#define AST2700_WDT_RESET5_I2C7 (1 << 8) ++#define AST2700_WDT_RESET5_I2C8 (1 << 9) ++#define AST2700_WDT_RESET5_I2C9 (1 << 10) ++#define AST2700_WDT_RESET5_I2C10 (1 << 11) ++#define AST2700_WDT_RESET5_I2C11 (1 << 12) ++#define AST2700_WDT_RESET5_I2C12 (1 << 13) ++#define AST2700_WDT_RESET5_I2C13 (1 << 14) ++#define AST2700_WDT_RESET5_I2C14 (1 << 15) ++#define AST2700_WDT_RESET5_I2C15 (1 << 16) ++#define AST2700_WDT_RESET5_UHCI (1 << 17) ++#define AST2700_WDT_RESET5_USB2_C_UART (1 << 18) ++#define AST2700_WDT_RESET5_USB2_C (1 << 19) ++#define AST2700_WDT_RESET5_USB2_D_UART (1 << 20) ++#define AST2700_WDT_RESET5_USB2_D (1 << 21) ++ ++#define AST2700_WDT_RESET5_DEFAULT 0x00320000 ++ + #endif +diff --git a/include/linux/aspeed-mctp.h b/include/linux/aspeed-mctp.h +--- a/include/linux/aspeed-mctp.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/linux/aspeed-mctp.h 2026-09-09 15:51:44.858235956 +0000 +@@ -0,0 +1,157 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* Copyright (c) 2025 ASPEED Tech */ ++ ++#ifndef __LINUX_ASPEED_MCTP_H ++#define __LINUX_ASPEED_MCTP_H ++ ++#include ++ ++struct mctp_client; ++struct aspeed_mctp; ++ ++struct pcie_transport_hdr { ++ u8 fmt_type; ++ u8 mbz; ++ u8 mbz_attr_len_hi; ++ u8 len_lo; ++ u16 requester; ++ u8 tag; ++ u8 code; ++ u16 target; ++ u16 vendor; ++} __packed; ++ ++struct mctp_protocol_hdr { ++ u8 ver; ++ u8 dest; ++ u8 src; ++ u8 flags_seq_tag; ++} __packed; ++ ++#define PCIE_VDM_HDR_SIZE 16 ++#define MCTP_BTU_SIZE 64 ++/* The MTU of the ASPEED MCTP can be 64/128/256 */ ++#define ASPEED_MCTP_MTU MCTP_BTU_SIZE ++#define PCIE_VDM_DATA_SIZE_DW (ASPEED_MCTP_MTU / 4) ++#define PCIE_VDM_HDR_SIZE_DW (PCIE_VDM_HDR_SIZE / 4) ++ ++#define PCIE_MCTP_MIN_PACKET_SIZE (PCIE_VDM_HDR_SIZE + 4) ++ ++struct mctp_pcie_packet_data_2500 { ++ u32 data[32]; ++}; ++ ++struct mctp_pcie_packet_data { ++ u32 hdr[PCIE_VDM_HDR_SIZE_DW]; ++ u32 payload[PCIE_VDM_DATA_SIZE_DW]; ++}; ++ ++struct mctp_pcie_packet { ++ struct mctp_pcie_packet_data data; ++ u32 size; ++}; ++ ++/** ++ * aspeed_mctp_add_type_handler() - register for the given MCTP message type ++ * @client: pointer to the existing mctp_client context ++ * @mctp_type: message type code according to DMTF DSP0239 spec. ++ * @pci_vendor_id: vendor ID (non-zero if msg_type is Vendor Defined PCI, ++ * otherwise it should be set to 0) ++ * @vdm_type: vendor defined message type (it should be set to 0 for non-Vendor ++ * Defined PCI message type) ++ * @vdm_mask: vendor defined message mask (it should be set to 0 for non-Vendor ++ * Defined PCI message type) ++ * ++ * Return: ++ * * 0 - success, ++ * * -EINVAL - arguments passed are incorrect, ++ * * -ENOMEM - cannot alloc a new handler, ++ * * -EBUSY - given message has already registered handler. ++ */ ++ ++int aspeed_mctp_add_type_handler(struct mctp_client *client, u8 mctp_type, ++ u16 pci_vendor_id, u16 vdm_type, u16 vdm_mask); ++ ++/** ++ * aspeed_mctp_create_client() - create mctp_client context ++ * @priv pointer to aspeed-mctp context ++ * ++ * Returns struct mctp_client or NULL. ++ */ ++struct mctp_client *aspeed_mctp_create_client(struct aspeed_mctp *priv); ++ ++/** ++ * aspeed_mctp_delete_client()- delete mctp_client context ++ * @client: pointer to existing mctp_client context ++ */ ++void aspeed_mctp_delete_client(struct mctp_client *client); ++ ++/** ++ * aspeed_mctp_send_packet() - send mctp_packet ++ * @client: pointer to existing mctp_client context ++ * @tx_packet: the allocated packet that needs to be send via aspeed-mctp ++ * ++ * After the function returns success, the packet is no longer owned by the ++ * caller, and as such, the caller should not attempt to free it. ++ * ++ * Return: ++ * * 0 - success, ++ * * -ENOSPC - failed to send packet due to lack of available space. ++ */ ++int aspeed_mctp_send_packet(struct mctp_client *client, ++ struct mctp_pcie_packet *tx_packet); ++ ++/** ++ * aspeed_mctp_receive_packet() - receive mctp_packet ++ * @client: pointer to existing mctp_client context ++ * @timeout: timeout, in jiffies ++ * ++ * The function will sleep for up to @timeout if no packet is ready to read. ++ * ++ * After the function returns valid packet, the caller takes its ownership and ++ * is responsible for freeing it. ++ * ++ * Returns struct mctp_pcie_packet from or ERR_PTR in case of error or the ++ * @timeout elapsed. ++ */ ++struct mctp_pcie_packet *aspeed_mctp_receive_packet(struct mctp_client *client, ++ unsigned long timeout); ++ ++/** ++ * aspeed_mctp_flush_rx_queue() - remove all mctp_packets from rx queue ++ * @client: pointer to existing mctp_client context ++ */ ++void aspeed_mctp_flush_rx_queue(struct mctp_client *client); ++ ++/** ++ * aspeed_mctp_get_eid_bdf() - return PCIe address for requested endpoint ID ++ * @client: pointer to existing mctp_client context ++ * @eid: requested eid ++ * @bdf: pointer to store BDF value ++ * ++ * Return: ++ * * 0 - success, ++ * * -ENOENT - there is no record for requested endpoint id. ++ */ ++int aspeed_mctp_get_eid_bdf(struct mctp_client *client, u8 eid, u16 *bdf); ++ ++/** ++ * aspeed_mctp_get_eid() - return EID for requested BDF and domainId. ++ * @client: pointer to existing mctp_client context ++ * @bdf: requested BDF value ++ * @domain_id: requested domainId ++ * @eid: pointer to store EID value ++ * ++ * Return: ++ * * 0 - success, ++ * * -ENOENT - there is no record for requested bdf/domainId. ++ */ ++int aspeed_mctp_get_eid(struct mctp_client *client, u16 bdf, ++ u8 domain_id, u8 *eid); ++ ++void *aspeed_mctp_packet_alloc(gfp_t flags); ++void aspeed_mctp_packet_free(void *packet); ++ ++int aspeed_mctp_register_default_handler(struct mctp_client *client); ++ ++#endif /* __LINUX_ASPEED_MCTP_H */ +diff --git a/include/linux/soc/aspeed/aspeed-otp.h b/include/linux/soc/aspeed/aspeed-otp.h +--- a/include/linux/soc/aspeed/aspeed-otp.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/linux/soc/aspeed/aspeed-otp.h 2026-09-09 15:51:44.848236107 +0000 +@@ -0,0 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ ++/* ++ * Copyright (C) 2021 ASPEED Technology Inc. ++ */ ++ ++#ifndef _LINUX_ASPEED_OTP_H ++#define _LINUX_ASPEED_OTP_H ++ ++void otp_read_data_buf(u32 offset, u32 *buf, u32 len); ++void otp_read_conf_buf(u32 offset, u32 *buf, u32 len); ++ ++#endif /* _LINUX_ASPEED_OTP_H */ +diff --git a/include/linux/soc/aspeed/aspeed-udma.h b/include/linux/soc/aspeed/aspeed-udma.h +--- a/include/linux/soc/aspeed/aspeed-udma.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/linux/soc/aspeed/aspeed-udma.h 2026-09-09 15:51:44.843236183 +0000 +@@ -0,0 +1,26 @@ ++#ifndef __ASPEED_UDMA_H__ ++#define __ASPEED_UDMA_H__ ++ ++typedef void (*aspeed_udma_cb_t)(int rb_rwptr, void *id); ++ ++enum aspeed_udma_ops { ++ ASPEED_UDMA_OP_ENABLE, ++ ASPEED_UDMA_OP_DISABLE, ++ ASPEED_UDMA_OP_RESET, ++}; ++ ++void aspeed_udma_set_tx_wptr(u32 ch_no, u32 wptr); ++void aspeed_udma_set_rx_rptr(u32 ch_no, u32 rptr); ++ ++void aspeed_udma_tx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op); ++void aspeed_udma_rx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op); ++ ++int aspeed_udma_request_tx_chan(u32 ch_no, dma_addr_t addr, u32 rb_sz, ++ aspeed_udma_cb_t cb, void *id, bool en_tmout); ++int aspeed_udma_request_rx_chan(u32 ch_no, dma_addr_t addr, u32 rb_sz, ++ aspeed_udma_cb_t cb, void *id, bool en_tmout); ++ ++int aspeed_udma_free_tx_chan(u32 ch_no); ++int aspeed_udma_free_rx_chan(u32 ch_no); ++ ++#endif +diff --git a/include/soc/aspeed/reset-aspeed.h b/include/soc/aspeed/reset-aspeed.h +--- a/include/soc/aspeed/reset-aspeed.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/soc/aspeed/reset-aspeed.h 2026-09-09 15:51:44.802236802 +0000 +@@ -0,0 +1,21 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2024 ASPEED Technology Inc. ++ * Author: Ryan Chen ++ */ ++ ++#ifndef __RESET_ASPEED_H__ ++#define __RESET_ASPEED_H__ ++ ++#if IS_ENABLED(CONFIG_RESET_ASPEED) ++int aspeed_reset_controller_register(struct device *clk_dev, void __iomem *base, ++ const char *adev_name); ++#else ++int aspeed_reset_controller_register(struct device *clk_dev, void __iomem *base, ++ const char *adev_name) ++{ ++ return -ENODEV; ++} ++#endif /* if IS_ENABLED(CONFIG_RESET_ASPEED) */ ++ ++#endif /* __RESET_ASPEED_H__ */ +diff --git a/include/uapi/linux/aspeed-mctp.h b/include/uapi/linux/aspeed-mctp.h +--- a/include/uapi/linux/aspeed-mctp.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/uapi/linux/aspeed-mctp.h 2026-09-09 15:51:44.838236258 +0000 +@@ -0,0 +1,136 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* Copyright (c) 2020 Intel Corporation */ ++ ++#ifndef _UAPI_LINUX_ASPEED_MCTP_H ++#define _UAPI_LINUX_ASPEED_MCTP_H ++ ++#include ++#include ++ ++/* ++ * aspeed-mctp is a simple device driver exposing a read/write interface: ++ * +----------------------+ ++ * | PCIe VDM Header | 16 bytes (Big Endian) ++ * +----------------------+ ++ * | MCTP Message Payload | 64/128/256/512 bytes (Big Endian) ++ * +----------------------+ ++ * ++ * MCTP packet description can be found in DMTF DSP0238, ++ * MCTP PCIe VDM Transport Specification. ++ */ ++ ++#define ASPEED_MCTP_PCIE_VDM_HDR_SIZE 16 ++ ++/* ++ * uevents generated by aspeed-mctp driver ++ */ ++#define ASPEED_MCTP_READY "PCIE_READY" ++ ++/* ++ * maximum possible number of struct eid_info elements stored in list ++ */ ++#define ASPEED_MCTP_EID_INFO_MAX 256 ++ ++/* ++ * MCTP operations ++ * @ASPEED_MCTP_IOCTL_FILTER_EID: enable/disable filter incoming packets based ++ * on Endpoint ID (BROKEN) ++ * @ASPEED_MCTP_IOCTL_GET_BDF: read PCI bus/device/function of MCTP Controller ++ * @ASPEED_MCTP_IOCTL_GET_MEDIUM_ID: read MCTP physical medium identifier ++ * related to PCIe revision ++ * @ASPEED_MCTP_IOCTL_GET_MTU: read max transmission unit (in bytes) ++ * @ASPEED_MCTP_IOCTL_REGISTER_DEFAULT_HANDLER Register client as default ++ * handler that receives all MCTP messages that were not dispatched to other ++ * clients ++ * @ASPEED_MCTP_IOCTL_REGISTER_TYPE_HANDLER Register client to receive all ++ * messages of specified MCTP type or PCI vendor defined type ++ * @ASPEED_MCTP_IOCTL_UNREGISTER_TYPE_HANDLER Unregister client as handler ++ * for specified MCTP type or PCI vendor defined message type ++ * @ASPEED_MCTP_GET_EID_INFO - deprecated, use ASPEED_MCTP_GET_EID_EXT instead ++ * @ASPEED_MCTP_SET_EID_INFO - deprecated, use ASPEED_MCTP_SET_EID_EXT instead ++ * @ASPEED_MCTP_GET_EID_EXT_INFO: read list of existing CPU EID and Domain ID ++ * mappings and return count which is lesser of the two: requested count and existing count ++ * @ASPEED_MCTP_SET_EID_EXT_INFO: write or overwrite already existing list of ++ * CPU EID and Domain ID mappings ++ * @ASPEED_MCTP_SET_OWN_EID: write/overwrite own EID information ++ */ ++ ++struct aspeed_mctp_filter_eid { ++ __u8 eid; ++ bool enable; ++}; ++ ++struct aspeed_mctp_get_bdf { ++ __u16 bdf; ++}; ++ ++struct aspeed_mctp_get_medium_id { ++ __u8 medium_id; ++}; ++ ++struct aspeed_mctp_get_mtu { ++ __u16 mtu; ++}; ++ ++struct aspeed_mctp_type_handler_ioctl { ++ __u8 mctp_type; /* MCTP message type as per DSP239*/ ++ /* Below params must be 0 if mctp_type is not Vendor Defined PCI */ ++ __u16 pci_vendor_id; /* PCI Vendor ID */ ++ __u16 vendor_type; /* Vendor specific type */ ++ __u16 vendor_type_mask; /* Mask applied to vendor type */ ++}; ++ ++struct aspeed_mctp_eid_info { ++ __u8 eid; ++ __u16 bdf; ++}; ++ ++struct aspeed_mctp_eid_ext_info { ++ __u8 eid; ++ __u16 bdf; ++ __u8 domain_id; ++}; ++ ++struct aspeed_mctp_get_eid_info { ++ __u64 ptr; ++ __u16 count; ++ __u8 start_eid; ++}; ++ ++struct aspeed_mctp_set_eid_info { ++ __u64 ptr; ++ __u16 count; ++}; ++ ++struct aspeed_mctp_set_own_eid { ++ __u8 eid; ++}; ++ ++#define ASPEED_MCTP_IOCTL_BASE 0x4d ++ ++#define ASPEED_MCTP_IOCTL_FILTER_EID \ ++ _IOW(ASPEED_MCTP_IOCTL_BASE, 0, struct aspeed_mctp_filter_eid) ++#define ASPEED_MCTP_IOCTL_GET_BDF \ ++ _IOR(ASPEED_MCTP_IOCTL_BASE, 1, struct aspeed_mctp_get_bdf) ++#define ASPEED_MCTP_IOCTL_GET_MEDIUM_ID \ ++ _IOR(ASPEED_MCTP_IOCTL_BASE, 2, struct aspeed_mctp_get_medium_id) ++#define ASPEED_MCTP_IOCTL_GET_MTU \ ++ _IOR(ASPEED_MCTP_IOCTL_BASE, 3, struct aspeed_mctp_get_mtu) ++#define ASPEED_MCTP_IOCTL_REGISTER_DEFAULT_HANDLER \ ++ _IO(ASPEED_MCTP_IOCTL_BASE, 4) ++#define ASPEED_MCTP_IOCTL_REGISTER_TYPE_HANDLER \ ++ _IOW(ASPEED_MCTP_IOCTL_BASE, 6, struct aspeed_mctp_type_handler_ioctl) ++#define ASPEED_MCTP_IOCTL_UNREGISTER_TYPE_HANDLER \ ++ _IOW(ASPEED_MCTP_IOCTL_BASE, 7, struct aspeed_mctp_type_handler_ioctl) ++#define ASPEED_MCTP_IOCTL_GET_EID_INFO \ ++ _IOWR(ASPEED_MCTP_IOCTL_BASE, 8, struct aspeed_mctp_get_eid_info) ++#define ASPEED_MCTP_IOCTL_SET_EID_INFO \ ++ _IOW(ASPEED_MCTP_IOCTL_BASE, 9, struct aspeed_mctp_set_eid_info) ++#define ASPEED_MCTP_IOCTL_GET_EID_EXT_INFO \ ++ _IOW(ASPEED_MCTP_IOCTL_BASE, 10, struct aspeed_mctp_get_eid_info) ++#define ASPEED_MCTP_IOCTL_SET_EID_EXT_INFO \ ++ _IOW(ASPEED_MCTP_IOCTL_BASE, 11, struct aspeed_mctp_set_eid_info) ++#define ASPEED_MCTP_IOCTL_SET_OWN_EID \ ++ _IOW(ASPEED_MCTP_IOCTL_BASE, 12, struct aspeed_mctp_set_own_eid) ++ ++#endif /* _UAPI_LINUX_ASPEED_MCTP_H */ +diff --git a/include/uapi/linux/aspeed-video.h b/include/uapi/linux/aspeed-video.h +--- a/include/uapi/linux/aspeed-video.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/uapi/linux/aspeed-video.h 2026-09-09 15:51:44.812236651 +0000 +@@ -8,6 +8,15 @@ + + #include + ++/* aspeed video's input types */ ++enum aspeed_video_input { ++ VIDEO_INPUT_VGA = 0, ++ VIDEO_INPUT_GFX, ++ VIDEO_INPUT_MEM, ++ VIDEO_INPUT_DVI, ++ VIDEO_INPUT_MAX ++}; ++ + #define V4L2_CID_ASPEED_HQ_MODE (V4L2_CID_USER_ASPEED_BASE + 1) + #define V4L2_CID_ASPEED_HQ_JPEG_QUALITY (V4L2_CID_USER_ASPEED_BASE + 2) + +diff --git a/include/uapi/linux/aspeed-xdma.h b/include/uapi/linux/aspeed-xdma.h +--- a/include/uapi/linux/aspeed-xdma.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/uapi/linux/aspeed-xdma.h 2026-09-09 15:51:44.822236500 +0000 +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ ++/* Copyright IBM Corp 2019 */ ++ ++#ifndef _UAPI_LINUX_ASPEED_XDMA_H_ ++#define _UAPI_LINUX_ASPEED_XDMA_H_ ++ ++#include ++#include ++ ++#define __ASPEED_XDMA_IOCTL_MAGIC 0xb7 ++#define ASPEED_XDMA_IOCTL_RESET _IO(__ASPEED_XDMA_IOCTL_MAGIC, 0) ++ ++/* ++ * aspeed_xdma_direction ++ * ++ * ASPEED_XDMA_DIRECTION_DOWNSTREAM: transfers data from the host to the BMC ++ * ++ * ASPEED_XDMA_DIRECTION_UPSTREAM: transfers data from the BMC to the host ++ */ ++enum aspeed_xdma_direction { ++ ASPEED_XDMA_DIRECTION_DOWNSTREAM = 0, ++ ASPEED_XDMA_DIRECTION_UPSTREAM, ++}; ++ ++/* ++ * aspeed_xdma_op ++ * ++ * host_addr: the DMA address on the host side, typically configured by PCI ++ * subsystem ++ * ++ * len: the size of the transfer in bytes ++ * ++ * direction: an enumerator indicating the direction of the DMA operation; see ++ * enum aspeed_xdma_direction ++ */ ++struct aspeed_xdma_op { ++ __u64 host_addr; ++ __u32 len; ++ __u32 direction; ++}; ++ ++#endif /* _UAPI_LINUX_ASPEED_XDMA_H_ */ +diff --git a/include/uapi/linux/otp_ast2600.h b/include/uapi/linux/otp_ast2600.h +--- a/include/uapi/linux/otp_ast2600.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/uapi/linux/otp_ast2600.h 2026-09-09 15:51:44.828236409 +0000 +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ ++/* ++ * Copyright (C) 2021 ASPEED Technology Inc. ++ */ ++ ++#ifndef _UAPI_LINUX_OTP_AST2600_H ++#define _UAPI_LINUX_OTP_AST2600_H ++ ++#include ++#include ++ ++struct otp_read { ++ unsigned int offset; ++ unsigned int len; ++ unsigned int *data; ++}; ++ ++struct otp_prog { ++ unsigned int dw_offset; ++ unsigned int bit_offset; ++ unsigned int value; ++}; ++ ++#define OTP_A0 0 ++#define OTP_A1 1 ++#define OTP_A2 2 ++#define OTP_A3 3 ++ ++#define OTPIOC_BASE 'O' ++ ++#define ASPEED_OTP_READ_DATA _IOR(OTPIOC_BASE, 0, struct otp_read) ++#define ASPEED_OTP_READ_CONF _IOR(OTPIOC_BASE, 1, struct otp_read) ++#define ASPEED_OTP_PROG_DATA _IOW(OTPIOC_BASE, 2, struct otp_prog) ++#define ASPEED_OTP_PROG_CONF _IOW(OTPIOC_BASE, 3, struct otp_prog) ++#define ASPEED_OTP_VER _IOR(OTPIOC_BASE, 4, unsigned int) ++#define ASPEED_OTP_SW_RID _IOR(OTPIOC_BASE, 5, u32 *) ++#define ASPEED_SEC_KEY_NUM _IOR(OTPIOC_BASE, 6, u32 *) ++ ++#endif /* _UAPI_LINUX_OTP_AST2600_H */ +diff --git a/include/uapi/linux/otp_ast2700.h b/include/uapi/linux/otp_ast2700.h +--- a/include/uapi/linux/otp_ast2700.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/uapi/linux/otp_ast2700.h 2026-09-09 15:51:44.807236727 +0000 +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ ++/* ++ * Copyright (C) 2021 ASPEED Technology Inc. ++ */ ++ ++#ifndef _UAPI_LINUX_OTP_AST2700_H ++#define _UAPI_LINUX_OTP_AST2700_H ++ ++#include ++#include ++ ++struct otp_read { ++ unsigned int offset; ++ unsigned int len; ++ uint8_t *data; ++}; ++ ++struct otp_prog { ++ unsigned int w_offset; ++ unsigned int len; ++ uint8_t *data; ++}; ++ ++struct otp_revid { ++ uint32_t revid0; ++ uint32_t revid1; ++}; ++ ++#define OTP_A0 0 ++#define OTP_A1 1 ++#define OTP_A2 2 ++#define OTP_A3 3 ++ ++#define OTPIOC_BASE 'O' ++ ++#define ASPEED_OTP_READ_DATA _IOR(OTPIOC_BASE, 0, struct otp_read) ++#define ASPEED_OTP_READ_CONF _IOR(OTPIOC_BASE, 1, struct otp_read) ++#define ASPEED_OTP_PROG_DATA _IOW(OTPIOC_BASE, 2, struct otp_prog) ++#define ASPEED_OTP_PROG_CONF _IOW(OTPIOC_BASE, 3, struct otp_prog) ++#define ASPEED_OTP_VER _IOR(OTPIOC_BASE, 4, unsigned int) ++#define ASPEED_OTP_SW_RID _IOR(OTPIOC_BASE, 5, u32 *) ++#define ASPEED_SEC_KEY_NUM _IOR(OTPIOC_BASE, 6, u32 *) ++#define ASPEED_OTP_GET_ECC _IOR(OTPIOC_BASE, 7, uint32_t) ++#define ASPEED_OTP_SET_ECC _IO(OTPIOC_BASE, 8) ++#define ASPEED_OTP_GET_REVID _IOR(OTPIOC_BASE, 9, struct otp_revid) ++ ++#endif /* _UAPI_LINUX_OTP_AST2700_H */ diff --git a/patches-sonic/0001-efi-memattr-Ignore-table-if-the-size-is-clearly-bogus.patch b/patches-sonic/0001-efi-memattr-Ignore-table-if-the-size-is-clearly-bogus.patch new file mode 100644 index 000000000..1fac195ec --- /dev/null +++ b/patches-sonic/0001-efi-memattr-Ignore-table-if-the-size-is-clearly-bogus.patch @@ -0,0 +1,66 @@ +From 8fbe4c49c0ccac9a6a3cff35a45fa55d4ae35d6e Mon Sep 17 00:00:00 2001 +From: Ard Biesheuvel +Date: Thu, 31 Oct 2024 18:58:23 +0100 +Subject: [PATCH] efi/memattr: Ignore table if the size is clearly bogus + +There are reports [0] of cases where a corrupt EFI Memory Attributes +Table leads to out of memory issues at boot because the descriptor size +and entry count in the table header are still used to reserve the entire +table in memory, even though the resulting region is gigabytes in size. + +Given that the EFI Memory Attributes Table is supposed to carry up to 3 +entries for each EfiRuntimeServicesCode region in the EFI memory map, +and given that there is no reason for the descriptor size used in the +table to exceed the one used in the EFI memory map, 3x the size of the +entire EFI memory map is a reasonable upper bound for the size of this +table. This means that sizes exceeding that are highly likely to be +based on corrupted data, and the table should just be ignored instead. + +[0] https://bugzilla.suse.com/show_bug.cgi?id=1231465 + +Cc: Gregory Price +Cc: Usama Arif +Acked-by: Jiri Slaby +Acked-by: Breno Leitao +Link: https://lore.kernel.org/all/20240912155159.1951792-2-ardb+git@google.com/ +Signed-off-by: Ard Biesheuvel +--- + drivers/firmware/efi/memattr.c | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/drivers/firmware/efi/memattr.c b/drivers/firmware/efi/memattr.c +index 164203429fa7a3..c38b1a335590d4 100644 +--- a/drivers/firmware/efi/memattr.c ++++ b/drivers/firmware/efi/memattr.c +@@ -22,6 +22,7 @@ unsigned long __ro_after_init efi_mem_attr_table = EFI_INVALID_TABLE_ADDR; + int __init efi_memattr_init(void) + { + efi_memory_attributes_table_t *tbl; ++ unsigned long size; + + if (efi_mem_attr_table == EFI_INVALID_TABLE_ADDR) + return 0; +@@ -39,7 +40,22 @@ int __init efi_memattr_init(void) + goto unmap; + } + +- tbl_size = sizeof(*tbl) + tbl->num_entries * tbl->desc_size; ++ ++ /* ++ * Sanity check: the Memory Attributes Table contains up to 3 entries ++ * for each entry of type EfiRuntimeServicesCode in the EFI memory map. ++ * So if the size of the table exceeds 3x the size of the entire EFI ++ * memory map, there is clearly something wrong, and the table should ++ * just be ignored altogether. ++ */ ++ size = tbl->num_entries * tbl->desc_size; ++ if (size > 3 * efi.memmap.nr_map * efi.memmap.desc_size) { ++ pr_warn(FW_BUG "Corrupted EFI Memory Attributes Table detected! (version == %u, desc_size == %u, num_entries == %u)\n", ++ tbl->version, tbl->desc_size, tbl->num_entries); ++ goto unmap; ++ } ++ ++ tbl_size = sizeof(*tbl) + size; + memblock_reserve(efi_mem_attr_table, tbl_size); + set_bit(EFI_MEM_ATTR, &efi.flags); + diff --git a/patches-sonic/0001-fix-os-crash-caused-by-optoe-when-class-switch.patch b/patches-sonic/0001-fix-os-crash-caused-by-optoe-when-class-switch.patch new file mode 100644 index 000000000..946ae4c3b --- /dev/null +++ b/patches-sonic/0001-fix-os-crash-caused-by-optoe-when-class-switch.patch @@ -0,0 +1,40 @@ +From 846abadb7505c10e85a3450a7343d6a89b5ecd96 Mon Sep 17 00:00:00 2001 +From: philo +Date: Fri, 12 Jul 2024 11:12:58 +0800 +Subject: [PATCH] fix os crash caused by optoe when class switch + +--- + drivers/misc/eeprom/optoe.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c +index 9129312a1..63bb8c46a 100644 +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -941,8 +941,10 @@ static ssize_t set_dev_class(struct device *dev, + } else { + /* one-address (eg QSFP) and CMIS family */ + /* if it exists, remove 0x51 i2c address */ +- if (optoe->client[1]) ++ if (optoe->client[1]) { + i2c_unregister_device(optoe->client[1]); ++ optoe->client[1] = NULL; ++ } + optoe->bin.size = ONE_ADDR_EEPROM_SIZE; + optoe->num_addresses = 1; + } +@@ -1224,8 +1226,10 @@ static int optoe_probe(struct i2c_client *client, + + err_struct: + if (num_addresses == 2) { +- if (optoe->client[1]) ++ if (optoe->client[1]) { + i2c_unregister_device(optoe->client[1]); ++ optoe->client[1] = NULL; ++ } + } + + kfree(optoe->writebuf); +-- +2.25.1 + diff --git a/patches-sonic/0001-hwmon-emc2305-Fix-unable-to-probe-emc2301-2-3.patch b/patches-sonic/0001-hwmon-emc2305-Fix-unable-to-probe-emc2301-2-3.patch new file mode 100644 index 000000000..d87131edd --- /dev/null +++ b/patches-sonic/0001-hwmon-emc2305-Fix-unable-to-probe-emc2301-2-3.patch @@ -0,0 +1,57 @@ +From e487bfd66267b942185a7124893c5f496bce768e Mon Sep 17 00:00:00 2001 +From: Natarajan Subbiramani +Date: Fri, 31 Mar 2023 16:06:56 +0000 +Subject: [PATCH] hwmon: (emc2305) Fix unable to probe emc2301/2/3 + +The definitions of 'EMC2305_REG_PRODUCT_ID' and 'EMC2305_REG_DEVICE' are +both '0xfd', they actually return the same value, but the values returned +by emc2301/2/3/5 are different, so probe emc2301/2/3 will fail, This patch +fixes that. + +Fix is available in upstream: +https://github.com/torvalds/linux/commit/4d50591ebf60ccf79380fff3a4c23659c61c482f + +Signed-off-by: Natarajan Subbiramani +Signed-off-by: Xingjiang Qiao +Link: https://lore.kernel.org/r/20221206055331.170459-1-nanpuyue@gmail.com +Fixes: 0d8400c ("hwmon: (emc2305) add support for EMC2301/2/3/5 RPM-based PWM Fan Speed Controller.") +Signed-off-by: Guenter Roeck +--- + drivers/hwmon/emc2305.c | 7 +------ + 1 file changed, 1 insertion(+), 6 deletions(-) + +diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c +index e0b7392..e1a21c7 100644 +--- a/drivers/hwmon/emc2305.c ++++ b/drivers/hwmon/emc2305.c +@@ -18,7 +18,6 @@ static const unsigned short + emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_END }; + + #define EMC2305_REG_DRIVE_FAIL_STATUS 0x27 +-#define EMC2305_REG_DEVICE 0xfd + #define EMC2305_REG_VENDOR 0xfe + #define EMC2305_FAN_MAX_NUM 5 + #define EMC2305_FAN_MAX 0xff /*100%*/ +@@ -432,7 +431,7 @@ static int emc2305_probe(struct i2c_client *client, const struct i2c_device_id * + struct device *dev = &client->dev; + u8 min_pwm, max_pwm, max_state; + struct emc2305_data *data; +- int vendor, device; ++ int vendor; + int ret; + int i; + +@@ -443,10 +442,6 @@ static int emc2305_probe(struct i2c_client *client, const struct i2c_device_id * + if (vendor != EMC2305_VENDOR) + return -ENODEV; + +- device = i2c_smbus_read_byte_data(client, EMC2305_REG_DEVICE); +- if (device != EMC2305_DEVICE) +- return -ENODEV; +- + max_state = EMC2305_FAN_MAX_STATE; + max_pwm = EMC2305_FAN_MAX; + min_pwm = EMC2305_FAN_MIN; +-- +2.25.1 + diff --git a/patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch b/patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch new file mode 100644 index 000000000..4481a28ce --- /dev/null +++ b/patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch @@ -0,0 +1,14812 @@ +From 640f8eae0603b564883861f895b0155f16795a72 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 18:26:40 +0300 +Subject: [PATCH 6.12 1/5] mctp: driver: net: Extend MCTP support. + +Extend the in-kernel MCTP stack with additional transports and the +supporting infrastructure used by NVIDIA BMC platforms. + +This adds new MCTP transport backends, including MCTP over SPI and the +NVIDIA OP-TEE VRoT transport, together with their headers and the +associated Kconfig and Makefile entries. It also adds infrastructure +shared by the transports: MCTP tracepoints, transport statistics, and +error-injection helpers (I2C/SPI/USB/socket) used for testing, and +extends the core MCTP routing, device and socket code accordingly. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/Kconfig | 34 + + drivers/net/mctp/Makefile | 7 + + drivers/net/mctp/glacier-spb-ap.h | 677 ++++++ + drivers/net/mctp/mctp-i2c-error-inject.c | 848 +++++++ + drivers/net/mctp/mctp-i2c-error-inject.h | 91 + + drivers/net/mctp/mctp-i2c-internal.h | 117 + + drivers/net/mctp/mctp-i2c.c | 671 +++++- + drivers/net/mctp/mctp-i3c.c | 298 ++- + drivers/net/mctp/mctp-serial.c | 5 +- + drivers/net/mctp/mctp-spi-error-inject.c | 596 +++++ + drivers/net/mctp/mctp-spi-error-inject.h | 86 + + drivers/net/mctp/mctp-spi-internal.h | 97 + + drivers/net/mctp/mctp-spi.c | 772 +++++++ + drivers/net/mctp/mctp-stats.h | 217 ++ + drivers/net/mctp/mctp-usb-error-inject.c | 1055 +++++++++ + drivers/net/mctp/mctp-usb-error-inject.h | 89 + + drivers/net/mctp/mctp-usb-internal.h | 98 + + drivers/net/mctp/mctp-usb.c | 1307 +++++++++++ + drivers/net/mctp/nvidia-optee-vrot.c | 1264 +++++++++++ + drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h | 151 ++ + drivers/soc/aspeed/aspeed-mctp.c | 27 +- + include/linux/i3c/mctp/i3c-mctp.h | 50 + + include/linux/usb/mctp-usb.h | 27 + + include/net/mctp.h | 120 +- + include/net/mctpdevice.h | 39 +- + include/trace/events/mctp.h | 455 ++++ + include/uapi/linux/if_link.h | 1 + + include/uapi/linux/mctp.h | 75 + + net/mctp/Makefile | 2 +- + net/mctp/af_mctp.c | 339 ++- + net/mctp/device.c | 73 +- + net/mctp/mctp-socket-error-inject.c | 221 ++ + net/mctp/mctp-socket-error-inject.h | 41 + + net/mctp/neigh.c | 16 + + net/mctp/route.c | 1982 +++++++++++++++-- + net/mctp/test/route-test.c | 612 +++-- + 36 files changed, 11971 insertions(+), 589 deletions(-) + create mode 100644 drivers/net/mctp/glacier-spb-ap.h + create mode 100644 drivers/net/mctp/mctp-i2c-error-inject.c + create mode 100644 drivers/net/mctp/mctp-i2c-error-inject.h + create mode 100644 drivers/net/mctp/mctp-i2c-internal.h + create mode 100644 drivers/net/mctp/mctp-spi-error-inject.c + create mode 100644 drivers/net/mctp/mctp-spi-error-inject.h + create mode 100644 drivers/net/mctp/mctp-spi-internal.h + create mode 100644 drivers/net/mctp/mctp-spi.c + create mode 100644 drivers/net/mctp/mctp-stats.h + create mode 100644 drivers/net/mctp/mctp-usb-error-inject.c + create mode 100644 drivers/net/mctp/mctp-usb-error-inject.h + create mode 100644 drivers/net/mctp/mctp-usb-internal.h + create mode 100644 drivers/net/mctp/mctp-usb.c + create mode 100644 drivers/net/mctp/nvidia-optee-vrot.c + create mode 100644 drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h + create mode 100644 include/linux/i3c/mctp/i3c-mctp.h + create mode 100644 include/linux/usb/mctp-usb.h + create mode 100644 net/mctp/mctp-socket-error-inject.c + create mode 100644 net/mctp/mctp-socket-error-inject.h + +diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig +index 15860d6ac..fea55902e 100644 +--- a/drivers/net/mctp/Kconfig ++++ b/drivers/net/mctp/Kconfig +@@ -47,6 +47,40 @@ config MCTP_TRANSPORT_I3C + A MCTP protocol network device is created for each I3C bus + having a "mctp-controller" devicetree property. + ++config MCTP_TRANSPORT_SPI ++ tristate "MCTP Over SPI Transprt" ++ help ++ This is in Kernel mode Mctp Over Spi Driver. ++ ++config MCTP_TRANSPORT_USB ++ tristate "MCTP USB transport" ++ depends on USB ++ help ++ Provides a driver to access MCTP devices over USB transport, ++ defined by DMTF specification DSP0283. ++ ++ MCTP-over-USB interfaces are peer-to-peer, so each interface ++ represents a physical connection to one remote MCTP endpoint. ++ ++ The driver supports TX packet batching, which can be controlled ++ at runtime via sysfs (/sys/class/net/mctpusb*/tx_batching). ++ Batching reduces USB overhead by packing multiple packets into ++ a single USB bulk transfer (up to 512 bytes). ++ ++config MCTP_TRANSPORT_PCIE_VDM ++ tristate "MCTP PCIe VDM transport" ++ depends on PCI ++ select MCTP_FLOWS ++ help ++ Provides a driver to access MCTP devices over PCIe VDM transport, ++ from DMTF specification DSP0238. ++config NVIDIA_OPTEE_VROT ++ tristate "NVIDIA OPTEE VROT support" ++ depends on OPTEE ++ help ++ Enable support for NVIDIA OPTEE VRoT. Select this option if you want ++ to use the NVIDIA VRoT functionality running on OPTEE. ++ + endmenu + + endif +diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile +index e1cb99ced..cfb127d78 100644 +--- a/drivers/net/mctp/Makefile ++++ b/drivers/net/mctp/Makefile +@@ -1,3 +1,10 @@ + obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o + obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o ++obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o ++obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi.o ++obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi-error-inject.o ++obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o ++obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb-error-inject.o ++obj-$(CONFIG_MCTP_TRANSPORT_PCIE_VDM) += mctp-pcie-vdm.o ++obj-$(CONFIG_NVIDIA_OPTEE_VROT) += nvidia-optee-vrot.o +diff --git a/drivers/net/mctp/glacier-spb-ap.h b/drivers/net/mctp/glacier-spb-ap.h +new file mode 100644 +index 000000000..5b74c7292 +--- /dev/null ++++ b/drivers/net/mctp/glacier-spb-ap.h +@@ -0,0 +1,677 @@ ++/* ++ * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. ++ * SPDX-License-Identifier: Apache-2.0 ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#ifndef __GLACIER_SPB_AP_H__ ++#define __GLACIER_SPB_AP_H__ ++ ++#include ++#include ++#include ++#include ++ ++typedef enum { ++ CMD_SREG_W8 = 0x9, ++ CMD_SREG_W16, ++ CMD_SREG_W32, ++ ++ CMD_SREG_R8 = 0xD, ++ CMD_SREG_R16, ++ CMD_SREG_R32, ++ ++ CMD_MEM_W8 = 0x21, ++ CMD_MEM_W16, ++ CMD_MEM_W32, ++ ++ CMD_MEM_R8 = 0x25, ++ CMD_MEM_R16, ++ CMD_MEM_R32, ++ ++ CMD_RD_SNGL_FIFO8 = 0x28, ++ CMD_RD_SNGL_FIFO16 = 0x29, ++ CMD_RD_SNGL_FIFO32 = 0x2b, ++ ++ CMD_POLL_LOW = 0x2C, ++ CMD_POLL_HIGH = 0x2D, ++ CMD_POLL_ALL = 0x2F, ++ ++ CMD_MEM_BLK_W1 = 0x80, ++ ++ CMD_MEM_BLK_R1 = 0xA0, ++ CMD_RD_BLK_FIFO1 = 0xC0, ++ ++ CMD_RD_SNGL_FIFO8_FSR = 0x68, ++ CMD_RD_SNGL_FIFO16_FSR = 0x69, ++ CMD_RD_SNGL_FIFO32_FSR = 0x6B, ++ CMD_BLK_RD_FIFO_FSR = 0xE0, ++} spb_spi_cmds_t; ++ ++typedef enum { ++ SPI_CFG = 0x00, ++ SPI_STS = 0x04, ++ SPI_EC_STS = 0x08, ++ SPI_IEN = 0x0C, ++ // ... ++ SPI_SPIM2EC_MBX = 0x44, ++ SPI_EC2SPIM_MBX = 0x48, ++} spb_spi_regs_t; ++ ++typedef enum { ++ EC_ACK = 0x01000000, ++ AP_REQUEST_WRITE = 0x02000000, ++ AP_READY_TO_READ = 0x03000000, ++ AP_FINISHED_READ = 0x04000000, ++ AP_REQUEST_RESET = 0x05000000, ++ EC_MSG_AVAILABLE = 0x10000000, ++} spb_spi_mailbox_cmds_t; ++ ++#define mctp_prerr(fmt, ...) \ ++ printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__) ++#define MCTP_ASSERT_RET(cond, ret, fmt, ...) \ ++ do { \ ++ if (!(cond)) { \ ++ mctp_prerr("at %s:%d " fmt, __func__, __LINE__, \ ++ ##__VA_ARGS__); \ ++ return (ret); \ ++ } \ ++ } while (0) ++ ++typedef enum { ++ SPB_AP_OK, ++ SPB_AP_MESSAGE_AVAILABLE, ++ SPB_AP_ERROR_INVALID_ARGUMENT, ++ SPB_AP_ERROR_TIMEOUT, ++ SPB_AP_ERROR_UNKNOWN, ++} SpbApStatus; ++ ++/// Callback required for libspb ++typedef struct { ++ int msgs_available; ++ uint32_t ec2spimb; ++ struct spi_device *spi; ++ wait_queue_head_t *gpio_intr_wq; ++ bool *gpio_intr_cond; ++ spinlock_t *gpio_intr_cond_lock; ++ int (*gpio_read_interrupt_pin)(void); ++ int (*on_mode_change)(bool quad, uint8_t waitCycles); ++ ++ // required for both interrupt and no interrupt ++ ssize_t (*spi_xfer)(struct spi_device *spi, const char *txbuf, size_t txlen, ++ const char *rxbuf, size_t rxlen, bool cs_change); ++} SpbAp; ++ ++#define POLL_SREG_TIMEOUT_MSECS 10000ULL ++#define POLL_INT_TIMEOUT_MSECS 1000ULL ++#define MAX_BYTES_PER_TRANSACTION 32 ++#define WAIT_CYCLES 0 ++#define TAR_CYCLES 1 // 1 in single, 4 in quad ++#define TAR_WAIT_CYCLES (WAIT_CYCLES + TAR_CYCLES) ++ ++#ifdef pr_fmt ++#undef pr_fmt ++#define pr_fmt(x) "spb-ap: " #x ++#endif ++ ++static inline uint64_t clock_msecs(void) ++{ ++ struct timespec64 ts; ++ ktime_get_ts64(&ts); ++ return (ts.tv_sec * 1000ULL + (ts.tv_nsec >> 20)); ++} ++ ++static const char *mailbox_str(uint32_t mb) ++{ ++ static char str[128]; ++ ++ memset(str, '\0', sizeof(str)); ++ switch (mb & 0x0F000000) { ++ case EC_ACK: ++ strcat(str, "EC_ACK"); ++ break; ++ case AP_REQUEST_WRITE: ++ strcat(str, "AP_REQUEST_WRITE"); ++ break; ++ case AP_READY_TO_READ: ++ strcat(str, "AP_READY_TO_READ"); ++ break; ++ case AP_FINISHED_READ: ++ strcat(str, "AP_FINISHED_READ"); ++ break; ++ case AP_REQUEST_RESET: ++ strcat(str, "AP_REQUEST_RESET"); ++ break; ++ default: ++ break; ++ } ++ ++ if (mb & 0xFF) { ++ sprintf(str + strlen(str), "LEN:%02x", mb & 0xFF); ++ } ++ ++ if (mb & EC_MSG_AVAILABLE) { ++ sprintf(str + strlen(str), " %x", mb); ++ ++ strcat(str, (strlen(str) > 0 ? "|EC_MSG_AVAILABLE" : ++ "EC_MSG_AVAILABLE")); ++ } ++ ++ if (strlen(str) == 0) { ++ sprintf(str, "UNKNOWN MAILBOX: %08x", mb); ++ } ++ return str; ++} ++ ++static inline uint32_t buf2dw(uint8_t *x) ++{ ++ return ntohl(*(uint32_t*)x); ++} ++ ++static inline uint16_t buf2w(uint8_t *x) ++{ ++ return ntohs(*(uint16_t*)x); ++} ++ ++static inline void dw2buf(uint32_t dw, uint8_t *buf) ++{ ++ *(uint32_t*)buf = htonl(dw); ++} ++ ++static inline void w2buf(uint16_t w, uint8_t *buf) ++{ ++ *(uint16_t*)buf = htons(w); ++} ++ ++static int wait_for_gpio_interrupt(wait_queue_head_t *gpio_intr_wq, bool *gpio_intr_cond, ++ spinlock_t *gpio_intr_cond_lock, long timeout) ++{ ++ if (wait_event_timeout(*gpio_intr_wq, *gpio_intr_cond, msecs_to_jiffies(timeout)) > 0) ++ { ++ unsigned long flags; ++ spin_lock_irqsave(gpio_intr_cond_lock, flags); ++ *gpio_intr_cond = false; ++ spin_unlock_irqrestore(gpio_intr_cond_lock, flags); ++ return 0; ++ } ++ else ++ { ++ return -1; ++ } ++} ++ ++static inline void spi_xfer(SpbAp *ap, int sendLen, uint8_t *xbuf, int recvLen, ++ uint8_t *rbuf, bool deassert) ++{ ++ ap->spi_xfer(ap->spi, xbuf, sendLen, rbuf, recvLen, deassert); ++} ++ ++static inline uint16_t sreg_write_8(SpbAp *ap, uint16_t addr, uint8_t value) ++{ ++ uint8_t buf[1 + 2 + 1 + TAR_WAIT_CYCLES + 2] = { 0 }; ++ ++ buf[0] = CMD_SREG_W8; ++ w2buf(addr, buf + 1); ++ buf[3] = value; ++ spi_xfer(ap, 1 + 2 + 1, buf, TAR_WAIT_CYCLES + 2, buf, true); ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint16_t sreg_write_32(SpbAp *ap, uint16_t addr, uint32_t value) ++{ ++ uint8_t buf[1 + 2 + 4 + TAR_WAIT_CYCLES + 2] = { 0 }; ++ ++ buf[0] = CMD_SREG_W32; ++ w2buf(addr, buf + 1); ++ dw2buf(value, buf + 3); ++ spi_xfer(ap, 7, buf, TAR_WAIT_CYCLES + 2, buf, true); ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint16_t sreg_read_8(SpbAp *ap, uint16_t addr, uint8_t *val) ++{ ++ uint8_t buf[1 + 2 + TAR_WAIT_CYCLES + 2 + 1] = { 0 }; ++ ++ buf[0] = CMD_SREG_R8; ++ w2buf(addr, buf + 1); ++ spi_xfer(ap, 3, buf, TAR_WAIT_CYCLES + 2 + 1, buf, true); ++ *val = buf[TAR_WAIT_CYCLES + 2]; ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint16_t sreg_read_32(SpbAp *ap, uint16_t addr, uint32_t *val) ++{ ++ uint8_t buf[1 + 2 + TAR_WAIT_CYCLES + 2 + 4] = { 0 }; ++ ++ buf[0] = CMD_SREG_R32; ++ w2buf(addr, buf + 1); ++ spi_xfer(ap, 3, buf, TAR_WAIT_CYCLES + 2 + 4, buf, true); ++ *val = buf2dw(buf + TAR_WAIT_CYCLES + 2); ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint32_t cmd_poll_all(SpbAp *ap) ++{ ++ uint8_t buf[1 + TAR_CYCLES + 4] = { 0 }; ++ ++ buf[0] = CMD_POLL_ALL; ++ spi_xfer(ap, 1, buf, TAR_CYCLES + 4, buf, true); ++ ++ return (buf2dw(buf + TAR_CYCLES)); ++} ++ ++// Read RX_FIFO_EMPTY ++static SpbApStatus wait_for_tx_fifo_not_empty(SpbAp *ap) ++{ ++ uint64_t start = clock_msecs(); ++ ++ while ((cmd_poll_all(ap) & 0x400) != 0) { ++ if (clock_msecs() - start > POLL_SREG_TIMEOUT_MSECS) { ++ return (SPB_AP_ERROR_TIMEOUT); ++ } ++ } ++ return (SPB_AP_OK); ++} ++ ++static inline uint16_t mailbox_write(SpbAp *ap, uint32_t v) ++{ ++ uint16_t status = sreg_write_32(ap, SPI_SPIM2EC_MBX, v); ++ ++ return (status); ++} ++ ++static inline uint32_t clear_memory_write_done(SpbAp *ap) ++{ ++ // MemoryWriteDone bit 0, write to clear ++ return (sreg_write_8(ap, SPI_STS, 0x01)); ++} ++ ++static inline uint32_t clear_memory_read_done(SpbAp *ap) ++{ ++ // MemoryReadDone bit 1, write to clear ++ return (sreg_write_8(ap, SPI_STS, 0x02)); ++} ++ ++// Polling procedures with timeouts ++static inline SpbApStatus wait_for_memory_write_busy_and_rx_fifo_empty(SpbAp *ap) ++{ ++ uint64_t start = clock_msecs(); ++ ++ // RxFifoEmpty Bit 8, MemoryWriteBusy Bit 3 ++ while ((cmd_poll_all(ap) & 0x108) == 0) { ++ if (clock_msecs() - start > POLL_SREG_TIMEOUT_MSECS) ++ return (SPB_AP_ERROR_TIMEOUT); ++ } ++ ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus spb_ap_check_ack(SpbAp *ap) ++{ ++ uint32_t sts = 0; ++ uint32_t mb = 1; ++ SpbApStatus ret = SPB_AP_OK; ++ ++ // check mailbox and reset interrupt ++ sts = sreg_read_32(ap, SPI_EC2SPIM_MBX, &mb); ++ ++ /* ++ * expect ACK but only MSG_AVAILABLE is set. The workaround was made to ++ * mark ACK is set. ++ */ ++ ++ if (mb & EC_MSG_AVAILABLE) { ++ ap->msgs_available++; ++ } ++ ++ ap->ec2spimb = mb & ~EC_MSG_AVAILABLE; ++ return (ret); ++} ++ ++static inline SpbApStatus spb_ap_wait_for_intr(SpbAp *ap, int timeout_ms, bool polling) ++{ ++ int status = 0; ++ ++ status = wait_for_gpio_interrupt(ap->gpio_intr_wq, ap->gpio_intr_cond, ap->gpio_intr_cond_lock, timeout_ms); ++ if (status < 0) ++ return (SPB_AP_ERROR_TIMEOUT); ++ ++ return (spb_ap_check_ack(ap)); ++} ++ ++static inline SpbApStatus wait_for_ack(SpbAp *ap) ++{ ++ SpbApStatus status = SPB_AP_OK; ++ uint64_t start = clock_msecs(); ++ ++ while (ap->ec2spimb != EC_ACK) { ++ /* Check for interrupts */ ++ if (clock_msecs() - start > POLL_INT_TIMEOUT_MSECS) { ++ if (ap->msgs_available > 0) ++ break; ++ else ++ return (SPB_AP_ERROR_TIMEOUT); ++ } ++ ++ spb_ap_wait_for_intr(ap, POLL_INT_TIMEOUT_MSECS, true); ++ } ++ ++ ap->ec2spimb = 0; ++ return (status); ++} ++ ++static inline SpbApStatus wait_for_length(SpbAp *ap, uint32_t *bytes) ++{ ++ SpbApStatus status = SPB_AP_OK; ++ uint64_t start = clock_msecs(); ++ ++ while ((ap->ec2spimb & 0xFF) == 0) { ++ if (clock_msecs() - start > POLL_INT_TIMEOUT_MSECS) ++ return (SPB_AP_ERROR_TIMEOUT); ++ ++ /* Check for interrupts */ ++ spb_ap_wait_for_intr(ap, POLL_INT_TIMEOUT_MSECS, true); ++ } ++ ++ *bytes = ap->ec2spimb & 0xFF; ++ ap->ec2spimb = 0; ++ return (status); ++} ++ ++// Write payload with maxiumum of 32 bytes per transfer ++static inline SpbApStatus posted_write(SpbAp *ap, uint16_t offset, int len, uint8_t *payload) ++{ ++ uint8_t buf[128] = { 0 }; ++ int off = 0; ++ SpbApStatus status; ++ ++ while (len > 0) { ++ int bytes = len > MAX_BYTES_PER_TRANSACTION ? ++ MAX_BYTES_PER_TRANSACTION : ++ len; ++ if (bytes >= 4) { ++ bytes &= 0xFFFFFFFC; ++ buf[0] = CMD_MEM_BLK_W1 + bytes / 4 - 1; ++ w2buf(offset + off, buf + 1); ++ // byteswap for DWORD ++ for (int ii = 0; ii < bytes; ii += 4) ++ *(uint32_t*)(buf + ii + 3) = __builtin_bswap32(*(uint32_t*)(payload + off + ii)); ++ ++ status = wait_for_memory_write_busy_and_rx_fifo_empty( ++ ap); ++ MCTP_ASSERT_RET( ++ status == SPB_AP_OK, status, ++ "wait_for_memory_write_busy_and_rx_fifo_empty failed"); ++ ++ spi_xfer(ap, 3 + bytes, buf, 0, buf, true); ++ clear_memory_write_done(ap); ++ } else { ++ for (int ii = 0; ii < bytes; ii++) { ++ buf[0] = CMD_MEM_W8; ++ w2buf(offset + off + ii, buf + 1); ++ buf[3] = payload[off + ii]; ++ ++ status = ++ wait_for_memory_write_busy_and_rx_fifo_empty( ++ ap); ++ MCTP_ASSERT_RET( ++ status == SPB_AP_OK, status, ++ "wait_for_memory_write_busy_and_rx_fifo_empty failed"); ++ ++ spi_xfer(ap, 3 + 1, buf, 0, buf, true); ++ clear_memory_write_done(ap); ++ } ++ } ++ off += bytes; ++ len -= bytes; ++ } ++ ++ return SPB_AP_OK; ++} ++ ++static SpbApStatus posted_read_helper(SpbAp *ap, uint8_t cmd, uint8_t cmd2, ++ uint16_t addr, int bytes, uint8_t *buf) ++{ ++ SpbApStatus status; ++ // Send the post read command ++ buf[0] = cmd; ++ w2buf(addr, buf + 1); ++ spi_xfer(ap, 3, buf, 0, buf, true); ++ ++ // check ok to read ++ status = wait_for_tx_fifo_not_empty(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "wait_for_tx_fifo_not_empty failed: %d", status); ++ ++ // Initiate FIFO READ ++ buf[0] = cmd2; ++ spi_xfer(ap, 1, buf, TAR_CYCLES + 2, buf, false); ++ ++ // Check MemoryReadDone ++ if ((buf[TAR_CYCLES + 1] & 0x02) == 0) { ++ // Poll status ++ // TODO: this section is not tested ++ uint64_t start = clock_msecs(); ++ do { ++ // read status until MemoryReadDone is set ++ buf[0] = 0; ++ buf[1] = 0; ++ spi_xfer(ap, 0, buf, 2, buf, false); ++ if (clock_msecs() - start > POLL_INT_TIMEOUT_MSECS) { ++ return SPB_AP_ERROR_TIMEOUT; ++ } ++ } while ((buf[1] & 0x02) == 0); ++ } ++ ++ // Read actual data ++ spi_xfer(ap, 0, buf, bytes, buf, true); ++ clear_memory_read_done(ap); ++ ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus posted_read(SpbAp *ap, int offset, int len, uint8_t *payload) ++{ ++ uint8_t buf[128] = { 0 }; ++ int off = 0; ++ SpbApStatus status = 0; ++ int reg_offset = 0; ++ ++ while (len > 0) { ++ int bytes = len > MAX_BYTES_PER_TRANSACTION ? ++ MAX_BYTES_PER_TRANSACTION : ++ len; ++ if (bytes >= 4) { ++ bytes &= 0xFFFFFFFC; ++ reg_offset = (bytes / 4) - 1; ++ ++ status = posted_read_helper( ++ ap, CMD_MEM_BLK_R1 + reg_offset, ++ CMD_BLK_RD_FIFO_FSR + reg_offset, offset + off, ++ bytes, buf); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "posted_read_helper failed"); ++ ++ for (int ii = 0; ii < bytes; ii += 4) ++ *(uint32_t*)(payload + off + ii) = __builtin_bswap32(*(uint32_t*)(buf + ii)); ++ } else { ++ for (int ii = 0; ii < bytes; ii++) { ++ status = posted_read_helper( ++ ap, CMD_MEM_R8, CMD_RD_SNGL_FIFO8_FSR, ++ offset + off + ii, 4, ++ buf); // TODO: why is this 4 ? ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "posted_read_helper failed"); ++ // TODO: offset 3 because we have to read 4 bytes ++ payload[off + ii] = buf[3]; ++ } ++ } ++ off += bytes; ++ len -= bytes; ++ } ++ ++ return (SPB_AP_OK); ++} ++ ++// --- public ++ ++static inline SpbApStatus spb_ap_reset(SpbAp *ap) ++{ ++ // reset globals ++ SpbApStatus status; ++ ++ ap->msgs_available = 0; ++ ap->ec2spimb = 0; ++ ++ // initiate reset ++ mailbox_write(ap, AP_REQUEST_RESET); ++ // wait for ack ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus spb_ap_initialize(SpbAp *ap) ++{ ++ MCTP_ASSERT_RET(ap != NULL, SPB_AP_ERROR_INVALID_ARGUMENT, ++ "ap is NULL."); ++ MCTP_ASSERT_RET(ap->spi_xfer != NULL, SPB_AP_ERROR_INVALID_ARGUMENT, ++ "spi_xfer is NULL"); ++ ++ ap->msgs_available = 0; ++ ap->ec2spimb = 0x00000000U; ++ ++ return (spb_ap_reset(ap)); ++} ++ ++static inline SpbApStatus spb_ap_set_cfg(SpbAp *ap, bool quad, uint8_t waitCycles) ++{ ++ SpbApStatus status = SPB_AP_OK; ++ uint8_t cmd[] = { 0x03, 0x00, waitCycles, ++ (uint8_t)(quad ? 0x01 : 0x00) }; ++ ++ // send set cfg ++ mailbox_write(ap, AP_REQUEST_WRITE); ++ ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ status = posted_write(ap, 0, sizeof(cmd), cmd); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "posted_write failed."); ++ ++ mailbox_write(ap, sizeof(cmd)); ++ ++ // mode has changed ++ if (ap->on_mode_change(quad, waitCycles)) { ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "wait_for_ack failed"); ++ return (SPB_AP_OK); ++ } ++ return (SPB_AP_ERROR_UNKNOWN); ++} ++ ++static inline SpbApStatus spb_ap_shutdown(SpbAp *ap) ++{ ++ // leave Glacier in single spi mode ++ return (spb_ap_set_cfg(ap, false, 0)); ++} ++ ++static inline SpbApStatus spb_ap_on_interrupt(SpbAp *ap) ++{ ++ uint32_t sts = 0; ++ uint32_t mb = 1; ++ SpbApStatus ret = SPB_AP_OK; ++ ++ // check mailbox and reset interrupt ++ sts = sreg_read_32(ap, SPI_EC2SPIM_MBX, &mb); ++ ++ if (mb & EC_MSG_AVAILABLE) { ++ ret = SPB_AP_MESSAGE_AVAILABLE; ++ ap->msgs_available++; ++ } ++ ++ ap->ec2spimb = mb & ~EC_MSG_AVAILABLE; ++ return (ret); ++} ++ ++static inline SpbApStatus spb_ap_send(SpbAp *ap, int len, void *buf) ++{ ++ SpbApStatus status; ++ ++ mailbox_write(ap, AP_REQUEST_WRITE); ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ status = posted_write(ap, 0, len, (uint8_t *)(buf)); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "posted_write failed."); ++ mailbox_write(ap, len); ++ ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus spb_ap_recv(SpbAp *ap, int len, void *buf) ++{ ++ uint32_t bytes; ++ SpbApStatus status; ++ ++ mailbox_write(ap, AP_READY_TO_READ); ++ ++ ap->msgs_available--; ++ status = wait_for_length(ap, &bytes); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_length failed."); ++ MCTP_ASSERT_RET(bytes <= (uint32_t)len, status, ++ "Data cannot fit into the buffer."); ++ ++ status = posted_read(ap, 0x8000, bytes, (uint8_t *)(buf)); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "posted_read failed."); ++ mailbox_write(ap, AP_FINISHED_READ); ++ ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ return (SPB_AP_OK); ++} ++ ++static inline int spb_ap_msgs_available(SpbAp *ap) ++{ ++ return (ap->msgs_available); ++} ++ ++static inline const char *spb_ap_strstatus(SpbApStatus status) ++{ ++ switch (status) { ++ case SPB_AP_OK: ++ return "OK"; ++ case SPB_AP_MESSAGE_AVAILABLE: ++ return "Message available"; ++ case SPB_AP_ERROR_INVALID_ARGUMENT: ++ return "Invalid argument"; ++ case SPB_AP_ERROR_TIMEOUT: ++ return "Timeout"; ++ case SPB_AP_ERROR_UNKNOWN: ++ return "Unknown"; ++ } ++ ++ return ("Invalid status code."); ++} ++ ++#endif // __GLACIER_SPB_AP_H__ +\ No newline at end of file +diff --git a/drivers/net/mctp/mctp-i2c-error-inject.c b/drivers/net/mctp/mctp-i2c-error-inject.c +new file mode 100644 +index 000000000..c10040f87 +--- /dev/null ++++ b/drivers/net/mctp/mctp-i2c-error-inject.c +@@ -0,0 +1,848 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * MCTP I2C Error Injection Implementation ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over I2C binding. ++ * ++ * Interface unified with USB error injection where applicable. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-i2c-internal.h" ++ ++/* Global debugfs root for all MCTP I2C error injection */ ++static struct dentry *mctp_i2c_error_inject_root; ++ ++/* Check if error should be injected based on mode and rate */ ++static bool mctp_should_inject_error(struct mctp_i2c_error_inject *ei, u32 rate, u32 *count) ++{ ++ bool inject = false; ++ ++ spin_lock_bh(&ei->lock); ++ ++ switch (ei->mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ inject = true; ++ break; ++ ++ case MCTP_ERR_MODE_RANDOM: ++ /* Random injection based on rate percentage */ ++ inject = (prandom_u32_state(&ei->rng) % 100) < rate; ++ break; ++ ++ case MCTP_ERR_MODE_COUNT: ++ /* Inject for count packets, then stop */ ++ if (*count > 0) { ++ (*count)--; ++ inject = true; ++ } ++ break; ++ } ++ ++ spin_unlock_bh(&ei->lock); ++ ++ return inject; ++} ++ ++/* Check if packet matches EID filter */ ++static bool mctp_error_inject_match_filter(struct mctp_i2c_error_inject *ei, ++ struct sk_buff *skb) ++{ ++ struct mctp_hdr *mh; ++ ++ if (!ei->eid_filter.enabled) ++ return true; ++ ++ mh = mctp_hdr(skb); ++ if (!mh) ++ return false; ++ ++ /* Check source EID */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) ++ return false; ++ ++ /* Check dest EID */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) ++ return false; ++ ++ return true; ++} ++ ++/** ++ * mctp_i2c_error_inject_tx - Inject TX error ++ * @midev: MCTP I2C device ++ * @skb: Packet being transmitted ++ * ++ * Returns: 0 for normal operation, negative error code to simulate failure ++ * ++ * Called before __i2c_transfer() to potentially inject a TX error. ++ * I2C transfer is synchronous, so there's only one injection point. ++ */ ++int mctp_i2c_error_inject_tx(struct mctp_i2c_dev *midev, struct sk_buff *skb) ++{ ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ struct mctp_hdr *mh; ++ ++ /* Early exit if TX error injection is disabled or code not set */ ++ if (!ei->enable_tx || ei->i2c_tx_error_code == 0) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb)) ++ return 0; ++ ++ if (!mctp_should_inject_error(ei, ei->i2c_tx_error_rate, ++ &ei->i2c_tx_inject_count)) ++ return 0; ++ ++ /* Inject delay if configured */ ++ if (ei->delay_ms > 0) ++ msleep(ei->delay_ms); ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->i2c_tx_errors_injected++; ++ ei->total_errors_injected++; ++ ei->total_packets_processed++; ++ spin_unlock_bh(&ei->lock); ++ ++ mh = mctp_hdr(skb); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: TX - error=%d, src_eid=%u, dest_eid=%u, " ++ "total_injected=%u, mode=%s\n", ++ ei->i2c_tx_error_code, mh->src, mh->dest, ++ ei->i2c_tx_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ return -ei->i2c_tx_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_tx); ++ ++/** ++ * mctp_i2c_error_inject_fragment - Inject fragment errors ++ * @midev: MCTP I2C device ++ * @skb: Packet being processed ++ * ++ * Returns: 0 to pass packet through, 1 to drop packet ++ * ++ * This function can: ++ * 1. Drop fragments (simulates fragment loss) ++ * 2. Corrupt sequence numbers (simulates protocol errors) ++ * 3. Clear SOM flag (simulates missing start-of-message) ++ */ ++int mctp_i2c_error_inject_fragment(struct mctp_i2c_dev *midev, struct sk_buff *skb) ++{ ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ struct mctp_hdr *mh; ++ u8 flags; ++ u8 seq; ++ ++ /* Early exit if RX error injection disabled or no fragment injection enabled */ ++ if (!ei->enable_rx || (!ei->enable_fragment_drop && !ei->enable_seq_corrupt && !ei->enable_som_clear)) ++ return 0; ++ ++ mh = mctp_hdr(skb); ++ if (!mh) ++ return 0; ++ ++ flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ seq = (mh->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) & MCTP_HDR_SEQ_MASK; ++ ++ /* Single-packet message (SOM+EOM) - pass through (not a fragment) */ ++ if (flags == (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM)) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb)) ++ return 0; ++ ++ /* ===== Injection Type 1: Clear SOM bit (first fragment) ===== */ ++ if (ei->enable_som_clear && (flags & MCTP_HDR_FLAG_SOM)) { ++ mh->flags_seq_tag &= ~MCTP_HDR_FLAG_SOM; ++ ++ spin_lock_bh(&ei->lock); ++ ei->som_clears++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: SOM bit CLEARED (first fragment, src=%u, dest=%u, seq=%u)\n", ++ mh->src, mh->dest, seq); ++ ++ return 0; /* Pass through with corrupted SOM */ ++ } ++ ++ /* ===== Injection Type 2: Corrupt sequence number (middle/end fragments) ===== */ ++ if (ei->enable_seq_corrupt && !(flags & MCTP_HDR_FLAG_SOM)) { ++ u8 old_seq = seq; ++ u8 corrupted_seq = (seq + 1) & MCTP_HDR_SEQ_MASK; ++ ++ mh->flags_seq_tag &= ~(MCTP_HDR_SEQ_MASK << MCTP_HDR_SEQ_SHIFT); ++ mh->flags_seq_tag |= (corrupted_seq << MCTP_HDR_SEQ_SHIFT); ++ ++ spin_lock_bh(&ei->lock); ++ ei->seq_corruptions++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: Sequence CORRUPTED (2nd+ fragment, src=%u, dest=%u, %u -> %u)\n", ++ mh->src, mh->dest, old_seq, corrupted_seq); ++ ++ return 0; /* Pass through with corrupted sequence */ ++ } ++ ++ /* ===== Injection Type 3: Drop fragment (2nd+ fragments) ===== */ ++ if (ei->enable_fragment_drop && !(flags & MCTP_HDR_FLAG_SOM)) { ++ spin_lock_bh(&ei->lock); ++ ei->fragments_dropped++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: Fragment DROPPED (2nd+ fragment, src=%u, dest=%u, seq=%u)\n", ++ mh->src, mh->dest, seq); ++ ++ return 1; /* Drop this fragment */ ++ } ++ ++ return 0; /* Pass through normally */ ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_fragment); ++ ++/* ===== Debugfs Interface - Unified with USB ===== */ ++ ++static struct mctp_i2c_dev *mctp_i2c_from_file(struct file *file) ++{ ++ return file->f_inode->i_private; ++} ++ ++/* enable_tx attribute */ ++static ssize_t mctp_debugfs_enable_tx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.enable_tx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_tx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.enable_tx = enable; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ netdev_dbg(midev->ndev, "TX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_tx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_tx_read, ++ .write = mctp_debugfs_enable_tx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* enable_rx attribute */ ++static ssize_t mctp_debugfs_enable_rx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.enable_rx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_rx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.enable_rx = enable; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ netdev_dbg(midev->ndev, "RX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_rx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_rx_read, ++ .write = mctp_debugfs_enable_rx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* mode attribute */ ++static ssize_t mctp_debugfs_mode_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ const char *mode_str; ++ char buf[16]; ++ int len; ++ ++ switch (midev->error_inject.mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ mode_str = "always\n"; ++ break; ++ case MCTP_ERR_MODE_RANDOM: ++ mode_str = "random\n"; ++ break; ++ case MCTP_ERR_MODE_COUNT: ++ mode_str = "count\n"; ++ break; ++ default: ++ mode_str = "unknown\n"; ++ break; ++ } ++ ++ len = snprintf(buf, sizeof(buf), "%s", mode_str); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_mode_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[16]; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ ++ if (strncmp(buf, "always", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ else if (strncmp(buf, "random", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_RANDOM; ++ else if (strncmp(buf, "count", 5) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_COUNT; ++ else { ++ spin_unlock_bh(&midev->error_inject.lock); ++ return -EINVAL; ++ } ++ ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_mode_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_mode_read, ++ .write = mctp_debugfs_mode_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Generic u32 read/write helpers */ ++#define MCTP_DEBUGFS_U32_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ u32 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou32(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.field = val; \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic int read/write helpers (for error codes) */ ++#define MCTP_DEBUGFS_INT_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.field = val; \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic bool read/write helpers */ ++#define MCTP_DEBUGFS_BOOL_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.field ? 1 : 0); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.field = (val != 0); \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Define all the file operations */ ++MCTP_DEBUGFS_INT_DEFINE(i2c_tx_error_code, i2c_tx_error_code) ++MCTP_DEBUGFS_U32_DEFINE(i2c_tx_error_rate, i2c_tx_error_rate) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_fragment_drop, enable_fragment_drop) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_seq_corrupt, enable_seq_corrupt) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_som_clear, enable_som_clear) ++MCTP_DEBUGFS_U32_DEFINE(delay_ms, delay_ms) ++ ++/* EID filter attributes */ ++#define MCTP_DEBUGFS_U8_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.eid_filter.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ u8 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou8(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.eid_filter.field = val; \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++static ssize_t mctp_debugfs_eid_filter_enable_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.eid_filter.enabled); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_eid_filter_enable_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.eid_filter.enabled = enable; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_eid_filter_enable_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_eid_filter_enable_read, ++ .write = mctp_debugfs_eid_filter_enable_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_src_eid, src_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_dest_eid, dest_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_msg_type, msg_type) ++ ++/* stats attribute (read-only) - unified with USB */ ++static ssize_t mctp_debugfs_stats_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ char *buf; ++ int len = 0; ++ ssize_t ret; ++ ++ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ spin_lock_bh(&ei->lock); ++ ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_tx: %d\n", ei->enable_tx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_rx: %d\n", ei->enable_rx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "mode: %s\n", ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ len += snprintf(buf + len, PAGE_SIZE - len, "i2c_tx_errors_injected: %u\n", ++ ei->i2c_tx_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "fragments_dropped: %u\n", ++ ei->fragments_dropped); ++ len += snprintf(buf + len, PAGE_SIZE - len, "seq_corruptions: %u\n", ++ ei->seq_corruptions); ++ len += snprintf(buf + len, PAGE_SIZE - len, "som_clears: %u\n", ++ ei->som_clears); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_packets_processed: %llu\n", ++ ei->total_packets_processed); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_errors_injected: %llu\n", ++ ei->total_errors_injected); ++ ++ spin_unlock_bh(&ei->lock); ++ ++ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static const struct file_operations mctp_debugfs_stats_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* reset attribute (write-only) - unified with USB */ ++static ssize_t mctp_debugfs_reset_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ char buf[8]; ++ int val; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtoint(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (val != 1) ++ return -EINVAL; ++ ++ spin_lock_bh(&ei->lock); ++ ++ /* Reset all state */ ++ ei->enable_tx = false; ++ ei->enable_rx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->i2c_tx_error_code = 0; ++ ei->i2c_tx_error_rate = 0; ++ ei->i2c_tx_inject_count = 0; ++ ei->enable_fragment_drop = false; ++ ei->enable_seq_corrupt = false; ++ ei->enable_som_clear = false; ++ ei->delay_ms = 0; ++ ei->eid_filter.enabled = false; ++ ei->eid_filter.src_eid = 0; ++ ei->eid_filter.dest_eid = 0; ++ ei->eid_filter.msg_type = 0; ++ ++ /* Reset statistics */ ++ ei->i2c_tx_errors_injected = 0; ++ ei->fragments_dropped = 0; ++ ei->seq_corruptions = 0; ++ ei->som_clears = 0; ++ ei->total_packets_processed = 0; ++ ei->total_errors_injected = 0; ++ ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, "Error injection reset\n"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_reset_fops = { ++ .owner = THIS_MODULE, ++ .write = mctp_debugfs_reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/** ++ * mctp_i2c_error_inject_init - Initialize per-device error injection ++ * @midev: MCTP I2C device ++ * ++ * Creates debugfs directory and files for this device ++ */ ++void mctp_i2c_error_inject_init(struct mctp_i2c_dev *midev) ++{ ++ struct dentry *dir, *eid_dir; ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ ++ /* Initialize error injection state */ ++ spin_lock_init(&ei->lock); ++ prandom_seed_state(&ei->rng, (u64)jiffies); ++ ei->enable_tx = false; ++ ei->enable_rx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ /* All other fields are zero-initialized */ ++ ++ if (!mctp_i2c_error_inject_root) ++ return; ++ ++ /* Create per-device debugfs directory */ ++ dir = debugfs_create_dir(netdev_name(midev->ndev), mctp_i2c_error_inject_root); ++ if (IS_ERR_OR_NULL(dir)) ++ return; ++ ++ midev->debugfs_dir = dir; ++ ++ /* Create error injection files - unified with USB */ ++ debugfs_create_file("enable_tx", 0600, dir, midev, &mctp_debugfs_enable_tx_fops); ++ debugfs_create_file("enable_rx", 0600, dir, midev, &mctp_debugfs_enable_rx_fops); ++ debugfs_create_file("mode", 0600, dir, midev, &mctp_debugfs_mode_fops); ++ debugfs_create_file("i2c_tx_error_code", 0600, dir, midev, &mctp_debugfs_i2c_tx_error_code_fops); ++ debugfs_create_file("i2c_tx_error_rate", 0600, dir, midev, &mctp_debugfs_i2c_tx_error_rate_fops); ++ debugfs_create_file("enable_fragment_drop", 0600, dir, midev, &mctp_debugfs_enable_fragment_drop_fops); ++ debugfs_create_file("enable_seq_corrupt", 0600, dir, midev, &mctp_debugfs_enable_seq_corrupt_fops); ++ debugfs_create_file("enable_som_clear", 0600, dir, midev, &mctp_debugfs_enable_som_clear_fops); ++ debugfs_create_file("delay_ms", 0600, dir, midev, &mctp_debugfs_delay_ms_fops); ++ debugfs_create_file("stats", 0400, dir, midev, &mctp_debugfs_stats_fops); ++ debugfs_create_file("reset", 0200, dir, midev, &mctp_debugfs_reset_fops); ++ ++ /* Create eid_filter subdirectory - unified with USB */ ++ eid_dir = debugfs_create_dir("eid_filter", dir); ++ if (!IS_ERR_OR_NULL(eid_dir)) { ++ debugfs_create_file("enable", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_enable_fops); ++ debugfs_create_file("src_eid", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_src_eid_fops); ++ debugfs_create_file("dest_eid", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_dest_eid_fops); ++ debugfs_create_file("msg_type", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_msg_type_fops); ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_init); ++ ++/** ++ * mctp_i2c_error_inject_cleanup - Cleanup per-device error injection ++ * @midev: MCTP I2C device ++ */ ++void mctp_i2c_error_inject_cleanup(struct mctp_i2c_dev *midev) ++{ ++ debugfs_remove_recursive(midev->debugfs_dir); ++ midev->debugfs_dir = NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_cleanup); ++ ++/** ++ * mctp_i2c_error_inject_module_init - Initialize global error injection ++ * ++ * Creates root debugfs directory for MCTP I2C error injection ++ */ ++int mctp_i2c_error_inject_module_init(void) ++{ ++ mctp_i2c_error_inject_root = debugfs_create_dir("mctp_i2c", NULL); ++ if (IS_ERR_OR_NULL(mctp_i2c_error_inject_root)) { ++ pr_warn("MCTP I2C: Failed to create debugfs root, error injection disabled\n"); ++ mctp_i2c_error_inject_root = NULL; ++ return -ENODEV; ++ } ++ ++ pr_info("MCTP I2C Error Injection enabled\n"); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_module_init); ++ ++/** ++ * mctp_i2c_error_inject_module_exit - Cleanup global error injection ++ */ ++void mctp_i2c_error_inject_module_exit(void) ++{ ++ debugfs_remove_recursive(mctp_i2c_error_inject_root); ++ mctp_i2c_error_inject_root = NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_module_exit); +diff --git a/drivers/net/mctp/mctp-i2c-error-inject.h b/drivers/net/mctp/mctp-i2c-error-inject.h +new file mode 100644 +index 000000000..5689d4b1d +--- /dev/null ++++ b/drivers/net/mctp/mctp-i2c-error-inject.h +@@ -0,0 +1,91 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP I2C Error Injection Infrastructure ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over I2C binding. ++ */ ++ ++#ifndef __MCTP_I2C_ERROR_INJECT_H ++#define __MCTP_I2C_ERROR_INJECT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Forward declarations */ ++struct dentry; ++struct mctp_i2c_client; ++ ++/* Error injection modes - unified with USB */ ++enum mctp_error_inject_mode { ++ MCTP_ERR_MODE_ALWAYS, ++ MCTP_ERR_MODE_RANDOM, ++ MCTP_ERR_MODE_COUNT ++}; ++ ++/* Error injection control structure - unified with USB where possible */ ++struct mctp_i2c_error_inject { ++ bool enable_tx; /* TX error injection enable */ ++ bool enable_rx; /* RX/fragment error injection enable */ ++ enum mctp_error_inject_mode mode; ++ ++ /* TX injection - I2C only has one synchronous path */ ++ int i2c_tx_error_code; /* Error code to inject (ENXIO, EAGAIN, EBUSY, etc.) */ ++ u32 i2c_tx_error_rate; /* Percentage (0-100) or count */ ++ u32 i2c_tx_inject_count; /* Counter for count mode */ ++ u32 i2c_tx_errors_injected; ++ ++ /* Fragment injection - same as USB */ ++ bool enable_fragment_drop; /* Drop 2nd+ fragments */ ++ bool enable_seq_corrupt; /* Corrupt sequence number in middle/end fragments */ ++ bool enable_som_clear; /* Clear SOM bit in first fragment */ ++ u32 fragments_dropped; ++ u32 seq_corruptions; ++ u32 som_clears; ++ ++ /* Delay injection */ ++ u32 delay_ms; ++ ++ /* EID filtering - unified with USB */ ++ struct { ++ bool enabled; ++ u8 src_eid; /* 0 = any */ ++ u8 dest_eid; /* 0 = any */ ++ u8 msg_type; /* 0 = any */ ++ } eid_filter; ++ ++ /* Statistics */ ++ u64 total_packets_processed; ++ u64 total_errors_injected; ++ ++ /* RNG state */ ++ struct rnd_state rng; ++ spinlock_t lock; ++}; ++ ++/* Forward declaration - full definition in mctp-i2c.c */ ++struct mctp_i2c_dev; ++ ++/* Module init/exit functions */ ++int mctp_i2c_error_inject_module_init(void); ++void mctp_i2c_error_inject_module_exit(void); ++ ++/* Public API for main driver */ ++void mctp_i2c_error_inject_init(struct mctp_i2c_dev *midev); ++void mctp_i2c_error_inject_cleanup(struct mctp_i2c_dev *midev); ++ ++int mctp_i2c_error_inject_tx(struct mctp_i2c_dev *midev, struct sk_buff *skb); ++int mctp_i2c_error_inject_fragment(struct mctp_i2c_dev *midev, struct sk_buff *skb); ++ ++#endif /* __MCTP_I2C_ERROR_INJECT_H */ ++ ++ +diff --git a/drivers/net/mctp/mctp-i2c-internal.h b/drivers/net/mctp/mctp-i2c-internal.h +new file mode 100644 +index 000000000..3e8768314 +--- /dev/null ++++ b/drivers/net/mctp/mctp-i2c-internal.h +@@ -0,0 +1,117 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP I2C Internal Definitions ++ * Shared between mctp-i2c.c and mctp-i2c-error-inject.c ++ */ ++ ++#ifndef __MCTP_I2C_INTERNAL_H ++#define __MCTP_I2C_INTERNAL_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-i2c-error-inject.h" ++ ++/* Constants from mctp-i2c.c */ ++#define MCTP_I2C_MAXBLOCK 255 ++#define MCTP_I2C_BUFSZ (3 + MCTP_I2C_MAXBLOCK + 1) ++ ++/* Forward declaration */ ++struct mctp_i2c_client; ++ ++/* The netdev structure. One of these per I2C adapter. */ ++struct mctp_i2c_dev { ++ struct net_device *ndev; ++ struct i2c_adapter *adapter; ++ struct mctp_i2c_client *client; ++ struct list_head list; /* For mctp_i2c_client.devs */ ++ ++ size_t rx_pos; ++ u8 rx_buffer[MCTP_I2C_BUFSZ]; ++ struct completion rx_done; ++ ++ struct task_struct *tx_thread; ++ wait_queue_head_t tx_wq; ++ struct sk_buff_head tx_queue; ++ u8 tx_scratch[MCTP_I2C_BUFSZ]; ++ ++ /* A fake entry in our tx queue to perform an unlock operation */ ++ struct sk_buff unlock_marker; ++ ++ /* NACK retry parameters */ ++ u32 nack_retries; ++ u32 nack_retry_delay_us; ++ u32 nack_retry_addr; ++ u32 nack_total_retries; ++ u32 nack_recovered; ++ u32 nack_failed; ++ u32 nack_max_depth; ++ ++ /* Spinlock protects i2c_lock_count, release_count, allow_rx */ ++ spinlock_t lock; ++ int i2c_lock_count; ++ int release_count; ++ /* Indicates that the netif is ready to receive incoming packets */ ++ bool allow_rx; ++ bool flows_enabled; /* Runtime flow control */ ++ ++ /* Error injection support */ ++ struct mctp_i2c_error_inject error_inject; ++ struct dentry *debugfs_dir; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (pre-parse errors, allocation failures, etc.) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_i2c_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_invalid_cmd; /* Invalid I2C command (tracked as UNKNOWN) */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_invalid_pec; /* PEC check failed (tracked as UNKNOWN) */ ++ u64 rx_drop_fragment_error; ++ u64 rx_drop_not_ready; ++ u64 rx_early_exit_no_rx; /* RX not allowed (tracked as UNKNOWN) */ ++ ++ /* TX stats */ ++ u64 tx_drop_no_memory; /* -ENOMEM: Memory allocation failure */ ++ u64 tx_drop_busy; /* -EBUSY: Bus busy too long */ ++ u64 tx_drop_no_device; /* -ENXIO: No device at address (no ACK) */ ++ u64 tx_drop_enodev; /* -ENODEV: Device not found */ ++ u64 tx_drop_arbitration_lost; /* -EAGAIN: Lost arbitration */ ++ u64 tx_drop_proto_error; /* -EPROTO: Protocol violation */ ++ u64 tx_drop_timeout; /* -ETIMEDOUT: Transfer timeout */ ++ u64 tx_drop_eopnotsupp; /* -EOPNOTSUPP: Operation not supported */ ++ u64 tx_drop_einval; /* -EINVAL: Invalid parameter */ ++ u64 tx_drop_eafnosupport; /* -EAFNOSUPPORT: 10-bit address not supported */ ++ u64 tx_drop_ebadmsg; /* -EBADMSG: Invalid SMBus PEC */ ++ u64 tx_drop_eshutdown; /* -ESHUTDOWN: Adapter suspended */ ++ u64 tx_drop_eio; /* -EIO: Generic I/O error */ ++ u64 tx_drop_invalid_len; /* Invalid packet length */ ++ u64 tx_drop_queue_full; /* TX queue full */ ++ u64 tx_drop_flow_invalid; /* Invalid flow state */ ++ u64 tx_early_exit_stopped; /* TX thread stopped (tracked as UNKNOWN) */ ++ ++ /* Retry/requeue */ ++ u64 tx_retries_attempted; ++ u64 tx_retry_success; ++ u64 tx_retry_exhausted; ++ u64 tx_requeued; ++ /* NACK retry stats */ ++ u64 tx_nack; ++ u64 tx_nack_retries; ++ u64 tx_nack_retry_depth; ++ } eid[257]; ++ } eid_stats; ++}; ++ ++#endif /* __MCTP_I2C_INTERNAL_H */ ++ +diff --git a/drivers/net/mctp/mctp-i2c.c b/drivers/net/mctp/mctp-i2c.c +index 503a91743..4fbab9a83 100644 +--- a/drivers/net/mctp/mctp-i2c.c ++++ b/drivers/net/mctp/mctp-i2c.c +@@ -22,16 +22,22 @@ + #include + #include + #include ++#include ++#include ++ + #include + #include + ++#include "mctp-i2c-error-inject.h" ++#include "mctp-stats.h" ++#include ++ + /* byte_count is limited to u8 */ +-#define MCTP_I2C_MAXBLOCK 255 ++#include "mctp-i2c-internal.h" ++ + /* One byte is taken by source_slave */ + #define MCTP_I2C_MAXMTU (MCTP_I2C_MAXBLOCK - 1) + #define MCTP_I2C_MINMTU (64 + 4) +-/* Allow space for dest_address, command, byte_count, data, PEC */ +-#define MCTP_I2C_BUFSZ (3 + MCTP_I2C_MAXBLOCK + 1) + #define MCTP_I2C_MINLEN 8 + #define MCTP_I2C_COMMANDCODE 0x0f + #define MCTP_I2C_TX_WORK_LEN 100 +@@ -40,6 +46,11 @@ + + #define MCTP_I2C_OF_PROP "mctp-controller" + ++#define NACK_RETRY_MAX 100 ++#define NACK_RETRY_DELAY_MAX_US 1000 ++#define NACK_RETRY_DELAY_SLACK_US 1000 ++#define NACK_RETRY_ADDR_DISABLED 0xFFFFFFFF ++ + enum { + MCTP_I2C_FLOW_STATE_NEW = 0, + MCTP_I2C_FLOW_STATE_ACTIVE, +@@ -53,36 +64,6 @@ enum { + static DEFINE_MUTEX(driver_clients_lock); + static LIST_HEAD(driver_clients); + +-struct mctp_i2c_client; +- +-/* The netdev structure. One of these per I2C adapter. */ +-struct mctp_i2c_dev { +- struct net_device *ndev; +- struct i2c_adapter *adapter; +- struct mctp_i2c_client *client; +- struct list_head list; /* For mctp_i2c_client.devs */ +- +- size_t rx_pos; +- u8 rx_buffer[MCTP_I2C_BUFSZ]; +- struct completion rx_done; +- +- struct task_struct *tx_thread; +- wait_queue_head_t tx_wq; +- struct sk_buff_head tx_queue; +- u8 tx_scratch[MCTP_I2C_BUFSZ]; +- +- /* A fake entry in our tx queue to perform an unlock operation */ +- struct sk_buff unlock_marker; +- +- /* Spinlock protects i2c_lock_count, release_count, allow_rx */ +- spinlock_t lock; +- int i2c_lock_count; +- int release_count; +- /* Indicates that the netif is ready to receive incoming packets */ +- bool allow_rx; +- +-}; +- + /* The i2c client structure. One per hardware i2c bus at the top of the + * mux tree, shared by multiple netdevs + */ +@@ -177,8 +158,7 @@ static struct mctp_i2c_client *mctp_i2c_new_client(struct i2c_client *client) + return mcli; + err: + if (mcli) { +- if (mcli->client) +- i2c_unregister_device(mcli->client); ++ i2c_unregister_device(mcli->client); + kfree(mcli); + } + return ERR_PTR(rc); +@@ -235,8 +215,12 @@ static int mctp_i2c_slave_cb(struct i2c_client *client, + + spin_lock_irqsave(&mcli->sel_lock, flags); + midev = mcli->sel; +- if (midev) ++ if (midev) { + dev_hold(midev->ndev); ++ /* Track early exit if RX not allowed */ ++ if (!midev->allow_rx) ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_early_exit_no_rx); ++ } + spin_unlock_irqrestore(&mcli->sel_lock, flags); + + if (!midev) +@@ -249,6 +233,7 @@ static int mctp_i2c_slave_cb(struct i2c_client *client, + midev->rx_pos++; + } else { + midev->ndev->stats.rx_over_errors++; ++ trace_mctp_transport_error("i2c", midev->ndev, "rx_over_errors", midev->rx_pos); + } + + break; +@@ -283,6 +268,7 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + /* + 1 for the PEC */ + if (midev->rx_pos < MCTP_I2C_MINLEN + 1) { + ndev->stats.rx_length_errors++; ++ trace_mctp_transport_error("i2c", ndev, "rx_short_packet", midev->rx_pos); + return -EINVAL; + } + /* recvlen excludes PEC */ +@@ -291,11 +277,18 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + hdr = (void *)midev->rx_buffer; + if (hdr->command != MCTP_I2C_COMMANDCODE) { + ndev->stats.rx_dropped++; ++ /* UNKNOWN: Command byte checked before MCTP header can be read */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_invalid_cmd); ++ netdev_dbg(ndev, "MCTP I2C: invalid command code 0x%02x\n", hdr->command); ++ trace_mctp_transport_error("i2c", ndev, "invalid_command_code", hdr->command); + return -EINVAL; + } + + if (hdr->byte_count + offsetof(struct mctp_i2c_hdr, source_slave) != recvlen) { + ndev->stats.rx_length_errors++; ++ netdev_dbg(ndev, "MCTP I2C: length mismatch (byte_count=%u, recvlen=%zu)\n", ++ hdr->byte_count, recvlen); ++ trace_mctp_transport_error("i2c", ndev, "length_mismatch", hdr->byte_count); + return -EINVAL; + } + +@@ -303,12 +296,22 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + calc_pec = i2c_smbus_pec(0, midev->rx_buffer, recvlen); + if (pec != calc_pec) { + ndev->stats.rx_crc_errors++; ++ /* PEC validation failed - cannot trust header, track as UNKNOWN */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_invalid_pec); ++ netdev_dbg(ndev, "MCTP I2C: PEC error (got 0x%02x, expected 0x%02x)\n", ++ pec, calc_pec); ++ trace_mctp_transport_error("i2c", ndev, "pec_error", pec); + return -EINVAL; + } + + skb = netdev_alloc_skb(ndev, recvlen); + if (!skb) { ++ struct mctp_hdr *mh = (void *)(midev->rx_buffer + sizeof(struct mctp_i2c_hdr)); ++ u8 src_eid = mh->src; ++ + ndev->stats.rx_dropped++; ++ MCTP_STAT_INC(midev, src_eid, rx_drop_no_memory); ++ trace_mctp_transport_error("i2c", ndev, "rx_drop_no_memory", recvlen); + return -ENOMEM; + } + +@@ -322,26 +325,57 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + cb->halen = 1; + cb->haddr[0] = hdr->source_slave >> 1; + +- /* We need to ensure that the netif is not used once netdev +- * unregister occurs ++ /* ERROR INJECTION POINT: Fragment drop/corruption ++ * At this point: ++ * - I2C header has been removed ++ * - skb->data points to MCTP header ++ * - Before packet sent to network stack ++ * This is the ideal location to inject fragment errors + */ +- spin_lock_irqsave(&midev->lock, flags); +- if (midev->allow_rx) { +- reinit_completion(&midev->rx_done); +- spin_unlock_irqrestore(&midev->lock, flags); ++ if (mctp_i2c_error_inject_fragment(midev, skb)) { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 src_eid = mh->src; + +- status = netif_rx(skb); +- complete(&midev->rx_done); +- } else { +- status = NET_RX_DROP; +- spin_unlock_irqrestore(&midev->lock, flags); ++ /* Drop this fragment */ ++ ndev->stats.rx_dropped++; ++ MCTP_STAT_INC(midev, src_eid, rx_drop_fragment_error); ++ trace_mctp_transport_error("i2c", ndev, "rx_drop_fragment_error", 0); ++ kfree_skb(skb); ++ return 0; + } + +- if (status == NET_RX_SUCCESS) { +- ndev->stats.rx_packets++; +- ndev->stats.rx_bytes += recvlen; +- } else { +- ndev->stats.rx_dropped++; ++ /* Capture source EID before skb is consumed by netif_rx */ ++ { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 src_eid = mh->src; ++ ++ /* We need to ensure that the netif is not used once netdev ++ * unregister occurs ++ */ ++ spin_lock_irqsave(&midev->lock, flags); ++ if (midev->allow_rx) { ++ reinit_completion(&midev->rx_done); ++ spin_unlock_irqrestore(&midev->lock, flags); ++ ++ status = netif_rx(skb); ++ complete(&midev->rx_done); ++ } else { ++ status = NET_RX_DROP; ++ spin_unlock_irqrestore(&midev->lock, flags); ++ } ++ ++ if (status == NET_RX_SUCCESS) { ++ ndev->stats.rx_packets++; ++ ndev->stats.rx_bytes += recvlen; ++ netdev_dbg(ndev, "MCTP I2C: RX success from 0x%02x, %zu bytes\n", ++ hdr->source_slave >> 1, recvlen); ++ trace_mctp_transport_rx("i2c", ndev, hdr->source_slave >> 1, recvlen); ++ } else { ++ ndev->stats.rx_dropped++; ++ MCTP_STAT_INC(midev, src_eid, rx_drop_not_ready); ++ netdev_dbg(ndev, "MCTP I2C: RX dropped, status=%d\n", status); ++ trace_mctp_transport_error("i2c", ndev, "rx_dropped", status); ++ } + } + return 0; + } +@@ -478,18 +512,85 @@ static void mctp_i2c_invalidate_tx_flow(struct mctp_i2c_dev *midev, + mctp_i2c_unlock_nest(midev); + } + ++/* Helper function to perform I2C transfer with retries */ ++static int mctp_i2c_transfer_with_retry(struct mctp_i2c_dev *midev, ++ struct i2c_msg *msg, u8 dest_eid) ++{ ++ int retries = 0; ++ int rc; ++ ++ do { ++ rc = __i2c_transfer(midev->adapter, msg, 1); ++ ++ /* Success */ ++ if (rc >= 0) { ++ if (retries > 0) { ++ midev->nack_recovered++; ++ if (retries > midev->eid_stats.eid[dest_eid].tx_nack_retry_depth) ++ midev->eid_stats.eid[dest_eid].tx_nack_retry_depth = retries; ++ } ++ return rc; ++ } ++ ++ /* Check for NACK (-ENXIO) and matching address */ ++ if (rc == -ENXIO && ++ msg->addr == midev->nack_retry_addr && ++ retries < midev->nack_retries) { ++ ++ if (retries == 0) { ++ MCTP_STAT_INC(midev, dest_eid, tx_nack); ++ set_bit(dest_eid, midev->eid_stats.active); ++ } ++ MCTP_STAT_INC(midev, dest_eid, tx_nack_retries); ++ ++ midev->nack_total_retries++; ++ if (retries + 1 > midev->nack_max_depth) ++ midev->nack_max_depth = retries + 1; ++ ++ if (midev->nack_retry_delay_us) ++ usleep_range(midev->nack_retry_delay_us, ++ midev->nack_retry_delay_us + NACK_RETRY_DELAY_SLACK_US); ++ ++ retries++; ++ continue; ++ } ++ ++ /* Other error or retries exhausted */ ++ if (retries > 0) { ++ midev->nack_failed++; ++ if (retries > midev->eid_stats.eid[dest_eid].tx_nack_retry_depth) ++ midev->eid_stats.eid[dest_eid].tx_nack_retry_depth = retries; ++ dev_dbg(midev->ndev->dev.parent, ++ "NACK retry exhausted after %d attempts (addr 0x%02x)\n", ++ retries, msg->addr); ++ } ++ break; ++ ++ } while (1); ++ ++ return rc; ++} ++ + static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + { + struct net_device_stats *stats = &midev->ndev->stats; + enum mctp_i2c_flow_state fs; + struct mctp_i2c_hdr *hdr; ++ struct mctp_hdr *mh; + struct i2c_msg msg = {0}; ++ u8 dest_eid; + u8 *pecp; + int rc; + +- fs = mctp_i2c_get_tx_flow_state(midev, skb); ++ if (midev->flows_enabled) ++ fs = mctp_i2c_get_tx_flow_state(midev, skb); ++ else ++ fs = MCTP_I2C_TX_FLOW_NONE; + + hdr = (void *)skb_mac_header(skb); ++ mh = mctp_hdr(skb); ++ dest_eid = mh->dest; ++ + /* Sanity check that packet contents matches skb length, + * and can't exceed MCTP_I2C_BUFSZ + */ +@@ -497,6 +598,8 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + dev_warn_ratelimited(&midev->adapter->dev, + "Bad tx length %d vs skb %u\n", + hdr->byte_count + 3, skb->len); ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_invalid_len); ++ trace_mctp_transport_error("i2c", midev->ndev, "tx_invalid_len", skb->len); + return; + } + +@@ -521,7 +624,12 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + /* no flow: full lock & unlock */ + mctp_i2c_lock_nest(midev); + mctp_i2c_device_select(midev->client, midev); +- rc = __i2c_transfer(midev->adapter, &msg, 1); ++ ++ /* ERROR INJECTION POINT: TX transfer (synchronous error) */ ++ rc = mctp_i2c_error_inject_tx(midev, skb); ++ if (rc == 0) ++ rc = mctp_i2c_transfer_with_retry(midev, &msg, dest_eid); ++ + mctp_i2c_unlock_nest(midev); + break; + +@@ -535,7 +643,11 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + + case MCTP_I2C_TX_FLOW_EXISTING: + /* existing flow: we already have the lock; just tx */ +- rc = __i2c_transfer(midev->adapter, &msg, 1); ++ ++ /* ERROR INJECTION POINT: TX transfer (synchronous error) */ ++ rc = mctp_i2c_error_inject_tx(midev, skb); ++ if (rc == 0) ++ rc = mctp_i2c_transfer_with_retry(midev, &msg, dest_eid); + + /* on tx errors, the flow can no longer be considered valid */ + if (rc < 0) +@@ -548,12 +660,65 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + } + + if (rc < 0) { +- dev_warn_ratelimited(&midev->adapter->dev, +- "__i2c_transfer failed %d\n", rc); +- stats->tx_errors++; ++ struct mctp_sk_key *key = NULL; ++ struct sock *sk; ++ ++ /* Report error to socket error queue ++ * Look up socket and key. Key provides orig_payload for error reporting. ++ * TX key existence proves this was our transaction. ++ */ ++ sk = mctp_lookup_sock_for_error(skb, midev->ndev, NULL, &key); ++ ++ if (sk) { ++ /* Pass key so error report uses orig_payload (before fragmentation) */ ++ mctp_queue_error(sk, skb, -rc, midev->ndev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_SMBUS, key); ++ sock_put(sk); ++ } ++ ++ stats->tx_errors++; ++ ++ /* Track specific error types per-EID */ ++ if (rc == -ENOMEM) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_no_memory); ++ } else if (rc == -EBUSY) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_busy); ++ } else if (rc == -ENXIO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_no_device); ++ } else if (rc == -ENODEV) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_enodev); ++ } else if (rc == -EAGAIN) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_arbitration_lost); ++ } else if (rc == -EPROTO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_proto_error); ++ } else if (rc == -ETIMEDOUT) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_timeout); ++ } else if (rc == -EOPNOTSUPP) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eopnotsupp); ++ } else if (rc == -EINVAL) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_einval); ++ } else if (rc == -EAFNOSUPPORT) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eafnosupport); ++ } else if (rc == -EBADMSG) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_ebadmsg); ++ } else if (rc == -ESHUTDOWN) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eshutdown); ++ } else if (rc == -EIO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eio); ++ } else { ++ /* Unexpected error - shouldn't happen with comprehensive I2C error coverage */ ++ netdev_warn(midev->ndev, "MCTP I2C: Unhandled TX error code %d\n", rc); ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eio); ++ } ++ netdev_dbg(midev->ndev, "MCTP I2C: TX failed to 0x%02x, error=%d\n", ++ hdr->dest_slave >> 1, rc); ++ trace_mctp_transport_error("i2c", midev->ndev, "i2c_transfer_failed", rc); + } else { + stats->tx_bytes += skb->len; + stats->tx_packets++; ++ netdev_dbg(midev->ndev, "MCTP I2C: TX success to 0x%02x, %u bytes\n", ++ hdr->dest_slave >> 1, skb->len); ++ trace_mctp_transport_tx("i2c", midev->ndev, hdr->dest_slave >> 1, skb->len); + } + } + +@@ -619,8 +784,10 @@ static int mctp_i2c_tx_thread(void *data) + unsigned long flags; + + for (;;) { +- if (kthread_should_stop()) ++ if (kthread_should_stop()) { ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, tx_early_exit_stopped); + break; ++ } + + spin_lock_irqsave(&midev->tx_queue.lock, flags); + skb = __skb_dequeue(&midev->tx_queue); +@@ -700,6 +867,182 @@ static void mctp_i2c_release_flow(struct mctp_dev *mdev, + } + } + ++/* Ethtool statistics support */ ++/* Per-EID stat descriptors with abbreviated names */ ++struct mctp_i2c_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_I2C_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_i2c_eid_stats, field) \ ++} ++ ++static const struct mctp_i2c_eid_stat_desc mctp_i2c_eid_stat_descs[] = { ++ /* RX stats */ ++ MCTP_I2C_EID_STAT("rx_drop_invalid_cmd", rx_drop_invalid_cmd), ++ MCTP_I2C_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_I2C_EID_STAT("rx_drop_invalid_pec", rx_drop_invalid_pec), ++ MCTP_I2C_EID_STAT("rx_drop_fragment_error", rx_drop_fragment_error), ++ MCTP_I2C_EID_STAT("rx_drop_not_ready", rx_drop_not_ready), ++ MCTP_I2C_EID_STAT("rx_early_exit_no_rx", rx_early_exit_no_rx), ++ ++ /* TX stats - I2C-specific error codes */ ++ MCTP_I2C_EID_STAT("tx_drop_no_memory", tx_drop_no_memory), ++ MCTP_I2C_EID_STAT("tx_drop_busy", tx_drop_busy), ++ MCTP_I2C_EID_STAT("tx_drop_no_device", tx_drop_no_device), ++ MCTP_I2C_EID_STAT("tx_drop_enodev", tx_drop_enodev), ++ MCTP_I2C_EID_STAT("tx_drop_arbitration_lost", tx_drop_arbitration_lost), ++ MCTP_I2C_EID_STAT("tx_drop_proto_error", tx_drop_proto_error), ++ MCTP_I2C_EID_STAT("tx_drop_timeout", tx_drop_timeout), ++ MCTP_I2C_EID_STAT("tx_drop_eopnotsupp", tx_drop_eopnotsupp), ++ MCTP_I2C_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_I2C_EID_STAT("tx_drop_eafnosupport", tx_drop_eafnosupport), ++ MCTP_I2C_EID_STAT("tx_drop_ebadmsg", tx_drop_ebadmsg), ++ MCTP_I2C_EID_STAT("tx_drop_eshutdown", tx_drop_eshutdown), ++ MCTP_I2C_EID_STAT("tx_drop_eio", tx_drop_eio), ++ ++ /* TX stats - General */ ++ MCTP_I2C_EID_STAT("tx_drop_invalid_len", tx_drop_invalid_len), ++ MCTP_I2C_EID_STAT("tx_drop_queue_full", tx_drop_queue_full), ++ MCTP_I2C_EID_STAT("tx_drop_flow_invalid", tx_drop_flow_invalid), ++ MCTP_I2C_EID_STAT("tx_early_exit_stopped", tx_early_exit_stopped), ++ ++ /* Retry/requeue stats */ ++ MCTP_I2C_EID_STAT("tx_retries_attempted", tx_retries_attempted), ++ MCTP_I2C_EID_STAT("tx_retry_success", tx_retry_success), ++ MCTP_I2C_EID_STAT("tx_retry_exhausted", tx_retry_exhausted), ++ MCTP_I2C_EID_STAT("tx_requeued", tx_requeued), ++ MCTP_I2C_EID_STAT("tx_nack", tx_nack), ++ MCTP_I2C_EID_STAT("tx_nack_retries", tx_nack_retries), ++ MCTP_I2C_EID_STAT("tx_nack_retry_depth", tx_nack_retry_depth), ++}; ++ ++#define MCTP_I2C_EID_NUM_STATS ARRAY_SIZE(mctp_i2c_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_i2c, struct mctp_i2c_dev, ++ struct mctp_i2c_eid_stats, mctp_i2c_eid_stat_descs) ++ ++static void mctp_i2c_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_i2c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_i2c_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i2c_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (pre-parse ++ * errors like invalid command codes, PEC failures, or allocation ++ * failures before packet parsing) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: corrupted/invalid pkt"); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i2c_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_i2c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_i2c_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_I2C_EID_NUM_STATS + 1 + mctp_i2c_count_eid_stats(midev); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_i2c_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ u8 *base = (u8 *)&midev->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_i2c_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_i2c_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i2c_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_i2c_eid_stats_total(midev, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i2c_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_i2c_ethtool_ops = { ++ .get_strings = mctp_i2c_get_strings, ++ .get_sset_count = mctp_i2c_get_sset_count, ++ .get_ethtool_stats = mctp_i2c_get_ethtool_stats, ++}; ++ + static const struct net_device_ops mctp_i2c_ops = { + .ndo_start_xmit = mctp_i2c_start_xmit, + .ndo_uninit = mctp_i2c_ndo_uninit, +@@ -728,7 +1071,105 @@ static void mctp_i2c_net_setup(struct net_device *dev) + + dev->netdev_ops = &mctp_i2c_ops; + dev->header_ops = &mctp_i2c_headops; ++ dev->ethtool_ops = &mctp_i2c_ethtool_ops; ++} ++ ++/* ---- sysfs attributes for NACK retry tuning ---- */ ++ ++static ssize_t nack_retries_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ return sysfs_emit(buf, "%u\n", midev->nack_retries); ++} ++ ++static ssize_t nack_retries_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ u32 val; ++ ++ if (kstrtou32(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val > NACK_RETRY_MAX) ++ val = NACK_RETRY_MAX; ++ ++ midev->nack_retries = val; ++ return count; + } ++static DEVICE_ATTR_RW(nack_retries); ++ ++static ssize_t nack_retry_delay_us_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ return sysfs_emit(buf, "%u\n", midev->nack_retry_delay_us); ++} ++ ++static ssize_t nack_retry_delay_us_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ u32 val; ++ ++ if (kstrtou32(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val > NACK_RETRY_DELAY_MAX_US) ++ val = NACK_RETRY_DELAY_MAX_US; ++ ++ midev->nack_retry_delay_us = val; ++ return count; ++} ++static DEVICE_ATTR_RW(nack_retry_delay_us); ++ ++static ssize_t nack_retry_addr_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ return sysfs_emit(buf, "0x%02x\n", midev->nack_retry_addr); ++} ++ ++static ssize_t nack_retry_addr_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ u32 val; ++ ++ if (kstrtou32(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val > 0x7f) ++ val = NACK_RETRY_ADDR_DISABLED; ++ ++ midev->nack_retry_addr = val; ++ return count; ++} ++static DEVICE_ATTR_RW(nack_retry_addr); ++ ++static struct attribute *mctp_i2c_nack_attrs[] = { ++ &dev_attr_nack_retries.attr, ++ &dev_attr_nack_retry_delay_us.attr, ++ &dev_attr_nack_retry_addr.attr, ++ NULL, ++}; ++ ++static const struct attribute_group mctp_i2c_nack_group = { ++ .attrs = mctp_i2c_nack_attrs, ++}; + + /* Populates the mctp_i2c_dev priv struct for a netdev. + * Returns an error pointer on failure. +@@ -750,6 +1191,7 @@ static struct mctp_i2c_dev *mctp_i2c_midev_init(struct net_device *dev, + midev->adapter = adap; + get_device(&mcli->client->dev); + midev->client = mcli; ++ midev->flows_enabled = true; /* Default to enabled */ + INIT_LIST_HEAD(&midev->list); + spin_lock_init(&midev->lock); + midev->i2c_lock_count = 0; +@@ -770,6 +1212,30 @@ static struct mctp_i2c_dev *mctp_i2c_midev_init(struct net_device *dev, + /* Start the worker thread */ + wake_up_process(midev->tx_thread); + ++ /* Read NACK retry params from the MCTP I2C client's DT node */ ++ if (mcli->client->dev.of_node) { ++ if (of_property_read_u32(mcli->client->dev.of_node, "i2c-nack-retries", &midev->nack_retries)) ++ midev->nack_retries = 0; ++ else if (midev->nack_retries > NACK_RETRY_MAX) ++ midev->nack_retries = NACK_RETRY_MAX; ++ ++ if (of_property_read_u32(mcli->client->dev.of_node, "i2c-nack-retry-delay-us", &midev->nack_retry_delay_us)) ++ midev->nack_retry_delay_us = 0; ++ else if (midev->nack_retry_delay_us > NACK_RETRY_DELAY_MAX_US) ++ midev->nack_retry_delay_us = NACK_RETRY_DELAY_MAX_US; ++ ++ if (of_property_read_u32(mcli->client->dev.of_node, "i2c-nack-retry-addr", &midev->nack_retry_addr)) ++ midev->nack_retry_addr = NACK_RETRY_ADDR_DISABLED; ++ else if (midev->nack_retry_addr > 0x7f) ++ midev->nack_retry_addr = NACK_RETRY_ADDR_DISABLED; ++ } else { ++ midev->nack_retry_addr = NACK_RETRY_ADDR_DISABLED; ++ } ++ ++ netdev_info(dev, "NACK retry config: retries=%u delay=%uus addr=0x%02x\n", ++ midev->nack_retries, midev->nack_retry_delay_us, ++ midev->nack_retry_addr); ++ + return midev; + } + +@@ -787,6 +1253,8 @@ static void mctp_i2c_midev_free(struct mctp_i2c_dev *midev) + /* Unconditionally unlock on close */ + mctp_i2c_unlock_reset(midev); + ++ sysfs_remove_group(&midev->ndev->dev.kobj, &mctp_i2c_nack_group); ++ + /* Remove the netdev from the parent i2c client. */ + spin_lock_irqsave(&mcli->sel_lock, flags); + list_del(&midev->list); +@@ -808,6 +1276,9 @@ static void mctp_i2c_unregister(struct mctp_i2c_dev *midev) + { + unsigned long flags; + ++ /* Cleanup error injection */ ++ mctp_i2c_error_inject_cleanup(midev); ++ + /* Stop tx thread prior to unregister, it uses netif_() functions */ + kthread_stop(midev->tx_thread); + midev->tx_thread = NULL; +@@ -885,7 +1356,8 @@ static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli, + goto err; + } + +- rc = mctp_register_netdev(ndev, &mctp_i2c_mctp_ops); ++ rc = mctp_register_netdev(ndev, &mctp_i2c_mctp_ops, ++ MCTP_PHYS_BINDING_SMBUS); + if (rc < 0) { + dev_err(&mcli->client->dev, + "register netdev \"%s\" failed %d\n", +@@ -893,6 +1365,26 @@ static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli, + goto err; + } + ++ if (adap->dev.of_node) { ++ u32 timeout_ms; ++ ++ if (!of_property_read_u32(adap->dev.of_node, ++ "mctp-timeout-ms", &timeout_ms)) { ++ mctp_dev_set_timeout(ndev, timeout_ms); ++ } ++ ++ if (of_property_read_bool(adap->dev.of_node, "mctp-no-flows")) { ++ midev->flows_enabled = false; ++ dev_info(&mcli->client->dev, "MCTP flows disabled by DTS\n"); ++ } ++ } ++ ++ /* Setup error injection after netdev registration (debugfs needs the netdev name) */ ++ mctp_i2c_error_inject_init(midev); ++ ++ if (sysfs_create_group(&ndev->dev.kobj, &mctp_i2c_nack_group)) ++ dev_warn(&mcli->client->dev, "Failed to create NACK sysfs attrs\n"); ++ + spin_lock_irqsave(&midev->lock, flags); + midev->allow_rx = false; + spin_unlock_irqrestore(&midev->lock, flags); +@@ -1090,6 +1582,44 @@ static struct notifier_block mctp_i2c_notifier = { + .notifier_call = mctp_i2c_notifier_call, + }; + ++/* Netdevice notifier to re-attach ops after namespace change. ++ * When a netdev moves namespaces, NETDEV_UNREGISTER fires in the old ns ++ * which destroys the mctp_dev, then NETDEV_REGISTER fires in the new ns ++ * which creates a new mctp_dev but without the ops pointer. ++ * We need to re-attach the ops here. ++ */ ++static int mctp_i2c_netdev_notify(struct notifier_block *nb, ++ unsigned long event, void *ptr) ++{ ++ struct net_device *dev = netdev_notifier_info_to_dev(ptr); ++ struct mctp_dev *mdev; ++ ++ if (event != NETDEV_REGISTER) ++ return NOTIFY_DONE; ++ ++ /* Only handle our devices - check if this is an mctp-i2c netdev */ ++ if (dev->netdev_ops != &mctp_i2c_ops) ++ return NOTIFY_DONE; ++ ++ /* Get the mctp_dev and re-attach the ops */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (mdev) { ++ if (!mdev->ops) { ++ mdev->ops = &mctp_i2c_mctp_ops; ++ netdev_info(dev, "re-attached mctp ops after namespace change\n"); ++ } ++ mctp_dev_put(mdev); ++ } ++ rcu_read_unlock(); ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block mctp_i2c_netdev_nb = { ++ .notifier_call = mctp_i2c_netdev_notify, ++}; ++ + static const struct i2c_device_id mctp_i2c_id[] = { + { "mctp-i2c-interface" }, + {} +@@ -1117,12 +1647,29 @@ static __init int mctp_i2c_mod_init(void) + int rc; + + pr_info("MCTP I2C interface driver\n"); ++ ++ /* Initialize error injection infrastructure */ ++ rc = mctp_i2c_error_inject_module_init(); ++ if (rc) ++ pr_warn("MCTP I2C: Error injection initialization failed, continuing without it\n"); ++ + rc = i2c_add_driver(&mctp_i2c_driver); +- if (rc < 0) ++ if (rc < 0) { ++ mctp_i2c_error_inject_module_exit(); + return rc; ++ } + rc = bus_register_notifier(&i2c_bus_type, &mctp_i2c_notifier); + if (rc < 0) { + i2c_del_driver(&mctp_i2c_driver); ++ mctp_i2c_error_inject_module_exit(); ++ return rc; ++ } ++ /* Register netdev notifier to handle namespace changes */ ++ rc = register_netdevice_notifier(&mctp_i2c_netdev_nb); ++ if (rc < 0) { ++ bus_unregister_notifier(&i2c_bus_type, &mctp_i2c_notifier); ++ i2c_del_driver(&mctp_i2c_driver); ++ mctp_i2c_error_inject_module_exit(); + return rc; + } + return 0; +@@ -1132,10 +1679,12 @@ static __exit void mctp_i2c_mod_exit(void) + { + int rc; + ++ unregister_netdevice_notifier(&mctp_i2c_netdev_nb); + rc = bus_unregister_notifier(&i2c_bus_type, &mctp_i2c_notifier); + if (rc < 0) + pr_warn("MCTP I2C could not unregister notifier, %d\n", rc); + i2c_del_driver(&mctp_i2c_driver); ++ mctp_i2c_error_inject_module_exit(); + } + + module_init(mctp_i2c_mod_init); +diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c +index 47513ebbc..00038ad69 100644 +--- a/drivers/net/mctp/mctp-i3c.c ++++ b/drivers/net/mctp/mctp-i3c.c +@@ -14,9 +14,13 @@ + #include + #include + #include ++#include + #include + #include + ++#include "mctp-stats.h" ++#include ++ + #define MCTP_I3C_MAXBUF 65536 + /* 48 bit Provisioned Id */ + #define PID_SIZE 6 +@@ -67,6 +71,45 @@ struct mctp_i3c_bus { + struct i3c_bus *bus; + /* Head of mctp_i3c_device.list. Protected by busdevs_lock */ + struct list_head devs; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (IBI events, device hotplug, allocation failures, pre-parse errors) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_i3c_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_length_error; /* Tracked as UNKNOWN (before EID parsed) */ ++ u64 rx_drop_pec_error; /* Tracked as UNKNOWN */ ++ u64 rx_ibi_received; /* Total IBIs received */ ++ u64 rx_ibi_missing_mdb; /* IBI without MDB (UNKNOWN) */ ++ u64 rx_early_exit_warn_once; /* IBI warning triggered (UNKNOWN) */ ++ ++ /* TX stats */ ++ u64 tx_drop_no_device; /* Device lookup failed */ ++ u64 tx_drop_mwl_exceeded; /* Exceeds device MWL */ ++ u64 tx_drop_io_error; /* I3C transfer failed (EIO) */ ++ u64 tx_drop_enomem; ++ u64 tx_drop_eagain; ++ u64 tx_drop_einval; ++ u64 tx_drop_eio; ++ u64 tx_drop_enxio; ++ u64 tx_drop_ebusy; ++ u64 tx_drop_enotsupp; ++ u64 tx_drop_emsgsize; ++ u64 tx_early_exit_queue_stopped; /* Queue already stopped */ ++ ++ /* Device management (global, tracked under UNKNOWN) */ ++ u64 devices_active; /* Current active devices */ ++ u64 devices_added; /* Total devices added */ ++ u64 devices_removed; /* Total devices removed */ ++ } eid[257]; ++ } eid_stats; + }; + + struct mctp_i3c_device { +@@ -100,6 +143,7 @@ struct mctp_i3c_internal_hdr { + static int mctp_i3c_read(struct mctp_i3c_device *mi) + { + struct i3c_priv_xfer xfer = { .rnw = 1, .len = mi->mrl }; ++ struct net_device *ndev = mi->mbus->ndev; + struct net_device_stats *stats = &mi->mbus->ndev->stats; + struct mctp_i3c_internal_hdr *ihdr = NULL; + struct sk_buff *skb = NULL; +@@ -111,6 +155,9 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + mi->mrl + sizeof(struct mctp_i3c_internal_hdr)); + if (!skb) { + stats->rx_dropped++; ++ /* Can't extract EID - no packet data received yet */ ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_drop_no_memory); ++ trace_mctp_transport_error("i3c", ndev, "rx_drop_no_memory", mi->mrl); + rc = -ENOMEM; + goto err; + } +@@ -128,16 +175,28 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + /* Make sure netif_rx() is read in the same order as i3c. */ + mutex_lock(&mi->lock); + rc = i3c_device_do_priv_xfers(mi->i3c, &xfer, 1); +- if (rc < 0) ++ if (rc < 0) { ++ trace_mctp_transport_error("i3c", ndev, "rx_i3c_xfer_failed", rc); + goto err; ++ } + + if (WARN_ON_ONCE(xfer.len > mi->mrl)) { + /* Bad i3c bus driver */ ++ trace_mctp_transport_error("i3c", ndev, "rx_len_exceeds_mrl", xfer.len); + rc = -EIO; + goto err; + } + if (xfer.len < MCTP_I3C_MINLEN) { + stats->rx_length_errors++; ++ /* Try to extract EID if we have at least MCTP header */ ++ if (xfer.len >= sizeof(struct mctp_hdr)) { ++ struct mctp_hdr *mh = (struct mctp_hdr *)xfer.data.in; ++ MCTP_STAT_INC(mi->mbus, mh->src, rx_drop_length_error); ++ } else { ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_drop_length_error); ++ } ++ netdev_dbg(ndev, "MCTP I3C: RX length error %d\n", xfer.len); ++ trace_mctp_transport_error("i3c", ndev, "rx_length_error", xfer.len); + rc = -EIO; + goto err; + } +@@ -148,6 +207,15 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + pec = i2c_smbus_pec(pec, xfer.data.in, xfer.len - 1); + if (pec != ((u8 *)xfer.data.in)[xfer.len - 1]) { + stats->rx_crc_errors++; ++ /* Extract EID for per-EID tracking (we have valid length, attempt EID extraction) */ ++ if (xfer.len >= sizeof(struct mctp_hdr) + 1) { /* +1 for PEC */ ++ struct mctp_hdr *mh = (struct mctp_hdr *)xfer.data.in; ++ MCTP_STAT_INC(mi->mbus, mh->src, rx_drop_pec_error); ++ } else { ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_drop_pec_error); ++ } ++ netdev_dbg(ndev, "MCTP I3C: PEC error\n"); ++ trace_mctp_transport_error("i3c", ndev, "pec_error", pec); + rc = -EINVAL; + goto err; + } +@@ -164,8 +232,12 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + if (net_status == NET_RX_SUCCESS) { + stats->rx_packets++; + stats->rx_bytes += xfer.len - 1; ++ netdev_dbg(ndev, "MCTP I3C: RX success, %u bytes\n", xfer.len - 1); ++ trace_mctp_transport_rx("i3c", ndev, 0, xfer.len - 1); + } else { + stats->rx_dropped++; ++ netdev_dbg(ndev, "MCTP I3C: RX dropped, status=%d\n", net_status); ++ trace_mctp_transport_error("i3c", ndev, "rx_dropped", net_status); + } + + mutex_unlock(&mi->lock); +@@ -181,8 +253,15 @@ static void mctp_i3c_ibi_handler(struct i3c_device *i3c, + { + struct mctp_i3c_device *mi = i3cdev_get_drvdata(i3c); + +- if (WARN_ON_ONCE(!mi)) ++ if (WARN_ON_ONCE(!mi)) { ++ /* Track early exit due to missing device data - no EID context */ ++ if (mi && mi->mbus) ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_early_exit_warn_once); + return; ++ } ++ ++ /* IBI received - tracked as UNKNOWN (no EID context at IBI time) */ ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_ibi_received); + + if (mi->have_mdb) { + if (payload->len > 0) { +@@ -195,6 +274,7 @@ static void mctp_i3c_ibi_handler(struct i3c_device *i3c, + * device didn't send one. + */ + dev_warn_once(i3cdev_to_dev(i3c), "IBI with missing MDB"); ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_ibi_missing_mdb); + } + } + +@@ -268,6 +348,9 @@ __must_hold(&busdevs_lock) + mi->i3c = i3c; + mutex_init(&mi->lock); + list_add(&mi->list, &mbus->devs); ++ /* Device management stats tracked as UNKNOWN (no EID context) */ ++ MCTP_STAT_INC(mbus, MCTP_EID_UNKNOWN, devices_added); ++ MCTP_STAT_INC(mbus, MCTP_EID_UNKNOWN, devices_active); + + i3cdev_set_drvdata(i3c, mi); + rc = mctp_i3c_setup(mi); +@@ -319,6 +402,10 @@ __must_hold(&busdevs_lock) + /* Counterpart of mctp_i3c_add_device */ + i3cdev_set_drvdata(mi->i3c, NULL); + list_del(&mi->list); ++ /* Device management stats tracked as UNKNOWN (no EID context) */ ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, devices_removed); ++ mi->mbus->eid_stats.eid[MCTP_EID_UNKNOWN].devices_active--; ++ set_bit(MCTP_EID_UNKNOWN, mi->mbus->eid_stats.active); + + /* Safe to unlock after removing from the list */ + mutex_unlock(&mi->lock); +@@ -359,19 +446,26 @@ mctp_i3c_lookup(struct mctp_i3c_bus *mbus, u64 pid) + + static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb) + { ++ struct net_device *ndev = mbus->ndev; + struct net_device_stats *stats = &mbus->ndev->stats; + struct i3c_priv_xfer xfer = { .rnw = false }; + struct mctp_i3c_internal_hdr *ihdr = NULL; + struct mctp_i3c_device *mi = NULL; ++ struct mctp_hdr *mh; + unsigned int data_len; + u8 *data = NULL; + u8 addr, pec; ++ u8 dest_eid; + int rc = 0; + u64 pid; + + skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr)); + data_len = skb->len; + ++ /* Extract destination EID for per-EID tracking */ ++ mh = mctp_hdr(skb); ++ dest_eid = mh->dest; ++ + ihdr = (void *)skb_mac_header(skb); + + pid = get_unaligned_be48(ihdr->dest); +@@ -379,15 +473,21 @@ static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb) + if (!mi) { + /* I3C endpoint went away after the packet was enqueued? */ + stats->tx_dropped++; ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_no_device); ++ trace_mctp_transport_error("i3c", ndev, "tx_no_device", 0); + goto out; + } + +- if (WARN_ON_ONCE(data_len + 1 > MCTP_I3C_MAXBUF)) ++ if (WARN_ON_ONCE(data_len + 1 > MCTP_I3C_MAXBUF)) { ++ trace_mctp_transport_error("i3c", ndev, "tx_len_exceeds_maxbuf", data_len); + goto out; ++ } + + if (data_len + 1 > (unsigned int)mi->mwl) { + /* Route MTU was larger than supported by the endpoint */ + stats->tx_dropped++; ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_mwl_exceeded); ++ trace_mctp_transport_error("i3c", ndev, "tx_len_exceeds_mwl", data_len); + goto out; + } + +@@ -413,8 +513,31 @@ static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb) + if (rc == 0) { + stats->tx_bytes += data_len; + stats->tx_packets++; ++ netdev_dbg(ndev, "MCTP I3C: TX success, %u bytes\n", data_len); ++ trace_mctp_transport_tx("i3c", ndev, 0, data_len); + } else { + stats->tx_errors++; ++ if (rc == -ENXIO) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_enxio); ++ } else if (rc == -EAGAIN) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_eagain); ++ } else if (rc == -EBUSY) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_ebusy); ++ } else if (rc == -ENOTSUPP || rc == -EOPNOTSUPP) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_enotsupp); ++ } else if (rc == -EINVAL) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_einval); ++ } else if (rc == -ENOMEM) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_enomem); ++ } else if (rc == -EMSGSIZE) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_emsgsize); ++ } else if (rc == -EIO) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_eio); ++ } else { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_io_error); ++ } ++ netdev_dbg(ndev, "MCTP I3C: TX failed, error=%d\n", rc); ++ trace_mctp_transport_error("i3c", ndev, "i3c_xfer_failed", rc); + } + + out: +@@ -461,6 +584,13 @@ static netdev_tx_t mctp_i3c_start_xmit(struct sk_buff *skb, + netif_stop_queue(ndev); + if (mbus->tx_skb) { + dev_warn_ratelimited(&ndev->dev, "TX with queue stopped"); ++ /* Extract dest EID for tracking */ ++ struct mctp_i3c_internal_hdr *ihdr = (void *)skb_mac_header(skb); ++ struct mctp_hdr *mh; ++ skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr)); ++ mh = mctp_hdr(skb); ++ MCTP_STAT_INC(mbus, mh->dest, tx_early_exit_queue_stopped); ++ skb_push(skb, sizeof(struct mctp_i3c_internal_hdr)); + ret = NETDEV_TX_BUSY; + } else { + mbus->tx_skb = skb; +@@ -523,6 +653,165 @@ static int mctp_i3c_header_create(struct sk_buff *skb, struct net_device *dev, + return 0; + } + ++/* Ethtool statistics support - Per-EID stat descriptors with abbreviated names */ ++struct mctp_i3c_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_I3C_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_i3c_eid_stats, field) \ ++} ++ ++static const struct mctp_i3c_eid_stat_desc mctp_i3c_eid_stat_descs[] = { ++ MCTP_I3C_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_I3C_EID_STAT("rx_drop_length_error", rx_drop_length_error), ++ MCTP_I3C_EID_STAT("rx_drop_pec_error", rx_drop_pec_error), ++ MCTP_I3C_EID_STAT("rx_ibi_received", rx_ibi_received), ++ MCTP_I3C_EID_STAT("rx_ibi_missing_mdb", rx_ibi_missing_mdb), ++ MCTP_I3C_EID_STAT("rx_early_exit_warn_once", rx_early_exit_warn_once), ++ MCTP_I3C_EID_STAT("tx_drop_no_device", tx_drop_no_device), ++ MCTP_I3C_EID_STAT("tx_drop_mwl_exceeded", tx_drop_mwl_exceeded), ++ MCTP_I3C_EID_STAT("tx_drop_io_error", tx_drop_io_error), ++ MCTP_I3C_EID_STAT("tx_drop_enomem", tx_drop_enomem), ++ MCTP_I3C_EID_STAT("tx_drop_eagain", tx_drop_eagain), ++ MCTP_I3C_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_I3C_EID_STAT("tx_drop_eio", tx_drop_eio), ++ MCTP_I3C_EID_STAT("tx_drop_enxio", tx_drop_enxio), ++ MCTP_I3C_EID_STAT("tx_drop_ebusy", tx_drop_ebusy), ++ MCTP_I3C_EID_STAT("tx_drop_enotsupp", tx_drop_enotsupp), ++ MCTP_I3C_EID_STAT("tx_drop_emsgsize", tx_drop_emsgsize), ++ MCTP_I3C_EID_STAT("tx_early_exit_queue_stopped", tx_early_exit_queue_stopped), ++ MCTP_I3C_EID_STAT("devices_active", devices_active), ++ MCTP_I3C_EID_STAT("devices_added", devices_added), ++ MCTP_I3C_EID_STAT("devices_removed", devices_removed), ++}; ++ ++#define MCTP_I3C_EID_NUM_STATS ARRAY_SIZE(mctp_i3c_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_i3c, struct mctp_i3c_bus, ++ struct mctp_i3c_eid_stats, mctp_i3c_eid_stat_descs) ++ ++static void mctp_i3c_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_i3c_bus *mbus = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_i3c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, mbus->eid_stats.active, 257) { ++ struct mctp_i3c_eid_stats *es = &mbus->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i3c_count_eid_nonzero(mbus, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (I3C IBI ++ * in-band interrupts, device hotplug events, allocation failures, ++ * or pre-parse errors like invalid packet length) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: IBI/device events "); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i3c_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_i3c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_i3c_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_i3c_bus *mbus = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_I3C_EID_NUM_STATS + 1 + mctp_i3c_count_eid_stats(mbus); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_i3c_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_i3c_bus *mbus = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, mbus->eid_stats.active, 257) { ++ u8 *base = (u8 *)&mbus->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_i3c_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, mbus->eid_stats.active, 257) { ++ struct mctp_i3c_eid_stats *es = &mbus->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i3c_count_eid_nonzero(mbus, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_i3c_eid_stats_total(mbus, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i3c_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_i3c_ethtool_ops = { ++ .get_strings = mctp_i3c_get_strings, ++ .get_sset_count = mctp_i3c_get_sset_count, ++ .get_ethtool_stats = mctp_i3c_get_ethtool_stats, ++}; ++ + static const struct net_device_ops mctp_i3c_ops = { + .ndo_start_xmit = mctp_i3c_start_xmit, + .ndo_uninit = mctp_i3c_ndo_uninit, +@@ -546,6 +835,7 @@ static void mctp_i3c_net_setup(struct net_device *dev) + + dev->netdev_ops = &mctp_i3c_ops; + dev->header_ops = &mctp_i3c_headops; ++ dev->ethtool_ops = &mctp_i3c_ethtool_ops; + } + + static bool mctp_i3c_is_mctp_controller(struct i3c_bus *bus) +@@ -619,7 +909,7 @@ __must_hold(&busdevs_lock) + goto err_free_uninit; + } + +- rc = mctp_register_netdev(ndev, NULL); ++ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_I3C); + if (rc < 0) { + dev_warn(&ndev->dev, "netdev register failed: %d\n", rc); + goto err_free_netdev; +diff --git a/drivers/net/mctp/mctp-serial.c b/drivers/net/mctp/mctp-serial.c +index e63720ec3..26c9a33fd 100644 +--- a/drivers/net/mctp/mctp-serial.c ++++ b/drivers/net/mctp/mctp-serial.c +@@ -23,6 +23,7 @@ + + #include + #include ++#include + #include + + #define MCTP_SERIAL_MTU 68 /* base mtu (64) + mctp header */ +@@ -470,7 +471,7 @@ static int mctp_serial_open(struct tty_struct *tty) + spin_lock_init(&dev->lock); + INIT_WORK(&dev->tx_work, mctp_serial_tx_work); + +- rc = register_netdev(ndev); ++ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_SERIAL); + if (rc) + goto free_netdev; + +@@ -492,7 +493,7 @@ static void mctp_serial_close(struct tty_struct *tty) + struct mctp_serial *dev = tty->disc_data; + int idx = dev->idx; + +- unregister_netdev(dev->netdev); ++ mctp_unregister_netdev(dev->netdev); + ida_free(&mctp_serial_ida, idx); + } + +diff --git a/drivers/net/mctp/mctp-spi-error-inject.c b/drivers/net/mctp/mctp-spi-error-inject.c +new file mode 100644 +index 000000000..b342c4744 +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi-error-inject.c +@@ -0,0 +1,596 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * MCTP SPI Error Injection Implementation ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over SPI binding. ++ * ++ * Interface unified with I2C/USB error injection where applicable. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-spi-internal.h" ++ ++/* Forward declare SpbApStatus enum values - avoid including glacier-spb-ap.h ++ * to prevent multiple definition linker errors */ ++#define SPB_AP_OK 0 ++#define SPB_AP_MESSAGE_AVAILABLE 1 ++#define SPB_AP_ERROR_INVALID_ARGUMENT 2 ++#define SPB_AP_ERROR_TIMEOUT 3 ++#define SPB_AP_ERROR_UNKNOWN 4 ++ ++/* Constants from mctp-spi.c */ ++#define BUFSIZE 256 ++ ++/* Global debugfs root for all MCTP SPI error injection */ ++static struct dentry *mctp_spi_error_inject_root; ++ ++/* Check if error should be injected based on mode and rate */ ++static bool mctp_should_inject_error(struct mctp_spi_error_inject *ei, u32 rate, u32 *count) ++{ ++ bool inject = false; ++ ++ spin_lock_bh(&ei->lock); ++ ++ switch (ei->mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ inject = true; ++ break; ++ ++ case MCTP_ERR_MODE_RANDOM: ++ /* Random injection based on rate percentage */ ++ inject = (prandom_u32_state(&ei->rng) % 100) < rate; ++ break; ++ ++ case MCTP_ERR_MODE_COUNT: ++ /* Inject for count packets, then stop */ ++ if (*count > 0) { ++ (*count)--; ++ inject = true; ++ } ++ break; ++ } ++ ++ spin_unlock_bh(&ei->lock); ++ ++ return inject; ++} ++ ++/* Check if packet matches EID filter */ ++static bool mctp_error_inject_match_filter(struct mctp_spi_error_inject *ei, ++ struct sk_buff *skb) ++{ ++ struct mctp_hdr *mh; ++ struct mctp_spi_hdr { ++ u8 command_code; ++ u8 byte_count; ++ u8 resrv[2]; ++ } *spi_hdr; ++ ++ if (!ei->eid_filter.enabled) ++ return true; ++ ++ /* Skip SPI header to get to MCTP header */ ++ spi_hdr = (void *)skb->data; ++ mh = (struct mctp_hdr *)(skb->data + sizeof(*spi_hdr)); ++ ++ /* Check source EID */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) ++ return false; ++ ++ /* Check dest EID */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) ++ return false; ++ ++ return true; ++} ++ ++/** ++ * mctp_spi_error_inject_tx - Inject TX error ++ * @midev: MCTP SPI device ++ * @skb: Packet being transmitted ++ * @injected_status: Output - SPB_AP status code to inject ++ * ++ * Returns: 0 for normal operation, 1 to inject error ++ * ++ * Called before spb_ap_send() to potentially inject a TX error. ++ */ ++int mctp_spi_error_inject_tx(struct mctp_spi *midev, struct sk_buff *skb, int *injected_status) ++{ ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ struct mctp_hdr *mh; ++ struct mctp_spi_hdr { ++ u8 command_code; ++ u8 byte_count; ++ u8 resrv[2]; ++ } *spi_hdr; ++ ++ /* Early exit if TX error injection is disabled or code not set */ ++ if (!ei->enable_tx || ei->spi_tx_error_code == 0) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb)) ++ return 0; ++ ++ if (!mctp_should_inject_error(ei, ei->spi_tx_error_rate, ++ &ei->spi_tx_inject_count)) ++ return 0; ++ ++ /* Inject delay if configured */ ++ if (ei->delay_ms > 0) ++ msleep(ei->delay_ms); ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->spi_tx_errors_injected++; ++ ei->total_errors_injected++; ++ ei->total_packets_processed++; ++ spin_unlock_bh(&ei->lock); ++ ++ spi_hdr = (void *)skb->data; ++ mh = (struct mctp_hdr *)(skb->data + sizeof(*spi_hdr)); ++ ++ netdev_info(midev->ndev, ++ "ERROR INJECTION: SPI TX - error=%d, src_eid=%u, dest_eid=%u, " ++ "total_injected=%u, mode=%s\n", ++ ei->spi_tx_error_code, mh->src, mh->dest, ++ ei->spi_tx_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ *injected_status = ei->spi_tx_error_code; ++ return 1; ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_tx); ++ ++/* Debugfs attribute files */ ++static ssize_t enable_tx_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[4]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.enable_tx); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t enable_tx_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[4]; ++ bool val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtobool(buf, &val); ++ if (ret) ++ return ret; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.enable_tx = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_tx_fops = { ++ .owner = THIS_MODULE, ++ .read = enable_tx_read, ++ .write = enable_tx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t error_code_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.spi_tx_error_code); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t error_code_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtoint(buf, 10, &val); ++ if (ret) ++ return ret; ++ ++ /* Valid SPB_AP error codes: 2=INVALID_ARG, 3=TIMEOUT, 4=UNKNOWN */ ++ if (val < 0 || val > 10) ++ return -EINVAL; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.spi_tx_error_code = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_spi_tx_error_code_fops = { ++ .owner = THIS_MODULE, ++ .read = error_code_read, ++ .write = error_code_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t mode_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ const char *mode_str; ++ char buf[32]; ++ int len; ++ ++ switch (midev->error_inject.mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ mode_str = "always\n"; ++ break; ++ case MCTP_ERR_MODE_RANDOM: ++ mode_str = "random\n"; ++ break; ++ case MCTP_ERR_MODE_COUNT: ++ mode_str = "count\n"; ++ break; ++ default: ++ mode_str = "unknown\n"; ++ } ++ ++ len = snprintf(buf, sizeof(buf), "%s", mode_str); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t mode_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ ++ if (strncmp(buf, "always", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ else if (strncmp(buf, "random", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_RANDOM; ++ else if (strncmp(buf, "count", 5) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_COUNT; ++ else { ++ spin_unlock_bh(&midev->error_inject.lock); ++ return -EINVAL; ++ } ++ ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_mode_fops = { ++ .owner = THIS_MODULE, ++ .read = mode_read, ++ .write = mode_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t rate_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.spi_tx_error_rate); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t rate_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ u32 val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtou32(buf, 10, &val); ++ if (ret) ++ return ret; ++ ++ if (val > 100) ++ return -EINVAL; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.spi_tx_error_rate = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_spi_tx_error_rate_fops = { ++ .owner = THIS_MODULE, ++ .read = rate_read, ++ .write = rate_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t inject_count_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.spi_tx_inject_count); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t inject_count_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ u32 val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtou32(buf, 10, &val); ++ if (ret) ++ return ret; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.spi_tx_inject_count = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++ ++/* stats attribute (read-only) - unified with USB/I2C */ ++static ssize_t stats_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ char *buf; ++ int len = 0; ++ ssize_t ret; ++ ++ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ spin_lock_bh(&ei->lock); ++ ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_tx: %d\n", ei->enable_tx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "mode: %s\n", ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ len += snprintf(buf + len, PAGE_SIZE - len, "spi_tx_errors_injected: %u\n", ++ ei->spi_tx_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_packets_processed: %llu\n", ++ ei->total_packets_processed); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_errors_injected: %llu\n", ++ ei->total_errors_injected); ++ ++ spin_unlock_bh(&ei->lock); ++ ++ ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static const struct file_operations mctp_debugfs_stats_fops = { ++ .owner = THIS_MODULE, ++ .read = stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* reset attribute (write-only) - unified with USB/I2C */ ++static ssize_t reset_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ char buf[8]; ++ int val; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtoint(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (val != 1) ++ return -EINVAL; ++ ++ spin_lock_bh(&ei->lock); ++ ++ /* Reset all configuration - unified with USB/I2C */ ++ ei->enable_tx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->spi_tx_error_code = 0; ++ ei->spi_tx_error_rate = 0; ++ ei->spi_tx_inject_count = 0; ++ ei->delay_ms = 0; ++ ei->eid_filter.enabled = false; ++ ei->eid_filter.src_eid = 0; ++ ei->eid_filter.dest_eid = 0; ++ ei->eid_filter.msg_type = 0; ++ ++ /* Reset all statistics */ ++ ei->spi_tx_errors_injected = 0; ++ ei->total_packets_processed = 0; ++ ei->total_errors_injected = 0; ++ ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, "Error injection reset\n"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_reset_fops = { ++ .owner = THIS_MODULE, ++ .write = reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/** ++ * mctp_spi_error_inject_init - Initialize error injection for a device ++ * @midev: MCTP SPI device ++ */ ++void mctp_spi_error_inject_init(struct mctp_spi *midev) ++{ ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ ++ /* Initialize error injection state */ ++ memset(ei, 0, sizeof(*ei)); ++ spin_lock_init(&ei->lock); ++ prandom_seed_state(&ei->rng, get_random_u32()); ++ ++ /* Default configuration */ ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->spi_tx_error_rate = 100; ++ ++ /* Create device-specific debugfs directory */ ++ if (!mctp_spi_error_inject_root) ++ return; ++ ++ midev->debugfs_dir = debugfs_create_dir(midev->ndev->name, ++ mctp_spi_error_inject_root); ++ if (IS_ERR_OR_NULL(midev->debugfs_dir)) { ++ netdev_warn(midev->ndev, "Failed to create debugfs directory\n"); ++ midev->debugfs_dir = NULL; ++ return; ++ } ++ ++ /* Create debugfs files - unified with USB/I2C */ ++ debugfs_create_file("enable_tx", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_enable_tx_fops); ++ debugfs_create_file("mode", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_mode_fops); ++ debugfs_create_file("spi_tx_error_code", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_spi_tx_error_code_fops); ++ debugfs_create_file("spi_tx_error_rate", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_spi_tx_error_rate_fops); ++ debugfs_create_file("stats", 0400, midev->debugfs_dir, midev, ++ &mctp_debugfs_stats_fops); ++ debugfs_create_file("reset", 0200, midev->debugfs_dir, midev, ++ &mctp_debugfs_reset_fops); ++ ++ netdev_info(midev->ndev, "Error injection debugfs created at /sys/kernel/debug/mctp_spi/%s\n", ++ midev->ndev->name); ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_init); ++ ++/** ++ * mctp_spi_error_inject_cleanup - Cleanup error injection for a device ++ * @midev: MCTP SPI device ++ */ ++void mctp_spi_error_inject_cleanup(struct mctp_spi *midev) ++{ ++ if (midev->debugfs_dir) { ++ debugfs_remove_recursive(midev->debugfs_dir); ++ midev->debugfs_dir = NULL; ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_cleanup); ++ ++/** ++ * mctp_spi_error_inject_module_init - Initialize module-wide debugfs ++ */ ++int mctp_spi_error_inject_module_init(void) ++{ ++ mctp_spi_error_inject_root = debugfs_create_dir("mctp_spi", NULL); ++ if (IS_ERR_OR_NULL(mctp_spi_error_inject_root)) { ++ pr_warn("MCTP SPI: Failed to create debugfs root, error injection disabled\n"); ++ mctp_spi_error_inject_root = NULL; ++ return -ENODEV; ++ } ++ ++ pr_info("MCTP SPI: Error injection initialized at /sys/kernel/debug/mctp_spi/\n"); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_module_init); ++ ++/** ++ * mctp_spi_error_inject_module_exit - Cleanup module-wide debugfs ++ */ ++void mctp_spi_error_inject_module_exit(void) ++{ ++ if (mctp_spi_error_inject_root) { ++ debugfs_remove_recursive(mctp_spi_error_inject_root); ++ mctp_spi_error_inject_root = NULL; ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_module_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MCTP SPI Error Injection"); ++ +diff --git a/drivers/net/mctp/mctp-spi-error-inject.h b/drivers/net/mctp/mctp-spi-error-inject.h +new file mode 100644 +index 000000000..bbf67e633 +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi-error-inject.h +@@ -0,0 +1,86 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP SPI Error Injection Infrastructure ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over SPI binding. ++ * Interface unified with I2C/USB error injection. ++ */ ++ ++#ifndef __MCTP_SPI_ERROR_INJECT_H ++#define __MCTP_SPI_ERROR_INJECT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Forward declarations */ ++struct dentry; ++ ++/* Error injection modes - unified with I2C/USB */ ++enum mctp_error_inject_mode { ++ MCTP_ERR_MODE_ALWAYS, ++ MCTP_ERR_MODE_RANDOM, ++ MCTP_ERR_MODE_COUNT ++}; ++ ++/* Error injection control structure - unified interface */ ++struct mctp_spi_error_inject { ++ bool enable_tx; /* TX error injection enable */ ++ enum mctp_error_inject_mode mode; ++ ++ /* TX injection - SPI uses SPB_AP return codes */ ++ int spi_tx_error_code; /* SPB_AP error code (SPB_AP_ERROR_TIMEOUT, etc.) */ ++ u32 spi_tx_error_rate; /* Percentage (0-100) or count */ ++ u32 spi_tx_inject_count; /* Counter for count mode */ ++ u32 spi_tx_errors_injected; ++ ++ /* Delay injection */ ++ u32 delay_ms; ++ ++ /* EID filtering - unified with I2C/USB */ ++ struct { ++ bool enabled; ++ u8 src_eid; /* 0 = any */ ++ u8 dest_eid; /* 0 = any */ ++ u8 msg_type; /* 0 = any */ ++ } eid_filter; ++ ++ /* Statistics */ ++ u64 total_packets_processed; ++ u64 total_errors_injected; ++ ++ /* RNG state */ ++ struct rnd_state rng; ++ spinlock_t lock; ++}; ++ ++/* Forward declaration - full definition in mctp-spi.c */ ++struct mctp_spi; ++ ++/* Module init/exit functions */ ++int mctp_spi_error_inject_module_init(void); ++void mctp_spi_error_inject_module_exit(void); ++ ++/* Public API for main driver */ ++void mctp_spi_error_inject_init(struct mctp_spi *midev); ++void mctp_spi_error_inject_cleanup(struct mctp_spi *midev); ++ ++int mctp_spi_error_inject_tx(struct mctp_spi *midev, struct sk_buff *skb, int *injected_status); ++ ++#endif /* __MCTP_SPI_ERROR_INJECT_H */ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/drivers/net/mctp/mctp-spi-internal.h b/drivers/net/mctp/mctp-spi-internal.h +new file mode 100644 +index 000000000..16d87268a +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi-internal.h +@@ -0,0 +1,97 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP SPI Internal Definitions ++ * Shared between mctp-spi.c and mctp-spi-error-inject.c ++ */ ++ ++#ifndef __MCTP_SPI_INTERNAL_H ++#define __MCTP_SPI_INTERNAL_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-spi-error-inject.h" ++#include "glacier-spb-ap.h" ++ ++#define RX_BUFFER_SIZE 1024 ++ ++/* SPI device data structure */ ++struct spidev_data { ++ dev_t devt; ++ spinlock_t spi_lock; ++ struct spi_device *spi; ++ struct list_head device_entry; ++ ++ struct mutex buf_lock; ++ unsigned users; ++ u8 *tx_buffer; ++ size_t tx_len; ++ u8 rx_buffer[RX_BUFFER_SIZE]; ++ size_t rx_len; ++ u32 speed_hz; ++}; ++ ++/* Main device structure - one per SPI device */ ++struct mctp_spi { ++ struct net_device *ndev; ++ struct spidev_data *spidev; ++ ++ struct task_struct *tx_thread; ++ wait_queue_head_t main_thread_wq; ++ struct sk_buff_head tx_queue; ++ spinlock_t lock; ++ bool allow_rx; ++ struct completion rx_done; ++ ++ struct gpio_desc *rx_alert; /* Input gpio to alert about the incoming package from SPI */ ++ int rx_alert_irq; ++ ++ SpbAp *ap; ++ wait_queue_head_t gpio_intr_wq; ++ bool gpio_intr_cond; ++ spinlock_t gpio_intr_cond_lock; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (GPIO interrupts, SPI transfer errors, allocation failures) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_spi_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_not_ready; /* Tracked as UNKNOWN (before EID known) */ ++ u64 rx_drop_spi_error; /* Tracked as UNKNOWN */ ++ ++ /* TX stats - SPB AP specific errors */ ++ u64 tx_drop_etimedout; /* SPB_AP_ERROR_TIMEOUT */ ++ u64 tx_drop_einval; /* SPB_AP_ERROR_INVALID_ARGUMENT */ ++ u64 tx_drop_eio; /* SPB_AP_ERROR_UNKNOWN */ ++ u64 tx_drop_spi_error; /* Catch-all for unmapped errors */ ++ ++ /* GPIO interrupt tracking (UNKNOWN - no EID context) */ ++ u64 gpio_interrupts; ++ } eid[257]; ++ } eid_stats; ++ ++ /* Error injection support */ ++ struct mctp_spi_error_inject error_inject; ++ struct dentry *debugfs_dir; ++}; ++ ++/* SPI transport header */ ++struct mctp_spi_hdr { ++ u8 command_code; ++ u8 byte_count; ++ u8 resrv[2]; ++}; ++ ++#endif /* __MCTP_SPI_INTERNAL_H */ ++ +diff --git a/drivers/net/mctp/mctp-spi.c b/drivers/net/mctp/mctp-spi.c +new file mode 100644 +index 000000000..606029dc5 +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi.c +@@ -0,0 +1,772 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "glacier-spb-ap.h" ++#include "mctp-spi-internal.h" ++#include "mctp-stats.h" ++#include "mctp-spi-error-inject.h" ++ ++static DEFINE_IDA(mctp_spi_ida); ++ ++#define MCTP_SPI_MAXMTU (64 + 4) ++#define MCTP_SPI_MINMTU (64 + 4) ++#define MCTP_SPI_TX_QUEUE_LEN 1100 ++#define BUFSIZE 256 ++#define MCTP_SPI_TX_WORK_LEN 100 ++#define MCTP_COMMAND_CODE 0x02 ++ ++#define ERR_SPI_RX_NO_DATA -2 ++ ++/** ++ * spb_ap_status_to_errno - Convert SPB AP status codes to Linux error codes ++ * @status: SPB AP status code from spb_ap_send() or other SPB AP functions ++ * ++ * Translates positive SPB AP status codes to negative Linux errno values ++ * for consistent error handling throughout the driver. ++ * ++ * Note: SPB_AP_MESSAGE_AVAILABLE is a success status (not an error) that ++ * indicates a message is available to read after a successful send. ++ */ ++static inline int spb_ap_status_to_errno(SpbApStatus status) ++{ ++ switch (status) { ++ case SPB_AP_OK: ++ case SPB_AP_MESSAGE_AVAILABLE: ++ return 0; ++ case SPB_AP_ERROR_TIMEOUT: ++ return -ETIMEDOUT; ++ case SPB_AP_ERROR_INVALID_ARGUMENT: ++ return -EINVAL; ++ case SPB_AP_ERROR_UNKNOWN: ++ default: ++ return -EIO; ++ } ++} ++ ++static LIST_HEAD(device_list); ++static DEFINE_MUTEX(device_list_lock); ++ ++static unsigned bufsiz = 4096; ++module_param(bufsiz, uint, S_IRUGO); ++MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); ++ ++static const struct spi_device_id mctp_spi_ids[] = { ++ { .name = "mctp-spi" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(spi, mctp_spi_ids); ++ ++static const struct of_device_id mctp_spi_dt_ids[] = { ++ { .compatible = "mctp-spi" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, mctp_spi_dt_ids); ++ ++/*-------------------------------------------------------------------------*/ ++ ++static netdev_tx_t mctp_spi_net_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct mctp_spi *midev = netdev_priv(dev); ++ ++ spin_lock(&midev->tx_queue.lock); ++ if (skb_queue_len(&midev->tx_queue) >= MCTP_SPI_TX_WORK_LEN) { ++ netif_stop_queue(dev); ++ spin_unlock(&midev->tx_queue.lock); ++ netdev_err(dev, "BUG! Tx Ring full when queue awake!\n"); ++ return NETDEV_TX_BUSY; ++ } ++ ++ __skb_queue_tail(&midev->tx_queue, skb); ++ if (skb_queue_len(&midev->tx_queue) == MCTP_SPI_TX_WORK_LEN) ++ netif_stop_queue(dev); ++ spin_unlock(&midev->tx_queue.lock); ++ ++ wake_up(&midev->main_thread_wq); ++ return NETDEV_TX_OK; ++} ++ ++static int mctp_spi_net_recv(struct mctp_spi *midev, uint8_t *rx_buffer) ++{ ++ struct net_device *ndev = midev->ndev; ++ struct sk_buff *skb; ++ struct mctp_skb_cb *cb; ++ unsigned long flags; ++ size_t recvlen; ++ int status; ++ ++ recvlen = midev->spidev->rx_len; ++ ++ skb = netdev_alloc_skb(ndev, recvlen); ++ if (!skb) { ++ ndev->stats.rx_dropped++; ++ /* Can't extract EID - no packet data available yet */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_no_memory); ++ trace_mctp_transport_error("spi", ndev, "rx_drop_no_memory", recvlen); ++ return -ENOMEM; ++ } ++ skb->protocol = htons(ETH_P_MCTP); ++ if (!rx_buffer) ++ rx_buffer = midev->spidev->rx_buffer; ++ skb_put_data(skb, rx_buffer, recvlen); ++ skb_reset_network_header(skb); ++ cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++ /* We need to ensure that the netif is not used once netdev ++ * unregister occurs ++ */ ++ spin_lock_irqsave(&midev->lock, flags); ++ if (midev->allow_rx) { ++ reinit_completion(&midev->rx_done); ++ spin_unlock_irqrestore(&midev->lock, flags); ++ status = netif_rx(skb); ++ complete(&midev->rx_done); ++ } else { ++ status = NET_RX_DROP; ++ spin_unlock_irqrestore(&midev->lock, flags); ++ } ++ ++ if (status == NET_RX_SUCCESS) { ++ ndev->stats.rx_packets++; ++ ndev->stats.rx_bytes += recvlen; ++ netdev_dbg(ndev, "MCTP SPI: RX success, %zu bytes\n", recvlen); ++ trace_mctp_transport_rx("spi", ndev, 0, recvlen); ++ } else { ++ ndev->stats.rx_dropped++; ++ /* Extract EID for per-EID tracking (SKB has packet data) */ ++ if (skb->len >= sizeof(struct mctp_spi_hdr) + sizeof(struct mctp_hdr)) { ++ struct mctp_hdr *mh = (struct mctp_hdr *)(skb->data + sizeof(struct mctp_spi_hdr)); ++ MCTP_STAT_INC(midev, mh->src, rx_drop_not_ready); ++ } else { ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_not_ready); ++ } ++ netdev_dbg(ndev, "MCTP SPI: RX dropped, status=%d\n", status); ++ trace_mctp_transport_error("spi", ndev, "rx_dropped", status); ++ } ++ ++ return 0; ++} ++ ++static int mctp_spi_rx(struct mctp_spi *midev) ++{ ++ ssize_t len; ++ ssize_t payload_len; ++ const size_t hdr_size = sizeof(struct mctp_spi_hdr); ++ SpbApStatus status = 0; ++ struct mctp_spi_hdr *spi_hdr_rx; ++ u8 tmp_rx_buffer[RX_BUFFER_SIZE]; ++ ++ spi_hdr_rx = (struct mctp_spi_hdr *)tmp_rx_buffer; ++ ++ status = spb_ap_recv(midev->ap, RX_BUFFER_SIZE, tmp_rx_buffer); ++ if(status != SPB_AP_OK) { ++ int err = spb_ap_status_to_errno(status); ++ ++ midev->ndev->stats.rx_dropped++; ++ /* Can't extract EID - SPI receive failed, no data */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_spi_error); ++ netdev_dbg(midev->ndev, "MCTP SPI: RX error, SPB status=%d, errno=%d\n", status, err); ++ trace_mctp_transport_error("spi", midev->ndev, "rx_spi_error", err); ++ return ERR_SPI_RX_NO_DATA; ++ } ++ ++ payload_len = spi_hdr_rx->byte_count; ++ len = payload_len + hdr_size; ++ ++ midev->spidev->rx_len = payload_len; ++ // memcpy(midev->spidev->rx_buffer, tmp_rx_buffer + hdr_size, payload_len); ++ mctp_spi_net_recv(midev, tmp_rx_buffer + hdr_size); ++ ++ return 0; ++} ++ ++static void mctp_spi_ndo_uninit(struct net_device *dev) ++{ ++ ++} ++ ++static int mctp_spi_ndo_open(struct net_device *dev) ++{ ++ struct mctp_spi *midev = netdev_priv(dev); ++ unsigned long flags; ++ ++ /* spi rx thread can only pass packets once the netdev is registered */ ++ spin_lock_irqsave(&midev->lock, flags); ++ midev->allow_rx = true; ++ spin_unlock_irqrestore(&midev->lock, flags); ++ return 0; ++} ++ ++static int mctp_spi_header_create(struct sk_buff *skb, struct net_device *dev, ++ unsigned short type, const void *daddr, ++ const void *saddr, unsigned int len) ++{ ++ struct mctp_spi_hdr *hdr; ++ ++ if (len > MCTP_SPI_MAXMTU) ++ return -EMSGSIZE; ++ ++ skb_push(skb, sizeof(struct mctp_spi_hdr)); ++ skb_reset_mac_header(skb); ++ hdr = (void *)skb_mac_header(skb); ++ hdr->command_code = MCTP_COMMAND_CODE; ++ hdr->byte_count = len; ++ hdr->resrv[0] = 0; ++ hdr->resrv[1] = 0; ++ ++ return sizeof(struct mctp_spi_hdr); ++} ++ ++/* Ethtool statistics support */ ++/* Per-EID stat descriptors with abbreviated names */ ++struct mctp_spi_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_SPI_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_spi_eid_stats, field) \ ++} ++ ++static const struct mctp_spi_eid_stat_desc mctp_spi_eid_stat_descs[] = { ++ MCTP_SPI_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_SPI_EID_STAT("rx_drop_not_ready", rx_drop_not_ready), ++ MCTP_SPI_EID_STAT("rx_drop_spi_error", rx_drop_spi_error), ++ MCTP_SPI_EID_STAT("tx_drop_etimedout", tx_drop_etimedout), ++ MCTP_SPI_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_SPI_EID_STAT("tx_drop_eio", tx_drop_eio), ++ MCTP_SPI_EID_STAT("tx_drop_spi_error", tx_drop_spi_error), ++ MCTP_SPI_EID_STAT("gpio_interrupts", gpio_interrupts), ++}; ++ ++#define MCTP_SPI_EID_NUM_STATS ARRAY_SIZE(mctp_spi_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_spi, struct mctp_spi, ++ struct mctp_spi_eid_stats, mctp_spi_eid_stat_descs) ++ ++static void mctp_spi_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_spi *midev = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_spi_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_spi_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_spi_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (GPIO interrupt ++ * events, low-level SPI transfer failures, or allocation failures ++ * before packet parsing) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: SPI/GPIO errors "); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_spi_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_spi_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_spi_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_spi *midev = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_SPI_EID_NUM_STATS + 1 + mctp_spi_count_eid_stats(midev); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_spi_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_spi *midev = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ u8 *base = (u8 *)&midev->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_spi_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_spi_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_spi_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_spi_eid_stats_total(midev, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_spi_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_spi_ethtool_ops = { ++ .get_strings = mctp_spi_get_strings, ++ .get_sset_count = mctp_spi_get_sset_count, ++ .get_ethtool_stats = mctp_spi_get_ethtool_stats, ++}; ++ ++static const struct net_device_ops mctp_spi_ops = { ++ .ndo_start_xmit = mctp_spi_net_xmit, ++ .ndo_uninit = mctp_spi_ndo_uninit, ++ .ndo_open = mctp_spi_ndo_open, ++}; ++ ++static const struct header_ops mctp_spi_headops = { ++ .create = mctp_spi_header_create, ++}; ++ ++static void mctp_spi_net_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ dev->mtu = MCTP_SPI_MAXMTU; ++ dev->min_mtu = MCTP_SPI_MINMTU; ++ dev->max_mtu = MCTP_SPI_MAXMTU; ++ dev->tx_queue_len = MCTP_SPI_TX_QUEUE_LEN; ++ ++ dev->hard_header_len = sizeof(struct mctp_spi_hdr); ++ dev->addr_len = 0; ++ ++ dev->netdev_ops = &mctp_spi_ops; ++ dev->header_ops = &mctp_spi_headops; ++ dev->ethtool_ops = &mctp_spi_ethtool_ops; ++} ++ ++static irqreturn_t mctp_pkg_rec_wake(int irq, void *data) ++{ ++ struct mctp_spi *midev = data; ++ spin_lock(&midev->gpio_intr_cond_lock); ++ midev->gpio_intr_cond = true; ++ /* GPIO interrupts have no EID context */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, gpio_interrupts); ++ spin_unlock(&midev->gpio_intr_cond_lock); ++ ++ wake_up(&midev->main_thread_wq); // Wake up the consumer waiting on the condition ++ return IRQ_HANDLED; ++} ++ ++ssize_t spi_raw_transfer(struct spi_device *spi, const char *txbuf, size_t txlen, ++ const char *rxbuf, size_t rxlen, bool cs_change) ++{ ++ ssize_t status; ++ // unsigned long missing; unused variable ++ struct spi_message msg; ++ ++ struct spi_transfer xfer = { ++ .tx_buf = txbuf, ++ .rx_buf = rxbuf, ++ .len = txlen + rxlen, ++ .delay.value = rxlen, ++ .bits_per_word = 8, ++ .cs_change = cs_change, ++ .speed_hz = 1000000, ++ }; ++ ++ spi_message_init(&msg); ++ spi_message_add_tail(&xfer, &msg); ++ status = spi_sync(spi, &msg); ++ return status; ++} ++ ++static bool is_gpio_interrupt(struct mctp_spi *midev) ++{ ++ bool tmp; ++ unsigned long flags; ++ spin_lock_irqsave(&midev->gpio_intr_cond_lock, flags); ++ tmp = midev->gpio_intr_cond; ++ spin_unlock_irqrestore(&midev->gpio_intr_cond_lock, flags); ++ return tmp; ++} ++ ++static bool is_skb_queue_empty(struct mctp_spi *midev) ++{ ++ bool tmp; ++ spin_lock(&midev->tx_queue.lock); ++ tmp = skb_queue_empty(&midev->tx_queue); ++ spin_unlock(&midev->tx_queue.lock); ++ return tmp; ++} ++ ++static int mctp_spi_tx_thread(void *data) ++{ ++ struct mctp_spi *midev = data; ++ struct sk_buff *skb; ++ unsigned long flags; ++ unsigned char txbuf[BUFSIZE]; ++ bool gpio_wake = false; ++ SpbApStatus status = 0; ++ ++ spb_ap_initialize(midev->ap); ++ ++ for (;;) { ++ if (kthread_should_stop()) ++ break; ++ ++ if(gpio_wake) { ++ gpio_wake = false; ++ spb_ap_on_interrupt(midev->ap); ++ while (midev->ap->msgs_available > 0) ++ mctp_spi_rx(midev); ++ } ++ ++ if (skb) { ++ int injected_status = 0; ++ /* Extract destination EID for per-EID tracking */ ++ struct mctp_hdr *mh = (void *)(skb->data + sizeof(struct mctp_spi_hdr)); ++ u8 dest_eid = mh->dest; ++ ++ skb_copy_bits(skb, 0, txbuf, skb->len); ++ ++ /* ERROR INJECTION POINT: Check if we should inject error */ ++ if (mctp_spi_error_inject_tx(midev, skb, &injected_status)) { ++ /* Inject the configured SPB_AP error */ ++ status = injected_status; ++ } else { ++ //Send SPI package ++ status = spb_ap_send(midev->ap, skb->len, txbuf); ++ } ++ ++ if(status == SPB_AP_OK) { ++ midev->ndev->stats.tx_packets++; ++ midev->ndev->stats.tx_bytes += skb->len; ++ netdev_dbg(midev->ndev, "MCTP SPI: TX success, %u bytes\n", skb->len); ++ trace_mctp_transport_tx("spi", midev->ndev, 0, skb->len); ++ } else { ++ int err = spb_ap_status_to_errno(status); ++ ++ midev->ndev->stats.tx_dropped++; ++ /* SPB AP only returns: TIMEOUT, INVALID_ARGUMENT, or UNKNOWN */ ++ if (err == -ETIMEDOUT) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_etimedout); ++ } else if (err == -EINVAL) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_einval); ++ } else if (err == -EIO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eio); ++ } else { ++ /* Catch-all for any unmapped errors */ ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_spi_error); ++ } ++ netdev_dbg(midev->ndev, "MCTP SPI: TX failed, SPB status=%d, errno=%d\n", status, err); ++ trace_mctp_transport_error("spi", midev->ndev, "spb_ap_send_failed", err); ++ ++ /* TX ERROR: Report to application via error queue */ ++ struct sock *sk; ++ struct mctp_sk_key *key = NULL; ++ int error_code; ++ ++ /* Map SPB_AP status codes to Linux errno */ ++ switch (status) { ++ case SPB_AP_ERROR_TIMEOUT: ++ error_code = ETIMEDOUT; ++ break; ++ case SPB_AP_ERROR_INVALID_ARGUMENT: ++ error_code = EINVAL; ++ break; ++ case SPB_AP_ERROR_UNKNOWN: ++ default: ++ error_code = EIO; ++ break; ++ } ++ ++ /* Remove SPI header to expose MCTP header for socket lookup. ++ * Since we're going to free the SKB anyway, we can modify it directly. ++ * This preserves skb->sk naturally without needing to clone. ++ */ ++ if (skb->len > sizeof(struct mctp_spi_hdr)) { ++ skb_pull(skb, sizeof(struct mctp_spi_hdr)); ++ skb_reset_network_header(skb); ++ ++ /* Lookup socket and report error */ ++ sk = mctp_lookup_sock_for_error(skb, midev->ndev, NULL, &key); ++ if (sk) { ++ mctp_queue_error(sk, skb, error_code, midev->ndev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_SERIAL, key); ++ sock_put(sk); ++ } ++ } ++ } ++ kfree_skb(skb); ++ while (midev->ap->msgs_available > 0) { ++ status = mctp_spi_rx(midev); ++ if (status == ERR_SPI_RX_NO_DATA) ++ break; ++ } ++ midev->ap->msgs_available = 0; ++ } ++ ++ wait_event_idle(midev->main_thread_wq, ++ !is_skb_queue_empty(midev) || ++ kthread_should_stop() || is_gpio_interrupt(midev)); ++ // Pop skb if any ++ spin_lock(&midev->tx_queue.lock); ++ skb = __skb_dequeue(&midev->tx_queue); ++ if (netif_queue_stopped(midev->ndev)) ++ netif_wake_queue(midev->ndev); ++ spin_unlock(&midev->tx_queue.lock); ++ // Check if this is GPIO interrupt wake ++ spin_lock_irqsave(&midev->gpio_intr_cond_lock, flags); ++ gpio_wake = midev->gpio_intr_cond; ++ midev->gpio_intr_cond = false; ++ spin_unlock_irqrestore(&midev->gpio_intr_cond_lock, flags); ++ } ++ ++ return 0; ++} ++ ++static int mctp_spi_probe(struct spi_device *spi) ++{ ++ int (*match)(struct device *dev); ++ struct spidev_data *spidev; ++ SpbAp *ap; ++ int status, idx, rc; ++ struct net_device *ndev; ++ struct mctp_spi *mctp_spi_dev; ++ char name[32]; ++ unsigned long flags; ++ ++ match = device_get_match_data(&spi->dev); ++ if (match) { ++ status = match(&spi->dev); ++ if (status) ++ return status; ++ } ++ ++ /* Allocate driver data */ ++ spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); ++ if (!spidev) ++ return -ENOMEM; ++ ++ /* Initialize the driver data */ ++ spidev->spi = spi; ++ spin_lock_init(&spidev->spi_lock); ++ mutex_init(&spidev->buf_lock); ++ ++ INIT_LIST_HEAD(&spidev->device_entry); ++ ++ spidev->speed_hz = spi->max_speed_hz; ++ ++ if (status == 0) ++ spi_set_drvdata(spi, spidev); ++ else ++ kfree(spidev); ++ ++ idx = ida_alloc(&mctp_spi_ida, GFP_KERNEL); ++ if (idx < 0) ++ return idx; ++ ++ snprintf(name, sizeof(name), "mctpspi%u_%u", spi->controller->bus_num, ++ spi_get_chipselect(spi, 0)); ++ ndev = alloc_netdev(sizeof(*mctp_spi_dev), name, NET_NAME_ENUM, ++ mctp_spi_net_setup); ++ if (!ndev) { ++ rc = -ENOMEM; ++ goto free_ida; ++ } ++ mctp_spi_dev = netdev_priv(ndev); ++ ++ mctp_spi_dev->rx_alert = devm_gpiod_get(&spi->dev, "alert", GPIOD_IN); ++ ++ SET_NETDEV_DEV(ndev, &spi->dev); ++ if(IS_ERR(mctp_spi_dev->rx_alert)) { ++ goto err_netdev; ++ } ++ ++ mctp_spi_dev->rx_alert_irq = gpiod_to_irq(mctp_spi_dev->rx_alert); ++ if (mctp_spi_dev->rx_alert_irq < 0) { ++ rc = mctp_spi_dev->rx_alert_irq; ++ goto err_netdev; ++ } ++ ++ /*Init GPIO Interrupt*/ ++ spin_lock_init(&mctp_spi_dev->gpio_intr_cond_lock); ++ init_waitqueue_head(&mctp_spi_dev->gpio_intr_wq); ++ mctp_spi_dev->gpio_intr_cond = 0; ++ ++ rc = request_irq(mctp_spi_dev->rx_alert_irq, mctp_pkg_rec_wake, ++ IRQF_TRIGGER_FALLING, "mctp_spi_pkg_rec_wake", mctp_spi_dev); ++ ++ if (rc < 0) ++ goto err_netdev; ++ ++ spin_lock_init(&mctp_spi_dev->lock); ++ init_waitqueue_head(&mctp_spi_dev->main_thread_wq); ++ skb_queue_head_init(&mctp_spi_dev->tx_queue); ++ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_SERIAL); ++ if (rc) ++ goto err_netdev; ++ init_completion(&mctp_spi_dev->rx_done); ++ complete(&mctp_spi_dev->rx_done); ++ spin_lock_irqsave(&mctp_spi_dev->lock, flags); ++ mctp_spi_dev->allow_rx = false; ++ spin_unlock_irqrestore(&mctp_spi_dev->lock, flags); ++ mctp_spi_dev->ndev = ndev; ++ mctp_spi_dev->spidev = spidev; ++ mctp_spi_dev->tx_thread = kthread_create(mctp_spi_tx_thread, mctp_spi_dev, ++ "%s/tx", ndev->name); ++ if (IS_ERR(mctp_spi_dev->tx_thread)) ++ return PTR_ERR(mctp_spi_dev->tx_thread); ++ ++ /* Init SPB */ ++ ap = kzalloc(sizeof(*ap), GFP_KERNEL); ++ ap->spi_xfer = spi_raw_transfer; ++ ap->spi = spi; ++ ap->gpio_intr_wq = &mctp_spi_dev->main_thread_wq; ++ ap->gpio_intr_cond = &mctp_spi_dev->gpio_intr_cond; ++ ap->gpio_intr_cond_lock = &mctp_spi_dev->gpio_intr_cond_lock; ++ mctp_spi_dev->ap = ap; ++ ++ /* Setup error injection after netdev registration (debugfs needs the netdev name) */ ++ mctp_spi_error_inject_init(mctp_spi_dev); ++ ++ /* Start the mctp tx worker thread */ ++ wake_up_process(mctp_spi_dev->tx_thread); ++ return status; ++err_netdev: ++ free_netdev(ndev); ++free_ida: ++ ida_free(&mctp_spi_ida, idx); ++ return rc; ++} ++ ++static void mctp_spi_remove(struct spi_device *spi) ++{ ++ struct spidev_data *spidev = spi_get_drvdata(spi); ++ /* prevent new opens */ ++ mutex_lock(&device_list_lock); ++ /* make sure ops on existing fds can abort cleanly */ ++ spin_lock_irq(&spidev->spi_lock); ++ spidev->spi = NULL; ++ spin_unlock_irq(&spidev->spi_lock); ++ ++ list_del(&spidev->device_entry); ++ if (spidev->users == 0) ++ kfree(spidev); ++ mutex_unlock(&device_list_lock); ++} ++ ++static struct spi_driver mctp_spi_driver = { ++ .driver = { ++ .name = "mctpspi", ++ .of_match_table = mctp_spi_dt_ids, ++ }, ++ .probe = mctp_spi_probe, ++ .remove = mctp_spi_remove, ++ .id_table = mctp_spi_ids, ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int __init mctp_spi_mod_init(void) ++{ ++ int rc; ++ ++ pr_info("MCTP SPI interface driver\n"); ++ ++ /* Initialize error injection infrastructure */ ++ rc = mctp_spi_error_inject_module_init(); ++ if (rc) ++ pr_warn("MCTP SPI: Error injection initialization failed, continuing without it\n"); ++ ++ rc = spi_register_driver(&mctp_spi_driver); ++ if (rc) { ++ mctp_spi_error_inject_module_exit(); ++ return rc; ++ } ++ ++ return 0; ++} ++module_init(mctp_spi_mod_init); ++ ++static void __exit mctp_spi_mod_exit(void) ++{ ++ spi_unregister_driver(&mctp_spi_driver); ++ mctp_spi_error_inject_module_exit(); ++} ++module_exit(mctp_spi_mod_exit); ++ ++MODULE_LICENSE("GPL"); +diff --git a/drivers/net/mctp/mctp-stats.h b/drivers/net/mctp/mctp-stats.h +new file mode 100644 +index 000000000..216a01201 +--- /dev/null ++++ b/drivers/net/mctp/mctp-stats.h +@@ -0,0 +1,217 @@ ++/* ++ * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. ++ * SPDX-License-Identifier: Apache-2.0 ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++/* ++ * MCTP Per-EID Statistics - Shared Infrastructure ++ * ++ * This header provides macros and helpers for tracking statistics ++ * per-endpoint-ID (EID) across all MCTP bindings. ++ */ ++ ++#ifndef __MCTP_STATS_H ++#define __MCTP_STATS_H ++ ++#include ++#include ++#include ++ ++/** ++ * MCTP_EID_UNKNOWN - Special EID value for errors where EID is not yet known ++ * ++ * Value: 256 (out-of-band value to avoid conflict with valid MCTP EIDs 0-254, ++ * and distinct from broadcast address 255) ++ * ++ * This EID is used for tracking errors that occur when the actual endpoint ID ++ * cannot be determined. This happens in several scenarios: ++ * ++ * 1. PRE-PARSE ERRORS: Packets are corrupted/invalid before we can read the ++ * MCTP header (e.g., invalid I2C command code, PEC checksum failure, ++ * packet too short) ++ * ++ * 2. ALLOCATION FAILURES: Memory allocation failed before we could parse the ++ * packet to extract the source/destination EID ++ * ++ * 3. ASYNCHRONOUS EVENTS: Hardware/transport events with no packet context ++ * (e.g., USB URB completion callbacks, I3C IBI events, GPIO interrupts, ++ * device hotplug events) ++ * ++ * 4. TRANSPORT-LEVEL ERRORS: Low-level bus failures before packet transmission ++ * (e.g., SPI transfer errors, I2C arbitration loss on unrelated traffic) ++ * ++ * IMPORTANT DISTINCTION from EID 0: ++ * - UNKNOWN (256): We don't know the EID (error occurred too early) ++ * - EID 0: We successfully read the EID from the packet, and it was 0 ++ * (EID 0 is the "null endpoint" per MCTP spec DSP0236, used for ++ * unallocated/unassigned endpoints) ++ */ ++#define MCTP_EID_UNKNOWN 256 ++ ++/** ++ * MCTP_STAT_INC - Increment per-EID statistics and mark EID as active ++ * @dev_ptr: Pointer to device structure (e.g., midev, mbus, musb) ++ * @eid_val: EID value (0-256) - use MCTP_EID_UNKNOWN if EID not yet known ++ * @stat_field: Name of the statistics field ++ * ++ * This macro is the SINGLE way to track statistics. It updates the per-EID ++ * stats counter for the specified field and marks the EID as active. ++ * ++ * Usage: ++ * // When EID is known: ++ * MCTP_STAT_INC(midev, dest_eid, tx_drop_timeout); ++ * ++ * // When EID is not yet known (before parsing): ++ * MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_invalid_cmd); ++ */ ++#define MCTP_STAT_INC(dev_ptr, eid_val, stat_field) do { \ ++ typeof(dev_ptr) __dev = (dev_ptr); \ ++ unsigned int __eid = (eid_val); \ ++ set_bit(__eid, __dev->eid_stats.active); \ ++ __dev->eid_stats.eid[__eid].stat_field++; \ ++} while (0) ++ ++/** ++ * MCTP_EID_STATS_HELPERS - Generate ethtool helper functions for per-EID stats ++ * @prefix: Prefix for generated function names (e.g., mctp_i2c, mctp_usb) ++ * @dev_type: Type of device structure (e.g., struct mctp_i2c_dev) ++ * @eid_stats_type: Type of per-EID stats structure ++ * @stat_descs: Array name of stat descriptors ++ * ++ * Generates three helper functions: ++ * - prefix_count_eid_nonzero(): Count non-zero stats for one EID ++ * - prefix_count_eid_stats(): Count total stats for all active EIDs ++ * - prefix_eid_stats_total(): Sum all stats for one EID ++ * ++ * These functions are used by ethtool callbacks to determine output size ++ * and generate the per-EID statistics display. ++ */ ++#define MCTP_EID_STATS_HELPERS(prefix, dev_type, eid_stats_type, stat_descs) \ ++\ ++static int prefix##_count_eid_nonzero(dev_type *dev, unsigned int eid) \ ++{ \ ++ eid_stats_type *es = &dev->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ int count = 0; \ ++ unsigned int i; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) { \ ++ if (*(u64 *)(base + stat_descs[i].offset) != 0) \ ++ count++; \ ++ } \ ++ return count; \ ++} \ ++\ ++static int prefix##_count_eid_stats(dev_type *dev) \ ++{ \ ++ int count = 0; \ ++ int eid; \ ++ \ ++ for_each_set_bit(eid, dev->eid_stats.active, 257) { \ ++ int nz = prefix##_count_eid_nonzero(dev, eid); \ ++ if (nz > 0) \ ++ count += 1 + nz; \ ++ } \ ++ return count; \ ++} \ ++\ ++static u64 prefix##_eid_stats_total(dev_type *dev, unsigned int eid) \ ++{ \ ++ eid_stats_type *es = &dev->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ u64 total = 0; \ ++ unsigned int i; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) \ ++ total += *(u64 *)(base + stat_descs[i].offset); \ ++ return total; \ ++} ++ ++/** ++ * MCTP_EID_GET_STRINGS - Generate ethtool string names for per-EID stats ++ * @dev: Device structure pointer ++ * @data_ptr: Pointer to data buffer pointer (will be advanced) ++ * @stat_descs: Array of stat descriptors ++ * ++ * Outputs per-EID stat names in ethtool format. Only outputs stats for ++ * active EIDs with non-zero values. EID 256 (MCTP_EID_UNKNOWN) is displayed ++ * as "UNKNOWN" instead of "EID_256". ++ */ ++#define MCTP_EID_GET_STRINGS(dev, data_ptr, stat_descs, count_nonzero_fn) do { \ ++ int eid; \ ++ unsigned int i; \ ++ \ ++ for_each_set_bit(eid, (dev)->eid_stats.active, 257) { \ ++ typeof(&(dev)->eid_stats.eid[0]) es = &(dev)->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ int nz = count_nonzero_fn((dev), eid); \ ++ \ ++ if (nz == 0) \ ++ continue; \ ++ \ ++ if (eid == MCTP_EID_UNKNOWN) \ ++ snprintf(*(data_ptr), ETH_GSTRING_LEN, "UNKNOWN "); \ ++ else \ ++ snprintf(*(data_ptr), ETH_GSTRING_LEN, "EID_%-3u ", eid); \ ++ *(data_ptr) += ETH_GSTRING_LEN; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) { \ ++ u64 val = *(u64 *)(base + stat_descs[i].offset); \ ++ if (val != 0) { \ ++ snprintf(*(data_ptr), ETH_GSTRING_LEN, "%-30s", \ ++ stat_descs[i].name); \ ++ *(data_ptr) += ETH_GSTRING_LEN; \ ++ } \ ++ } \ ++ } \ ++} while (0) ++ ++/** ++ * MCTP_EID_GET_STATS - Generate ethtool stat values for per-EID stats ++ * @dev: Device structure pointer ++ * @data_ptr: Pointer to data array pointer (will be advanced) ++ * @stat_descs: Array of stat descriptors ++ * @count_nonzero_fn: Function to count non-zero stats ++ * @total_fn: Function to get total for an EID ++ * ++ * Outputs per-EID stat values in ethtool format. Only outputs stats for ++ * active EIDs with non-zero values. ++ */ ++#define MCTP_EID_GET_STATS(dev, data_ptr, stat_descs, count_nonzero_fn, total_fn) do { \ ++ int eid; \ ++ unsigned int i; \ ++ \ ++ for_each_set_bit(eid, (dev)->eid_stats.active, 257) { \ ++ typeof(&(dev)->eid_stats.eid[0]) es = &(dev)->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ int nz = count_nonzero_fn((dev), eid); \ ++ \ ++ if (nz == 0) \ ++ continue; \ ++ \ ++ (*(data_ptr))[0] = total_fn((dev), eid); \ ++ (*data_ptr)++; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) { \ ++ u64 val = *(u64 *)(base + stat_descs[i].offset); \ ++ if (val != 0) { \ ++ (*(data_ptr))[0] = val; \ ++ (*data_ptr)++; \ ++ } \ ++ } \ ++ } \ ++} while (0) ++ ++#endif /* __MCTP_STATS_H */ +diff --git a/drivers/net/mctp/mctp-usb-error-inject.c b/drivers/net/mctp/mctp-usb-error-inject.c +new file mode 100644 +index 000000000..24c113ea8 +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb-error-inject.c +@@ -0,0 +1,1055 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * mctp-usb-error-inject.c - Error injection infrastructure for MCTP USB ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "mctp-usb-internal.h" ++ ++/* Global debugfs root directory */ ++static struct dentry *mctp_usb_debugfs_root; ++ ++/* ===== Error Injection Helper Functions ===== */ ++ ++/* Check if error should be injected based on mode and rate ++ * Note: Caller must check ei->enable_tx or ei->enable_rx before calling this ++ */ ++static bool mctp_should_inject_error(struct mctp_error_inject *ei, u32 rate, u32 *count) ++{ ++ bool inject = false; ++ ++ spin_lock_bh(&ei->lock); ++ ++ switch (ei->mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ inject = true; ++ break; ++ ++ case MCTP_ERR_MODE_RANDOM: ++ /* Random injection based on rate percentage */ ++ inject = (prandom_u32_state(&ei->rng) % 100) < rate; ++ break; ++ ++ case MCTP_ERR_MODE_COUNT: ++ /* Inject for count packets, then stop */ ++ if (*count > 0) { ++ (*count)--; ++ inject = true; ++ } ++ break; ++ } ++ ++ spin_unlock_bh(&ei->lock); ++ ++ return inject; ++} ++ ++/* Check if packet matches EID filter */ ++static bool mctp_error_inject_match_filter(struct mctp_error_inject *ei, ++ struct sk_buff *skb, ++ struct net_device *netdev) ++{ ++ struct mctp_hdr *mh; ++ ++ if (!ei->eid_filter.enabled) ++ return true; /* No filter, match all */ ++ ++ /* Check if SKB has enough data for USB header + MCTP header */ ++ if (skb->len < (sizeof(struct mctp_usb_hdr) + sizeof(struct mctp_hdr))) ++ return false; ++ ++ /* ++ * Access MCTP header manually - SKB has USB header prepended at this point. ++ * Cannot use mctp_hdr(skb) as it has WARN_ON check that expects MCTP header ++ * at skb->data, but we have: [USB HDR][MCTP HDR][PAYLOAD] ++ */ ++ mh = (struct mctp_hdr *)(skb->data + sizeof(struct mctp_usb_hdr)); ++ ++ /* Check source EID */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) { ++ netdev_dbg(netdev, ++ "Error injection: Packet filtered out (src EID %u != filter %u)\n", ++ mh->src, ei->eid_filter.src_eid); ++ return false; ++ } ++ ++ /* Check dest EID */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) { ++ netdev_dbg(netdev, ++ "Error injection: Packet filtered out (dest EID %u != filter %u)\n", ++ mh->dest, ei->eid_filter.dest_eid); ++ return false; ++ } ++ ++ /* Packet matches filter */ ++ netdev_dbg(netdev, ++ "Error injection: Packet matches filter (src=%u, dest=%u)\n", ++ mh->src, mh->dest); ++ ++ return true; ++} ++ ++/* TX synchronous error injection (before URB submission) */ ++int mctp_usb_error_inject_tx_sync(struct mctp_usb *mctp_usb, struct sk_buff *skb) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ struct mctp_hdr *mh; ++ ++ /* Early exit if TX error injection is disabled or sync code not set - zero overhead */ ++ if (!ei->enable_tx || ei->urb_tx_sync_error_code == 0) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb, mctp_usb->netdev)) ++ return 0; ++ ++ if (!mctp_should_inject_error(ei, ei->urb_tx_error_rate, ++ &ei->urb_tx_sync_inject_count)) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB submission (sync) - not injecting (mode/rate check)\n"); ++ return 0; ++ } ++ ++ /* Inject delay if configured */ ++ if (ei->delay_ms > 0) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: Injecting %u ms delay\n", ++ ei->delay_ms); ++ msleep(ei->delay_ms); ++ } ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->urb_tx_sync_errors_injected++; ++ ei->total_errors_injected++; ++ ei->total_packets_processed++; ++ spin_unlock_bh(&ei->lock); ++ ++ /* ++ * Access MCTP header manually - SKB has USB header prepended at this point. ++ * Cannot use mctp_hdr(skb) as it has WARN_ON check that expects MCTP header ++ * at skb->data, but we have: [USB HDR][MCTP HDR][PAYLOAD] ++ */ ++ mh = (struct mctp_hdr *)(skb->data + sizeof(struct mctp_usb_hdr)); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB submission failed (sync) - error=%d, src_eid=%u, dest_eid=%u, " ++ "total_injected=%u, mode=%s\n", ++ ei->urb_tx_sync_error_code, mh->src, mh->dest, ++ ei->urb_tx_sync_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ return ei->urb_tx_sync_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_tx_sync); ++ ++/* TX asynchronous error injection (URB completion) ++ * @mctp_usb: MCTP USB device ++ * @urb: The completed URB (used for status and, when EID filter enabled, to parse first packet) ++ * ++ * Returns: Status to use (original urb->status or injected error code). ++ * ++ * EID filtering: When eid_filter.enabled, we parse the first packet in the URB buffer ++ * and match src_eid/dest_eid (0 = any). Batched URBs contain fragments of one message ++ * (same EID pair), so checking the first packet is sufficient. ++ */ ++int mctp_usb_error_inject_tx_async(struct mctp_usb *mctp_usb, struct urb *urb) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ int original_status = urb->status; ++ ++ /* Only inject if original status was success */ ++ if (original_status != 0) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB already has error status %d, not injecting\n", ++ original_status); ++ return original_status; ++ } ++ ++ /* Early exit if TX error injection is disabled or async code not set - zero overhead */ ++ if (!ei->enable_tx || ei->urb_tx_async_error_code == 0) ++ return original_status; ++ ++ /* EID filter: parse first packet and match src_eid/dest_eid (0 = any) */ ++ if (ei->eid_filter.enabled) { ++ if (urb->transfer_buffer && urb->transfer_buffer_length >= sizeof(struct mctp_usb_hdr) + sizeof(struct mctp_hdr)) { ++ struct mctp_usb_hdr *usb_hdr = (struct mctp_usb_hdr *)urb->transfer_buffer; ++ struct mctp_hdr *mctp_hdr = (struct mctp_hdr *)(usb_hdr + 1); ++ u8 src = mctp_hdr->src; ++ u8 dest = mctp_hdr->dest; ++ bool src_ok = (ei->eid_filter.src_eid == 0 || ei->eid_filter.src_eid == src); ++ bool dest_ok = (ei->eid_filter.dest_eid == 0 || ei->eid_filter.dest_eid == dest); ++ ++ if (!src_ok || !dest_ok) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB async - EID filter mismatch (src_eid=%u, dest_eid=%u, pkt src=%u dest=%u), not injecting\n", ++ ei->eid_filter.src_eid, ei->eid_filter.dest_eid, src, dest); ++ return original_status; ++ } ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB async - EID filter match (src=%u dest=%u)\n", ++ src, dest); ++ } else { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB async - buffer too small for EID parsing, not injecting\n"); ++ return original_status; ++ } ++ } ++ ++ if (!mctp_should_inject_error(ei, ei->urb_tx_error_rate, ++ &ei->urb_tx_async_inject_count)) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB completion (async) - not injecting (mode/rate check)\n"); ++ return original_status; ++ } ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->urb_tx_async_errors_injected++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB completion failed (async) - error=%d, total_injected=%u, mode=%s (URB-level, may affect multiple packets)\n", ++ ei->urb_tx_async_error_code, ++ ei->urb_tx_async_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ return ei->urb_tx_async_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_tx_async); ++ ++/* RX error injection */ ++int mctp_usb_error_inject_rx(struct mctp_usb *mctp_usb, int original_status) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ ++ if (original_status != 0) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX URB already has error status %d, not injecting\n", ++ original_status); ++ return original_status; ++ } ++ ++ /* Early exit if RX error injection is disabled - zero overhead */ ++ if (!ei->enable_rx || ei->urb_rx_error_code == 0) ++ return original_status; ++ ++ /* RX: Can't check EID filter yet as packet not parsed */ ++ if (ei->eid_filter.enabled) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX - EID filter enabled but cannot filter (packet not parsed yet)\n"); ++ } ++ ++ if (!mctp_should_inject_error(ei, ei->urb_rx_error_rate, ++ &ei->urb_rx_inject_count)) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX URB completion - not injecting (mode/rate check)\n"); ++ return original_status; ++ } ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->urb_rx_errors_injected++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX URB completion failed - error=%d, total_injected=%u, mode=%s\n", ++ ei->urb_rx_error_code, ++ ei->urb_rx_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX packet will be DROPPED due to injected error\n"); ++ ++ return ei->urb_rx_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_rx); ++ ++/* Fragment error injection ++ * Integrated with RX error injection infrastructure: ++ * - Requires enable_rx to be set ++ * - Supports EID filtering (src_eid, dest_eid, msg_type) ++ * - Three injection modes: ++ * 1. enable_fragment_drop: Drop 2nd+ fragments (triggers reassembly timeout) ++ * 2. enable_seq_corrupt: Corrupt sequence number in middle/end fragments (triggers sequence error) ++ * 3. enable_som_clear: Clear SOM bit in first fragment (triggers missing SOM error) ++ * ++ * Returns: ++ * 0 - Pass through (normally or with corruption) ++ * 1 - Drop this fragment ++ */ ++int mctp_usb_error_inject_fragment(struct mctp_usb *mctp_usb, struct sk_buff *skb) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ struct mctp_hdr *mh; ++ u8 flags; ++ u8 msg_type; ++ u8 seq; ++ ++ /* Early exit if RX error injection disabled or no fragment injection enabled */ ++ if (!ei->enable_rx || (!ei->enable_fragment_drop && !ei->enable_seq_corrupt && !ei->enable_som_clear)) ++ return 0; ++ ++ /* Check if we have enough data for MCTP header + message type */ ++ if (skb->len < sizeof(struct mctp_hdr) + 1) ++ return 0; ++ ++ /* Parse MCTP header (skb->data points to MCTP header after USB header removed) */ ++ mh = (struct mctp_hdr *)skb->data; ++ flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ seq = (mh->flags_seq_tag >> 4) & 0x03; ++ ++ /* Single-packet message (SOM+EOM) - pass through (not a fragment) */ ++ if (flags == (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM)) ++ return 0; ++ ++ /* Apply EID filtering if enabled */ ++ if (ei->eid_filter.enabled) { ++ /* Check source EID filter */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) { ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet filtered out (src EID %u != filter %u)\n", ++ mh->src, ei->eid_filter.src_eid); ++ return 0; /* Does not match filter, pass through */ ++ } ++ ++ /* Check destination EID filter */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) { ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet filtered out (dest EID %u != filter %u)\n", ++ mh->dest, ei->eid_filter.dest_eid); ++ return 0; /* Does not match filter, pass through */ ++ } ++ ++ /* Check message type filter (first byte after MCTP header) */ ++ msg_type = *((u8 *)(skb->data + sizeof(struct mctp_hdr))); ++ if (ei->eid_filter.msg_type != 0 && ++ ei->eid_filter.msg_type != msg_type) { ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet filtered out (msg_type 0x%02x != filter 0x%02x)\n", ++ msg_type, ei->eid_filter.msg_type); ++ return 0; /* Does not match filter, pass through */ ++ } ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet MATCHES filter (src=%u, dest=%u, msg_type=0x%02x)\n", ++ mh->src, mh->dest, msg_type); ++ } ++ ++ /* ===== Injection Type 1: Clear SOM bit (first fragment) ===== */ ++ if (ei->enable_som_clear && (flags & MCTP_HDR_FLAG_SOM)) { ++ /* Clear the SOM bit in first fragment */ ++ mh->flags_seq_tag &= ~MCTP_HDR_FLAG_SOM; ++ ++ spin_lock_bh(&ei->lock); ++ ei->som_clears++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: SOM bit CLEARED (first fragment, src=%u, dest=%u, seq=%u)%s\n", ++ mh->src, mh->dest, seq, ++ ei->eid_filter.enabled ? " [EID filter ACTIVE]" : ""); ++ ++ return 0; /* Pass through with corrupted SOM */ ++ } ++ ++ /* ===== Injection Type 2: Corrupt sequence number (middle/end fragments) ===== */ ++ if (ei->enable_seq_corrupt && !(flags & MCTP_HDR_FLAG_SOM)) { ++ /* This is a middle or end fragment - corrupt the sequence number */ ++ u8 corrupted_seq = (seq + 1) & 0x03; /* Increment sequence (wrap at 4) */ ++ ++ /* Clear old sequence and set corrupted one */ ++ mh->flags_seq_tag &= ~(0x03 << 4); /* Clear bits 4-5 */ ++ mh->flags_seq_tag |= (corrupted_seq << 4); /* Set corrupted sequence */ ++ ++ spin_lock_bh(&ei->lock); ++ ei->seq_corruptions++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: Sequence CORRUPTED (2nd+ fragment, src=%u, dest=%u, orig_seq=%u -> corrupted_seq=%u)%s\n", ++ mh->src, mh->dest, seq, corrupted_seq, ++ ei->eid_filter.enabled ? " [EID filter ACTIVE]" : ""); ++ ++ return 0; /* Pass through with corrupted sequence */ ++ } ++ ++ /* ===== Injection Type 3: Drop fragment (2nd+ fragments) ===== */ ++ if (ei->enable_fragment_drop && !(flags & MCTP_HDR_FLAG_SOM)) { ++ /* Drop this fragment (2nd onwards) */ ++ spin_lock_bh(&ei->lock); ++ ei->fragments_dropped++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: Fragment DROPPED (2nd+ fragment, src=%u, dest=%u, seq=%u)%s\n", ++ mh->src, mh->dest, seq, ++ ei->eid_filter.enabled ? " [EID filter ACTIVE]" : ""); ++ ++ return 1; /* Drop this fragment */ ++ } ++ ++ /* No injection applied - pass through normally */ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_fragment); ++ ++/* ===== Debugfs Interface ===== */ ++ ++/* Helper to get mctp_usb from file */ ++static struct mctp_usb *mctp_usb_from_file(struct file *file) ++{ ++ return file->f_inode->i_private; ++} ++ ++/* enable_tx attribute */ ++static ssize_t mctp_debugfs_enable_tx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.enable_tx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_tx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ mctp_usb->error_inject.enable_tx = enable; ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ netdev_dbg(mctp_usb->netdev, "TX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_tx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_tx_read, ++ .write = mctp_debugfs_enable_tx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* enable_rx attribute */ ++static ssize_t mctp_debugfs_enable_rx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.enable_rx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_rx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ mctp_usb->error_inject.enable_rx = enable; ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ netdev_dbg(mctp_usb->netdev, "RX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_rx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_rx_read, ++ .write = mctp_debugfs_enable_rx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* mode attribute */ ++static ssize_t mctp_debugfs_mode_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ const char *mode_str; ++ char buf[16]; ++ int len; ++ ++ switch (mctp_usb->error_inject.mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ mode_str = "always\n"; ++ break; ++ case MCTP_ERR_MODE_RANDOM: ++ mode_str = "random\n"; ++ break; ++ case MCTP_ERR_MODE_COUNT: ++ mode_str = "count\n"; ++ break; ++ default: ++ mode_str = "unknown\n"; ++ break; ++ } ++ ++ len = snprintf(buf, sizeof(buf), "%s", mode_str); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_mode_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[16]; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ ++ if (strncmp(buf, "always", 6) == 0) ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ else if (strncmp(buf, "random", 6) == 0) ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_RANDOM; ++ else if (strncmp(buf, "count", 5) == 0) ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_COUNT; ++ else { ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ return -EINVAL; ++ } ++ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_mode_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_mode_read, ++ .write = mctp_debugfs_mode_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Generic u32 read/write helpers */ ++#define MCTP_DEBUGFS_U32_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", mctp_usb->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ u32 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou32(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.field = val; \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic int read/write helpers (for error codes) */ ++#define MCTP_DEBUGFS_INT_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.field = val; \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic bool read/write helpers */ ++#define MCTP_DEBUGFS_BOOL_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.field ? 1 : 0); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.field = (val != 0); \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Define all the file operations */ ++MCTP_DEBUGFS_INT_DEFINE(urb_tx_sync_error_code, urb_tx_sync_error_code) ++MCTP_DEBUGFS_INT_DEFINE(urb_tx_async_error_code, urb_tx_async_error_code) ++MCTP_DEBUGFS_U32_DEFINE(urb_tx_error_rate, urb_tx_error_rate) ++MCTP_DEBUGFS_INT_DEFINE(urb_rx_error_code, urb_rx_error_code) ++MCTP_DEBUGFS_U32_DEFINE(urb_rx_error_rate, urb_rx_error_rate) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_fragment_drop, enable_fragment_drop) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_seq_corrupt, enable_seq_corrupt) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_som_clear, enable_som_clear) ++MCTP_DEBUGFS_U32_DEFINE(delay_ms, delay_ms) ++ ++/* EID filter attributes */ ++#define MCTP_DEBUGFS_U8_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", mctp_usb->error_inject.eid_filter.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ u8 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou8(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.eid_filter.field = val; \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++static ssize_t mctp_debugfs_eid_filter_enable_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.eid_filter.enabled); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_eid_filter_enable_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ mctp_usb->error_inject.eid_filter.enabled = enable; ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_eid_filter_enable_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_eid_filter_enable_read, ++ .write = mctp_debugfs_eid_filter_enable_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_src_eid, src_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_dest_eid, dest_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_msg_type, msg_type) ++ ++/* stats attribute (read-only) */ ++static ssize_t mctp_debugfs_stats_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ char *buf; ++ int len = 0; ++ ssize_t ret; ++ ++ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ spin_lock_bh(&ei->lock); ++ ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_tx: %d\n", ei->enable_tx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_rx: %d\n", ei->enable_rx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "mode: %s\n", ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ len += snprintf(buf + len, PAGE_SIZE - len, "tx_sync_errors_injected: %u\n", ++ ei->urb_tx_sync_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "tx_async_errors_injected: %u\n", ++ ei->urb_tx_async_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "rx_errors_injected: %u\n", ++ ei->urb_rx_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "fragments_dropped: %u\n", ++ ei->fragments_dropped); ++ len += snprintf(buf + len, PAGE_SIZE - len, "seq_corruptions: %u\n", ++ ei->seq_corruptions); ++ len += snprintf(buf + len, PAGE_SIZE - len, "som_clears: %u\n", ++ ei->som_clears); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_packets_processed: %llu\n", ++ ei->total_packets_processed); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_errors_injected: %llu\n", ++ ei->total_errors_injected); ++ ++ spin_unlock_bh(&ei->lock); ++ ++ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static const struct file_operations mctp_debugfs_stats_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* reset attribute (write-only) */ ++static ssize_t mctp_debugfs_reset_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ char buf[8]; ++ int val; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtoint(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (val != 1) ++ return -EINVAL; ++ ++ spin_lock_bh(&ei->lock); ++ ++ /* Reset all state */ ++ ei->enable_tx = false; ++ ei->enable_rx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->urb_tx_sync_error_code = 0; ++ ei->urb_tx_async_error_code = 0; ++ ei->urb_tx_error_rate = 0; ++ ei->urb_tx_sync_inject_count = 0; ++ ei->urb_tx_async_inject_count = 0; ++ ei->urb_rx_error_code = 0; ++ ei->urb_rx_error_rate = 0; ++ ei->urb_rx_inject_count = 0; ++ ei->enable_fragment_drop = false; ++ ei->enable_seq_corrupt = false; ++ ei->enable_som_clear = false; ++ ei->delay_ms = 0; ++ ei->eid_filter.enabled = false; ++ ei->eid_filter.src_eid = 0; ++ ei->eid_filter.dest_eid = 0; ++ ei->eid_filter.msg_type = 0; ++ ++ /* Reset statistics */ ++ ei->urb_tx_sync_errors_injected = 0; ++ ei->urb_tx_async_errors_injected = 0; ++ ei->urb_rx_errors_injected = 0; ++ ei->fragments_dropped = 0; ++ ei->seq_corruptions = 0; ++ ei->som_clears = 0; ++ ei->total_packets_processed = 0; ++ ei->total_errors_injected = 0; ++ ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, "Error injection reset\n"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_reset_fops = { ++ .owner = THIS_MODULE, ++ .write = mctp_debugfs_reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Initialize error injection - called from main driver probe */ ++void mctp_usb_error_inject_init(struct mctp_usb *mctp_usb) ++{ ++ struct dentry *dir, *eid_dir; ++ ++ /* Initialize error injection state */ ++ spin_lock_init(&mctp_usb->error_inject.lock); ++ prandom_seed_state(&mctp_usb->error_inject.rng, (u64)jiffies); ++ mctp_usb->error_inject.enable_tx = false; ++ mctp_usb->error_inject.enable_rx = false; ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ /* All other fields are zero-initialized */ ++ ++ /* Setup debugfs */ ++ if (!mctp_usb_debugfs_root) ++ return; ++ ++ /* Use USB interface device name (stable, unique, persistent across reboots) ++ * instead of netdev name (which can be renamed by udev after probe). ++ * This prevents debugfs directory name collisions when devices are hot-plugged. ++ * Example: "1-1.1.1.1:1.0" instead of "mctpusb0" ++ */ ++ dir = debugfs_create_dir(dev_name(&mctp_usb->intf->dev), mctp_usb_debugfs_root); ++ if (IS_ERR_OR_NULL(dir)) { ++ netdev_warn(mctp_usb->netdev, ++ "Failed to create debugfs directory '%s', error injection disabled\n", ++ dev_name(&mctp_usb->intf->dev)); ++ return; ++ } ++ ++ mctp_usb->debugfs_dir = dir; ++ ++ /* Create error_inject files */ ++ debugfs_create_file("enable_tx", 0600, dir, mctp_usb, &mctp_debugfs_enable_tx_fops); ++ debugfs_create_file("enable_rx", 0600, dir, mctp_usb, &mctp_debugfs_enable_rx_fops); ++ debugfs_create_file("mode", 0600, dir, mctp_usb, &mctp_debugfs_mode_fops); ++ debugfs_create_file("urb_tx_sync_error_code", 0600, dir, mctp_usb, &mctp_debugfs_urb_tx_sync_error_code_fops); ++ debugfs_create_file("urb_tx_async_error_code", 0600, dir, mctp_usb, &mctp_debugfs_urb_tx_async_error_code_fops); ++ debugfs_create_file("urb_tx_error_rate", 0600, dir, mctp_usb, &mctp_debugfs_urb_tx_error_rate_fops); ++ debugfs_create_file("urb_rx_error_code", 0600, dir, mctp_usb, &mctp_debugfs_urb_rx_error_code_fops); ++ debugfs_create_file("urb_rx_error_rate", 0600, dir, mctp_usb, &mctp_debugfs_urb_rx_error_rate_fops); ++ debugfs_create_file("enable_fragment_drop", 0600, dir, mctp_usb, &mctp_debugfs_enable_fragment_drop_fops); ++ debugfs_create_file("enable_seq_corrupt", 0600, dir, mctp_usb, &mctp_debugfs_enable_seq_corrupt_fops); ++ debugfs_create_file("enable_som_clear", 0600, dir, mctp_usb, &mctp_debugfs_enable_som_clear_fops); ++ debugfs_create_file("delay_ms", 0600, dir, mctp_usb, &mctp_debugfs_delay_ms_fops); ++ debugfs_create_file("stats", 0400, dir, mctp_usb, &mctp_debugfs_stats_fops); ++ debugfs_create_file("reset", 0200, dir, mctp_usb, &mctp_debugfs_reset_fops); ++ ++ /* Create eid_filter subdirectory */ ++ eid_dir = debugfs_create_dir("eid_filter", dir); ++ if (!IS_ERR_OR_NULL(eid_dir)) { ++ debugfs_create_file("enable", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_enable_fops); ++ debugfs_create_file("src_eid", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_src_eid_fops); ++ debugfs_create_file("dest_eid", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_dest_eid_fops); ++ debugfs_create_file("msg_type", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_msg_type_fops); ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_init); ++ ++/* Cleanup error injection - called from main driver disconnect */ ++void mctp_usb_error_inject_cleanup(struct mctp_usb *mctp_usb) ++{ ++ debugfs_remove_recursive(mctp_usb->debugfs_dir); ++ mctp_usb->debugfs_dir = NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_cleanup); ++ ++/* Module init for debugfs root */ ++int __init mctp_usb_error_inject_module_init(void) ++{ ++ /* Create debugfs root directory */ ++ mctp_usb_debugfs_root = debugfs_create_dir("mctp_usb", NULL); ++ if (IS_ERR_OR_NULL(mctp_usb_debugfs_root)) { ++ pr_warn("MCTP USB: Failed to create debugfs root, error injection disabled\n"); ++ mctp_usb_debugfs_root = NULL; ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++/* Module exit for debugfs root ++ * Note: Not marked __exit because it's called from __init error path in mctp-usb.c ++ */ ++void mctp_usb_error_inject_module_exit(void) ++{ ++ debugfs_remove_recursive(mctp_usb_debugfs_root); ++ mctp_usb_debugfs_root = NULL; ++} ++ +diff --git a/drivers/net/mctp/mctp-usb-error-inject.h b/drivers/net/mctp/mctp-usb-error-inject.h +new file mode 100644 +index 000000000..bc77fb8eb +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb-error-inject.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * mctp-usb-error-inject.h - Error injection infrastructure for MCTP USB ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ */ ++ ++#ifndef _MCTP_USB_ERROR_INJECT_H ++#define _MCTP_USB_ERROR_INJECT_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct mctp_usb; ++ ++/* Error injection modes */ ++enum mctp_error_inject_mode { ++ MCTP_ERR_MODE_ALWAYS, ++ MCTP_ERR_MODE_RANDOM, ++ MCTP_ERR_MODE_COUNT ++}; ++ ++/* Per-interface error injection state */ ++struct mctp_error_inject { ++ bool enable_tx; /* Separate enable for TX path */ ++ bool enable_rx; /* Separate enable for RX path */ ++ enum mctp_error_inject_mode mode; ++ ++ /* TX injection - separate sync and async */ ++ int urb_tx_sync_error_code; /* Sync error (URB submission) */ ++ int urb_tx_async_error_code; /* Async error (URB completion) */ ++ u32 urb_tx_error_rate; /* Percentage (0-100) or count */ ++ u32 urb_tx_sync_inject_count; /* Counter for sync count mode */ ++ u32 urb_tx_async_inject_count;/* Counter for async count mode */ ++ u32 urb_tx_sync_errors_injected; ++ u32 urb_tx_async_errors_injected; ++ ++ /* RX injection */ ++ int urb_rx_error_code; ++ u32 urb_rx_error_rate; ++ u32 urb_rx_inject_count; ++ u32 urb_rx_errors_injected; ++ ++ /* Fragment injection - drop, corrupt sequence, clear SOM */ ++ bool enable_fragment_drop; /* Drop 2nd+ fragments */ ++ bool enable_seq_corrupt; /* Corrupt sequence number in middle/end fragments */ ++ bool enable_som_clear; /* Clear SOM bit in first fragment */ ++ u32 fragments_dropped; ++ u32 seq_corruptions; ++ u32 som_clears; ++ ++ /* Delay injection */ ++ u32 delay_ms; ++ ++ /* EID filtering */ ++ struct { ++ bool enabled; ++ u8 src_eid; /* 0 = any */ ++ u8 dest_eid; /* 0 = any */ ++ u8 msg_type; /* 0 = any */ ++ } eid_filter; ++ ++ /* Statistics */ ++ u64 total_packets_processed; ++ u64 total_errors_injected; ++ ++ /* RNG state */ ++ struct rnd_state rng; ++ spinlock_t lock; ++}; ++ ++/* Module init/exit functions */ ++int mctp_usb_error_inject_module_init(void); ++void mctp_usb_error_inject_module_exit(void); ++ ++/* Public API for main driver */ ++void mctp_usb_error_inject_init(struct mctp_usb *mctp_usb); ++void mctp_usb_error_inject_cleanup(struct mctp_usb *mctp_usb); ++ ++int mctp_usb_error_inject_tx_sync(struct mctp_usb *mctp_usb, struct sk_buff *skb); ++int mctp_usb_error_inject_tx_async(struct mctp_usb *mctp_usb, struct urb *urb); ++int mctp_usb_error_inject_rx(struct mctp_usb *mctp_usb, int original_status); ++int mctp_usb_error_inject_fragment(struct mctp_usb *mctp_usb, struct sk_buff *skb); ++ ++#endif /* _MCTP_USB_ERROR_INJECT_H */ ++ +diff --git a/drivers/net/mctp/mctp-usb-internal.h b/drivers/net/mctp/mctp-usb-internal.h +new file mode 100644 +index 000000000..7b385ffc3 +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb-internal.h +@@ -0,0 +1,98 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP USB Internal Definitions ++ * Shared between mctp-usb.c and mctp-usb-error-inject.c ++ */ ++ ++#ifndef __MCTP_USB_INTERNAL_H ++#define __MCTP_USB_INTERNAL_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-usb-error-inject.h" ++ ++/* Number of IN/OUT URBs to queue */ ++#define MCTP_USB_N_RX_QUEUE 8 ++#define MCTP_USB_N_TX_QUEUE 8 ++ ++/* Main device structure - one per USB interface */ ++struct mctp_usb { ++ struct usb_device *usbdev; ++ struct usb_interface *intf; ++ bool stopped; ++ ++ struct net_device *netdev; ++ ++ u8 ep_in; ++ u8 ep_out; ++ ++ struct usb_anchor rx_anchor; ++ struct usb_anchor tx_anchor; ++ /* number of urbs currently queued */ ++ atomic_t rx_qlen, tx_qlen; ++ ++ struct delayed_work rx_retry_work; ++ ++ /* TX batching support - controlled via sysfs */ ++ bool tx_batching_enabled; ++ ++ /* Error injection support */ ++ struct mctp_error_inject error_inject; ++ struct dentry *debugfs_dir; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (URB completion callbacks, allocation failures, pre-parse errors) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_usb_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_urb_error; ++ u64 rx_drop_invalid_len; ++ u64 rx_drop_parse_error; ++ u64 rx_drop_fragment_error; ++ /* TX stats - Valid USB error codes only */ ++ u64 tx_drop_no_memory; /* -ENOMEM */ ++ u64 tx_drop_urb_error; /* Generic/unknown errors */ ++ u64 tx_drop_ebusy; /* -EBUSY: URB already active */ ++ u64 tx_drop_enodev; /* -ENODEV: Device/bus doesn't exist */ ++ u64 tx_drop_enoent; /* -ENOENT: Interface/endpoint doesn't exist */ ++ u64 tx_drop_enxio; /* -ENXIO: Host controller doesn't support URB type */ ++ u64 tx_drop_einval; /* -EINVAL: Invalid transfer parameters */ ++ u64 tx_drop_exdev; /* -EXDEV: ISO frames expired */ ++ u64 tx_drop_efbig; /* -EFBIG: Too many ISO frames */ ++ u64 tx_drop_epipe; /* -EPIPE: Pipe type mismatch */ ++ u64 tx_drop_emsgsize; /* -EMSGSIZE: Transfer length invalid */ ++ u64 tx_drop_enospc; /* -ENOSPC: Bandwidth overcommit */ ++ u64 tx_drop_eshutdown; /* -ESHUTDOWN: Device/HC disabled */ ++ u64 tx_drop_eperm; /* -EPERM: URB rejected */ ++ u64 tx_drop_ehostunreach; /* -EHOSTUNREACH: Device suspended */ ++ u64 tx_drop_enoexec; /* -ENOEXEC: Control URB missing Setup packet */ ++ u64 tx_drop_queue_full; /* Queue full condition */ ++ u64 tx_requeued; ++ u64 rx_requeued; ++ u64 rx_urb_submitted; /* RX URBs submitted */ ++ u64 tx_urb_submitted; /* TX URBs submitted */ ++ } eid[257]; ++ } eid_stats; ++}; ++ ++/* Structure to track batched packets in URB context */ ++struct mctp_usb_batch_ctx { ++ struct net_device *netdev; ++ struct sk_buff_head skbs; ++ unsigned int num_packets; ++}; ++ ++#endif /* __MCTP_USB_INTERNAL_H */ ++ ++ +diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c +new file mode 100644 +index 000000000..b9bca7067 +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb.c +@@ -0,0 +1,1307 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * mctp-usb.c - MCTP-over-USB (DMTF DSP0283) transport binding driver. ++ * ++ * DSP0283 is available at: ++ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf ++ * ++ * Copyright (C) 2024-2025 Code Construct Pty Ltd ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "mctp-usb-internal.h" ++#include "mctp-stats.h" ++#include ++ ++/* number of IN/OUT urbs to queue */ ++const unsigned int n_rx_queue = MCTP_USB_N_RX_QUEUE; ++const unsigned int n_tx_queue = MCTP_USB_N_TX_QUEUE; ++ ++/** ++ * mctp_usb_handle_tx_urb_status - Handle TX URB completion status ++ * @mctp_usb: MCTP USB device ++ * @netdev: Network device ++ * @status: URB completion status code ++ * @num_packets: Number of packets in the batch ++ * @actual_length: Actual transfer length (for success case tracing) ++ * ++ * Processes TX URB completion status and updates statistics accordingly. ++ * All async URB errors are tracked under MCTP_EID_UNKNOWN since batch ++ * completion cannot determine individual packet EIDs. ++ */ ++static void mctp_usb_handle_tx_urb_status(struct mctp_usb *mctp_usb, ++ struct net_device *netdev, ++ int status, ++ unsigned int num_packets, ++ unsigned int actual_length) ++{ ++ switch (status) { ++ /* Valid USB error codes only */ ++ case -EBUSY: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_ebusy += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENODEV: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enodev += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENOENT: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enoent += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENXIO: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enxio += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EINVAL: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_einval += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EXDEV: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_exdev += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EFBIG: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_efbig += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EPIPE: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_epipe += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EMSGSIZE: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_emsgsize += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENOSPC: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enospc += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ESHUTDOWN: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_eshutdown += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EPERM: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_eperm += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EHOSTUNREACH: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_ehostunreach += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENOEXEC: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enoexec += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case 0: ++ netdev->stats.tx_packets += num_packets; ++ /* tx_bytes already updated per packet during batching */ ++ trace_mctp_transport_tx("usb", netdev, 0, actual_length); ++ break; ++ default: ++ if (net_ratelimit()) { ++ netdev_warn(netdev, "unexpected tx urb status: %d\n", ++ status); ++ } ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_urb_error += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ trace_mctp_transport_error("usb", netdev, "tx_urb_unexpected", status); ++ } ++} ++ ++/** ++ * mctp_usb_handle_tx_sync_error - Handle synchronous TX error statistics ++ * @mctp_usb: MCTP USB device ++ * @dest_eid: Destination EID from MCTP header ++ * @error_code: Error code from URB submission ++ * ++ * Maps synchronous TX errors (URB submission failures) to per-EID statistics. ++ * Unlike async URB completion errors, these can be attributed to a specific ++ * destination EID since the packet header is still available. ++ * Only handles valid USB error codes per USB subsystem specification. ++ */ ++static void mctp_usb_handle_tx_sync_error(struct mctp_usb *mctp_usb, ++ u8 dest_eid, ++ int error_code) ++{ ++ switch (error_code) { ++ /* Valid USB error codes only */ ++ case -ENOMEM: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_no_memory); ++ break; ++ case -EBUSY: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ebusy); ++ break; ++ case -ENODEV: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enodev); ++ break; ++ case -ENOENT: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoent); ++ break; ++ case -ENXIO: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enxio); ++ break; ++ case -EINVAL: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_einval); ++ break; ++ case -EXDEV: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_exdev); ++ break; ++ case -EFBIG: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_efbig); ++ break; ++ case -EPIPE: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_epipe); ++ break; ++ case -EMSGSIZE: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_emsgsize); ++ break; ++ case -ENOSPC: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enospc); ++ break; ++ case -ESHUTDOWN: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eshutdown); ++ break; ++ case -EPERM: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eperm); ++ break; ++ case -EHOSTUNREACH: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ehostunreach); ++ break; ++ case -ENOEXEC: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoexec); ++ break; ++ default: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_urb_error); ++ break; ++ } ++} ++ ++static void mctp_usb_out_complete(struct urb *urb) ++{ ++ struct mctp_usb_batch_ctx *ctx = urb->context; ++ struct net_device *netdev = ctx->netdev; ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ struct sk_buff *skb; ++ int status; ++ ++ usb_unanchor_urb(urb); ++ if (atomic_dec_return(&mctp_usb->tx_qlen) < n_tx_queue) ++ netif_wake_queue(netdev); ++ ++ status = urb->status; ++ ++ /* ERROR INJECTION POINT: TX URB completion (asynchronous error) ++ * Note: Injection happens at URB level (may affect multiple batched packets). ++ * Pass urb so async path can apply EID filter from first packet. ++ */ ++ status = mctp_usb_error_inject_tx_async(mctp_usb, urb); ++ ++ /* Handle TX URB status and update statistics */ ++ mctp_usb_handle_tx_urb_status(mctp_usb, netdev, status, ++ ctx->num_packets, urb->actual_length); ++ ++ if (status != 0) { ++ /* Report error to socket error queue for batched URB. ++ */ ++ skb = skb_dequeue(&ctx->skbs); ++ if (skb) { ++ if (skb->len >= sizeof(struct mctp_usb_hdr)) { ++ struct mctp_usb_hdr *hdr; ++ struct sk_buff *pkt_skb; ++ struct mctp_sk_key *key = NULL; ++ struct sock *sk; ++ unsigned int pkt_total_len; ++ ++ netdev_dbg(netdev, "Async TX error: Processing batched SKB len=%u\n", skb->len); ++ ++ /* Parse ONLY the first packet for error reporting */ ++ hdr = (struct mctp_usb_hdr *)skb->data; ++ pkt_total_len = hdr->len; ++ ++ if (pkt_total_len <= skb->len) { ++ /* Create temp SKB with first packet only */ ++ pkt_skb = alloc_skb(pkt_total_len, GFP_ATOMIC); ++ if (pkt_skb) { ++ skb_put_data(pkt_skb, skb->data, pkt_total_len); ++ pkt_skb->dev = netdev; ++ ++ /* Preserve socket for error reporting (response packets) */ ++ if (skb->sk) ++ skb_set_owner_w(pkt_skb, skb->sk); ++ ++ /* Remove USB header to expose MCTP header */ ++ if (skb_pull(pkt_skb, sizeof(struct mctp_usb_hdr))) { ++ skb_reset_network_header(pkt_skb); ++ ++ /* Look up socket and report ONE error for entire batch */ ++ sk = mctp_lookup_sock_for_error(pkt_skb, netdev, NULL, &key); ++ ++ if (sk) { ++ netdev_dbg(netdev, "Async TX error: Reporting error %d for entire batch (key=%p)\n", ++ status, key); ++ mctp_queue_error(sk, pkt_skb, -status, netdev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_USB, key); ++ sock_put(sk); ++ } else { ++ netdev_dbg(netdev, "Async TX error: Not reported (no TX key or error queue disabled)\n"); ++ } ++ } ++ kfree_skb(pkt_skb); ++ } ++ } ++ } ++ /* Always free the dequeued SKB, regardless of whether we could report error */ ++ kfree_skb(skb); ++ } ++ } ++ ++ /* Free all batched skbs */ ++ while ((skb = skb_dequeue(&ctx->skbs)) != NULL) { ++ if (status == 0) ++ consume_skb(skb); ++ else ++ kfree_skb(skb); ++ } ++ ++ kfree(ctx); ++ usb_free_urb(urb); ++} ++/* Fast path: send a single packet without batching. ++ * This avoids lock overhead when batching is disabled. ++ */ ++static netdev_tx_t mctp_usb_send_single(struct mctp_usb *mctp_usb, ++ struct sk_buff *skb) ++{ ++ struct net_device *netdev = mctp_usb->netdev; ++ struct mctp_usb_batch_ctx *ctx; ++ struct urb *urb; ++ u8 *buf; ++ unsigned int pkt_len = skb->len; ++ int rc; ++ ++ /* Queue check is done in mctp_usb_start_xmit() before modifying SKB */ ++ ++ /* Allocate minimal context for single packet */ ++ ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); ++ if (!ctx) ++ goto err_drop; ++ ++ skb_queue_head_init(&ctx->skbs); ++ ctx->netdev = netdev; ++ ctx->num_packets = 1; ++ ++ /* Allocate URB and buffer */ ++ urb = usb_alloc_urb(0, GFP_ATOMIC); ++ if (!urb) { ++ trace_mctp_transport_error("usb", netdev, "tx_urb_alloc_failed", 0); ++ goto err_free_ctx; ++ } ++ ++ buf = kmalloc(pkt_len, GFP_ATOMIC); ++ if (!buf) { ++ trace_mctp_transport_error("usb", netdev, "tx_buf_alloc_failed", 0); ++ goto err_free_urb; ++ } ++ ++ /* Copy packet data */ ++ skb_copy_bits(skb, 0, buf, pkt_len); ++ skb_queue_tail(&ctx->skbs, skb); ++ ++ netdev->stats.tx_bytes += pkt_len - sizeof(struct mctp_usb_hdr); ++ ++ /* ERROR INJECTION POINT: TX URB submission (synchronous error) */ ++ rc = mctp_usb_error_inject_tx_sync(mctp_usb, skb); ++ if (rc) { ++ /* Simulate URB submission failure */ ++ netdev_info(netdev, "TX single packet: URB submit SIMULATED failure (error injection, code %d)\n", rc); ++ goto err_free_urb; ++ } ++ ++ /* Submit URB */ ++ usb_fill_bulk_urb(urb, mctp_usb->usbdev, ++ usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), ++ buf, pkt_len, mctp_usb_out_complete, ctx); ++ urb->transfer_flags |= URB_FREE_BUFFER; ++ ++ usb_anchor_urb(urb, &mctp_usb->tx_anchor); ++ atomic_inc(&mctp_usb->tx_qlen); ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) ++ netif_stop_queue(netdev); ++ rc = usb_submit_urb(urb, GFP_ATOMIC); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ if (atomic_dec_return(&mctp_usb->tx_qlen) < n_tx_queue) ++ netif_wake_queue(netdev); ++ trace_mctp_transport_error("usb", netdev, "tx_urb_submit_failed", rc); ++ goto err_free_urb; ++ } ++ ++ return NETDEV_TX_OK; ++ ++err_free_urb: ++ usb_free_urb(urb); ++err_free_ctx: ++ skb_dequeue(&ctx->skbs); ++ kfree(ctx); ++err_drop: ++ /* Report synchronous TX error if URB submission failed. ++ * SKB still has USB header, remove it for socket lookup. ++ */ ++ if (rc != 0 && skb->len >= sizeof(struct mctp_usb_hdr)) { ++ struct sock *sk; ++ struct mctp_sk_key *key = NULL; ++ ++ skb_pull(skb, sizeof(struct mctp_usb_hdr)); ++ skb_reset_network_header(skb); ++ ++ sk = mctp_lookup_sock_for_error(skb, netdev, NULL, &key); ++ if (sk) { ++ netdev_dbg(netdev, "TX single: Reporting sync error (code=%d)\n", -rc); ++ mctp_queue_error(sk, skb, -rc, netdev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_USB, key); ++ sock_put(sk); ++ } ++ } ++ ++ netdev->stats.tx_dropped++; ++ ++ /* Track per-EID stats (USB header already pulled above, MCTP header accessible) */ ++ { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 dest_eid = mh->dest; ++ ++ mctp_usb_handle_tx_sync_error(mctp_usb, dest_eid, rc); ++ } ++ netdev_dbg(netdev, "MCTP USB: TX single dropped, error=%d\n", rc); ++ trace_mctp_transport_error("usb", netdev, "tx_single_drop", rc); ++ kfree_skb(skb); ++ return NETDEV_TX_OK; ++} ++ ++/* Callback invoked by route.c to fill in USB transport header during batching. ++ * This is called once per packet in the batch, with the exact location and size. ++ */ ++static void mctp_usb_fill_batch_hdr(void *hdr, unsigned int pkt_len) ++{ ++ struct mctp_usb_hdr *usb_hdr = (struct mctp_usb_hdr *)hdr; ++ ++ usb_hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); ++ usb_hdr->rsvd = 0; ++ usb_hdr->len = pkt_len; ++} ++ ++static const struct mctp_netdev_ops mctp_usb_ops = { ++ .fill_batch_hdr = mctp_usb_fill_batch_hdr, ++}; ++ ++/* Send a pre-batched SKB (from route.c) with multiple MCTP packets. ++ * The SKB data has headers pre-filled by route.c via our callback. ++ */ ++static netdev_tx_t mctp_usb_send_batch(struct mctp_usb *mctp_usb, ++ struct sk_buff *skb) ++{ ++ struct net_device *netdev = mctp_usb->netdev; ++ struct mctp_usb_batch_ctx *ctx; ++ struct urb *urb; ++ int rc; ++ ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) { ++ netif_stop_queue(netdev); ++ return NETDEV_TX_BUSY; ++ } ++ ++ /* Restore the correct protocol now that we're committed to sending */ ++ skb->protocol = htons(ETH_P_MCTP); ++ ++ /* Allocate context */ ++ ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); ++ if (!ctx) ++ goto err_drop; ++ ++ skb_queue_head_init(&ctx->skbs); ++ ctx->netdev = netdev; ++ ctx->num_packets = 1; /* We'll count on completion */ ++ ++ /* Allocate URB */ ++ urb = usb_alloc_urb(0, GFP_ATOMIC); ++ if (!urb) ++ goto err_free_ctx; ++ ++ skb_queue_tail(&ctx->skbs, skb); ++ ++ netdev_dbg(netdev, "Sending batched SKB: %u bytes\n", skb->len); ++ ++ /* ERROR INJECTION POINT: TX URB submission (synchronous error) ++ * Added support for batched packets ++ */ ++ rc = mctp_usb_error_inject_tx_sync(mctp_usb, skb); ++ if (rc) { ++ goto err_free_urb; ++ } ++ ++ /* Submit URB with pre-filled SKB data directly - headers already done by route.c! */ ++ usb_fill_bulk_urb(urb, mctp_usb->usbdev, ++ usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), ++ skb->data, skb->len, mctp_usb_out_complete, ctx); ++ ++ usb_anchor_urb(urb, &mctp_usb->tx_anchor); ++ atomic_inc(&mctp_usb->tx_qlen); ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) ++ netif_stop_queue(netdev); ++ rc = usb_submit_urb(urb, GFP_ATOMIC); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ if (atomic_dec_return(&mctp_usb->tx_qlen) < n_tx_queue) ++ netif_wake_queue(netdev); ++ trace_mctp_transport_error("usb", netdev, "tx_urb_submit_failed", rc); ++ goto err_free_urb; ++ } ++ ++ return NETDEV_TX_OK; ++ ++err_free_urb: ++ usb_free_urb(urb); ++err_free_ctx: ++ skb_dequeue(&ctx->skbs); ++ kfree(ctx); ++err_drop: ++ /* TX Synchronous Error Reporting for batched SKB: ++ */ ++ if (rc != 0) { ++ struct mctp_usb_hdr *hdr; ++ struct sk_buff *pkt_skb; ++ struct sock *sk; ++ struct mctp_sk_key *key = NULL; ++ unsigned int pkt_total_len; ++ ++ netdev_info(netdev, "TX batch DROPPED (URB submit failed, error %d)\n", rc); ++ ++ /* Parse ONLY the first packet for error reporting */ ++ if (skb->len >= sizeof(struct mctp_usb_hdr)) { ++ hdr = (struct mctp_usb_hdr *)skb->data; ++ ++ /* Validate USB header */ ++ if (be16_to_cpu(hdr->id) == MCTP_USB_DMTF_ID) { ++ pkt_total_len = hdr->len; ++ ++ if (pkt_total_len <= skb->len) { ++ /* Create temp SKB for first packet only */ ++ pkt_skb = alloc_skb(pkt_total_len, GFP_ATOMIC); ++ if (pkt_skb) { ++ skb_put_data(pkt_skb, skb->data, pkt_total_len); ++ pkt_skb->dev = netdev; ++ ++ /* Preserve socket for error reporting (response packets) */ ++ if (skb->sk) ++ skb_set_owner_w(pkt_skb, skb->sk); ++ ++ /* Remove USB header to expose MCTP header */ ++ if (skb_pull(pkt_skb, sizeof(struct mctp_usb_hdr))) { ++ skb_reset_network_header(pkt_skb); ++ ++ /* Look up socket and report ONE error for entire batch */ ++ sk = mctp_lookup_sock_for_error(pkt_skb, netdev, NULL, &key); ++ if (sk) { ++ netdev_dbg(netdev, "TX batch sync error: Reporting for entire batch\n"); ++ mctp_queue_error(sk, pkt_skb, -rc, netdev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_USB, key); ++ sock_put(sk); ++ } ++ } ++ kfree_skb(pkt_skb); ++ } ++ } ++ } ++ } ++ } ++ ++ /* Batch TX errors - tracked as UNKNOWN (can't determine individual EIDs in batch) */ ++ netdev->stats.tx_dropped++; ++ mctp_usb_handle_tx_sync_error(mctp_usb, MCTP_EID_UNKNOWN, rc); ++ netdev_dbg(netdev, "MCTP USB: TX batch dropped, error=%d\n", rc); ++ trace_mctp_transport_error("usb", netdev, "tx_batch_drop", rc); ++ kfree_skb(skb); ++ return NETDEV_TX_OK; ++} ++ ++static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct mctp_usb *mctp_usb = netdev_priv(dev); ++ struct mctp_usb_hdr *hdr; ++ unsigned int plen, pkt_len; ++ int rc; ++ ++ /* Detect batched SKBs: route.c sets a special protocol marker ++ * (ETH_P_MCTP | 0x8000) to indicate a pre-batched multi-packet SKB. ++ * This avoids relying on skb->cb which may not be initialized. ++ */ ++ if (skb->protocol == htons(ETH_P_MCTP | 0x8000)) { ++ /* This is a batched SKB - don't clear protocol until after queue check! */ ++ netdev_dbg(dev, "Detected batched SKB: len=%u\n", skb->len); ++ return mctp_usb_send_batch(mctp_usb, skb); ++ } ++ ++ /* Single packet path */ ++ plen = skb->len; ++ pkt_len = plen + sizeof(*hdr); ++ ++ /* Single packet larger than max transfer size - can't send */ ++ if (pkt_len > MCTP_USB_XFER_SIZE) { ++ /* Extract EID for tracking before dropping */ ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ MCTP_STAT_INC(mctp_usb, mh->dest, tx_drop_emsgsize); ++ trace_mctp_transport_error("usb", dev, "tx_len_exceeds_max", pkt_len); ++ goto err_drop; ++ } ++ ++ /* Check queue BEFORE modifying the SKB, so retries don't double-add headers */ ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) { ++ netif_stop_queue(dev); ++ return NETDEV_TX_BUSY; ++ } ++ ++ rc = skb_cow_head(skb, sizeof(*hdr)); ++ if (rc) { ++ trace_mctp_transport_error("usb", dev, "tx_skb_head_error", rc); ++ goto err_drop; ++ } ++ ++ hdr = skb_push(skb, sizeof(*hdr)); ++ if (!hdr) { ++ trace_mctp_transport_error("usb", dev, "tx_skb_push_error", 0); ++ goto err_drop; ++ } ++ ++ hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); ++ hdr->rsvd = 0; ++ hdr->len = pkt_len; ++ ++ /* Send single packet */ ++ return mctp_usb_send_single(mctp_usb, skb); ++ ++err_drop: ++ dev->stats.tx_dropped++; ++ trace_mctp_transport_error("usb", dev, "tx_dropped_start_xmit", 0); ++ kfree_skb(skb); ++ return NETDEV_TX_OK; ++} ++ ++static void mctp_usb_in_complete(struct urb *urb); ++ ++/* If we fail to queue an in urb atomically (either due to skb allocation or ++ * urb submission), we will schedule a rx queue in nonatomic context ++ * after a delay, specified in jiffies ++ */ ++static const unsigned long RX_RETRY_DELAY = HZ / 4; ++ ++static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, struct urb *urb, ++ gfp_t gfp) ++{ ++ struct sk_buff *skb; ++ int rc; ++ ++ /* no point allocating if the queue is going to be rejected */ ++ if (READ_ONCE(mctp_usb->stopped)) ++ return 0; ++ ++ skb = __netdev_alloc_skb(mctp_usb->netdev, MCTP_USB_XFER_SIZE, gfp); ++ if (!skb) { ++ trace_mctp_transport_error("usb", mctp_usb->netdev, "rx_alloc_skb_failed", 0); ++ return -ENOMEM; ++ } ++ ++ usb_fill_bulk_urb(urb, mctp_usb->usbdev, ++ usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in), ++ skb->data, MCTP_USB_XFER_SIZE, ++ mctp_usb_in_complete, skb); ++ ++ atomic_inc(&mctp_usb->rx_qlen); ++ rc = usb_submit_urb(urb, gfp); ++ if (rc) { ++ netdev_dbg(mctp_usb->netdev, "rx urb submit failure: %d\n", rc); ++ trace_mctp_transport_error("usb", mctp_usb->netdev, "rx_urb_submit_failed", rc); ++ atomic_dec(&mctp_usb->rx_qlen); ++ kfree_skb(skb); ++ return rc; ++ } ++ ++ return 0; ++} ++ ++static void mctp_usb_in_complete(struct urb *urb) ++{ ++ struct sk_buff *skb = urb->context; ++ struct net_device *netdev = skb->dev; ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ struct mctp_skb_cb *cb; ++ unsigned int len; ++ int status, rc; ++ ++ status = urb->status; ++ atomic_dec(&mctp_usb->rx_qlen); ++ ++ /* ERROR INJECTION POINT: RX URB completion error */ ++ status = mctp_usb_error_inject_rx(mctp_usb, status); ++ ++ switch (status) { ++ case -ENOENT: ++ case -ESHUTDOWN: ++ if (net_ratelimit()) { ++ netdev_warn(netdev, ++ "rx urb shutdown/error status: %d\n", ++ status); ++ } ++ usb_unanchor_urb(urb); ++ usb_free_urb(urb); ++ if (mctp_usb->error_inject.enable_rx) { ++ netdev_info(netdev, ++ "RX packet DROPPED (error %d) - error injection is ACTIVE\n", ++ status); ++ } else { ++ netdev_dbg(netdev, ++ "RX packet DROPPED (error %d) - expected shutdown/reset\n", ++ status); ++ } ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_urb_error); ++ trace_mctp_transport_error("usb", netdev, "rx_urb_error_shutdown", status); ++ kfree_skb(skb); ++ return; ++ case 0: ++ break; ++ default: ++ if (net_ratelimit()) { ++ netdev_warn(netdev, ++ "unexpected rx urb status: %d, requeuing\n", ++ status); ++ } ++ if (mctp_usb->error_inject.enable_rx) { ++ netdev_info(netdev, ++ "RX packet DROPPED (error %d) - error injection is ACTIVE\n", ++ status); ++ } else { ++ netdev_info(netdev, ++ "RX packet DROPPED (error %d) - real error\n", ++ status); ++ } ++ netdev->stats.rx_errors++; ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_urb_error); ++ trace_mctp_transport_error("usb", netdev, "rx_urb_error_unexpected", status); ++ kfree_skb(skb); ++ goto requeue; ++ } ++ ++ len = urb->actual_length; ++ __skb_put(skb, len); ++ ++ while (skb) { ++ struct sk_buff *skb2 = NULL; ++ struct mctp_usb_hdr *hdr; ++ u8 pkt_len; /* length of MCTP packet, no USB header */ ++ ++ hdr = skb_pull_data(skb, sizeof(*hdr)); ++ if (!hdr) ++ break; ++ ++ if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) { ++ netdev_dbg(netdev, "rx: invalid id %04x\n", ++ be16_to_cpu(hdr->id)); ++ netdev->stats.rx_errors++; ++ netdev->stats.rx_dropped++; ++ /* Try to extract EID for per-EID tracking (USB hdr was pulled, MCTP hdr should be accessible) */ ++ if (skb->len >= sizeof(struct mctp_hdr)) { ++ struct mctp_hdr *mh = (struct mctp_hdr *)skb->data; ++ MCTP_STAT_INC(mctp_usb, mh->src, rx_drop_parse_error); ++ } else { ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_parse_error); ++ } ++ trace_mctp_transport_error("usb", netdev, "rx_invalid_id", be16_to_cpu(hdr->id)); ++ break; ++ } ++ ++ if (hdr->len < ++ sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { ++ netdev_dbg(netdev, "rx: short packet (hdr) %d\n", ++ hdr->len); ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_invalid_len); ++ trace_mctp_transport_error("usb", netdev, "rx_short_packet_hdr", hdr->len); ++ break; ++ } ++ ++ /* we know we have at least sizeof(struct mctp_usb_hdr) here */ ++ pkt_len = hdr->len - sizeof(struct mctp_usb_hdr); ++ if (pkt_len > skb->len) { ++ netdev_dbg(netdev, ++ "rx: short packet (xfer) %d, actual %d\n", ++ hdr->len, skb->len); ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_invalid_len); ++ trace_mctp_transport_error("usb", netdev, "rx_short_packet_xfer", skb->len); ++ break; ++ } ++ ++ if (pkt_len < skb->len) { ++ /* more packets may follow - clone to a new ++ * skb to use on the next iteration ++ */ ++ skb2 = skb_clone(skb, GFP_ATOMIC); ++ if (skb2) { ++ if (!skb_pull(skb2, pkt_len)) { ++ kfree_skb(skb2); ++ skb2 = NULL; ++ } ++ } ++ skb_trim(skb, pkt_len); ++ } ++ ++ netdev->stats.rx_packets++; ++ netdev->stats.rx_bytes += skb->len; ++ ++ skb->protocol = htons(ETH_P_MCTP); ++ skb_reset_network_header(skb); ++ cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++ /* ERROR INJECTION POINT: Fragment drop/corruption ++ * At this point: ++ * - USB header has been removed ++ * - skb->data points to MCTP header ++ * - Before packet sent to network stack ++ * This is the ideal location to inject fragment errors ++ */ ++ if (mctp_usb->error_inject.enable_fragment_drop || ++ mctp_usb->error_inject.enable_seq_corrupt || ++ mctp_usb->error_inject.enable_som_clear || ++ mctp_usb->error_inject.enable_rx) { ++ int inject_action = mctp_usb_error_inject_fragment(mctp_usb, skb); ++ ++ if (inject_action == 1) { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 src_eid = mh->src; ++ ++ /* Drop this fragment */ ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, src_eid, rx_drop_fragment_error); ++ trace_mctp_transport_error("usb", netdev, "rx_drop_fragment_error", 0); ++ kfree_skb(skb); ++ skb = skb2; ++ continue; ++ } ++ /* inject_action == 0: pass through (normally or with corruption) */ ++ } ++ ++ trace_mctp_transport_rx("usb", netdev, 0, skb->len); ++ netif_rx(skb); ++ ++ skb = skb2; ++ } ++ ++ if (skb) ++ kfree_skb(skb); ++ ++requeue: ++ /* URB was automatically unanchored by USB core on completion, ++ * re-anchor before resubmit so it's tracked for shutdown. ++ */ ++ usb_anchor_urb(urb, &mctp_usb->rx_anchor); ++ rc = mctp_usb_rx_queue(mctp_usb, urb, GFP_ATOMIC); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ usb_free_urb(urb); ++ schedule_delayed_work(&mctp_usb->rx_retry_work, RX_RETRY_DELAY); ++ } ++} ++ ++static int mctp_usb_rx_queue_fill(struct mctp_usb *mctp_usb) ++{ ++ int i, qlen, rc = 0; ++ ++ qlen = atomic_read(&mctp_usb->rx_qlen); ++ if (qlen < 0 || qlen >= n_rx_queue) ++ return 0; ++ ++ for (i = 0; i < n_rx_queue - qlen; i++) { ++ struct urb *urb = usb_alloc_urb(0, GFP_KERNEL); ++ ++ if (!urb) { ++ rc = -ENOMEM; ++ break; ++ } ++ ++ usb_anchor_urb(urb, &mctp_usb->rx_anchor); ++ ++ rc = mctp_usb_rx_queue(mctp_usb, urb, GFP_KERNEL); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ usb_free_urb(urb); ++ break; ++ } ++ } ++ ++ return rc; ++} ++ ++static void mctp_usb_rx_retry_work(struct work_struct *work) ++{ ++ struct mctp_usb *mctp_usb = container_of(work, struct mctp_usb, ++ rx_retry_work.work); ++ int rc; ++ ++ if (READ_ONCE(mctp_usb->stopped)) ++ return; ++ ++ rc = mctp_usb_rx_queue_fill(mctp_usb); ++ if (rc) ++ schedule_delayed_work(&mctp_usb->rx_retry_work, RX_RETRY_DELAY); ++} ++ ++static int mctp_usb_open(struct net_device *dev) ++{ ++ struct mctp_usb *mctp_usb = netdev_priv(dev); ++ ++ WRITE_ONCE(mctp_usb->stopped, false); ++ ++ netif_start_queue(dev); ++ ++ return mctp_usb_rx_queue_fill(mctp_usb); ++} ++ ++static int mctp_usb_stop(struct net_device *dev) ++{ ++ struct mctp_usb *mctp_usb = netdev_priv(dev); ++ ++ netif_stop_queue(dev); ++ ++ /* prevent RX submission retry */ ++ WRITE_ONCE(mctp_usb->stopped, true); ++ ++ /* Cancel retry work before unlinking so no new URBs are submitted */ ++ cancel_delayed_work_sync(&mctp_usb->rx_retry_work); ++ ++ /* ++ * Unlink URBs asynchronously. Do not use usb_kill_anchored_urbs() here ++ * as it waits indefinitely for each URB and can block under RTNL (e.g. ++ * "ip link set mctpusbx down") long enough to trigger RCU stalls if the ++ * USB host or device is slow to complete the unlink. Completions will ++ * run later with -ENOENT/-ECONNRESET and clean up. Safe even if the ++ * device is brought up again immediately. ++ */ ++ usb_unlink_anchored_urbs(&mctp_usb->rx_anchor); ++ usb_unlink_anchored_urbs(&mctp_usb->tx_anchor); ++ ++ return 0; ++} ++ ++/* sysfs attribute for runtime control of TX batching */ ++static ssize_t tx_batching_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *netdev = to_net_dev(dev); ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ ++ return sprintf(buf, "%d\n", mctp_usb->tx_batching_enabled ? 1 : 0); ++} ++ ++static ssize_t tx_batching_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct net_device *netdev = to_net_dev(dev); ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ struct mctp_dev *mdev; ++ bool enabled; ++ int rc; ++ ++ rc = kstrtobool(buf, &enabled); ++ if (rc) ++ return rc; ++ ++ mctp_usb->tx_batching_enabled = enabled; ++ ++ /* Also update the mctp_dev flag for use in route.c */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(netdev); ++ if (mdev) { ++ mdev->tx_batching_enabled = enabled; ++ netdev_info(netdev, ++ "TX batching %s (hdr_len=%u, max_xfer=%u)\n", ++ enabled ? "enabled" : "disabled", ++ mdev->tx_batch_hdr_len, mdev->tx_batch_max_xfer); ++ mctp_dev_put( ++ mdev); /* Release reference taken by __mctp_dev_get() */ ++ } else { ++ netdev_err(netdev, "Failed to get mctp_dev!\n"); ++ } ++ rcu_read_unlock(); ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(tx_batching); ++ ++static struct attribute *mctp_usb_attrs[] = { ++ &dev_attr_tx_batching.attr, ++ NULL, ++}; ++ ++static const struct attribute_group mctp_usb_attr_group = { ++ .attrs = mctp_usb_attrs, ++}; ++ ++/* Ethtool statistics support */ ++/* Per-EID stat descriptors with abbreviated names */ ++struct mctp_usb_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_USB_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_usb_eid_stats, field) \ ++} ++ ++static const struct mctp_usb_eid_stat_desc mctp_usb_eid_stat_descs[] = { ++ /* RX statistics */ ++ MCTP_USB_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_USB_EID_STAT("rx_drop_urb_error", rx_drop_urb_error), ++ MCTP_USB_EID_STAT("rx_drop_invalid_len", rx_drop_invalid_len), ++ MCTP_USB_EID_STAT("rx_drop_parse_error", rx_drop_parse_error), ++ MCTP_USB_EID_STAT("rx_drop_fragment_error", rx_drop_fragment_error), ++ /* TX statistics - Valid USB error codes only */ ++ MCTP_USB_EID_STAT("tx_drop_no_memory", tx_drop_no_memory), ++ MCTP_USB_EID_STAT("tx_drop_urb_error", tx_drop_urb_error), ++ MCTP_USB_EID_STAT("tx_drop_ebusy", tx_drop_ebusy), ++ MCTP_USB_EID_STAT("tx_drop_enodev", tx_drop_enodev), ++ MCTP_USB_EID_STAT("tx_drop_enoent", tx_drop_enoent), ++ MCTP_USB_EID_STAT("tx_drop_enxio", tx_drop_enxio), ++ MCTP_USB_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_USB_EID_STAT("tx_drop_exdev", tx_drop_exdev), ++ MCTP_USB_EID_STAT("tx_drop_efbig", tx_drop_efbig), ++ MCTP_USB_EID_STAT("tx_drop_epipe", tx_drop_epipe), ++ MCTP_USB_EID_STAT("tx_drop_emsgsize", tx_drop_emsgsize), ++ MCTP_USB_EID_STAT("tx_drop_enospc", tx_drop_enospc), ++ MCTP_USB_EID_STAT("tx_drop_eshutdown", tx_drop_eshutdown), ++ MCTP_USB_EID_STAT("tx_drop_eperm", tx_drop_eperm), ++ MCTP_USB_EID_STAT("tx_drop_ehostunreach", tx_drop_ehostunreach), ++ MCTP_USB_EID_STAT("tx_drop_enoexec", tx_drop_enoexec), ++ MCTP_USB_EID_STAT("tx_drop_queue_full", tx_drop_queue_full), ++ /* General statistics */ ++ MCTP_USB_EID_STAT("tx_requeued", tx_requeued), ++ MCTP_USB_EID_STAT("rx_requeued", rx_requeued), ++ MCTP_USB_EID_STAT("rx_urb_submitted", rx_urb_submitted), ++ MCTP_USB_EID_STAT("tx_urb_submitted", tx_urb_submitted), ++}; ++ ++#define MCTP_USB_EID_NUM_STATS ARRAY_SIZE(mctp_usb_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_usb, struct mctp_usb, ++ struct mctp_usb_eid_stats, mctp_usb_eid_stat_descs) ++ ++static void mctp_usb_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_usb *musb = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_usb_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, musb->eid_stats.active, 257) { ++ struct mctp_usb_eid_stats *es = &musb->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_usb_count_eid_nonzero(musb, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (async URB ++ * completion callbacks, batch submission failures, or packets ++ * too short to contain valid MCTP header) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: URB/pre-parse errors "); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_usb_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_usb_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_usb_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_usb *musb = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_USB_EID_NUM_STATS + 1 + mctp_usb_count_eid_stats(musb); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_usb_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_usb *musb = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, musb->eid_stats.active, 257) { ++ u8 *base = (u8 *)&musb->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_usb_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, musb->eid_stats.active, 257) { ++ struct mctp_usb_eid_stats *es = &musb->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_usb_count_eid_nonzero(musb, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_usb_eid_stats_total(musb, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_usb_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_usb_ethtool_ops = { ++ .get_strings = mctp_usb_get_strings, ++ .get_sset_count = mctp_usb_get_sset_count, ++ .get_ethtool_stats = mctp_usb_get_ethtool_stats, ++}; ++ ++static const struct net_device_ops mctp_usb_netdev_ops = { ++ .ndo_start_xmit = mctp_usb_start_xmit, ++ .ndo_open = mctp_usb_open, ++ .ndo_stop = mctp_usb_stop, ++}; ++ ++static void mctp_usb_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ dev->mtu = MCTP_USB_MTU_MIN; ++ dev->ethtool_ops = &mctp_usb_ethtool_ops; ++ dev->min_mtu = MCTP_USB_MTU_MIN; ++ dev->max_mtu = MCTP_USB_MTU_MAX; ++ ++ dev->hard_header_len = sizeof(struct mctp_usb_hdr); ++ dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; ++ dev->flags = IFF_NOARP; ++ dev->netdev_ops = &mctp_usb_netdev_ops; ++} ++ ++static int mctp_usb_probe(struct usb_interface *intf, ++ const struct usb_device_id *id) ++{ ++ struct usb_endpoint_descriptor *ep_in, *ep_out; ++ struct usb_host_interface *iface_desc; ++ struct net_device *netdev; ++ struct mctp_usb *dev; ++ int rc; ++ ++ /* only one alternate */ ++ iface_desc = intf->cur_altsetting; ++ ++ rc = usb_find_common_endpoints(iface_desc, &ep_in, &ep_out, NULL, NULL); ++ if (rc) { ++ dev_err(&intf->dev, "invalid endpoints on device?\n"); ++ return rc; ++ } ++ ++ netdev = alloc_netdev(sizeof(*dev), "mctpusb%d", NET_NAME_ENUM, ++ mctp_usb_netdev_setup); ++ if (!netdev) ++ return -ENOMEM; ++ ++ SET_NETDEV_DEV(netdev, &intf->dev); ++ dev = netdev_priv(netdev); ++ dev->netdev = netdev; ++ dev->usbdev = usb_get_dev(interface_to_usbdev(intf)); ++ dev->intf = intf; ++ usb_set_intfdata(intf, dev); ++ ++ dev->ep_in = ep_in->bEndpointAddress; ++ dev->ep_out = ep_out->bEndpointAddress; ++ ++ init_usb_anchor(&dev->rx_anchor); ++ init_usb_anchor(&dev->tx_anchor); ++ ++ INIT_DELAYED_WORK(&dev->rx_retry_work, mctp_usb_rx_retry_work); ++ ++ /* Enable TX batching by default */ ++ dev->tx_batching_enabled = true; ++ ++ rc = mctp_register_netdev(netdev, &mctp_usb_ops, MCTP_PHYS_BINDING_USB); ++ if (rc) ++ goto err_free_netdev; ++ ++ /* Set the mctp_dev batching parameters for use in route.c */ ++ { ++ struct mctp_dev *mdev; ++ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(netdev); ++ if (mdev) { ++ mdev->tx_batching_enabled = true; ++ mdev->tx_batch_hdr_len = sizeof(struct mctp_usb_hdr); ++ mdev->tx_batch_max_xfer = MCTP_USB_XFER_SIZE; ++ mctp_dev_put( ++ mdev); /* Release reference taken by __mctp_dev_get() */ ++ } ++ rcu_read_unlock(); ++ } ++ ++ /* Register sysfs attribute for runtime control */ ++ rc = sysfs_create_group(&netdev->dev.kobj, &mctp_usb_attr_group); ++ if (rc) { ++ netdev_err(netdev, "failed to create sysfs group: %d\n", rc); ++ goto err_unregister_netdev; ++ } ++ ++ /* Setup error injection after netdev registration (debugfs needs the netdev name) */ ++ mctp_usb_error_inject_init(dev); ++ ++ return 0; ++ ++err_unregister_netdev: ++ mctp_unregister_netdev(netdev); ++err_free_netdev: ++ free_netdev(netdev); ++ return rc; ++} ++ ++static void mctp_usb_disconnect(struct usb_interface *intf) ++{ ++ struct mctp_usb *dev = usb_get_intfdata(intf); ++ ++ /* Remove sysfs attribute group */ ++ sysfs_remove_group(&dev->netdev->dev.kobj, &mctp_usb_attr_group); ++ ++ /* Cleanup error injection */ ++ mctp_usb_error_inject_cleanup(dev); ++ ++ mctp_unregister_netdev(dev->netdev); ++ usb_put_dev(dev->usbdev); ++ free_netdev(dev->netdev); ++} ++ ++static const struct usb_device_id mctp_usb_devices[] = { ++ { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x1) }, ++ { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x2) }, ++ { 0 }, ++}; ++ ++MODULE_DEVICE_TABLE(usb, mctp_usb_devices); ++ ++static struct usb_driver mctp_usb_driver = { ++ .name = "mctp-usb", ++ .id_table = mctp_usb_devices, ++ .probe = mctp_usb_probe, ++ .disconnect = mctp_usb_disconnect, ++}; ++ ++static int __init mctp_usb_init(void) ++{ ++ int rc; ++ ++ /* Initialize error injection infrastructure */ ++ rc = mctp_usb_error_inject_module_init(); ++ if (rc) ++ pr_warn("MCTP USB: Error injection initialization failed, continuing without it\n"); ++ ++ rc = usb_register(&mctp_usb_driver); ++ if (rc) ++ mctp_usb_error_inject_module_exit(); ++ ++ return rc; ++} ++ ++static void __exit mctp_usb_exit(void) ++{ ++ usb_deregister(&mctp_usb_driver); ++ mctp_usb_error_inject_module_exit(); ++} ++ ++module_init(mctp_usb_init); ++module_exit(mctp_usb_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Jeremy Kerr "); ++MODULE_DESCRIPTION("MCTP USB transport"); +diff --git a/drivers/net/mctp/nvidia-optee-vrot.c b/drivers/net/mctp/nvidia-optee-vrot.c +new file mode 100644 +index 000000000..e0c714024 +--- /dev/null ++++ b/drivers/net/mctp/nvidia-optee-vrot.c +@@ -0,0 +1,1264 @@ ++/// @file ++/// @brief Kernel module for MCTP communication with NVIDIA's VRoT. ++/// @details VRoTs are optee Trusted Applications that each manage a single endpoint. ++/// Enable with the following device tree to create a mctp device called mctpvrot0. ++/// @code{.dts} ++/// { ++/// mctpvrot0 { ++/// compatible = "nvidia,optee,vrot"; ++/// nvidia,ta-uuid = "14fe76f6-6114-499c-9b81-f90ebde9c50c"; ++/// nvidia,mctp-uuid = "14fe76f6-6114-499c-9b81-f90ebde9c50c"; ++/// nvidia,mctp-mtu = <5000>; ++/// nvidia,mctp-protocol-versions = <0x1 0xF1F0F000 ++/// 0x5 0x0 ++/// 0x7e 0xF1F0FF00>; ++/// status = "okay"; ++/// }; ++/// }; ++/// @endcode ++/// ++/// Device Tree Properties: ++/// - nvidia,ta-uuid: The UUID of the trusted application. ++/// - nvidia,mctp-uuid: The UUID used in the GET UUID MCTP control message. ++/// - nvidia,mctp-mtu: The max transmission unit for MCTP communication. ++/// - nvidia,mctp-protocol-versions: A mapping of supported MCTP procotols ++/// to the version that should be reported in a Get MCTP Version Support command. ++/// Use zero to not respond to version requests. ++/// Non-zero values are formatted in the standard MCTP version format. ++/// The example devicetree reports PLDM version 1.0.0 SPDM version unknown ++/// and vendor defined 1.0. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define HEARTBEAT_COMMAND_INTERVAL_MS 30000 ++#define TEE_IOCTL_PARAM_ATTR_TYPE_NONE 0 ++ ++#define LOG_IMPL(lvl, fmt, ...) \ ++ printk(lvl "NVIDIA-OPTEE-VROT: " fmt "\n", ##__VA_ARGS__) ++ ++/// @brief Helper logging macros. ++/// @details Has the same API as printf(). ++/// @{ ++#ifdef DEBUG ++#define LOG_INF(...) LOG_IMPL(KERN_INFO, __VA_ARGS__) ++#else ++#define LOG_INF(...) \ ++ do { \ ++ } while (0) ++#endif ++#define LOG_WRN(...) LOG_IMPL(KERN_WARNING, __VA_ARGS__) ++#define LOG_ERR(...) LOG_IMPL(KERN_ERR, __VA_ARGS__) ++/// @} ++ ++#define TA_COMMAND_ID_WRITE_PACKET 0 ++#define TA_COMMAND_ID_HANDLE_NON_ATOMIC_OPERATION 1 ++#define TA_COMMAND_ID_HEARTBEAT 3 ++ ++#define NVIDIA_VROT_QUEUE_SIZE 20 ++ ++#define NVIDIA_VROT_MAX_PROTOCOLS 10 ++ ++/// @brief Entries that are encoded in the nvidia,mctp-protocol-versions property. ++/// @details The property is an array of u32. ++/// Odd indexes represent protocol IDs and even indexes are the version of the protocol. ++/// Is needed because the VRoTs do not implement the control MCTP protocol so we ++/// fake it in the kernel module. ++/// @note Entries should not be provided for the control protocol or base MCTP version ++/// as that is controlled by this module. ++struct nvidia_ta_mctp_protocol_version { ++ /// @brief The MCTP protocol ID. ++ /// @example 1 is PLDM, 5 is SPDM. ++ u8 protocol; ++ /// @brief The version number. ++ /// @note Use 0 if you do not which to respond to a ++ /// Get MCTP Version Support command for this protocol. ++ /// @note This is a u32 in native endianness, using a u8[4] to reduce padding. ++ /// @example 0xF1F0F000 is 1.0.0. ++ u8 version[4]; ++}; ++ ++/// @brief Immutable config taken from device tree. ++struct nvidia_ta_mctp_config { ++ /// @brief nvidia,ta-uuid property. ++ /// @details The UUID of the trusted application that acts as a VRoT. ++ uuid_t ta_uuid; ++ /// @brief nvidia,mctp-uuid property. ++ /// @details The UUID to use in response to a Get Endpoint UUID MCTP Control Message. ++ uuid_t mctp_uuid; ++ /// @brief nvidia,mctp-mtu property. ++ /// @details The max allowed MTU for the VRoT trusted application. ++ /// @note Is not discovered at startup via TEE function calls because we want to delay ++ /// TA initialization to as late as possible to allow for tee-supplicant to be started ++ // which may occur after kernel module loading. ++ u32 mtu; ++ /// @brief The number of valid entries in protocols. ++ u32 protocols_size; ++ /// @brief nvidia,mctp-protocol-versions property. ++ struct nvidia_ta_mctp_protocol_version ++ protocols[NVIDIA_VROT_MAX_PROTOCOLS]; ++}; ++ ++/// @brief State used by callbacks. ++struct nvidia_ta_mctp_driver { ++ /// @brief Info from device tree. ++ struct nvidia_ta_mctp_config config; ++ ++ /// @brief Trusted execution context. ++ struct tee_context *tee_ctx; ++ /// @brief Shared Memory with the trusted environment. ++ struct tee_shm *tee_pool; ++ /// @brief Trusted execution session id. ++ u32 tee_session_id; ++ ++ /// @brief Network device used by the linux kernel for this driver. ++ struct net_device *netdev; ++ ++ /// @brief Worker task for handling MCTP packets. ++ struct task_struct *worker_task; ++ ++ /// @brief wait queue that is woken when tx_queue is pushed to ++ wait_queue_head_t wq; ++ ++ /// @brief Packet queue from the network device to the tx worker thread. ++ struct sk_buff_head tx_queue; ++ ++ /// @brief The timestamp when we should service non-atomic work. ++ /// @details Is only valid if non_atomic is true. ++ u64 non_atomic_work_jiffies_64; ++ ++ /// @brief The timestamp when we should next send a heartbeat. ++ /// @details Is only valid if tee_initialized is true. ++ u64 next_heartbeat_jiffies_64; ++ ++ /// @brief If the tee is initialized. Used to deduplicate initialization since since we initialize once on first write (to ensure TEE-Supplicant is running) ++ u32 tee_initialized : 1; ++ ++ /// @brief Non-atomic operation flag ++ u32 non_atomic : 1; ++}; ++ ++/// @brief Private state for the mctp network device. ++struct nvidia_vrot_netdev_priv { ++ struct nvidia_ta_mctp_driver *driver; ++}; ++ ++static inline void log_buffer_hex_chunks(const char *prefix, const void *buf, ++ size_t count) ++{ ++#ifndef DEBUG ++ (void)prefix; ++ (void)buf; ++ (void)count; ++#else ++#define CHUNK_SIZE 16 ++ const unsigned char *ubuf = (const unsigned char *)buf; ++ size_t i, j; ++ char line[CHUNK_SIZE * ++ 3]; // 2 chars per byte + 1 space or null terminator per byte ++ for (i = 0; i < count; i += CHUNK_SIZE) { ++ size_t chunk = (count - i > CHUNK_SIZE) ? CHUNK_SIZE : ++ (count - i); ++ size_t pos = 0; ++ for (j = 0; j < chunk; j++) { ++ pos += sprintf(&line[pos], "%02x%s", ubuf[i + j], ++ (j < chunk - 1) ? " " : ""); ++ } ++ line[pos] = '\0'; ++ LOG_INF("%s [%04zu-%04zu]: %s", prefix, i, i + chunk - 1, line); ++ } ++#undef CHUNK_SIZE ++#endif ++} ++ ++static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) ++{ ++ switch (ver->impl_id) { ++ case TEE_IMPL_ID_OPTEE: ++ return 1; ++ default: ++ return 0; ++ } ++} ++ ++static int open_ta_session(struct nvidia_ta_mctp_driver *driver) ++{ ++ int ret; ++ struct tee_ioctl_open_session_arg sess_arg; ++ memset(&sess_arg, 0, sizeof(sess_arg)); ++ ++ if (driver == NULL) { ++ LOG_ERR("open_ta_session: driver is NULL"); ++ return -EIO; ++ } ++ ++ export_uuid(sess_arg.uuid, &driver->config.ta_uuid); ++ sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; ++ sess_arg.num_params = 0; ++ /* Open the session */ ++ ret = tee_client_open_session(driver->tee_ctx, &sess_arg, NULL); ++ if (ret < 0) { ++ LOG_ERR("Failed to open OP-TEE session"); ++ return -EINVAL; ++ } ++ ++ if (sess_arg.ret != 0) { ++ LOG_ERR("Session open failed with TA error: 0x%x", ++ sess_arg.ret); ++ return -EINVAL; ++ } ++ ++ /* Store the session ID */ ++ driver->tee_session_id = sess_arg.session; ++ LOG_INF("Kernel: OP-TEE session opened with ID: %u", ++ driver->tee_session_id); ++ ++ return 0; ++} ++ ++static int send_heartbeat_command_to_tee(struct nvidia_ta_mctp_driver *driver) ++{ ++ driver->next_heartbeat_jiffies_64 = ++ get_jiffies_64() + ++ msecs_to_jiffies(HEARTBEAT_COMMAND_INTERVAL_MS); ++ ++ struct tee_ioctl_invoke_arg invoke_arg = { ++ .func = TA_COMMAND_ID_HEARTBEAT, ++ .session = driver->tee_session_id, ++ .num_params = 4 ++ }; ++ ++ struct tee_param params[4] = { { 0 } }; ++ void *va; ++ int ret; ++ ++ // Get virtual address of shared memory ++ va = tee_shm_get_va(driver->tee_pool, 0); ++ if (IS_ERR(va)) { ++ LOG_ERR("Failed to get virtual address: %ld", PTR_ERR(va)); ++ return -EINVAL; ++ } ++ // Setup parameter for TA ++ // only parameter is the shared memory buffer to hold the vrot state ++ params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT; ++ params[0].u.memref.shm = driver->tee_pool; ++ params[0].u.memref.shm_offs = 0; ++ params[0].u.memref.size = driver->config.mtu; ++ ++ // Setup parameters 1-3: TYPE_NONE ++ params[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ params[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ params[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ ++ LOG_INF("Sending param types: p0=0x%llx, p1=0x%llx, p2=0x%llx, p3=0x%llx", ++ params[0].attr, params[1].attr, params[2].attr, params[3].attr); ++ ++ // Invoke TA ++ ret = tee_client_invoke_func(driver->tee_ctx, &invoke_arg, params); ++ LOG_INF("TA invocation returned: ret=%d, invoke_arg.ret=0x%x", ret, ++ invoke_arg.ret); ++ if (ret < 0 || invoke_arg.ret != 0) { ++ if (ret < 0) { ++ LOG_ERR("TA invocation failed: ret=%d. Check if TA is available and initialized on the system.", ++ ret); ++ return -EIO; ++ } ++ if (invoke_arg.ret != 0) { ++ LOG_ERR("TA invocation returned error: ta_ret=0x%x", ++ invoke_arg.ret); ++ } ++ return -EIO; ++ } ++ ++ LOG_INF("TA invocation completed"); ++ size_t out_size = params[0].u.memref.size; ++ ++ // at this point, we have the data in the shared memory from the tee invocation. ++ char *vrot_state = tee_shm_get_va(driver->tee_pool, 0); ++ if (vrot_state == NULL) { ++ LOG_ERR("Failed to get virtual address of shared memory"); ++ return -EFAULT; ++ } ++ ++ log_buffer_hex_chunks("VRoT state", vrot_state, out_size); ++ return 0; ++} ++ ++static int init_tee(struct nvidia_ta_mctp_driver *driver) ++{ ++ int ret; ++ if (driver == NULL) { ++ LOG_ERR("init_tee: driver is NULL"); ++ return -EIO; ++ } ++ ++ if (driver->tee_initialized) { ++ return 0; // Already initialized ++ } ++ ++ driver->tee_ctx = ++ tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); ++ if (IS_ERR_OR_NULL(driver->tee_ctx)) { ++ LOG_ERR("Failed to open TEE context: %ld", ++ PTR_ERR(driver->tee_ctx)); ++ return -ENODEV; ++ } ++ ++ ret = open_ta_session(driver); ++ if (ret < 0) { ++ tee_client_close_context(driver->tee_ctx); ++ driver->tee_ctx = NULL; ++ return ret; ++ } ++ ++ driver->tee_pool = ++ tee_shm_alloc_kernel_buf(driver->tee_ctx, driver->config.mtu); ++ if (IS_ERR_OR_NULL(driver->tee_pool)) { ++ LOG_ERR("Failed to allocate shared memory: %ld", ++ PTR_ERR(driver->tee_pool)); ++ tee_client_close_session(driver->tee_ctx, ++ driver->tee_session_id); ++ tee_client_close_context(driver->tee_ctx); ++ driver->tee_ctx = NULL; ++ driver->tee_session_id = 0; ++ return -ENOMEM; ++ } ++ ++ LOG_INF("Shared memory allocated: size=%zu", driver->tee_pool->size); ++ driver->tee_initialized = true; ++ // want an immediate heartbeat ++ driver->next_heartbeat_jiffies_64 = get_jiffies_64(); ++ return 0; ++} ++ ++static int invoke_ta_function(struct nvidia_ta_mctp_driver *driver, ++ struct sk_buff *skb, int command_id, ++ size_t *out_size) ++{ ++ if (driver == NULL) { ++ LOG_ERR("invoke_ta_function: driver is NULL"); ++ return -EIO; ++ } ++ ++ size_t count = 0; ++ if (skb) { ++ count = skb->len; ++ } ++ ++ struct tee_ioctl_invoke_arg invoke_arg = { ++ .func = command_id, ++ .session = driver->tee_session_id, ++ .num_params = 4 ++ }; ++ ++ struct tee_param params[4] = { { 0 } }; ++ void *va; ++ int ret; ++ ++ if (!driver->tee_pool || !driver->tee_ctx) { ++ LOG_ERR("Shared memory or context not initialized"); ++ return -EINVAL; ++ } ++ ++ // Get virtual address of shared memory ++ va = tee_shm_get_va(driver->tee_pool, 0); ++ if (IS_ERR(va)) { ++ LOG_ERR("Failed to get virtual address: %ld", PTR_ERR(va)); ++ return -EINVAL; ++ } ++ ++ // Copy input data to shared memory ++ if (count > driver->tee_pool->size) { ++ LOG_WRN("Input size (%zu) exceeds shared memory size (%zu)", ++ count, driver->tee_pool->size); ++ count = driver->tee_pool->size; ++ } ++ ++ // copy input data to shared memory ++ if (count != 0) { ++ skb_copy_bits(skb, 0, va, count); ++ log_buffer_hex_chunks("Tx to TA", va, count); ++ } ++ ++ // Setup parameter for TA ++ // first parameter is the shared memory buffer ++ params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT; ++ params[0].u.memref.shm = driver->tee_pool; ++ params[0].u.memref.shm_offs = 0; ++ params[0].u.memref.size = count; ++ ++ // second parameter will be used to get bool indicating whether the TA is handling a non-atomic operation, and a delay for invoking the TA again ++ params[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; ++ params[1].u.value.a = 0; ++ params[1].u.value.b = 0; ++ ++ // Setup parameters 2-3: TYPE_NONE for now ++ params[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ params[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ ++ LOG_INF("Sending param types: p0=0x%llx, p1=0x%llx, p2=0x%llx, p3=0x%llx", ++ params[0].attr, params[1].attr, params[2].attr, params[3].attr); ++ LOG_INF("Sending size: %zu", count); ++ ++ // Invoke TA ++ ret = tee_client_invoke_func(driver->tee_ctx, &invoke_arg, params); ++ LOG_INF("TA invocation returned: ret=%d, invoke_arg.ret=0x%x", ret, ++ invoke_arg.ret); ++ if (ret < 0 || invoke_arg.ret != 0) { ++ *out_size = 0; ++ driver->non_atomic = false; ++ driver->non_atomic_work_jiffies_64 = 0; ++ if (ret < 0) { ++ LOG_ERR("TA invocation failed: ret=%d. Check if TA is available and initialized on the system.", ++ ret); ++ return -EIO; ++ } ++ if (invoke_arg.ret != 0) { ++ LOG_ERR("TA invocation returned error: ta_ret=0x%x", ++ invoke_arg.ret); ++ return -EIO; ++ } ++ } ++ ++ LOG_INF("TA invocation completed"); ++ *out_size = params[0].u.memref.size; ++ driver->non_atomic = (bool)params[1].u.value.a; ++ const u32 delay = params[1].u.value.b; ++ if (driver->non_atomic) { ++ driver->non_atomic_work_jiffies_64 = get_jiffies_64() + delay; ++ LOG_INF("TA is handling a non-atomic operation, delay: %u", ++ delay); ++ } else { ++ driver->non_atomic_work_jiffies_64 = 0; ++ } ++ return count; ++} ++ ++static int handle_data_from_ta(struct nvidia_ta_mctp_driver *driver, ++ size_t size) ++{ ++ int ret = -EINVAL; ++ ++ const char *const shared_mem_data = tee_shm_get_va(driver->tee_pool, 0); ++ if (shared_mem_data == NULL) { ++ LOG_ERR("Failed to get virtual address of shared memory"); ++ return -EFAULT; ++ } ++ ++ if (size <= 4) { ++ LOG_ERR("Response size too small for MCTP packet: %u", ++ (unsigned)size); ++ return -EINVAL; ++ } ++ ++ log_buffer_hex_chunks("Rx from TA", shared_mem_data, size); ++ ++ struct sk_buff *skb; ++ skb = netdev_alloc_skb(driver->netdev, size); ++ if (!skb) { ++ LOG_ERR("failed to allocate skb for read packet"); ++ return -ENOMEM; ++ } ++ skb->protocol = htons(ETH_P_MCTP); ++ (void)skb_put_data(skb, shared_mem_data, size); ++ skb_reset_mac_header(skb); ++ skb_reset_network_header(skb); ++ ++ struct mctp_skb_cb *const cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++ ret = netif_receive_skb(skb); ++ if (ret != NET_RX_SUCCESS) { ++ LOG_ERR("netif_rx failed with %d", (int)ret); ++ // NOTE: skb is already freed by netif_receive_skb on error ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static void nvidia_ta_write_to_tee(struct nvidia_ta_mctp_driver *driver, ++ struct sk_buff *skb, int command_id) ++{ ++ int ret = -EINVAL; ++ LOG_INF("nvidia_ta_write_to_tee: Writing to TEE"); ++ ret = init_tee(driver); // Initialize on first write ++ if (ret < 0) { ++ LOG_ERR("Kernel: TEE initialization failed: %d", ret); ++ return; ++ } ++ ++ size_t out_size = 0; ++ ret = invoke_ta_function(driver, skb, command_id, &out_size); ++ if (ret < 0) { ++ LOG_ERR("Kernel: Failed to invoke TA function: %d", ret); ++ return; ++ } ++ ++ if (out_size == 0) { ++ return; ++ } ++ ++ ret = handle_data_from_ta(driver, out_size); ++ if (ret < 0) { ++ LOG_ERR("Kernel: Failed to handle data read from TA: %d", ret); ++ return; ++ } ++} ++ ++/// @brief Performs cleanup for our custom driver bound to the given platform_device. ++/// @param pdev The device having its driver cleaned up. ++/// @note The driver can be in any stage of initialization including completely unbound. ++static void clean_driver(struct platform_device *pdev) ++{ ++ if (pdev == NULL) { ++ return; ++ } ++ ++ struct nvidia_ta_mctp_driver *const driver = ++ (struct nvidia_ta_mctp_driver *)platform_get_drvdata(pdev); ++ if (driver == NULL) { ++ return; ++ } ++ ++ // perform any needed cleanup ++ // Stop the worker thread first ++ if (driver->worker_task != NULL) { ++ kthread_stop(driver->worker_task); ++ driver->worker_task = NULL; ++ } ++ ++ // Ensure network device is down and purge any pending packets ++ if (driver->netdev != NULL) { ++ // Stop the queue to prevent new packets ++ netif_tx_disable(driver->netdev); ++ ++ // Purge any remaining packets in the queue ++ skb_queue_purge(&driver->tx_queue); ++ ++ // Explicitly bring the device down if it's still running ++ rtnl_lock(); ++ if (netif_running(driver->netdev)) { ++ dev_close(driver->netdev); ++ } ++ rtnl_unlock(); ++ ++ // Unregister the netdev - this should handle final cleanup ++ mctp_unregister_netdev(driver->netdev); ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ } ++ ++ if (driver->tee_initialized) { ++ tee_client_close_session(driver->tee_ctx, ++ driver->tee_session_id); ++ driver->tee_initialized = false; ++ } ++ if (driver->tee_pool != NULL) { ++ tee_shm_free(driver->tee_pool); ++ driver->tee_pool = NULL; ++ } ++ if (driver->tee_ctx != NULL) { ++ tee_client_close_context(driver->tee_ctx); ++ driver->tee_ctx = NULL; ++ } ++ ++ // the driver is always heap allocated and we have exclusive ownership of it, free it. ++ platform_set_drvdata(pdev, NULL); ++ kfree(driver); ++} ++ ++netdev_tx_t nvidia_vrot_start_xmit(struct sk_buff *skb, struct net_device *dev) ++{ ++ netdev_tx_t status = NETDEV_TX_BUSY; ++ ++ struct nvidia_vrot_netdev_priv *const priv = netdev_priv(dev); ++ struct nvidia_ta_mctp_driver *const driver = priv->driver; ++ ++ unsigned long flags; ++ ++ // locking the queue for the entire push so we can atomically inspect ++ // the length to manage the network interface queue enable state. ++ spin_lock_irqsave(&driver->tx_queue.lock, flags); ++ if (skb_queue_len(&driver->tx_queue) >= NVIDIA_VROT_QUEUE_SIZE) { ++ // error: queue already full ++ status = NETDEV_TX_BUSY; ++ LOG_WRN("mctp tx queue overflow"); ++ netif_stop_queue(dev); ++ } else { ++ // push the packet ++ status = NETDEV_TX_OK; ++ __skb_queue_tail(&driver->tx_queue, skb); ++ if (skb_queue_len(&driver->tx_queue) == ++ NVIDIA_VROT_QUEUE_SIZE) { ++ // stop the queue to prevent excessive RAM usage ++ netif_stop_queue(dev); ++ } ++ } ++ spin_unlock_irqrestore(&driver->tx_queue.lock, flags); ++ ++ if (status == NETDEV_TX_OK) { ++ wake_up(&driver->wq); ++ } ++ return status; ++} ++ ++int nvidia_vrot_open(struct net_device *dev) ++{ ++ // TODO: could start/resume worker threads ++ netif_start_queue(dev); ++ return 0; ++} ++ ++int nvidia_vrot_stop(struct net_device *dev) ++{ ++ // TODO: could stop worker threads ++ netif_stop_queue(dev); ++ return 0; ++} ++ ++static struct net_device_ops nvidia_vrot_nops = { ++ .ndo_start_xmit = nvidia_vrot_start_xmit, ++ .ndo_open = nvidia_vrot_open, ++ .ndo_stop = nvidia_vrot_stop, ++}; ++ ++// mctp header byte indexes ++static const size_t nv_mctp_ver_index = 0; ++static const size_t nv_mctp_dst_index = 1; ++static const size_t nv_mctp_src_index = 2; ++static const size_t nv_mctp_flags_index = 3; ++ ++// only support version 1 packets with no reserved bits set ++static const u8 nv_mctp_expected_version_byte = 1; ++ ++// flag bits of interest ++static const u8 nv_mctp_som_bit = 1 << 7; ++static const u8 nv_mctp_eom_bit = 1 << 6; ++static const u8 nv_mctp_to_bit = 1 << 3; ++ ++// control header bytes ++static const size_t nv_mctp_ctrl_msg_type_index = 4; ++static const size_t nv_mctp_ctrl_flags_index = 5; ++static const size_t nv_mctp_ctrl_cc_index = 6; ++static const size_t nv_mctp_ctrl_rsp_index = 7; ++ ++// control header values ++static const u8 nv_mctp_ctrl_msg_type = 0; ++ ++// ctrl flags bits of interest ++static const u8 nv_mctp_ctrl_req_bit = 1 << 7; ++static const u8 nv_mctp_ctrl_datagram_bit = 1 << 6; ++ ++// used ctrl response codes ++static const u8 nv_mctp_ctrl_rsp_ok = 0; ++static const u8 nv_mctp_ctrl_rsp_error = 1; ++static const u8 nv_mctp_ctrl_rsp_bad_data = 2; ++static const u8 nv_mctp_ctrl_rsp_bad_cmd = 5; ++ ++// control header sizes ++static const size_t nv_mctp_ctrl_cmd_data_start = 7; ++static const size_t nv_mctp_ctrl_rsp_data_start = 8; ++ ++// must have entire header ++static const size_t nv_min_ctrl_req_size = nv_mctp_ctrl_cmd_data_start; ++// must always fit in a min MTU packet ++#define NV_MAX_CTRL_MSG_SIZE 68 ++ ++static bool nvidia_ta_is_control_request(struct sk_buff *skb) ++{ ++ if (skb->len < nv_min_ctrl_req_size) { ++ return false; ++ } ++ if (skb->len > NV_MAX_CTRL_MSG_SIZE) { ++ return false; ++ } ++ ++ unsigned char data[NV_MAX_CTRL_MSG_SIZE] = { 0 }; ++ if (0 != skb_copy_bits(skb, 0, data, nv_min_ctrl_req_size)) { ++ LOG_WRN("skb_copy_bits failed when detecting packet type"); ++ return false; ++ } ++ if (data[nv_mctp_ver_index] != nv_mctp_expected_version_byte) { ++ return false; ++ } ++ ++ const u8 required_bits = nv_mctp_som_bit | nv_mctp_eom_bit | ++ nv_mctp_to_bit; ++ if ((data[nv_mctp_flags_index] & required_bits) != required_bits) { ++ return false; ++ } ++ if (data[nv_mctp_ctrl_msg_type_index] != nv_mctp_ctrl_msg_type) { ++ // different packet type ++ return false; ++ } ++ return true; ++} ++ ++static int ++nvidia_ta_handle_control_request(struct nvidia_ta_mctp_driver *driver, ++ const struct sk_buff *skb) ++{ ++ // Ensure buffer is large enough for maximum control responses ++ BUILD_BUG_ON(nv_mctp_ctrl_rsp_data_start + 16 > ++ NV_MAX_CTRL_MSG_SIZE); // Get Endpoint UUID ++ BUILD_BUG_ON(nv_mctp_ctrl_rsp_data_start + 1 + ++ NVIDIA_VROT_MAX_PROTOCOLS > ++ NV_MAX_CTRL_MSG_SIZE); // Get Message Type Support ++ ++ LOG_INF("handling control message..."); ++ ++ unsigned char cmd[NV_MAX_CTRL_MSG_SIZE] = { 0 }; ++ if (skb_copy_bits(skb, 0, cmd, skb->len) != 0) { ++ return -EFAULT; ++ } ++ log_buffer_hex_chunks("Control CMD", cmd, skb->len); ++ ++ unsigned char rsp[NV_MAX_CTRL_MSG_SIZE] = { 0 }; ++ ++ // fill MCTP header ++ rsp[nv_mctp_ver_index] = cmd[nv_mctp_ver_index]; ++ rsp[nv_mctp_dst_index] = cmd[nv_mctp_src_index]; ++ rsp[nv_mctp_src_index] = cmd[nv_mctp_dst_index]; ++ rsp[nv_mctp_flags_index] = cmd[nv_mctp_flags_index] & ~nv_mctp_to_bit; ++ ++ // fill control header ++ rsp[nv_mctp_ctrl_msg_type_index] = cmd[nv_mctp_ctrl_msg_type_index]; ++ rsp[nv_mctp_ctrl_flags_index] = cmd[nv_mctp_ctrl_flags_index] & ++ ~nv_mctp_ctrl_req_bit; ++ rsp[nv_mctp_ctrl_cc_index] = cmd[nv_mctp_ctrl_cc_index]; ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_error; ++ ++ // payload-less length ++ size_t rsp_len = nv_mctp_ctrl_rsp_data_start; ++ ++ // supporting only mandatory and actually used commands ++ switch (cmd[nv_mctp_ctrl_cc_index]) { ++ case 1: ++ LOG_INF("Set Endpoint ID"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ // accepted + no pool ++ rsp[nv_mctp_ctrl_rsp_data_start + 0] = 0; ++ // echo back EID, is 2nd data byte in command ++ rsp[nv_mctp_ctrl_rsp_data_start + 1] = ++ cmd[nv_mctp_ctrl_cmd_data_start + 1]; ++ // no pool ++ rsp[nv_mctp_ctrl_rsp_data_start + 2] = 0; ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 3; ++ break; ++ ++ case 2: ++ LOG_INF("Get Endpoint ID"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ // echo back endpoint ID ++ rsp[nv_mctp_ctrl_rsp_data_start + 0] = cmd[nv_mctp_dst_index]; ++ // simple endpoint + dynamic ID ++ rsp[nv_mctp_ctrl_rsp_data_start + 1] = 0; ++ // no transport specific info ++ rsp[nv_mctp_ctrl_rsp_data_start + 2] = 0; ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 3; ++ break; ++ ++ case 3: ++ LOG_INF("Get Endpoint UUID"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ memcpy(&rsp[nv_mctp_ctrl_rsp_data_start], ++ &driver->config.mctp_uuid, 16); ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 16; ++ break; ++ ++ case 4: { ++ LOG_INF("Get Version Support"); ++ u32 version = 0; ++ switch (cmd[nv_mctp_ctrl_cmd_data_start]) { ++ case 0x00: ++ // control version 1.3.1 ++ version = 0xF1F3F100; ++ break; ++ ++ case 0xFF: ++ // base version 1.3.1 ++ version = 0xF1F3F100; ++ break; ++ ++ default: ++ // other protocols, use version set via device tree ++ for (u32 i = 0; i < driver->config.protocols_size; ++ ++i) { ++ if (driver->config.protocols[i].protocol == ++ cmd[nv_mctp_ctrl_cmd_data_start]) { ++ memcpy(&version, ++ driver->config.protocols[i] ++ .version, ++ 4); ++ break; ++ } ++ } ++ break; ++ } ++ ++ if (version == 0) { ++ // version unknown, error ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_bad_data; ++ } else { ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ // versions are stored locally in native endianness, but MCTP wants big endian. ++ version = cpu_to_be32(version); ++ memcpy(&rsp[nv_mctp_ctrl_rsp_data_start], &version, ++ sizeof(version)); ++ rsp_len = nv_mctp_ctrl_rsp_data_start + sizeof(version); ++ } ++ break; ++ } ++ ++ case 5: ++ LOG_INF("Get Message Type Support"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ rsp[nv_mctp_ctrl_rsp_data_start + 0] = ++ driver->config.protocols_size; ++ for (u32 i = 0; i < driver->config.protocols_size; ++i) { ++ rsp[nv_mctp_ctrl_rsp_data_start + 1 + i] = ++ driver->config.protocols[i].protocol; ++ } ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 1 + ++ driver->config.protocols_size; ++ break; ++ ++ default: ++ LOG_WRN("Unsupported control command: 0x%02X", ++ cmd[nv_mctp_ctrl_cc_index]); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_bad_cmd; ++ break; ++ } ++ ++ log_buffer_hex_chunks("Control RSP", rsp, rsp_len); ++ ++ struct sk_buff *rsp_skb; ++ rsp_skb = netdev_alloc_skb(driver->netdev, rsp_len); ++ if (!rsp_skb) { ++ LOG_ERR("failed to allocate skb for read packet"); ++ return -ENOMEM; ++ } ++ rsp_skb->protocol = htons(ETH_P_MCTP); ++ (void)skb_put_data(rsp_skb, rsp, rsp_len); ++ skb_reset_mac_header(rsp_skb); ++ skb_reset_network_header(rsp_skb); ++ ++ struct mctp_skb_cb *const cb = __mctp_cb(rsp_skb); ++ cb->halen = 0; ++ ++ const int ret = netif_receive_skb(rsp_skb); ++ if (ret != NET_RX_SUCCESS) { ++ LOG_ERR("netif_rx failed with %d", (int)ret); ++ // NOTE: skb is already freed by netif_receive_skb on error ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++enum nvidia_ta_timeout_cause { ++ /// @brief There is no reason to ever timeout. ++ NVIDIA_TA_TIMEOUT_NONE, ++ /// @brief Should timeout to process delayed work. ++ NVIDIA_TA_TIMEOUT_WORK_TO_DO, ++ /// @brief Should timeout to send a heartbeat. ++ NVIDIA_TA_TIMEOUT_HEARTBEAT, ++}; ++ ++struct nvidia_ta_timeout { ++ enum nvidia_ta_timeout_cause cause; ++ /// @note Will be MAX_SCHEDULE_TIMEOUT if cause is none. ++ u32 duration_jiffies; ++}; ++ ++static struct nvidia_ta_timeout ++nvidia_ta_get_next_timeout(struct nvidia_ta_mctp_driver *driver) ++{ ++ const u64 now = get_jiffies_64(); ++ ++ bool should_timeout = false; ++ u32 jiffies_til_work = (u32)-1; ++ if (driver->tee_initialized && driver->non_atomic) { ++ should_timeout = true; ++ if (driver->non_atomic_work_jiffies_64 < now) { ++ jiffies_til_work = 0; ++ } else { ++ u64 diff = driver->non_atomic_work_jiffies_64 - now; ++ jiffies_til_work = ++ (u32)min_t(u64, diff, MAX_SCHEDULE_TIMEOUT); ++ } ++ } ++ ++ u32 jiffies_til_heartbeat = (u32)-1; ++ if (driver->tee_initialized) { ++ should_timeout = true; ++ if (driver->next_heartbeat_jiffies_64 < now) { ++ jiffies_til_heartbeat = 0; ++ } else { ++ u64 diff = driver->next_heartbeat_jiffies_64 - now; ++ jiffies_til_heartbeat = ++ (u32)min_t(u64, diff, MAX_SCHEDULE_TIMEOUT); ++ } ++ } ++ ++ if (!should_timeout) { ++ struct nvidia_ta_timeout timeout = { ++ .cause = NVIDIA_TA_TIMEOUT_NONE, ++ .duration_jiffies = MAX_SCHEDULE_TIMEOUT, ++ }; ++ return timeout; ++ } ++ ++ if (jiffies_til_work <= jiffies_til_heartbeat) { ++ struct nvidia_ta_timeout timeout = { ++ .cause = NVIDIA_TA_TIMEOUT_WORK_TO_DO, ++ .duration_jiffies = jiffies_til_work, ++ }; ++ return timeout; ++ } ++ ++ struct nvidia_ta_timeout timeout = { ++ .cause = NVIDIA_TA_TIMEOUT_HEARTBEAT, ++ .duration_jiffies = jiffies_til_heartbeat, ++ }; ++ return timeout; ++} ++ ++static int nvidia_ta_worker(void *data) ++{ ++ struct nvidia_ta_mctp_driver *driver = data; ++ ++ struct sk_buff *skb = NULL; ++ ++ for (;;) { ++ struct nvidia_ta_timeout timeout; ++ ++ // attempt to get a packet to send until we need to handle a non-atomic operation or heartbeat ++ while (!skb) { ++ timeout = nvidia_ta_get_next_timeout(driver); ++ if (timeout.cause != NVIDIA_TA_TIMEOUT_NONE && ++ timeout.duration_jiffies == 0) { ++ // already past a deadline, handle it without blocking ++ break; ++ } ++ bool timeout_expired = false; ++ if (timeout.cause == NVIDIA_TA_TIMEOUT_NONE) { ++ // wait forever ++ wait_event_interruptible( ++ driver->wq, ++ !skb_queue_empty(&driver->tx_queue) || ++ kthread_should_stop()); ++ } else { ++ // wait with timeout ++ const int wait_status = ++ wait_event_interruptible_timeout( ++ driver->wq, ++ !skb_queue_empty( ++ &driver->tx_queue) || ++ kthread_should_stop(), ++ timeout.duration_jiffies); ++ if (wait_status == 0) { ++ timeout_expired = true; ++ } ++ } ++ if (kthread_should_stop()) { ++ return 0; ++ } ++ if (timeout_expired) { ++ // hit timeout, break to handle delayed work or heartbeat ++ break; ++ } ++ ++ // got woken up before timeout, check for a packet ++ skb = skb_dequeue(&driver->tx_queue); ++ netif_start_queue(driver->netdev); ++ if (skb) { ++ break; ++ } ++ } ++ ++ if (!skb) { ++ // handle the cause of the timeout ++ switch (timeout.cause) { ++ case NVIDIA_TA_TIMEOUT_WORK_TO_DO: ++ nvidia_ta_write_to_tee( ++ driver, NULL, ++ TA_COMMAND_ID_HANDLE_NON_ATOMIC_OPERATION); ++ break; ++ ++ case NVIDIA_TA_TIMEOUT_HEARTBEAT: ++ send_heartbeat_command_to_tee(driver); ++ break; ++ ++ default: ++ LOG_WRN("unexpected read timeout cause: %d", ++ (int)timeout.cause); ++ break; ++ } ++ } else if (nvidia_ta_is_control_request(skb)) { ++ // handle control packet locally ++ const int ret = ++ nvidia_ta_handle_control_request(driver, skb); ++ if (ret != 0) { ++ LOG_ERR("nvidia_ta_handle_control_request failed, ret: %d", ++ ret); ++ } ++ } else { ++ // forward non-control packet ++ nvidia_ta_write_to_tee(driver, skb, ++ TA_COMMAND_ID_WRITE_PACKET); ++ } ++ ++ if (skb) { ++ kfree_skb(skb); ++ skb = NULL; ++ } ++ } ++ ++ return 0; ++} ++ ++void nvidia_vrot_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ // NOTE: MTU reconfigured outside of setup ++ dev->min_mtu = 68; ++ dev->max_mtu = 68; ++ dev->mtu = 68; ++ ++ dev->hard_header_len = 0; ++ dev->tx_queue_len = NVIDIA_VROT_QUEUE_SIZE; ++ dev->netdev_ops = &nvidia_vrot_nops; ++ ++ dev->addr_len = 0; ++} ++ ++/// @brief Initializes the endpoint driver. ++/// @param [in,out] driver Endpoint driver. driver->config is already init. ++/// @param [in] name The name to use for the network device and worker thread. ++/// @returns 0 on success, -error code on failure. ++static int ++nvidia_ta_mctp_init_endpoint(struct nvidia_ta_mctp_driver *const driver, ++ const char *name) ++{ ++ int result = -1; ++ ++ init_waitqueue_head(&driver->wq); ++ skb_queue_head_init(&driver->tx_queue); ++ ++ driver->non_atomic = false; ++ driver->non_atomic_work_jiffies_64 = 0; ++ driver->next_heartbeat_jiffies_64 = 0; ++ driver->tee_initialized = false; ++ ++ // create the mctp network device ++ driver->netdev = alloc_netdev(sizeof(struct nvidia_vrot_netdev_priv), ++ name, NET_NAME_PREDICTABLE, ++ nvidia_vrot_netdev_setup); ++ if (!driver->netdev) { ++ LOG_ERR("failed to allocate netdev"); ++ return -ENOMEM; ++ } ++ driver->netdev->min_mtu = driver->config.mtu; ++ driver->netdev->mtu = driver->config.mtu; ++ driver->netdev->max_mtu = driver->config.mtu; ++ struct nvidia_vrot_netdev_priv *priv = netdev_priv(driver->netdev); ++ priv->driver = driver; ++ result = mctp_register_netdev(driver->netdev, NULL, ++ MCTP_PHYS_BINDING_VENDOR); ++ if (result) { ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ LOG_ERR("failed to register netdev"); ++ return result; ++ } ++ ++ driver->worker_task = ++ kthread_run(nvidia_ta_worker, driver, "%s-worker", name); ++ if (IS_ERR(driver->worker_task)) { ++ LOG_ERR("failed to start worker thread"); ++ driver->worker_task = NULL; ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int nvidia_vrot_load_devicetree(struct platform_device *pdev, ++ struct nvidia_ta_mctp_config *config) ++{ ++ if (!pdev) { ++ LOG_ERR("platform_device pointer is NULL"); ++ return -EINVAL; ++ } ++ ++ const struct device_node *np = pdev->dev.of_node; ++ int ret = 0; ++ ++ if (!np || !config) { ++ LOG_ERR("Missing device tree node or config struct"); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,ta-uuid ++ const char *ta_uuid_str; ++ ret = of_property_read_string(np, "nvidia,ta-uuid", &ta_uuid_str); ++ if (ret) { ++ LOG_ERR("Failed to read nvidia,ta-uuid from device tree"); ++ return ret; ++ } ++ ret = uuid_parse(ta_uuid_str, &config->ta_uuid); ++ if (ret) { ++ LOG_ERR("Failed to parse ta_uuid string '%s'", ta_uuid_str); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,mctp-uuid ++ const char *mctp_uuid_str; ++ ret = of_property_read_string(np, "nvidia,mctp-uuid", &mctp_uuid_str); ++ if (ret) { ++ LOG_ERR("Failed to read nvidia,mctp-uuid from device tree"); ++ return ret; ++ } ++ ret = uuid_parse(mctp_uuid_str, &config->mctp_uuid); ++ if (ret) { ++ LOG_ERR("Failed to parse mctp_uuid string '%s'", mctp_uuid_str); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,mctp-mtu ++ ret = of_property_read_u32(np, "nvidia,mctp-mtu", &config->mtu); ++ if (ret) { ++ LOG_ERR("Failed to read nvidia,mctp-mtu from device tree"); ++ return ret; ++ } ++ if (config->mtu < 68 || config->mtu > 65536) { ++ LOG_ERR("Invalid nvidia,mctp-mtu value %u (must be between 68 and 65536)", ++ config->mtu); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,mctp-protocol-versions (array of u32) ++ const __be32 *prop; ++ int len; ++ prop = of_get_property(np, "nvidia,mctp-protocol-versions", &len); ++ if (!prop || len < 0) { ++ LOG_ERR("Failed to get nvidia,mctp-protocol-versions from device tree"); ++ return -EINVAL; ++ } ++ int num_entries = len / sizeof(u32); ++ if (num_entries % 2 != 0) { ++ LOG_ERR("nvidia,mctp-protocol-versions must have even number of entries (protocol/version pairs), got %d", ++ num_entries); ++ return -EINVAL; ++ } ++ config->protocols_size = num_entries / 2; ++ if (config->protocols_size > NVIDIA_VROT_MAX_PROTOCOLS) { ++ LOG_ERR("protocols_size (%u) exceeds max supported %d", ++ config->protocols_size, NVIDIA_VROT_MAX_PROTOCOLS); ++ return -EINVAL; ++ } ++ ++ for (u32 i = 0; i < config->protocols_size; ++i) { ++ const u32 protocol = be32_to_cpu(prop[i * 2]); ++ const u32 version = be32_to_cpu(prop[i * 2 + 1]); ++ if (protocol > 0xFF) { ++ LOG_ERR("protocol value 0x%x at index %u exceeds u8 range", ++ protocol, i); ++ return -EINVAL; ++ } ++ if (protocol == 0x00) { ++ LOG_ERR("protocol value 0x%x at index %u is reserved (control protocol)", ++ protocol, i); ++ return -EINVAL; ++ } ++ if (protocol == 0xFF) { ++ LOG_ERR("protocol value 0x%x at index %u is reserved (base version)", ++ protocol, i); ++ return -EINVAL; ++ } ++ config->protocols[i].protocol = (u8)protocol; ++ memcpy(config->protocols[i].version, &version, sizeof(version)); ++ } ++ ++ return 0; ++} ++ ++static int nvidia_vrot_probe(struct platform_device *pdev) ++{ ++ int ret = -1; ++ struct device *const dev = &pdev->dev; ++ dev_info(dev, "Probe starting for device: %s\n", dev_name(dev)); ++ ++ struct nvidia_ta_mctp_driver *const driver = ++ kzalloc(sizeof(struct nvidia_ta_mctp_driver), GFP_KERNEL); ++ if (!driver) { ++ dev_err(dev, "Failed to allocate memory for driver\n"); ++ return -ENOMEM; ++ } ++ platform_set_drvdata(pdev, driver); ++ ++ ret = nvidia_vrot_load_devicetree(pdev, &driver->config); ++ if (ret != 0) { ++ dev_err(dev, ++ "Failed to load device tree properties for device '%s' (devicetree path: %s), ret: %d\n", ++ dev_name(dev), of_node_full_name(dev->of_node), ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ ret = nvidia_ta_mctp_init_endpoint(driver, dev_name(dev)); ++ if (ret != 0) { ++ dev_err(dev, ++ "failed to init endpoint for device '%s' (devicetree path: %s), ret: %d\n", ++ dev_name(dev), of_node_full_name(dev->of_node), ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ dev_info(dev, "Probe successful for device: %s\n", dev_name(dev)); ++ return 0; ++} ++ ++static void nvidia_vrot_remove(struct platform_device *pdev) ++{ ++ dev_info(&pdev->dev, "Removing device: %s\n", dev_name(&pdev->dev)); ++ clean_driver(pdev); ++ dev_info(&pdev->dev, "Device removed: %s\n", dev_name(&pdev->dev)); ++} ++ ++static const struct of_device_id nvidia_vrot_match[] = { ++ { .compatible = "nvidia,optee,vrot" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, nvidia_vrot_match); ++ ++static struct platform_driver nvidia_vrot_driver = { ++ .probe = nvidia_vrot_probe, ++ .remove = nvidia_vrot_remove, ++ .driver = ++ { ++ .name = "nvidia-optee-vrot", ++ .of_match_table = nvidia_vrot_match, ++ }, ++}; ++ ++module_platform_driver(nvidia_vrot_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("NVIDIA VRoT MCTP Trusted Application Driver"); +diff --git a/drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h b/drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h +new file mode 100644 +index 000000000..3278dbe81 +--- /dev/null ++++ b/drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h +@@ -0,0 +1,151 @@ ++/// @file ++/// @brief Message types for the nvidia IRoT <-> A35 IPC connection. ++/// @details These messages are restricted to 32 bytes but can contain pointers. ++/// @note The messages are always padded to 32 bytes. For future compatibility ++/// all unused fields must be written zero and read ignored. ++ ++#pragma once ++ ++#ifdef __KERNEL__ ++#include ++typedef u32 nvidia_irot_message_u32; ++#else ++#include ++typedef uint32_t nvidia_irot_message_u32; ++#endif ++ ++/// @brief Command codes. ++/// @details Commands are even and responses are odd. ++/// See nvidia_irot_message_data for info on the data payload for each command. ++/// @note Zero is intentially not used. ++enum nvidia_irot_command_code { ++ /// @brief A ping. ++ /// @note In the future this command may be expanded to be used in feature ++ /// detection as it contains a large number of reserved bytes. ++ /// @note If multiple pings are in flight then the responder should respond ++ /// to them in order, but if any responses are dropped then prefer to drop ++ /// older ones. ++ nvidia_irot_cc_ping = 2, ++ /// @brief Response to the ping. ++ nvidia_irot_cc_ping_rsp, ++ ++ /// @brief Request for the IRoT to send buffer addresses. ++ nvidia_irot_cc_get_mctp_buffers, ++ /// @brief The requested buffer addresses. ++ /// @note This response should never change unless the system reboots. ++ nvidia_irot_cc_mctp_buffer, ++ ++ /// @brief A new MCTP message. ++ nvidia_irot_cc_mctp, ++ /// @brief Notification that the MCTP message has been processed and its ++ /// buffer is free to be used. ++ nvidia_irot_cc_mctp_done, ++}; ++ ++/// @brief Memory address in the A35's physical address space. ++/// @note Is encoded in two u32 to avoid alignment and padding issues. ++struct nvidia_irot_message_address { ++ /// @brief The low 32 bits of the address. ++ nvidia_irot_message_u32 low; ++ /// @brief The high 32 bits of the address. ++ nvidia_irot_message_u32 high; ++}; ++ ++/// @brief Memory region in the A35's physical address space. ++struct nvidia_irot_message_span { ++ /// @brief Start address. ++ struct nvidia_irot_message_address address; ++ /// @brief Size in bytes. ++ nvidia_irot_message_u32 size; ++}; ++ ++/// @brief Empty message. ++struct nvidia_irot_message_data_empty {}; ++ ++/// @brief Information about the shared buffers between the IRoT and A35. ++struct nvidia_irot_message_data_buffers { ++ /// @brief Used to compute the maximum transmission unit. ++ /// @details The read_mtu is min(mtu_limit, a35_read.size) and ++ /// the write_mtu is min(mtu_limit, a35_write.size). ++ nvidia_irot_message_u32 mtu_limit; ++ /// @brief Span for the A35 to read from. ++ struct nvidia_irot_message_span a35_read; ++ /// @brief Span for the A35 to write into. ++ struct nvidia_irot_message_span a35_write; ++}; ++ ++/// @brief MCTP packet message data. ++struct nvidia_irot_message_data_mctp { ++ /// @brief MCTP packet counter. ++ /// @details Must be echoed back in the response to the message. ++ /// Additionally if two messages are received back to back they ++ /// must be deduplicated but the response must still be sent. ++ nvidia_irot_message_u32 counter; ++ /// @brief Span of memory that contains the MCTP packet. ++ /// @details The span must be fully contained within the expected ++ /// region from the nvidia_irot_cc_mctp_buffer message. ++ /// If the packet is out of bounds the memory must not be accessed. ++ struct nvidia_irot_message_span packet; ++}; ++ ++/// @brief Data payload for IPC messages. ++union nvidia_irot_message_data { ++ /// @brief Raw arguments. ++ /// @details Used only for initializing and debugging. ++ nvidia_irot_message_u32 args[7]; ++ ++ /// @brief Empty payload. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_get_mctp_buffers ++ struct nvidia_irot_message_data_empty empty; ++ ++ /// @brief A single value. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_ping: for a value that is echoed back. ++ /// - nvidia_irot_cc_ping_rsp: for the value that is echoed back. ++ /// - nvidia_irot_cc_mctp_done: for the packet counter. ++ nvidia_irot_message_u32 value; ++ ++ /// @brief Shared buffer information. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_mctp_buffer ++ struct nvidia_irot_message_data_buffers buffers; ++ ++ /// @brief Mctp packet. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_mctp ++ struct nvidia_irot_message_data_mctp mctp; ++}; ++ ++/// @brief 32-byte IPC message type. ++struct nvidia_irot_message { ++ /// @brief The nvidia_irot_command_code. ++ /// @note Zero is used for empty locally, but should never be sent over IPC. ++ nvidia_irot_message_u32 command; ++ /// @brief The data payload. ++ union nvidia_irot_message_data data; ++}; ++ ++/// @brief Initializer for struct nvidia_irot_message. ++/// @details Zeros out all fields. ++#define NVIDIA_IROT_MESSAGE_INIT \ ++ { \ ++ .command = 0, .data = {.args = { 0 } } \ ++ } ++ ++#if defined(__cplusplus) ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) static_assert(expr); ++#elif __STDC_VERSION__ >= 202311L ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) static_assert(expr); ++#elif __STDC_VERSION__ >= 201112L ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) _Static_assert(expr); ++#else ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) ++#endif ++ ++// Ensure that our message struct is exactly 32 bytes. ++// Some APIs unconditionally copy the max 32 bytes size so both ++// undersized type and oversized types would be a problem. ++NVIDIA_IROT_MESSAGE_STATIC_ASSERT(sizeof(struct nvidia_irot_message) == 32) ++ ++#undef NVIDIA_IROT_MESSAGE_STATIC_ASSERT +diff --git a/drivers/soc/aspeed/aspeed-mctp.c b/drivers/soc/aspeed/aspeed-mctp.c +index b549013b7..3b37d969a 100644 +--- a/drivers/soc/aspeed/aspeed-mctp.c ++++ b/drivers/soc/aspeed/aspeed-mctp.c +@@ -145,7 +145,7 @@ + + /* HW buffer sizes */ + #define TX_PACKET_COUNT 48 +-#define RX_PACKET_COUNT 96 ++#define RX_PACKET_COUNT 1024 + #if (RX_PACKET_COUNT % 4 != 0) + #error The Rx buffer size should be 4-aligned. + #error 1.Make runaway wrap boundary can be determined in Ast2600 A1/A2. +@@ -155,8 +155,8 @@ + #define RX_MAX_PACKET_COUNT (RX_BUF_RD_PTR_MASK + 1) + + /* Per client packet cache sizes */ +-#define RX_RING_COUNT 64 +-#define TX_RING_COUNT 64 ++#define RX_RING_COUNT 1024 ++#define TX_RING_COUNT 512 + + /* PCIe Host Controller registers */ + #define ASPEED_PCIE_LINK 0x0c0 +@@ -2286,7 +2286,8 @@ static int aspeed_mctp_dma_init(struct aspeed_mctp *priv) + struct mctp_channel *tx = &priv->tx; + struct mctp_channel *rx = &priv->rx; + size_t alloc_size; +- int ret = -ENOMEM; ++ int ret; ++ bool use_reserved_mem = false; + + BUILD_BUG_ON(TX_PACKET_COUNT >= TX_MAX_PACKET_COUNT); + BUILD_BUG_ON(RX_PACKET_COUNT >= RX_MAX_PACKET_COUNT); +@@ -2298,17 +2299,21 @@ static int aspeed_mctp_dma_init(struct aspeed_mctp *priv) + } + + ret = of_reserved_mem_device_init(priv->dev); +- if (ret) { +- dev_err(priv->dev, "device does not have specific DMA pool: %d\n", +- ret); ++ if (ret && ret != -ENODEV) { ++ dev_err(priv->dev, "Failed to check reserved DMA pool: %d\n", ret); + return ret; + } + +- ret = devm_add_action_or_reset(priv->dev, aspeed_release_rmem, +- priv->dev); +- if (ret) +- return ret; ++ if (!ret) { ++ /* Reserved memory found, register cleanup action */ ++ use_reserved_mem = true; ++ ret = devm_add_action_or_reset(priv->dev, aspeed_release_rmem, ++ priv->dev); ++ if (ret) ++ return ret; ++ } + ++ dev_info(priv->dev, "%s DMA pool\n", use_reserved_mem ? "Reserved" : "Dynamic"); + alloc_size = PAGE_ALIGN(priv->rx_packet_count * priv->match_data->packet_unit_size); + rx->data.vaddr = + dma_alloc_coherent(priv->dev, alloc_size, &rx->data.dma_handle, GFP_KERNEL); +diff --git a/include/linux/i3c/mctp/i3c-mctp.h b/include/linux/i3c/mctp/i3c-mctp.h +new file mode 100644 +index 000000000..dd20750d7 +--- /dev/null ++++ b/include/linux/i3c/mctp/i3c-mctp.h +@@ -0,0 +1,50 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2022 Intel Corporation.*/ ++ ++#ifndef I3C_MCTP_H ++#define I3C_MCTP_H ++ ++#define I3C_MCTP_PACKET_SIZE 68 ++#define I3C_MCTP_PAYLOAD_SIZE 64 ++#define I3C_MCTP_HDR_SIZE 4 ++ ++/* PECI MCTP Intel VDM definitions */ ++#define MCTP_MSG_TYPE_VDM_PCI 0x7E ++#define MCTP_VDM_PCI_INTEL_VENDOR_ID 0x8086 ++#define MCTP_VDM_PCI_INTEL_PECI 0x2 ++ ++/* MCTP message header offsets */ ++#define MCTP_MSG_HDR_MSG_TYPE_OFFSET 0 ++#define MCTP_MSG_HDR_VENDOR_OFFSET 1 ++#define MCTP_MSG_HDR_OPCODE_OFFSET 4 ++ ++struct i3c_mctp_client; ++ ++struct mctp_protocol_hdr { ++ u8 ver; ++ u8 dest; ++ u8 src; ++ u8 flags_seq_tag; ++} __packed; ++ ++struct i3c_mctp_packet_data { ++ u8 protocol_hdr[I3C_MCTP_HDR_SIZE]; ++ u8 payload[I3C_MCTP_PAYLOAD_SIZE]; ++}; ++ ++struct i3c_mctp_packet { ++ struct i3c_mctp_packet_data data; ++ u32 size; ++}; ++ ++void *i3c_mctp_packet_alloc(gfp_t flags); ++void i3c_mctp_packet_free(void *packet); ++ ++int i3c_mctp_get_eid(struct i3c_mctp_client *client, u8 domain_id, u8 *eid); ++int i3c_mctp_send_packet(struct i3c_device *i3c, struct i3c_mctp_packet *tx_packet); ++struct i3c_mctp_packet *i3c_mctp_receive_packet(struct i3c_mctp_client *client, ++ unsigned long timeout); ++struct i3c_mctp_client *i3c_mctp_add_peci_client(struct i3c_device *i3c); ++void i3c_mctp_remove_peci_client(struct i3c_mctp_client *client); ++ ++#endif /* I3C_MCTP_H */ +diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h +new file mode 100644 +index 000000000..880ff54ac +--- /dev/null ++++ b/include/linux/usb/mctp-usb.h +@@ -0,0 +1,27 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * mctp-usb.h - MCTP USB transport binding: common definitions. ++ * ++ * These are shared between the host and gadget drivers. ++ * ++ * Copyright (C) 2024 Code Construct Pty Ltd ++ */ ++ ++#ifndef __LINUX_USB_MCTP_USB_H ++#define __LINUX_USB_MCTP_USB_H ++ ++#include ++ ++struct mctp_usb_hdr { ++ __be16 id; ++ __u8 rsvd; ++ __u8 len; ++} __packed; ++ ++#define MCTP_USB_XFER_SIZE 512 ++#define MCTP_USB_BTU 68 ++#define MCTP_USB_MTU_MIN MCTP_USB_BTU ++#define MCTP_USB_MTU_MAX (U8_MAX - sizeof(struct mctp_usb_hdr)) ++#define MCTP_USB_DMTF_ID 0x1ab4 ++ ++#endif /* __LINUX_USB_MCTP_USB_H */ +diff --git a/include/net/mctp.h b/include/net/mctp.h +index 28d59ae94..51f1d5a4c 100644 +--- a/include/net/mctp.h ++++ b/include/net/mctp.h +@@ -38,6 +38,8 @@ struct mctp_hdr { + + #define MCTP_INITIAL_DEFAULT_NET 1 + ++#define MCTP_DEFAULT_LIFETIME (6 * CONFIG_HZ) ++ + static inline bool mctp_address_unicast(mctp_eid_t eid) + { + return eid >= 8 && eid < 255; +@@ -64,6 +66,21 @@ static inline struct mctp_hdr *mctp_hdr(struct sk_buff *skb) + } + + /* socket implementation */ ++/* Pending error context for deferred error reporting via workqueue */ ++struct mctp_pending_error { ++ struct list_head list; ++ struct sk_buff *skb; /* First fragment SKB (for addressing) */ ++ struct sock *sk; /* Socket for error reporting (refcounted) */ ++ int error_code; ++ struct net_device *dev; ++ u8 direction; ++ u8 binding; ++ /* For RX timeout: original request payload from key */ ++ u8 orig_msg_type; ++ u16 orig_payload_len; ++ u8 orig_payload[32]; /* First 32 bytes of original request */ ++}; ++ + struct mctp_sock { + struct sock sk; + +@@ -84,6 +101,14 @@ struct mctp_sock { + * tag, and any netdev state for a request/response pairing + */ + struct timer_list key_expiry; ++ ++ /* Error queue control */ ++ bool enable_errqueue; ++ ++ /* Deferred error reporting (to avoid deadlock in timer context) */ ++ struct work_struct error_report_work; ++ struct list_head pending_errors; ++ spinlock_t error_queue_lock; + }; + + /* Key for matching incoming packets to sockets or reassembly contexts. +@@ -178,15 +203,23 @@ struct mctp_sk_key { + * is used. + */ + bool manual_alloc; ++ ++ /* Original message header for error reporting on fragmented messages. ++ * Captured from first fragment (SOM=1) to ensure errors on middle/end ++ * fragments can still report original header to application. ++ */ ++ u8 orig_msg_type; /* Message type (PLDM, SPDM, etc) */ ++ u16 orig_payload_len; /* Captured payload length */ ++ u8 orig_payload[32]; /* First 32 bytes of original message */ + }; + + struct mctp_skb_cb { + unsigned int magic; + unsigned int net; +- int ifindex; /* extended/direct addressing if set */ +- mctp_eid_t src; ++ /* fields below provide extended addressing for ingress to recvmsg() */ ++ int ifindex; + unsigned char halen; +- unsigned char haddr[MAX_ADDR_LEN]; ++ unsigned char haddr[MAX_ADDR_LEN]; + }; + + /* skb control-block accessors with a little extra debugging for initial +@@ -222,6 +255,8 @@ struct mctp_flow { + struct mctp_sk_key *key; + }; + ++struct mctp_dst; ++ + /* Route definition. + * + * These are held in the pernet->mctp.routes list, with RCU protection for +@@ -229,16 +264,25 @@ struct mctp_flow { + * dropped on NETDEV_UNREGISTER events. + * + * Updates to the route table are performed under rtnl; all reads under RCU, +- * so routes cannot be referenced over a RCU grace period. Specifically: A +- * caller cannot block between mctp_route_lookup and mctp_route_release() ++ * so routes cannot be referenced over a RCU grace period. + */ + struct mctp_route { + mctp_eid_t min, max; + + unsigned char type; ++ + unsigned int mtu; +- struct mctp_dev *dev; +- int (*output)(struct mctp_route *route, ++ ++ enum { ++ MCTP_ROUTE_DIRECT, ++ MCTP_ROUTE_GATEWAY, ++ } dst_type; ++ union { ++ struct mctp_dev *dev; ++ struct mctp_fq_addr gateway; ++ }; ++ ++ int (*output)(struct mctp_dst *dst, + struct sk_buff *skb); + + struct list_head list; +@@ -246,19 +290,43 @@ struct mctp_route { + struct rcu_head rcu; + }; + ++/* Route lookup result: dst. Represents the results of a routing decision, ++ * but is only held over the individual routing operation. ++ * ++ * Will typically be stored on the caller stack, and must be released after ++ * usage. ++ */ ++struct mctp_dst { ++ struct mctp_dev *dev; ++ unsigned int mtu; ++ mctp_eid_t nexthop; ++ ++ /* set for direct addressing */ ++ unsigned char halen; ++ unsigned char haddr[MAX_ADDR_LEN]; ++ ++ int (*output)(struct mctp_dst *dst, struct sk_buff *skb); ++}; ++ ++int mctp_dst_from_extaddr(struct mctp_dst *dst, struct net *net, int ifindex, ++ unsigned char halen, const unsigned char *haddr); ++ + /* route interfaces */ +-struct mctp_route *mctp_route_lookup(struct net *net, unsigned int dnet, +- mctp_eid_t daddr); ++int mctp_route_lookup(struct net *net, unsigned int dnet, ++ mctp_eid_t daddr, struct mctp_dst *dst); ++ ++void mctp_dst_release(struct mctp_dst *dst); + + /* always takes ownership of skb */ +-int mctp_local_output(struct sock *sk, struct mctp_route *rt, ++int mctp_local_output(struct sock *sk, struct mctp_dst *dst, + struct sk_buff *skb, mctp_eid_t daddr, u8 req_tag); + + void mctp_key_unref(struct mctp_sk_key *key); + struct mctp_sk_key *mctp_alloc_local_tag(struct mctp_sock *msk, + unsigned int netid, + mctp_eid_t local, mctp_eid_t peer, +- bool manual, u8 *tagp); ++ bool manual, u8 *tagp, ++ unsigned long lifetime); + + /* routing <--> device interface */ + unsigned int mctp_default_net(struct net *net); +@@ -298,4 +366,34 @@ void mctp_routes_exit(void); + int mctp_device_init(void); + void mctp_device_exit(void); + ++/* Error queue support */ ++u8 mctp_get_binding_type(struct net_device *dev); ++void mctp_queue_error(struct sock *sk, struct sk_buff *skb, ++ int error_code, struct net_device *dev, u8 direction, u8 binding, ++ struct mctp_sk_key *key); ++struct sock *mctp_lookup_sock_by_key(struct sk_buff *skb, struct net_device *dev, ++ struct mctp_sk_key **found_key); ++struct sock *mctp_lookup_sock_for_error(struct sk_buff *skb, ++ struct net_device *dev, ++ struct mctp_sk_key *key, ++ struct mctp_sk_key **found_key); ++ ++/* MCTP IDs and Codes from DMTF specification ++ * "DSP0239 Management Component Transport Protocol (MCTP) IDs and Codes" ++ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0239_1.11.1.pdf ++ */ ++enum mctp_phys_binding { ++ MCTP_PHYS_BINDING_UNSPEC = 0x00, ++ MCTP_PHYS_BINDING_SMBUS = 0x01, ++ MCTP_PHYS_BINDING_PCIE_VDM = 0x02, ++ MCTP_PHYS_BINDING_USB = 0x03, ++ MCTP_PHYS_BINDING_KCS = 0x04, ++ MCTP_PHYS_BINDING_SERIAL = 0x05, ++ MCTP_PHYS_BINDING_I3C = 0x06, ++ MCTP_PHYS_BINDING_MMBI = 0x07, ++ MCTP_PHYS_BINDING_PCC = 0x08, ++ MCTP_PHYS_BINDING_UCIE = 0x09, ++ MCTP_PHYS_BINDING_VENDOR = 0xFF, ++}; ++ + #endif /* __NET_MCTP_H */ +diff --git a/include/net/mctpdevice.h b/include/net/mctpdevice.h +index 5c0d04b5c..a3cb57624 100644 +--- a/include/net/mctpdevice.h ++++ b/include/net/mctpdevice.h +@@ -16,12 +16,31 @@ + + struct mctp_sk_key; + ++#define MCTP_NF_TRACK_BUCKETS 64 ++#define MCTP_NF_TRACK_MAX 1024 ++ ++struct mctp_nf_track_entry { ++ struct hlist_node node; ++ mctp_eid_t src; ++ mctp_eid_t dst; ++ u8 tag; ++ u8 verdict; ++ unsigned long expires; ++}; ++ ++struct mctp_nf_track_table { ++ struct hlist_head buckets[MCTP_NF_TRACK_BUCKETS]; ++ spinlock_t lock; ++ unsigned int count; ++}; ++ + struct mctp_dev { + struct net_device *dev; + + refcount_t refs; + + unsigned int net; ++ enum mctp_phys_binding binding; + + const struct mctp_netdev_ops *ops; + +@@ -30,12 +49,27 @@ struct mctp_dev { + size_t num_addrs; + spinlock_t addrs_lock; + ++ unsigned long key_lifetime; ++ ++ /* TX batching support - set by transport drivers */ ++ bool tx_batching_enabled; ++ unsigned int tx_batch_hdr_len; /* per-packet header overhead */ ++ unsigned int tx_batch_max_xfer; /* max batch transfer size */ ++ ++ /* MCTP netdev netfilter fragment tracking (per-netdev) */ ++ struct mctp_nf_track_table nf_track; ++ + struct rcu_head rcu; + }; + + struct mctp_netdev_ops { + void (*release_flow)(struct mctp_dev *dev, + struct mctp_sk_key *key); ++ /* Called during batch packing to fill in the transport header. ++ * @hdr: pointer to reserved space where header should be written ++ * @pkt_len: total packet length (including this header) ++ */ ++ void (*fill_batch_hdr)(void *hdr, unsigned int pkt_len); + }; + + #define MCTP_INITIAL_DEFAULT_NET 1 +@@ -44,7 +78,8 @@ struct mctp_dev *mctp_dev_get_rtnl(const struct net_device *dev); + struct mctp_dev *__mctp_dev_get(const struct net_device *dev); + + int mctp_register_netdev(struct net_device *dev, +- const struct mctp_netdev_ops *ops); ++ const struct mctp_netdev_ops *ops, ++ enum mctp_phys_binding binding); + void mctp_unregister_netdev(struct net_device *dev); + + void mctp_dev_hold(struct mctp_dev *mdev); +@@ -53,4 +88,6 @@ void mctp_dev_put(struct mctp_dev *mdev); + void mctp_dev_set_key(struct mctp_dev *dev, struct mctp_sk_key *key); + void mctp_dev_release_key(struct mctp_dev *dev, struct mctp_sk_key *key); + ++void mctp_dev_set_timeout(struct net_device *dev, unsigned int timeout_ms); ++ + #endif /* __NET_MCTPDEVICE_H */ +diff --git a/include/trace/events/mctp.h b/include/trace/events/mctp.h +index 165cf25f7..1d371a76c 100644 +--- a/include/trace/events/mctp.h ++++ b/include/trace/events/mctp.h +@@ -73,6 +73,461 @@ TRACE_EVENT(mctp_key_release, + ) + ); + ++TRACE_EVENT(mctp_rx_packet, ++ TP_PROTO(struct sk_buff *skb), ++ TP_ARGS(skb), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, flags_seq_tag) ++ __array(u8, data, 32) ++ ), ++ TP_fast_assign( ++ struct mctp_hdr *mh = skb->len >= sizeof(*mh) ? ++ mctp_hdr(skb) : NULL; ++ __entry->len = skb->len; ++ __entry->src = mh ? mh->src : 0; ++ __entry->dest = mh ? mh->dest : 0; ++ __entry->flags_seq_tag = mh ? mh->flags_seq_tag : 0; ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 32)); ++ ), ++ TP_printk("RX: src=%02x dst=%02x flags=%02x len=%u data=%s", ++ __entry->src, __entry->dest, __entry->flags_seq_tag, ++ __entry->len, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 32)) ++ ) ++); ++ ++TRACE_EVENT(mctp_rx_socket, ++ TP_PROTO(struct sk_buff *skb, int rc), ++ TP_ARGS(skb, rc), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(int, rc) ++ __array(u8, data, 32) ++ ), ++ TP_fast_assign( ++ __entry->len = skb->len; ++ __entry->rc = rc; ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 32)); ++ ), ++ TP_printk("RX_SOCKET: len=%u rc=%d data=%s", ++ __entry->len, __entry->rc, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 32)) ++ ) ++); ++ ++TRACE_EVENT(mctp_tx_packet, ++ TP_PROTO(struct sk_buff *skb), ++ TP_ARGS(skb), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __array(u8, data, 32) ++ ), ++ TP_fast_assign( ++ __entry->len = skb->len; ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 32)); ++ ), ++ TP_printk("TX: len=%u data=%s", ++ __entry->len, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 32)) ++ ) ++); ++ ++TRACE_EVENT(mctp_drop_packet, ++ TP_PROTO(struct sk_buff *skb, const char *reason), ++ TP_ARGS(skb, reason), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(u8, src) ++ __field(u8, dest) ++ __string(reason, reason) ++ __array(u8, data, 16) ++ ), ++ TP_fast_assign( ++ struct mctp_hdr *mh = skb->len >= sizeof(*mh) ? mctp_hdr(skb) : NULL; ++ __entry->len = skb->len; ++ __entry->src = mh ? mh->src : 0; ++ __entry->dest = mh ? mh->dest : 0; ++ __assign_str(reason); ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 16)); ++ ), ++ TP_printk("DROP at %s: src=%02x dst=%02x len=%u data=%s", ++ __get_str(reason), __entry->src, __entry->dest, __entry->len, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 16)) ++ ) ++); ++ ++TRACE_EVENT(mctp_device_register, ++ TP_PROTO(const struct net_device *dev, unsigned int net_id), ++ TP_ARGS(dev, net_id), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(unsigned int, net_id) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->net_id = net_id; ++ ), ++ TP_printk("dev=%s ifindex=%d net=%u", ++ __get_str(name), __entry->ifindex, __entry->net_id ++ ) ++); ++ ++TRACE_EVENT(mctp_device_unregister, ++ TP_PROTO(const struct net_device *dev), ++ TP_ARGS(dev), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ ), ++ TP_printk("dev=%s ifindex=%d", ++ __get_str(name), __entry->ifindex ++ ) ++); ++ ++TRACE_EVENT(mctp_address_add, ++ TP_PROTO(const struct net_device *dev, u8 addr), ++ TP_ARGS(dev, addr), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, addr) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->addr = addr; ++ ), ++ TP_printk("dev=%s ifindex=%d addr=%02x", ++ __get_str(name), __entry->ifindex, __entry->addr ++ ) ++); ++ ++TRACE_EVENT(mctp_address_del, ++ TP_PROTO(const struct net_device *dev, u8 addr), ++ TP_ARGS(dev, addr), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, addr) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->addr = addr; ++ ), ++ TP_printk("dev=%s ifindex=%d addr=%02x", ++ __get_str(name), __entry->ifindex, __entry->addr ++ ) ++); ++ ++TRACE_EVENT(mctp_neighbor_add, ++ TP_PROTO(const struct net_device *dev, u8 eid, const void *lladdr, size_t len), ++ TP_ARGS(dev, eid, lladdr, len), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, eid) ++ __field(size_t, lladdr_len) ++ __array(u8, lladdr, MAX_ADDR_LEN) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->eid = eid; ++ __entry->lladdr_len = len; ++ memcpy(__entry->lladdr, lladdr, min_t(size_t, len, MAX_ADDR_LEN)); ++ ), ++ TP_printk("dev=%s ifindex=%d eid=%02x lladdr=%s", ++ __get_str(name), __entry->ifindex, __entry->eid, ++ __print_hex(__entry->lladdr, min_t(size_t, __entry->lladdr_len, MAX_ADDR_LEN)) ++ ) ++); ++ ++TRACE_EVENT(mctp_neighbor_del, ++ TP_PROTO(const struct net_device *dev, u8 eid), ++ TP_ARGS(dev, eid), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, eid) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->eid = eid; ++ ), ++ TP_printk("dev=%s ifindex=%d eid=%02x", ++ __get_str(name), __entry->ifindex, __entry->eid ++ ) ++); ++ ++TRACE_EVENT(mctp_neighbor_lookup, ++ TP_PROTO(const struct net_device *dev, u8 eid, int rc), ++ TP_ARGS(dev, eid, rc), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, eid) ++ __field(int, rc) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->eid = eid; ++ __entry->rc = rc; ++ ), ++ TP_printk("dev=%s ifindex=%d eid=%02x rc=%d", ++ __get_str(name), __entry->ifindex, __entry->eid, __entry->rc ++ ) ++); ++ ++TRACE_EVENT(mctp_route_add, ++ TP_PROTO(const struct net_device *dev, u8 daddr_start, u8 daddr_extent, ++ unsigned int mtu), ++ TP_ARGS(dev, daddr_start, daddr_extent, mtu), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, daddr_start) ++ __field(u8, daddr_extent) ++ __field(unsigned int, mtu) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->daddr_start = daddr_start; ++ __entry->daddr_extent = daddr_extent; ++ __entry->mtu = mtu; ++ ), ++ TP_printk("dev=%s ifindex=%d daddr=%02x-%02x mtu=%u", ++ __get_str(name), __entry->ifindex, __entry->daddr_start, ++ __entry->daddr_start + __entry->daddr_extent, __entry->mtu ++ ) ++); ++ ++TRACE_EVENT(mctp_route_del, ++ TP_PROTO(const struct net_device *dev, u8 daddr_start, u8 daddr_extent), ++ TP_ARGS(dev, daddr_start, daddr_extent), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, daddr_start) ++ __field(u8, daddr_extent) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->daddr_start = daddr_start; ++ __entry->daddr_extent = daddr_extent; ++ ), ++ TP_printk("dev=%s ifindex=%d daddr=%02x-%02x", ++ __get_str(name), __entry->ifindex, __entry->daddr_start, ++ __entry->daddr_start + __entry->daddr_extent ++ ) ++); ++ ++TRACE_EVENT(mctp_route_lookup, ++ TP_PROTO(u8 daddr, const struct net_device *dev, bool found), ++ TP_ARGS(daddr, dev, found), ++ TP_STRUCT__entry( ++ __field(u8, daddr) ++ __string(name, dev ? dev->name : "none") ++ __field(int, ifindex) ++ __field(bool, found) ++ ), ++ TP_fast_assign( ++ __entry->daddr = daddr; ++ __assign_str(name); ++ __entry->ifindex = dev ? dev->ifindex : 0; ++ __entry->found = found; ++ ), ++ TP_printk("daddr=%02x dev=%s ifindex=%d found=%d", ++ __entry->daddr, __get_str(name), __entry->ifindex, __entry->found ++ ) ++); ++ ++TRACE_EVENT(mctp_route_output, ++ TP_PROTO(struct sk_buff *skb, const struct net_device *dev), ++ TP_ARGS(skb, dev), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(u8, src) ++ __field(u8, dest) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ ), ++ TP_fast_assign( ++ struct mctp_hdr *mh = skb->len >= sizeof(*mh) ? mctp_hdr(skb) : NULL; ++ __entry->len = skb->len; ++ __entry->src = mh ? mh->src : 0; ++ __entry->dest = mh ? mh->dest : 0; ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ ), ++ TP_printk("dev=%s ifindex=%d src=%02x dst=%02x len=%u", ++ __get_str(name), __entry->ifindex, __entry->src, __entry->dest, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_local_output, ++ TP_PROTO(u8 src, u8 dest, u8 tag, size_t len), ++ TP_ARGS(src, dest, tag, len), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, tag) ++ __field(size_t, len) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->tag = tag; ++ __entry->len = len; ++ ), ++ TP_printk("src=%02x dst=%02x tag=%02x len=%zu", ++ __entry->src, __entry->dest, __entry->tag, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_fragment, ++ TP_PROTO(u8 src, u8 dest, u8 seq, size_t frag_len, size_t total_len), ++ TP_ARGS(src, dest, seq, frag_len, total_len), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, seq) ++ __field(size_t, frag_len) ++ __field(size_t, total_len) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->seq = seq; ++ __entry->frag_len = frag_len; ++ __entry->total_len = total_len; ++ ), ++ TP_printk("src=%02x dst=%02x seq=%u frag_len=%zu total_len=%zu", ++ __entry->src, __entry->dest, __entry->seq, __entry->frag_len, __entry->total_len ++ ) ++); ++ ++TRACE_EVENT(mctp_reassemble_start, ++ TP_PROTO(u8 src, u8 dest, u8 seq), ++ TP_ARGS(src, dest, seq), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, seq) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->seq = seq; ++ ), ++ TP_printk("src=%02x dst=%02x seq=%u", ++ __entry->src, __entry->dest, __entry->seq ++ ) ++); ++ ++TRACE_EVENT(mctp_reassemble_finish, ++ TP_PROTO(u8 src, u8 dest, size_t total_len), ++ TP_ARGS(src, dest, total_len), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(size_t, total_len) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->total_len = total_len; ++ ), ++ TP_printk("src=%02x dst=%02x total_len=%zu", ++ __entry->src, __entry->dest, __entry->total_len ++ ) ++); ++ ++TRACE_EVENT(mctp_transport_tx, ++ TP_PROTO(const char *transport, const struct net_device *dev, ++ u8 dest_addr, size_t len), ++ TP_ARGS(transport, dev, dest_addr, len), ++ TP_STRUCT__entry( ++ __string(transport, transport) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, dest_addr) ++ __field(size_t, len) ++ ), ++ TP_fast_assign( ++ __assign_str(transport); ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->dest_addr = dest_addr; ++ __entry->len = len; ++ ), ++ TP_printk("%s dev=%s ifindex=%d dest=%02x len=%zu", ++ __get_str(transport), __get_str(name), __entry->ifindex, ++ __entry->dest_addr, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_transport_rx, ++ TP_PROTO(const char *transport, const struct net_device *dev, ++ u8 src_addr, size_t len), ++ TP_ARGS(transport, dev, src_addr, len), ++ TP_STRUCT__entry( ++ __string(transport, transport) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, src_addr) ++ __field(size_t, len) ++ ), ++ TP_fast_assign( ++ __assign_str(transport); ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->src_addr = src_addr; ++ __entry->len = len; ++ ), ++ TP_printk("%s dev=%s ifindex=%d src=%02x len=%zu", ++ __get_str(transport), __get_str(name), __entry->ifindex, ++ __entry->src_addr, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_transport_error, ++ TP_PROTO(const char *transport, const struct net_device *dev, ++ const char *error, int rc), ++ TP_ARGS(transport, dev, error, rc), ++ TP_STRUCT__entry( ++ __string(transport, transport) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __string(error, error) ++ __field(int, rc) ++ ), ++ TP_fast_assign( ++ __assign_str(transport); ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __assign_str(error); ++ __entry->rc = rc; ++ ), ++ TP_printk("%s dev=%s ifindex=%d error=%s rc=%d", ++ __get_str(transport), __get_str(name), __entry->ifindex, ++ __get_str(error), __entry->rc ++ ) ++); ++ + #endif + + #include +diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h +index 2acc7687e..9e76f8e59 100644 +--- a/include/uapi/linux/if_link.h ++++ b/include/uapi/linux/if_link.h +@@ -1957,6 +1957,7 @@ struct ifla_rmnet_flags { + enum { + IFLA_MCTP_UNSPEC, + IFLA_MCTP_NET, ++ IFLA_MCTP_PHYS_BINDING, + __IFLA_MCTP_MAX, + }; + +diff --git a/include/uapi/linux/mctp.h b/include/uapi/linux/mctp.h +index e1db65df9..f15646c95 100644 +--- a/include/uapi/linux/mctp.h ++++ b/include/uapi/linux/mctp.h +@@ -37,6 +37,14 @@ struct sockaddr_mctp_ext { + __u8 smctp_haddr[MAX_ADDR_LEN]; + }; + ++/* A "fully qualified" MCTP address, which includes the system-local network ID, ++ * required to uniquely resolve a routable EID. ++ */ ++struct mctp_fq_addr { ++ unsigned int net; ++ mctp_eid_t eid; ++}; ++ + #define MCTP_NET_ANY 0x0 + + #define MCTP_ADDR_NULL 0x00 +@@ -47,6 +55,7 @@ struct sockaddr_mctp_ext { + #define MCTP_TAG_PREALLOC 0x10 + + #define MCTP_OPT_ADDR_EXT 1 ++#define MCTP_OPT_ENABLE_ERRQUEUE 2 + + #define SIOCMCTPALLOCTAG (SIOCPROTOPRIVATE + 0) + #define SIOCMCTPDROPTAG (SIOCPROTOPRIVATE + 1) +@@ -97,4 +106,70 @@ struct mctp_ioc_tag_ctl2 { + + }; + ++/* ++ * MCTP Error Queue Support ++ * For receiving asynchronous errors via recvmsg(MSG_ERRQUEUE) ++ */ ++ ++#define MCTP_ERROR_PAYLOAD_SIZE 32 /* Capture first 32 bytes of payload */ ++ ++/* Control message type for reading errors */ ++#define MCTP_RECVERR 1 ++ ++/* Direction values */ ++#define MCTP_DIR_TX 0 ++#define MCTP_DIR_RX 1 ++ ++/** ++ * struct mctp_error - MCTP error information for applications ++ * ++ * This structure is returned to applications via recvmsg(MSG_ERRQUEUE). ++ * Contains everything needed to identify and handle binding layer errors. ++ * ++ * @error_code: Error number (ETIMEDOUT, EPIPE, EPROTO, etc.) ++ * @direction: 0=TX, 1=RX ++ * @binding: DMTF binding type (1=SMBus, 2=PCIe VDM, 3=USB, 4=KCS, 5=Serial, 6=I3C) ++ * @src_eid: Source EID ++ * @dest_eid: Destination EID ++ * @tag: MCTP tag value (0-7) ++ * @msg_type: MCTP message type (0x01=PLDM, 0x05=SPDM, etc.) ++ * @timestamp_ns: When error occurred (nanoseconds since boot) ++ * @payload_len: Length of captured payload ++ * @payload: First N bytes of message payload (includes protocol headers) ++ * ++ * For PLDM messages, payload contains: ++ * payload[0] = Instance ID byte (bits 4-0 = instance ID) ++ * payload[1] = PLDM Type byte (bits 5-0 = type: 2=T2, 5=T5) ++ * payload[2] = Command code ++ * ++ * For SPDM messages, payload contains: ++ * payload[0] = SPDM version ++ * payload[1] = Request/Response code ++ * payload[2+] = Parameters and session context ++ */ ++struct mctp_error { ++ /* Error Information */ ++ __u32 error_code; /* errno value */ ++ __u8 direction; /* MCTP_DIR_TX or MCTP_DIR_RX */ ++ __u8 binding; /* enum mctp_phys_binding */ ++ __u16 reserved1; ++ ++ /* MCTP Addressing */ ++ __u8 src_eid; /* Source EID */ ++ __u8 dest_eid; /* Destination EID */ ++ __u8 tag; /* MCTP tag (0-7) */ ++ __u8 msg_type; /* MCTP message type */ ++ ++ /* Timestamp */ ++ __u64 timestamp_ns; /* Error timestamp */ ++ ++ /* Payload Capture */ ++ __u16 payload_len; /* Captured payload length */ ++ __u16 reserved2; ++ __u8 payload[MCTP_ERROR_PAYLOAD_SIZE]; ++ ++ /* Reserved for future use */ ++ __u32 reserved3[2]; ++} __attribute__((packed)); ++ + #endif /* __UAPI_MCTP_H */ +diff --git a/net/mctp/Makefile b/net/mctp/Makefile +index 6cd55233e..b52b33bdb 100644 +--- a/net/mctp/Makefile ++++ b/net/mctp/Makefile +@@ -1,6 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0 + obj-$(CONFIG_MCTP) += mctp.o +-mctp-objs := af_mctp.o device.o route.o neigh.o ++mctp-objs := af_mctp.o device.o route.o neigh.o mctp-socket-error-inject.o + + # tests + obj-$(CONFIG_MCTP_TEST) += test/utils.o +diff --git a/net/mctp/af_mctp.c b/net/mctp/af_mctp.c +index 57850d4da..d99c35b1c 100644 +--- a/net/mctp/af_mctp.c ++++ b/net/mctp/af_mctp.c +@@ -20,6 +20,8 @@ + #define CREATE_TRACE_POINTS + #include + ++#include "mctp-socket-error-inject.h" ++ + /* socket implementation */ + + static void mctp_sk_expire_keys(struct timer_list *timer); +@@ -83,7 +85,7 @@ static int mctp_bind(struct socket *sock, struct sockaddr *addr, int addrlen) + msk->bind_type = smctp->smctp_type & 0x7f; /* ignore the IC bit */ + + rc = sk->sk_prot->hash(sk); +- ++ + out_release: + release_sock(sk); + +@@ -97,8 +99,8 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + struct sock *sk = sock->sk; + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); + struct mctp_skb_cb *cb; +- struct mctp_route *rt; + struct sk_buff *skb = NULL; ++ struct mctp_dst dst; + int hlen; + + if (addr) { +@@ -126,6 +128,17 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + if (!capable(CAP_NET_RAW)) + return -EACCES; + ++ /* Socket-level error injection - test application error handling ++ * Simulates various sendto() failures at socket layer (binding-agnostic): ++ * - EBUSY: Tag allocation failure (all 8 tags in use) ++ * - EHOSTUNREACH: No route to destination ++ * - ENOBUFS/ENOMEM: Memory allocation failure ++ * - EAGAIN: Would block (non-blocking socket) ++ */ ++ rc = mctp_socket_error_inject_sendmsg(); ++ if (rc < 0) ++ return rc; ++ + if (addr->smctp_network == MCTP_NET_ANY) + addr->smctp_network = mctp_default_net(sock_net(sk)); + +@@ -133,34 +146,43 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + if (msk->addr_ext && addrlen >= sizeof(struct sockaddr_mctp_ext)) { + DECLARE_SOCKADDR(struct sockaddr_mctp_ext *, + extaddr, msg->msg_name); +- struct net_device *dev; +- +- rc = -EINVAL; +- rcu_read_lock(); +- dev = dev_get_by_index_rcu(sock_net(sk), extaddr->smctp_ifindex); +- /* check for correct halen */ +- if (dev && extaddr->smctp_halen == dev->addr_len) { +- hlen = LL_RESERVED_SPACE(dev) + sizeof(struct mctp_hdr); +- rc = 0; +- } +- rcu_read_unlock(); ++ if (!mctp_sockaddr_ext_is_ok(extaddr)) ++ return -EINVAL; ++ ++ rc = mctp_dst_from_extaddr(&dst, sock_net(sk), ++ extaddr->smctp_ifindex, ++ extaddr->smctp_halen, ++ extaddr->smctp_haddr); + if (rc) +- goto err_free; +- rt = NULL; ++ return rc; ++ ++ /* Check SO_BINDTODEVICE constraint */ ++ if (READ_ONCE(sk->sk_bound_dev_if) && dst.dev && ++ READ_ONCE(sk->sk_bound_dev_if) != dst.dev->dev->ifindex) { ++ mctp_dst_release(&dst); ++ return -EINVAL; ++ } ++ + } else { +- rt = mctp_route_lookup(sock_net(sk), addr->smctp_network, +- addr->smctp_addr.s_addr); +- if (!rt) { +- rc = -EHOSTUNREACH; +- goto err_free; ++ rc = mctp_route_lookup(sock_net(sk), addr->smctp_network, ++ addr->smctp_addr.s_addr, &dst); ++ if (rc) ++ return rc; ++ ++ /* Check SO_BINDTODEVICE constraint */ ++ if (READ_ONCE(sk->sk_bound_dev_if) && dst.dev && ++ READ_ONCE(sk->sk_bound_dev_if) != dst.dev->dev->ifindex) { ++ mctp_dst_release(&dst); ++ return -EINVAL; + } +- hlen = LL_RESERVED_SPACE(rt->dev->dev) + sizeof(struct mctp_hdr); + } + ++ hlen = LL_RESERVED_SPACE(dst.dev->dev) + sizeof(struct mctp_hdr); ++ + skb = sock_alloc_send_skb(sk, hlen + 1 + len, + msg->msg_flags & MSG_DONTWAIT, &rc); + if (!skb) +- return rc; ++ goto err_release_dst; + + skb_reserve(skb, hlen); + +@@ -175,35 +197,22 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + cb = __mctp_cb(skb); + cb->net = addr->smctp_network; + +- if (!rt) { +- /* fill extended address in cb */ +- DECLARE_SOCKADDR(struct sockaddr_mctp_ext *, +- extaddr, msg->msg_name); +- +- if (!mctp_sockaddr_ext_is_ok(extaddr) || +- extaddr->smctp_halen > sizeof(cb->haddr)) { +- rc = -EINVAL; +- goto err_free; +- } +- +- cb->ifindex = extaddr->smctp_ifindex; +- /* smctp_halen is checked above */ +- cb->halen = extaddr->smctp_halen; +- memcpy(cb->haddr, extaddr->smctp_haddr, cb->halen); +- } +- +- rc = mctp_local_output(sk, rt, skb, addr->smctp_addr.s_addr, ++ trace_mctp_tx_packet(skb); ++ rc = mctp_local_output(sk, &dst, skb, addr->smctp_addr.s_addr, + addr->smctp_tag); + ++ mctp_dst_release(&dst); + return rc ? : len; + + err_free: + kfree_skb(skb); ++err_release_dst: ++ mctp_dst_release(&dst); + return rc; + } + + static int mctp_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, +- int flags) ++ int flags) + { + DECLARE_SOCKADDR(struct sockaddr_mctp *, addr, msg->msg_name); + struct sock *sk = sock->sk; +@@ -213,6 +222,10 @@ static int mctp_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, + u8 type; + int rc; + ++ /* Handle error queue read */ ++ if (flags & MSG_ERRQUEUE) ++ return sock_recv_errqueue(sk, msg, len, SOL_MCTP, MCTP_RECVERR); ++ + if (flags & ~(MSG_DONTWAIT | MSG_TRUNC | MSG_PEEK)) + return -EOPNOTSUPP; + +@@ -278,6 +291,103 @@ static int mctp_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, + return rc; + } + ++/* Work function - called by kernel worker thread to report errors ++ * Context: Process context, NO locks held ++ * ++ * This defers error reporting from timer/softirq context to avoid deadlock. ++ * The timer path (mctp_sk_expire_keys) holds keys_lock, which would cause ++ * deadlock if we call sk_error_report() directly (wake-up callback might ++ * need keys_lock). By deferring to workqueue, we ensure error reporting ++ * happens after keys_lock is released. ++ */ ++static void mctp_error_report_work_fn(struct work_struct *work) ++{ ++ struct mctp_sock *msk = container_of(work, struct mctp_sock, ++ error_report_work); ++ struct mctp_pending_error *perr, *tmp; ++ struct list_head local_list; ++ ++ /* Move all pending errors to local list to minimize lock time */ ++ INIT_LIST_HEAD(&local_list); ++ ++ spin_lock_bh(&msk->error_queue_lock); ++ list_splice_init(&msk->pending_errors, &local_list); ++ spin_unlock_bh(&msk->error_queue_lock); ++ ++ /* Process all errors without holding locks - safe! */ ++ list_for_each_entry_safe(perr, tmp, &local_list, list) { ++ /* Report error to application - safe, no MCTP locks held. ++ * ++ * For RX reassembly timeout, we use the socket saved in perr->sk ++ * (from the RX key at timeout). This avoids redundant socket lookup ++ * and ensures we report to the correct socket even if RX key is gone. ++ * ++ * For ETIMEDOUT, we have orig_payload saved in perr, so we build ++ * the error structure directly instead of calling mctp_queue_error. ++ */ ++ if (perr->error_code == ETIMEDOUT && perr->sk && perr->orig_payload_len > 0) { ++ /* RX timeout with saved request payload - build error directly */ ++ struct mctp_error *mctp_err; ++ struct sk_buff *err_skb; ++ struct mctp_hdr *mh; ++ ++ if (!perr->skb || perr->skb->len < sizeof(struct mctp_hdr)) ++ goto cleanup; ++ ++ err_skb = alloc_skb(sizeof(*mctp_err), GFP_KERNEL); ++ if (!err_skb) ++ goto cleanup; ++ ++ mctp_err = (struct mctp_error *)skb_put(err_skb, sizeof(*mctp_err)); ++ memset(mctp_err, 0, sizeof(*mctp_err)); ++ ++ /* Fill error information */ ++ mctp_err->error_code = perr->error_code; ++ mctp_err->direction = perr->direction; ++ mctp_err->binding = perr->binding; ++ mctp_err->timestamp_ns = ktime_get_ns(); ++ ++ /* Fill addressing from first fragment SKB */ ++ mh = mctp_hdr(perr->skb); ++ mctp_err->src_eid = mh->src; ++ mctp_err->dest_eid = mh->dest; ++ mctp_err->tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ /* Use saved REQUEST payload from perr */ ++ mctp_err->msg_type = perr->orig_msg_type; ++ mctp_err->payload_len = min_t(u16, perr->orig_payload_len, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, perr->orig_payload, mctp_err->payload_len); ++ ++ /* Queue error to socket */ ++ if (sock_queue_err_skb(perr->sk, err_skb) == 0) { ++ sk_error_report(perr->sk); ++ pr_debug("MCTP timeout: Error queued (REQUEST payload, len=%u)\n", ++ mctp_err->payload_len); ++ } else { ++ kfree_skb(err_skb); ++ pr_debug("MCTP timeout: Failed to queue error to socket\n"); ++ } ++cleanup: ++ sock_put(perr->sk); ++ } else if (perr->sk) { ++ /* Other error types - use mctp_queue_error */ ++ mctp_queue_error(perr->sk, perr->skb, perr->error_code, ++ perr->dev, perr->direction, perr->binding, NULL); ++ sock_put(perr->sk); ++ } else { ++ /* Fallback: Look up socket from SKB (future error types) */ ++ mctp_queue_error(&msk->sk, perr->skb, perr->error_code, ++ perr->dev, perr->direction, perr->binding, NULL); ++ } ++ ++ /* Cleanup */ ++ list_del(&perr->list); ++ kfree_skb(perr->skb); ++ kfree(perr); ++ } ++} ++ + /* We're done with the key; invalidate, stop reassembly, and remove from lists. + */ + static void __mctp_key_remove(struct mctp_sk_key *key, struct net *net, +@@ -292,6 +402,82 @@ __must_hold(&net->mctp.keys_lock) + key->reasm_head = NULL; + key->reasm_dead = true; + key->valid = false; ++ ++ /* Reassembly timeout - queue error for deferred reporting */ ++ if (reason == MCTP_TRACE_KEY_TIMEOUT && skb && key->dev && key->dev->dev && ++ key->sk) { ++ struct mctp_sock *msk = container_of(key->sk, struct mctp_sock, sk); ++ ++ netdev_warn(key->dev->dev, ++ "MCTP RX: Reassembly timeout - partial message discarded " ++ "(src=%u, dest=%u, tag=%u, fragments incomplete)\n", ++ key->peer_addr, key->local_addr, key->tag); ++ ++ /* Queue error for deferred reporting via workqueue if enabled. ++ * Use ETIMEDOUT for reassembly timeout - semantically clearer than EPROTO ++ * (timeout is caused by missing fragments that never arrive). ++ * ++ * CRITICAL: Only report if this key originated from TX (has orig_payload). ++ * This ensures we only report errors for responses to OUR requests, ++ * not for unsolicited requests from device. ++ * ++ * For TX-originated keys: ++ * - key->sk = application that sent original request ++ * - key->reasm_head (skb) = first fragment of response (used for addressing) ++ * - key->orig_payload = original REQUEST payload (from TX phase) ++ * ++ * We pass socket + first fragment SKB (for addressing) to workqueue. ++ * Workqueue will extract REQUEST payload from key->orig_payload, ++ * allowing applications to identify which transaction timed out. ++ */ ++ if (msk->enable_errqueue && key->orig_payload_len > 0) { ++ struct mctp_pending_error *perr; ++ ++ perr = kmalloc(sizeof(*perr), GFP_ATOMIC); ++ if (perr) { ++ /* Clone first fragment SKB for later use by workqueue */ ++ perr->skb = skb_clone(skb, GFP_ATOMIC); ++ if (perr->skb) { ++ /* Save socket with refcount - ensures it stays alive ++ * until workqueue processes the error. ++ * This is the application that sent the original request. ++ */ ++ perr->sk = key->sk; ++ sock_hold(perr->sk); ++ ++ perr->error_code = ETIMEDOUT; ++ perr->dev = key->dev->dev; ++ perr->direction = MCTP_DIR_RX; ++ perr->binding = mctp_get_binding_type(key->dev ? key->dev->dev : NULL); ++ ++ /* Copy original request payload from key for error reporting */ ++ perr->orig_msg_type = key->orig_msg_type; ++ perr->orig_payload_len = key->orig_payload_len; ++ memcpy(perr->orig_payload, key->orig_payload, ++ min_t(size_t, key->orig_payload_len, sizeof(perr->orig_payload))); ++ ++ /* Add to pending list and schedule work ++ * Work will run later in process context with no locks held ++ */ ++ spin_lock_bh(&msk->error_queue_lock); ++ list_add_tail(&perr->list, &msk->pending_errors); ++ spin_unlock_bh(&msk->error_queue_lock); ++ ++ pr_debug("MCTP: RX timeout queued for deferred error reporting (key has TX origin, orig_payload_len=%u)\n", ++ key->orig_payload_len); ++ ++ /* Schedule work - returns immediately */ ++ schedule_work(&msk->error_report_work); ++ } else { ++ kfree(perr); ++ } ++ } ++ } else if (msk->enable_errqueue && key->orig_payload_len == 0) { ++ /* RX-only key (unsolicited request from device) - don't report */ ++ pr_debug("MCTP: RX timeout NOT reported - no TX origin (unsolicited request, orig_payload_len=0)\n"); ++ } ++ } ++ + mctp_dev_release_key(key->dev, key); + spin_unlock_irqrestore(&key->lock, flags); + +@@ -323,6 +509,17 @@ static int mctp_setsockopt(struct socket *sock, int level, int optname, + return 0; + } + ++ if (optname == MCTP_OPT_ENABLE_ERRQUEUE) { ++ if (optlen != sizeof(int)) ++ return -EINVAL; ++ if (copy_from_sockptr(&val, optval, sizeof(int))) ++ return -EFAULT; ++ ++ msk->enable_errqueue = !!val; ++ ++ return 0; ++ } ++ + return -ENOPROTOOPT; + } + +@@ -347,7 +544,7 @@ static int mctp_getsockopt(struct socket *sock, int level, int optname, + return 0; + } + +- return -EINVAL; ++ return -ENOPROTOOPT; + } + + /* helpers for reading/writing the tag ioc, handling compatibility across the +@@ -439,7 +636,8 @@ static int mctp_ioctl_alloctag(struct mctp_sock *msk, bool tagv2, + return -EINVAL; + + key = mctp_alloc_local_tag(msk, ctl.net, MCTP_ADDR_ANY, +- ctl.peer_addr, true, &tag); ++ ctl.peer_addr, true, &tag, ++ MCTP_DEFAULT_LIFETIME); + if (IS_ERR(key)) + return PTR_ERR(key); + +@@ -547,6 +745,22 @@ static int mctp_compat_ioctl(struct socket *sock, unsigned int cmd, + } + #endif + ++static __poll_t mctp_poll(struct file *file, struct socket *sock, ++ poll_table *wait) ++{ ++ struct sock *sk = sock->sk; ++ __poll_t mask; ++ ++ /* Use standard datagram_poll for normal data */ ++ mask = datagram_poll(file, sock, wait); ++ ++ /* Check error queue */ ++ if (!skb_queue_empty_lockless(&sk->sk_error_queue)) ++ mask |= EPOLLERR | EPOLLPRI; ++ ++ return mask; ++} ++ + static const struct proto_ops mctp_dgram_ops = { + .family = PF_MCTP, + .release = mctp_release, +@@ -555,7 +769,7 @@ static const struct proto_ops mctp_dgram_ops = { + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = sock_no_getname, +- .poll = datagram_poll, ++ .poll = mctp_poll, + .ioctl = mctp_ioctl, + .gettstamp = sock_gettstamp, + .listen = sock_no_listen, +@@ -612,12 +826,21 @@ static void mctp_sk_expire_keys(struct timer_list *timer) + mod_timer(timer, next_expiry); + } + ++/* Forward declaration for workqueue callback */ ++static void mctp_error_report_work_fn(struct work_struct *work); ++ + static int mctp_sk_init(struct sock *sk) + { + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); + + INIT_HLIST_HEAD(&msk->keys); + timer_setup(&msk->key_expiry, mctp_sk_expire_keys, 0); ++ ++ /* Initialize deferred error reporting workqueue */ ++ INIT_WORK(&msk->error_report_work, mctp_error_report_work_fn); ++ INIT_LIST_HEAD(&msk->pending_errors); ++ spin_lock_init(&msk->error_queue_lock); ++ + return 0; + } + +@@ -633,6 +856,9 @@ static int mctp_sk_hash(struct sock *sk) + /* Bind lookup runs under RCU, remain live during that. */ + sock_set_flag(sk, SOCK_RCU_FREE); + ++ /* Bind lookup runs under RCU, remain live during that. */ ++ sock_set_flag(sk, SOCK_RCU_FREE); ++ + mutex_lock(&net->mctp.bind_lock); + sk_add_node_rcu(sk, &net->mctp.binds); + mutex_unlock(&net->mctp.bind_lock); +@@ -667,6 +893,22 @@ static void mctp_sk_unhash(struct sock *sk) + * as the sk is no longer observable + */ + del_timer_sync(&msk->key_expiry); ++ ++ /* Cancel pending error reporting work and free any pending errors */ ++ cancel_work_sync(&msk->error_report_work); ++ { ++ struct mctp_pending_error *perr, *tmp_err; ++ ++ spin_lock_bh(&msk->error_queue_lock); ++ list_for_each_entry_safe(perr, tmp_err, &msk->pending_errors, list) { ++ list_del(&perr->list); ++ if (perr->sk) ++ sock_put(perr->sk); ++ kfree_skb(perr->skb); ++ kfree(perr); ++ } ++ spin_unlock_bh(&msk->error_queue_lock); ++ } + } + + static void mctp_sk_destruct(struct sock *sk) +@@ -763,6 +1005,10 @@ static __init int mctp_init(void) + if (rc) + goto err_unreg_neigh; + ++ rc = mctp_socket_error_inject_init(); ++ if (rc) ++ pr_warn("MCTP: Socket error injection init failed, continuing without it\n"); ++ + return 0; + + err_unreg_neigh: +@@ -779,6 +1025,7 @@ static __init int mctp_init(void) + + static __exit void mctp_exit(void) + { ++ mctp_socket_error_inject_cleanup(); + mctp_device_exit(); + mctp_neigh_exit(); + mctp_routes_exit(); +@@ -793,3 +1040,7 @@ MODULE_DESCRIPTION("MCTP core"); + MODULE_AUTHOR("Jeremy Kerr "); + + MODULE_ALIAS_NETPROTO(PF_MCTP); ++ ++#if IS_ENABLED(CONFIG_MCTP_TEST) ++#include "test/sock-test.c" ++#endif +diff --git a/net/mctp/device.c b/net/mctp/device.c +index 8d1386601..79cfc6d50 100644 +--- a/net/mctp/device.c ++++ b/net/mctp/device.c +@@ -19,6 +19,8 @@ + #include + #include + ++#include ++ + struct mctp_dump_cb { + unsigned long ifindex; + size_t a_idx; +@@ -39,6 +41,7 @@ struct mctp_dev *__mctp_dev_get(const struct net_device *dev) + return NULL; + return mdev; + } ++EXPORT_SYMBOL_GPL(__mctp_dev_get); + + /* Returned mctp_dev does not have refcount incremented. The returned pointer + * remains live while rtnl_lock is held, as that prevents mctp_unregister() +@@ -219,12 +222,16 @@ static int mctp_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, + return -EINVAL; + + /* Prevent duplicates. Under RTNL so don't need to lock for reading */ +- if (memchr(mdev->addrs, addr->s_addr, mdev->num_addrs)) ++ if (memchr(mdev->addrs, addr->s_addr, mdev->num_addrs)) { ++ netdev_dbg(dev, "MCTP: address %u already exists\n", addr->s_addr); + return -EEXIST; ++ } + + tmp_addrs = kmalloc(mdev->num_addrs + 1, GFP_KERNEL); +- if (!tmp_addrs) ++ if (!tmp_addrs) { ++ netdev_dbg(dev, "MCTP: failed to allocate memory for address %u\n", addr->s_addr); + return -ENOMEM; ++ } + memcpy(tmp_addrs, mdev->addrs, mdev->num_addrs); + tmp_addrs[mdev->num_addrs] = addr->s_addr; + +@@ -236,6 +243,9 @@ static int mctp_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, + + kfree(tmp_addrs); + ++ netdev_dbg(dev, "MCTP: address %u added (total %u addresses)\n", ++ addr->s_addr, mdev->num_addrs); ++ trace_mctp_address_add(dev, addr->s_addr); + mctp_addr_notify(mdev, addr->s_addr, RTM_NEWADDR, skb, nlh); + mctp_route_add_local(mdev, addr->s_addr); + +@@ -279,8 +289,10 @@ static int mctp_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, + return -ENODEV; + + pos = memchr(mdev->addrs, addr->s_addr, mdev->num_addrs); +- if (!pos) ++ if (!pos) { ++ netdev_dbg(dev, "MCTP: address %u not found for deletion\n", addr->s_addr); + return -ENOENT; ++ } + + rc = mctp_route_remove_local(mdev, addr->s_addr); + // we can ignore -ENOENT in the case a route was already removed +@@ -292,6 +304,9 @@ static int mctp_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, + mdev->num_addrs--; + spin_unlock_irqrestore(&mdev->addrs_lock, flags); + ++ netdev_dbg(dev, "MCTP: address %u deleted (remaining %u addresses)\n", ++ addr->s_addr, mdev->num_addrs); ++ trace_mctp_address_del(dev, addr->s_addr); + mctp_addr_notify(mdev, addr->s_addr, RTM_DELADDR, skb, nlh); + + return 0; +@@ -305,11 +320,27 @@ void mctp_dev_hold(struct mctp_dev *mdev) + void mctp_dev_put(struct mctp_dev *mdev) + { + if (mdev && refcount_dec_and_test(&mdev->refs)) { ++ unsigned int i; ++ struct mctp_nf_track_entry *entry; ++ struct hlist_node *tmp; ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ for (i = 0; i < MCTP_NF_TRACK_BUCKETS; i++) { ++ hlist_for_each_entry_safe(entry, tmp, ++ &mdev->nf_track.buckets[i], ++ node) { ++ hlist_del(&entry->node); ++ kfree(entry); ++ } ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++ + kfree(mdev->addrs); + dev_put(mdev->dev); + kfree_rcu(mdev, rcu); + } + } ++EXPORT_SYMBOL_GPL(mctp_dev_put); + + void mctp_dev_release_key(struct mctp_dev *dev, struct mctp_sk_key *key) + __must_hold(&key->lock) +@@ -332,6 +363,7 @@ void mctp_dev_set_key(struct mctp_dev *dev, struct mctp_sk_key *key) + static struct mctp_dev *mctp_add_dev(struct net_device *dev) + { + struct mctp_dev *mdev; ++ unsigned int i; + + ASSERT_RTNL(); + +@@ -340,8 +372,13 @@ static struct mctp_dev *mctp_add_dev(struct net_device *dev) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&mdev->addrs_lock); ++ spin_lock_init(&mdev->nf_track.lock); ++ for (i = 0; i < MCTP_NF_TRACK_BUCKETS; i++) ++ INIT_HLIST_HEAD(&mdev->nf_track.buckets[i]); ++ mdev->nf_track.count = 0; + + mdev->net = mctp_default_net(dev_net(dev)); ++ mdev->key_lifetime = MCTP_DEFAULT_LIFETIME; + + /* associate to net_device */ + refcount_set(&mdev->refs, 1); +@@ -350,6 +387,8 @@ static struct mctp_dev *mctp_add_dev(struct net_device *dev) + dev_hold(dev); + mdev->dev = dev; + ++ netdev_dbg(dev, "MCTP: device registered (net %u)\n", mdev->net); ++ trace_mctp_device_register(dev, mdev->net); + return mdev; + } + +@@ -363,6 +402,8 @@ static int mctp_fill_link_af(struct sk_buff *skb, + return -ENODATA; + if (nla_put_u32(skb, IFLA_MCTP_NET, mdev->net)) + return -EMSGSIZE; ++ if (nla_put_u8(skb, IFLA_MCTP_PHYS_BINDING, mdev->binding)) ++ return -EMSGSIZE; + return 0; + } + +@@ -377,6 +418,7 @@ static size_t mctp_get_link_af_size(const struct net_device *dev, + if (!mdev) + return 0; + ret = nla_total_size(4); /* IFLA_MCTP_NET */ ++ ret += nla_total_size(1); /* IFLA_MCTP_PHYS_BINDING */ + mctp_dev_put(mdev); + return ret; + } +@@ -424,6 +466,8 @@ static void mctp_unregister(struct net_device *dev) + if (!mdev) + return; + ++ netdev_dbg(dev, "MCTP: device unregistering (%u addresses)\n", mdev->num_addrs); ++ trace_mctp_device_unregister(dev); + RCU_INIT_POINTER(mdev->dev->mctp_ptr, NULL); + + mctp_route_remove_dev(mdev); +@@ -472,7 +516,8 @@ static int mctp_dev_notify(struct notifier_block *this, unsigned long event, + } + + static int mctp_register_netdevice(struct net_device *dev, +- const struct mctp_netdev_ops *ops) ++ const struct mctp_netdev_ops *ops, ++ enum mctp_phys_binding binding) + { + struct mctp_dev *mdev; + +@@ -481,17 +526,19 @@ static int mctp_register_netdevice(struct net_device *dev, + return PTR_ERR(mdev); + + mdev->ops = ops; ++ mdev->binding = binding; + + return register_netdevice(dev); + } + + int mctp_register_netdev(struct net_device *dev, +- const struct mctp_netdev_ops *ops) ++ const struct mctp_netdev_ops *ops, ++ enum mctp_phys_binding binding) + { + int rc; + + rtnl_lock(); +- rc = mctp_register_netdevice(dev, ops); ++ rc = mctp_register_netdevice(dev, ops, binding); + rtnl_unlock(); + + return rc; +@@ -504,6 +551,20 @@ void mctp_unregister_netdev(struct net_device *dev) + } + EXPORT_SYMBOL_GPL(mctp_unregister_netdev); + ++void mctp_dev_set_timeout(struct net_device *dev, unsigned int timeout_ms) ++{ ++ struct mctp_dev *mdev; ++ ++ rtnl_lock(); ++ mdev = mctp_dev_get_rtnl(dev); ++ if (mdev) { ++ mdev->key_lifetime = msecs_to_jiffies(timeout_ms); ++ netdev_info(dev, "MCTP timeout set to %u ms\n", timeout_ms); ++ } ++ rtnl_unlock(); ++} ++EXPORT_SYMBOL_GPL(mctp_dev_set_timeout); ++ + static struct rtnl_af_ops mctp_af_ops = { + .family = AF_MCTP, + .fill_link_af = mctp_fill_link_af, +diff --git a/net/mctp/mctp-socket-error-inject.c b/net/mctp/mctp-socket-error-inject.c +new file mode 100644 +index 000000000..b8d03f2d0 +--- /dev/null ++++ b/net/mctp/mctp-socket-error-inject.c +@@ -0,0 +1,221 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * MCTP Socket Error Injection ++ * ++ * Binding-agnostic error injection at AF_MCTP socket layer. ++ * Allows testing application handling of sendto() API failures: ++ * - EBUSY: Tag allocation failure (all tags in use) ++ * - EHOSTUNREACH: No route to destination ++ * - ENOBUFS/ENOMEM: Memory exhaustion ++ * - EAGAIN: Would block ++ */ ++ ++#include ++#include ++#include ++#include ++#include "mctp-socket-error-inject.h" ++ ++/* Global error injection state */ ++struct mctp_socket_error_inject mctp_socket_ei; ++ ++static struct dentry *mctp_socket_debugfs_root; ++ ++/* Main injection function called from sendmsg() */ ++int mctp_socket_error_inject_sendmsg(void) ++{ ++ int ret = 0; ++ ++ spin_lock_bh(&mctp_socket_ei.lock); ++ mctp_socket_ei.sendmsg_calls++; ++ ++ if (mctp_socket_ei.enabled) { ++ mctp_socket_ei.errors_injected++; ++ ret = mctp_socket_ei.error_code; ++ ++ pr_debug("MCTP Socket Error Injection: sendmsg() returning %d, " ++ "total_injected=%llu\n", ++ ret, mctp_socket_ei.errors_injected); ++ } ++ ++ spin_unlock_bh(&mctp_socket_ei.lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(mctp_socket_error_inject_sendmsg); ++ ++/* Debugfs: enable */ ++static ssize_t enable_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_socket_ei.enabled ? 1 : 0); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t enable_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[8]; ++ size_t len = min(count, sizeof(buf) - 1); ++ int value; ++ ++ if (copy_from_user(buf, userbuf, len)) ++ return -EFAULT; ++ buf[len] = '\0'; ++ ++ if (kstrtoint(buf, 0, &value)) ++ return -EINVAL; ++ ++ mctp_socket_ei.enabled = (value != 0); ++ ++ pr_debug("MCTP Socket Error Injection: %s\n", ++ mctp_socket_ei.enabled ? "ENABLED" : "DISABLED"); ++ return count; ++} ++ ++static const struct file_operations enable_fops = { ++ .read = enable_read, ++ .write = enable_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Debugfs: stats */ ++static ssize_t stats_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[256]; ++ int len; ++ ++ spin_lock_bh(&mctp_socket_ei.lock); ++ len = snprintf(buf, sizeof(buf), ++ "MCTP Socket Error Injection Statistics:\n" ++ " Enabled: %s\n" ++ " Error Code: %d\n" ++ " Total sendmsg() calls: %llu\n" ++ " Errors injected: %llu\n", ++ mctp_socket_ei.enabled ? "yes" : "no", ++ mctp_socket_ei.error_code, ++ mctp_socket_ei.sendmsg_calls, ++ mctp_socket_ei.errors_injected); ++ spin_unlock_bh(&mctp_socket_ei.lock); ++ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static const struct file_operations stats_fops = { ++ .read = stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Debugfs: error_code */ ++static ssize_t error_code_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[32]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_socket_ei.error_code); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t error_code_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[32]; ++ size_t len = min(count, sizeof(buf) - 1); ++ int value; ++ ++ if (copy_from_user(buf, userbuf, len)) ++ return -EFAULT; ++ buf[len] = '\0'; ++ ++ if (kstrtoint(buf, 0, &value)) ++ return -EINVAL; ++ ++ mctp_socket_ei.error_code = value; ++ return count; ++} ++ ++static const struct file_operations error_code_fops = { ++ .read = error_code_read, ++ .write = error_code_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Debugfs: reset */ ++static ssize_t reset_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[8]; ++ size_t len = min(count, sizeof(buf) - 1); ++ int value; ++ ++ if (copy_from_user(buf, userbuf, len)) ++ return -EFAULT; ++ buf[len] = '\0'; ++ ++ if (kstrtoint(buf, 0, &value)) ++ return -EINVAL; ++ ++ if (value != 1) ++ return -EINVAL; ++ ++ /* Reset all state */ ++ spin_lock_bh(&mctp_socket_ei.lock); ++ mctp_socket_ei.enabled = false; ++ mctp_socket_ei.error_code = 0; ++ mctp_socket_ei.sendmsg_calls = 0; ++ mctp_socket_ei.errors_injected = 0; ++ spin_unlock_bh(&mctp_socket_ei.lock); ++ ++ pr_debug("MCTP Socket Error Injection: Reset (disabled and cleared)\n"); ++ return count; ++} ++ ++static const struct file_operations reset_fops = { ++ .write = reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Initialization */ ++int mctp_socket_error_inject_init(void) ++{ ++ memset(&mctp_socket_ei, 0, sizeof(mctp_socket_ei)); ++ spin_lock_init(&mctp_socket_ei.lock); ++ ++ /* Default settings */ ++ mctp_socket_ei.enabled = false; ++ mctp_socket_ei.error_code = -EBUSY; /* Default: simulate tag exhaustion */ ++ ++ /* Create debugfs directory */ ++ mctp_socket_debugfs_root = debugfs_create_dir("mctp_socket", NULL); ++ if (IS_ERR_OR_NULL(mctp_socket_debugfs_root)) { ++ pr_warn("MCTP Socket Error Injection: Failed to create debugfs directory\n"); ++ return PTR_ERR(mctp_socket_debugfs_root); ++ } ++ ++ mctp_socket_ei.debugfs_dir = mctp_socket_debugfs_root; ++ ++ /* Create debugfs files */ ++ debugfs_create_file("enable", 0644, mctp_socket_debugfs_root, NULL, &enable_fops); ++ debugfs_create_file("error_code", 0644, mctp_socket_debugfs_root, NULL, &error_code_fops); ++ debugfs_create_file("stats", 0444, mctp_socket_debugfs_root, NULL, &stats_fops); ++ debugfs_create_file("reset", 0200, mctp_socket_debugfs_root, NULL, &reset_fops); ++ ++ pr_info("MCTP Socket Error Injection: Initialized at /sys/kernel/debug/mctp_socket/\n"); ++ return 0; ++} ++ ++void mctp_socket_error_inject_cleanup(void) ++{ ++ debugfs_remove_recursive(mctp_socket_debugfs_root); ++ pr_info("MCTP Socket Error Injection: Cleaned up\n"); ++} ++ +diff --git a/net/mctp/mctp-socket-error-inject.h b/net/mctp/mctp-socket-error-inject.h +new file mode 100644 +index 000000000..07aa263b9 +--- /dev/null ++++ b/net/mctp/mctp-socket-error-inject.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP Socket Error Injection - Header ++ * ++ * Binding-agnostic error injection at AF_MCTP socket layer. ++ * Tests application handling of sendto() failures. ++ */ ++ ++#ifndef __MCTP_SOCKET_ERROR_INJECT_H ++#define __MCTP_SOCKET_ERROR_INJECT_H ++ ++#include ++#include ++ ++/* Socket-level error injection state */ ++struct mctp_socket_error_inject { ++ /* Error injection control */ ++ bool enabled; /* Enable/disable error injection */ ++ int error_code; /* Error to return (e.g. -EBUSY, -EHOSTUNREACH) */ ++ ++ /* Statistics */ ++ u64 sendmsg_calls; /* Total sendmsg() calls */ ++ u64 errors_injected; /* Errors injected */ ++ ++ /* Debugfs */ ++ struct dentry *debugfs_dir; ++ spinlock_t lock; ++}; ++ ++/* Global instance */ ++extern struct mctp_socket_error_inject mctp_socket_ei; ++ ++/* Initialize/cleanup */ ++int mctp_socket_error_inject_init(void); ++void mctp_socket_error_inject_cleanup(void); ++ ++/* Check if error should be injected in sendmsg() */ ++int mctp_socket_error_inject_sendmsg(void); ++ ++#endif /* __MCTP_SOCKET_ERROR_INJECT_H */ ++ +diff --git a/net/mctp/neigh.c b/net/mctp/neigh.c +index 590f64241..66d9b3acb 100644 +--- a/net/mctp/neigh.c ++++ b/net/mctp/neigh.c +@@ -21,6 +21,8 @@ + #include + #include + ++#include ++ + static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid, + enum mctp_neigh_source source, + size_t lladdr_len, const void *lladdr) +@@ -31,17 +33,21 @@ static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid, + + mutex_lock(&net->mctp.neigh_lock); + if (mctp_neigh_lookup(mdev, eid, NULL) == 0) { ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u already exists\n", eid); + rc = -EEXIST; + goto out; + } + + if (lladdr_len > sizeof(neigh->ha)) { ++ netdev_dbg(mdev->dev, "MCTP: neighbor lladdr too long (%zu > %zu)\n", ++ lladdr_len, sizeof(neigh->ha)); + rc = -EINVAL; + goto out; + } + + neigh = kzalloc(sizeof(*neigh), GFP_KERNEL); + if (!neigh) { ++ netdev_dbg(mdev->dev, "MCTP: failed to allocate neighbor entry for eid %u\n", eid); + rc = -ENOMEM; + goto out; + } +@@ -53,6 +59,9 @@ static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid, + memcpy(neigh->ha, lladdr, lladdr_len); + + list_add_rcu(&neigh->list, &net->mctp.neighbours); ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u added (lladdr_len %zu)\n", ++ eid, lladdr_len); ++ trace_mctp_neighbor_add(mdev->dev, eid, lladdr, lladdr_len); + rc = 0; + out: + mutex_unlock(&net->mctp.neigh_lock); +@@ -96,6 +105,8 @@ static int mctp_neigh_remove(struct mctp_dev *mdev, mctp_eid_t eid, + list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) { + if (neigh->dev == mdev && neigh->eid == eid && + neigh->source == source) { ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u removed\n", eid); ++ trace_mctp_neighbor_del(mdev->dev, eid); + list_del_rcu(&neigh->list); + /* TODO: immediate RTM_DELNEIGH */ + call_rcu(&neigh->rcu, __mctp_neigh_free); +@@ -104,6 +115,8 @@ static int mctp_neigh_remove(struct mctp_dev *mdev, mctp_eid_t eid, + } + + mutex_unlock(&net->mctp.neigh_lock); ++ if (!dropped) ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u not found for removal\n", eid); + return dropped ? 0 : -ENOENT; + } + +@@ -293,6 +306,9 @@ int mctp_neigh_lookup(struct mctp_dev *mdev, mctp_eid_t eid, void *ret_hwaddr) + } + } + rcu_read_unlock(); ++ if (rc) ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u lookup failed\n", eid); ++ trace_mctp_neighbor_lookup(mdev->dev, eid, rc); + return rc; + } + +diff --git a/net/mctp/route.c b/net/mctp/route.c +index d9c8e5a5f..a62788b09 100644 +--- a/net/mctp/route.c ++++ b/net/mctp/route.c +@@ -14,8 +14,12 @@ + #include + #include + #include ++#include + #include + #include ++#include ++ ++#include + + #include + +@@ -27,12 +31,283 @@ + #include + + static const unsigned int mctp_message_maxlen = 64 * 1024; +-static const unsigned long mctp_key_lifetime = 6 * CONFIG_HZ; ++static const unsigned long mctp_nf_track_timeout = 2 * CONFIG_HZ; ++ ++/* Helper to determine binding type from network device ++ * Returns the physical binding type from mctp_dev, which is set during ++ * device registration and remains constant regardless of device renaming. ++ */ ++u8 mctp_get_binding_type(struct net_device *dev) ++{ ++ struct mctp_dev *mdev; ++ u8 binding = 0; ++ ++ if (!dev) ++ return 0; /* Unknown */ ++ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (mdev) { ++ binding = mdev->binding; ++ mctp_dev_put(mdev); ++ } ++ rcu_read_unlock(); ++ ++ return binding; ++} ++ + + static void mctp_flow_prepare_output(struct sk_buff *skb, struct mctp_dev *dev); + ++enum mctp_nf_track_verdict { ++ MCTP_NF_VERDICT_ACCEPT = 0, ++ MCTP_NF_VERDICT_DROP = 1, ++}; ++ ++static u32 mctp_nf_track_hash(mctp_eid_t src, mctp_eid_t dst, u8 tag) ++{ ++ return jhash_3words(src, dst, tag, 0) & (MCTP_NF_TRACK_BUCKETS - 1); ++} ++ ++static bool mctp_nf_track_lookup(struct mctp_dev *mdev, mctp_eid_t src, ++ mctp_eid_t dst, u8 tag, u8 *verdict) ++{ ++ struct mctp_nf_track_entry *entry; ++ struct hlist_node *tmp; ++ unsigned long now = jiffies; ++ u32 idx = mctp_nf_track_hash(src, dst, tag); ++ bool found = false; ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ hlist_for_each_entry_safe(entry, tmp, &mdev->nf_track.buckets[idx], ++ node) { ++ if (entry->src != src || entry->dst != dst || entry->tag != tag) ++ continue; ++ ++ if (time_after(now, entry->expires)) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf track: expiry src=%u dst=%u tag=0x%x\n", ++ src, dst, tag); ++ hlist_del(&entry->node); ++ kfree(entry); ++ mdev->nf_track.count--; ++ break; ++ } ++ ++ *verdict = entry->verdict; ++ found = true; ++ break; ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++ ++ if (found) ++ netdev_dbg(mdev->dev, ++ "MCTP nf track lookup: src=%u dst=%u tag=0x%x verdict=%s\n", ++ src, dst, tag, *verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop"); ++ ++ return found; ++} ++ ++static void mctp_nf_track_delete(struct mctp_dev *mdev, mctp_eid_t src, ++ mctp_eid_t dst, u8 tag) ++{ ++ struct mctp_nf_track_entry *entry; ++ struct hlist_node *tmp; ++ u32 idx = mctp_nf_track_hash(src, dst, tag); ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ hlist_for_each_entry_safe(entry, tmp, &mdev->nf_track.buckets[idx], ++ node) { ++ if (entry->src != src || entry->dst != dst || entry->tag != tag) ++ continue; ++ netdev_dbg(mdev->dev, ++ "MCTP nf track delete: src=%u dst=%u tag=0x%x\n", ++ src, dst, tag); ++ hlist_del(&entry->node); ++ kfree(entry); ++ mdev->nf_track.count--; ++ break; ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++} ++ ++static void mctp_nf_track_store(struct mctp_dev *mdev, mctp_eid_t src, ++ mctp_eid_t dst, u8 tag, u8 verdict) ++{ ++ struct mctp_nf_track_entry *entry, *new_entry; ++ u32 idx = mctp_nf_track_hash(src, dst, tag); ++ ++ new_entry = kzalloc(sizeof(*new_entry), GFP_ATOMIC); ++ if (!new_entry) ++ return; ++ ++ new_entry->src = src; ++ new_entry->dst = dst; ++ new_entry->tag = tag; ++ new_entry->verdict = verdict; ++ new_entry->expires = jiffies + mctp_nf_track_timeout; ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ hlist_for_each_entry(entry, &mdev->nf_track.buckets[idx], node) { ++ if (entry->src != src || entry->dst != dst || entry->tag != tag) ++ continue; ++ entry->verdict = verdict; ++ entry->expires = jiffies + mctp_nf_track_timeout; ++ spin_unlock_bh(&mdev->nf_track.lock); ++ netdev_dbg(mdev->dev, ++ "MCTP nf track store (update): src=%u dst=%u tag=0x%x verdict=%s\n", ++ src, dst, tag, ++ verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop"); ++ kfree(new_entry); ++ return; ++ } ++ ++ if (mdev->nf_track.count < MCTP_NF_TRACK_MAX) { ++ hlist_add_head(&new_entry->node, &mdev->nf_track.buckets[idx]); ++ mdev->nf_track.count++; ++ netdev_dbg(mdev->dev, ++ "MCTP nf track store: src=%u dst=%u tag=0x%x verdict=%s count=%u\n", ++ src, dst, tag, ++ verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop", ++ mdev->nf_track.count); ++ new_entry = NULL; ++ } else { ++ netdev_dbg(mdev->dev, ++ "MCTP nf track store: table full, drop entry src=%u dst=%u tag=0x%x\n", ++ src, dst, tag); ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++ ++ kfree(new_entry); ++} ++ ++static bool mctp_nf_ingress_check(struct sk_buff *skb, struct mctp_dev *mdev, ++ const struct mctp_hdr *mh) ++{ ++ u8 flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ u8 tag = mh->flags_seq_tag & (MCTP_HDR_TAG_MASK | MCTP_HDR_FLAG_TO); ++ u8 verdict; ++ u8 msg_type = 0; ++ int ret; ++ bool som = !!(flags & MCTP_HDR_FLAG_SOM); ++ bool eom = !!(flags & MCTP_HDR_FLAG_EOM); ++ unsigned int type_off = skb_network_offset(skb) + sizeof(struct mctp_hdr); ++ ++ if (skb->len >= type_off + 1 && skb_copy_bits(skb, type_off, &msg_type, 1) == 0) ++ msg_type &= 0x7f; ++ ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: src=%u dst=%u tag=0x%x SOM=%d EOM=%d type=%u\n", ++ mh->src, mh->dest, tag, som, eom, msg_type); ++ ++ if (!(flags & MCTP_HDR_FLAG_SOM)) { ++ if (mctp_nf_track_lookup(mdev, mh->src, mh->dest, tag, ++ &verdict)) { ++ if (flags & MCTP_HDR_FLAG_EOM) ++ mctp_nf_track_delete(mdev, mh->src, mh->dest, tag); ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: fragment verdict=%s\n", ++ verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop"); ++ return verdict == MCTP_NF_VERDICT_ACCEPT; ++ } ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: fragment no track entry, pass to hook\n"); ++ } ++ ++ if (!nf_hook_ingress_active(skb)) { ++ netdev_dbg(mdev->dev, "MCTP nf ingress: no hooks, accept\n"); ++ return true; ++ } ++ ++ rcu_read_lock(); ++ ret = nf_hook_ingress(skb); ++ rcu_read_unlock(); ++ ++ if (ret < 0) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: hook dropped (ret=%d)\n", ret); ++ if (!(flags & MCTP_HDR_FLAG_EOM)) ++ mctp_nf_track_store(mdev, mh->src, mh->dest, tag, ++ MCTP_NF_VERDICT_DROP); ++ return false; ++ } ++ ++ if (!(flags & MCTP_HDR_FLAG_EOM)) ++ mctp_nf_track_store(mdev, mh->src, mh->dest, tag, ++ MCTP_NF_VERDICT_ACCEPT); ++ ++ netdev_dbg(mdev->dev, "MCTP nf ingress: hook accept\n"); ++ return true; ++} ++ ++static int mctp_nf_egress_check(struct sk_buff **pskb, struct mctp_dev *mdev, ++ const struct mctp_hdr *mh) ++{ ++ struct sk_buff *skb = *pskb; ++ u8 flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ u8 tag = mh->flags_seq_tag & (MCTP_HDR_TAG_MASK | MCTP_HDR_FLAG_TO); ++ u8 verdict; ++ u8 msg_type = 0; ++ int rc = NET_XMIT_SUCCESS; ++ bool som = !!(flags & MCTP_HDR_FLAG_SOM); ++ bool eom = !!(flags & MCTP_HDR_FLAG_EOM); ++ unsigned int type_off = skb_network_offset(skb) + sizeof(struct mctp_hdr); ++ ++ if (skb->len >= type_off + 1 && skb_copy_bits(skb, type_off, &msg_type, 1) == 0) ++ msg_type &= 0x7f; ++ ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: src=%u dst=%u tag=0x%x SOM=%d EOM=%d type=%u\n", ++ mh->src, mh->dest, tag, som, eom, msg_type); ++ ++ if (!(flags & MCTP_HDR_FLAG_SOM)) { ++ if (mctp_nf_track_lookup(mdev, mh->src, mh->dest, tag, ++ &verdict)) { ++ if (flags & MCTP_HDR_FLAG_EOM) ++ mctp_nf_track_delete(mdev, mh->src, mh->dest, tag); ++ if (verdict == MCTP_NF_VERDICT_DROP) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: fragment verdict=drop\n"); ++ kfree_skb(skb); ++ *pskb = NULL; ++ return -EPERM; ++ } ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: fragment verdict=accept\n"); ++ return 0; ++ } ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: fragment no track entry, pass to hook\n"); ++ } ++ ++ if (!nf_hook_egress_active()) { ++ netdev_dbg(mdev->dev, "MCTP nf egress: no hooks, accept\n"); ++ return 0; ++ } ++ ++ rcu_read_lock_bh(); ++ skb = nf_hook_egress(skb, &rc, mdev->dev); ++ rcu_read_unlock_bh(); ++ ++ if (!skb) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: hook dropped (rc=%d)\n", rc); ++ *pskb = NULL; ++ return net_xmit_errno(rc); ++ } ++ ++ *pskb = skb; ++ ++ if (!(flags & MCTP_HDR_FLAG_EOM)) ++ mctp_nf_track_store(mdev, mh->src, mh->dest, tag, ++ MCTP_NF_VERDICT_ACCEPT); ++ ++ netdev_dbg(mdev->dev, "MCTP nf egress: hook accept\n"); ++ return 0; ++} ++ + /* route output callbacks */ +-static int mctp_route_discard(struct mctp_route *route, struct sk_buff *skb) ++static int mctp_dst_discard(struct mctp_dst *dst, struct sk_buff *skb) + { + kfree_skb(skb); + return 0; +@@ -57,6 +332,7 @@ static struct mctp_sock *mctp_lookup_bind(struct net *net, struct sk_buff *skb) + + sk_for_each_rcu(sk, &net->mctp.binds) { + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); ++ int bound_dev_if; + + if (msk->bind_net != MCTP_NET_ANY && msk->bind_net != cb->net) + continue; +@@ -67,6 +343,12 @@ static struct mctp_sock *mctp_lookup_bind(struct net *net, struct sk_buff *skb) + if (!mctp_address_matches(msk->bind_addr, mh->dest)) + continue; + ++ /* Check SO_BINDTODEVICE constraint */ ++ bound_dev_if = READ_ONCE(sk->sk_bound_dev_if); ++ if (bound_dev_if && skb->dev && ++ bound_dev_if != skb->dev->ifindex) ++ continue; ++ + return msk; + } + +@@ -209,7 +491,8 @@ void mctp_key_unref(struct mctp_sk_key *key) + kfree(key); + } + +-static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk) ++static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk, ++ unsigned long lifetime) + { + struct net *net = sock_net(&msk->sk); + struct mctp_sk_key *tmp; +@@ -237,7 +520,7 @@ static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk) + + if (!rc) { + refcount_inc(&key->refs); +- key->expiry = jiffies + mctp_key_lifetime; ++ key->expiry = jiffies + lifetime; + timer_reduce(&msk->key_expiry, key->expiry); + + hlist_add_head(&key->hlist, &net->mctp.keys); +@@ -325,17 +608,21 @@ static void mctp_skb_set_flow(struct sk_buff *skb, struct mctp_sk_key *key) {} + static void mctp_flow_prepare_output(struct sk_buff *skb, struct mctp_dev *dev) {} + #endif + ++/* takes ownership of skb, both in success and failure cases */ + static int mctp_frag_queue(struct mctp_sk_key *key, struct sk_buff *skb) + { + struct mctp_hdr *hdr = mctp_hdr(skb); + u8 exp_seq, this_seq; ++ int rc = 0; + + this_seq = (hdr->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) + & MCTP_HDR_SEQ_MASK; + + if (!key->reasm_head) { +- /* Since we're manipulating the shared frag_list, ensure it isn't +- * shared with any other SKBs. ++ /* Since we're manipulating the shared frag_list, ensure it ++ * isn't shared with any other SKBs. In the cloned case, ++ * this will free the skb; callers can no longer access it ++ * safely. + */ + key->reasm_head = skb_unshare(skb, GFP_ATOMIC); + if (!key->reasm_head) +@@ -343,16 +630,21 @@ static int mctp_frag_queue(struct mctp_sk_key *key, struct sk_buff *skb) + + key->reasm_tailp = &(skb_shinfo(key->reasm_head)->frag_list); + key->last_seq = this_seq; ++ trace_mctp_reassemble_start(hdr->src, hdr->dest, this_seq); + return 0; + } + + exp_seq = (key->last_seq + 1) & MCTP_HDR_SEQ_MASK; + +- if (this_seq != exp_seq) +- return -EINVAL; ++ if (this_seq != exp_seq) { ++ rc = -EINVAL; ++ goto err_free; ++ } + +- if (key->reasm_head->len + skb->len > mctp_message_maxlen) +- return -EINVAL; ++ if (key->reasm_head->len + skb->len > mctp_message_maxlen) { ++ rc = -EMSGSIZE; ++ goto err_free; ++ } + + skb->next = NULL; + skb->sk = NULL; +@@ -365,10 +657,134 @@ static int mctp_frag_queue(struct mctp_sk_key *key, struct sk_buff *skb) + key->reasm_head->len += skb->len; + key->reasm_head->truesize += skb->truesize; + +- return 0; ++ return rc; ++ ++err_free: ++ kfree_skb(skb); ++ return rc; ++} ++ ++/* ++ * mctp_report_rx_missing_som - Report error for middle/end fragment without SOM ++ * @key: RX MCTP key for this transaction (provides addressing for TX key lookup) ++ * @skb: The failed fragment ++ * @mh: MCTP header ++ * @tag: Tag value ++ * @flags: Lock flags (will be used to release/reacquire key->lock) ++ * ++ * This function must be called with key->lock held. It will temporarily release ++ * the lock to report the error (to avoid deadlock), then reacquire it. ++ * ++ * NEW BEHAVIOR: Looks up TX key and only reports if found. This ensures we only ++ * report errors for responses to OUR requests. ++ */ ++static void mctp_report_rx_missing_som(struct mctp_sk_key *key, ++ struct sk_buff *skb, ++ struct mctp_hdr *mh, ++ unsigned long tag, ++ unsigned long *flags) ++{ ++ struct sock *sk; ++ ++ netdev_err(skb->dev, ++ "MCTP RX: Middle/End fragment without SOM (src=%u, dest=%u, tag=%lu)\n", ++ mh->src, mh->dest, tag & MCTP_HDR_TAG_MASK); ++ ++ /* CRITICAL: Release key->lock BEFORE error reporting to avoid deadlock. ++ * mctp_queue_error() needs to acquire keys_lock for TX key lookup, but ++ * correct lock order is keys_lock → key->lock. We currently hold key->lock, ++ * so we must release it first. ++ */ ++ spin_unlock_irqrestore(&key->lock, *flags); ++ ++ /* Look up socket and report error. ++ * mctp_queue_error() will look up TX key internally and only report ++ * if TX key is found (our transaction). ++ */ ++ sk = mctp_lookup_sock_for_error(skb, skb->dev, key, NULL); ++ if (sk) { ++ /* Pass RX key - mctp_queue_error will use it to find TX key */ ++ mctp_queue_error(sk, skb, EPROTO, skb->dev, MCTP_DIR_RX, ++ mctp_get_binding_type(skb->dev), key); ++ sock_put(sk); ++ } ++ ++ /* Re-acquire key->lock for caller */ ++ spin_lock_irqsave(&key->lock, *flags); ++} ++ ++/* ++ * mctp_report_rx_sequence_error - Report RX sequence/size error ++ * @key: RX MCTP key for this transaction (provides addressing for TX key lookup) ++ * @skb: The failed fragment ++ * @mh: MCTP header ++ * @tag: Tag value ++ * @flags: Lock flags (will be used to release/reacquire key->lock) ++ * @err_code: Error code from mctp_frag_queue (-EINVAL or -EMSGSIZE) ++ * ++ * This function must be called with key->lock held. It will temporarily release ++ * the lock to report the error (to avoid deadlock), then reacquire it. ++ * ++ * NEW BEHAVIOR: Looks up TX key and only reports if found. This ensures we only ++ * report errors for responses to OUR requests. ++ */ ++static void mctp_report_rx_sequence_error(struct mctp_sk_key *key, ++ struct sk_buff *skb, ++ struct mctp_hdr *mh, ++ unsigned long tag, ++ unsigned long *flags, ++ int err_code) ++{ ++ struct sock *sk; ++ struct sk_buff *report_skb; ++ u8 exp_seq = (key->last_seq + 1) & MCTP_HDR_SEQ_MASK; ++ u8 this_seq = (mh->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) & MCTP_HDR_SEQ_MASK; ++ int report_errno; ++ ++ /* Determine error type and error code to report */ ++ if (err_code == -EMSGSIZE) { ++ netdev_err(skb->dev, ++ "MCTP RX: Message too large - exceeds 64KB limit (src=%u, dest=%u, tag=%lu)\n", ++ mh->src, mh->dest, tag & MCTP_HDR_TAG_MASK); ++ report_errno = EMSGSIZE; ++ } else { ++ /* err_code == -EINVAL - sequence error */ ++ netdev_err(skb->dev, ++ "MCTP RX: Sequence error - expected %u, got %u (src=%u, dest=%u, tag=%lu)\n", ++ exp_seq, this_seq, mh->src, mh->dest, ++ tag & MCTP_HDR_TAG_MASK); ++ report_errno = EPROTO; ++ } ++ ++ /* Use first response fragment (reasm_head) for addressing if available. ++ * Fall back to failed fragment only if reasm_head is NULL. ++ */ ++ report_skb = key->reasm_head ? key->reasm_head : skb; ++ ++ /* CRITICAL: Release key->lock BEFORE error reporting to avoid deadlock. ++ * mctp_queue_error() needs to acquire keys_lock for TX key lookup, but ++ * correct lock order is keys_lock → key->lock. We currently hold key->lock, ++ * so we must release it first. ++ */ ++ spin_unlock_irqrestore(&key->lock, *flags); ++ ++ /* Look up socket and report error. ++ * mctp_queue_error() will look up TX key internally and only report ++ * if TX key is found (our transaction). ++ */ ++ sk = mctp_lookup_sock_for_error(report_skb, skb->dev, key, NULL); ++ if (sk) { ++ /* Pass RX key - mctp_queue_error will use it to find TX key */ ++ mctp_queue_error(sk, report_skb, report_errno, skb->dev, MCTP_DIR_RX, ++ mctp_get_binding_type(skb->dev), key); ++ sock_put(sk); ++ } ++ ++ /* Re-acquire key->lock for caller */ ++ spin_lock_irqsave(&key->lock, *flags); + } + +-static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) ++static int mctp_dst_input(struct mctp_dst *dst, struct sk_buff *skb) + { + struct mctp_sk_key *key, *any_key = NULL; + struct net *net = dev_net(skb->dev); +@@ -392,9 +808,14 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + */ + skb_orphan(skb); + ++ if (skb->pkt_type == PACKET_OUTGOING) ++ skb->pkt_type = PACKET_LOOPBACK; ++ + /* ensure we have enough data for a header and a type */ +- if (skb->len < sizeof(struct mctp_hdr) + 1) ++ if (skb->len < sizeof(struct mctp_hdr) + 1) { ++ trace_mctp_drop_packet(skb, "packet_too_short"); + goto out; ++ } + + /* grab header, advance data ptr */ + mh = mctp_hdr(skb); +@@ -439,6 +860,7 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + msk = mctp_lookup_bind(net, skb); + + if (!msk) { ++ trace_mctp_drop_packet(skb, "no_socket_bound"); + rc = -ENOENT; + goto out_unlock; + } +@@ -447,9 +869,14 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + * pending key. + */ + if (flags & MCTP_HDR_FLAG_EOM) { ++ trace_mctp_rx_packet(skb); + rc = sock_queue_rcv_skb(&msk->sk, skb); +- if (!rc) ++ trace_mctp_rx_socket(skb, rc); ++ if (!rc) { + skb = NULL; ++ } else { ++ trace_mctp_drop_packet(skb, "sock_queue_failed"); ++ } + if (key) { + /* we've hit a pending reassembly; not much we + * can do but drop it +@@ -465,6 +892,14 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + * packets for this message + */ + if (!key) { ++ struct mctp_dev *mdev; ++ unsigned long lifetime; ++ ++ mdev = __mctp_dev_get(skb->dev); ++ lifetime = mdev ? mdev->key_lifetime : MCTP_DEFAULT_LIFETIME; ++ if (mdev) ++ mctp_dev_put(mdev); ++ + key = mctp_key_alloc(msk, netid, mh->dest, mh->src, + tag, GFP_ATOMIC); + if (!key) { +@@ -473,21 +908,42 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + } + + /* we can queue without the key lock here, as the +- * key isn't observable yet +- */ ++ * key isn't observable yet ++ */ + mctp_frag_queue(key, skb); ++ ++ /* Cache message type and payload from first fragment (SOM) for error reporting. ++ * If middle/end fragments are missing (timeout), we can still report ++ * the correct message headers to the application. ++ * Use skb_network_header() not skb->data for consistency. ++ */ ++ if (skb && skb->len > sizeof(struct mctp_hdr)) { ++ u8 *msg_start = (u8 *)skb_network_header(skb); ++ size_t payload_offset, available, capture_len; ++ ++ /* Capture message type (first byte after MCTP header) */ ++ key->orig_msg_type = *(msg_start + sizeof(struct mctp_hdr)); ++ ++ /* Capture first 32 bytes of payload (after message type) for RX timeout errors */ ++ payload_offset = sizeof(struct mctp_hdr) + 1; ++ if (skb->len > payload_offset) { ++ available = skb->len - payload_offset; ++ capture_len = min_t(size_t, available, sizeof(key->orig_payload)); ++ memcpy(key->orig_payload, msg_start + payload_offset, capture_len); ++ key->orig_payload_len = capture_len; ++ } ++ } ++ ++ skb = NULL; + + /* if the key_add fails, we've raced with another + * SOM packet with the same src, dest and tag. There's + * no way to distinguish future packets, so all we +- * can do is drop; we'll free the skb on exit from +- * this function. ++ * can do is drop. + */ +- rc = mctp_key_add(key, msk); +- if (!rc) { ++ rc = mctp_key_add(key, msk, lifetime); ++ if (!rc) + trace_mctp_key_acquire(key); +- skb = NULL; +- } + + /* we don't need to release key->lock on exit, so + * clean up here and suppress the unlock via +@@ -505,8 +961,7 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + key = NULL; + } else { + rc = mctp_frag_queue(key, skb); +- if (!rc) +- skb = NULL; ++ skb = NULL; + } + } + +@@ -516,24 +971,38 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + */ + + /* we need to be continuing an existing reassembly... */ +- if (!key->reasm_head) ++ if (!key->reasm_head) { + rc = -EINVAL; +- else ++ mctp_report_rx_missing_som(key, skb, mh, tag, &f); ++ } else { + rc = mctp_frag_queue(key, skb); ++ ++ if (rc == -EINVAL || rc == -EMSGSIZE) { ++ /* Reassembly failure: sequence error (-EINVAL) or message too large (-EMSGSIZE) */ ++ mctp_report_rx_sequence_error(key, skb, mh, tag, &f, rc); ++ } ++ ++ skb = NULL; ++ } + + if (rc) + goto out_unlock; + +- /* we've queued; the queue owns the skb now */ +- skb = NULL; +- + /* end of message? deliver to socket, and we're done with + * the reassembly/response key + */ + if (flags & MCTP_HDR_FLAG_EOM) { ++ if (key->reasm_head) { ++ trace_mctp_reassemble_finish(mh->src, mh->dest, key->reasm_head->len); ++ trace_mctp_rx_packet(key->reasm_head); ++ } + rc = sock_queue_rcv_skb(key->sk, key->reasm_head); +- if (!rc) ++ if (key->reasm_head) { ++ trace_mctp_rx_socket(key->reasm_head, rc); ++ } ++ if (!rc) { + key->reasm_head = NULL; ++ } + __mctp_key_done_in(key, net, f, MCTP_TRACE_KEY_REPLIED); + key = NULL; + } +@@ -556,39 +1025,48 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + return rc; + } + +-static unsigned int mctp_route_mtu(struct mctp_route *rt) +-{ +- return rt->mtu ?: READ_ONCE(rt->dev->dev->mtu); +-} +- +-static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) ++static int mctp_dst_output(struct mctp_dst *dst, struct sk_buff *skb) + { +- struct mctp_skb_cb *cb = mctp_cb(skb); + struct mctp_hdr *hdr = mctp_hdr(skb); + char daddr_buf[MAX_ADDR_LEN]; + char *daddr = NULL; +- unsigned int mtu; + int rc; + +- skb->protocol = htons(ETH_P_MCTP); ++ /* Tunnel: packet egress from one net dev to another (e.g. USB -> I2C). ++ * Must do neighbour lookup for egress device; stashed hwaddr is for ingress. ++ */ ++ bool is_tunnel = (skb->dev != dst->dev->dev); + +- mtu = READ_ONCE(skb->dev->mtu); +- if (skb->len > mtu) { +- kfree_skb(skb); +- return -EMSGSIZE; ++ /* Update skb->dev to the outgoing device (needed for forwarding). */ ++ skb->dev = dst->dev->dev; ++ skb->pkt_type = PACKET_OUTGOING; ++ ++ /* Check if this is a batched SKB (marked by protocol field with high bit ++ * set). Batched SKBs are intentionally larger than MTU as they contain ++ * multiple MCTP packets packed together. The driver will clear this marker. ++ */ ++ bool is_batched = (skb->protocol == htons(ETH_P_MCTP | 0x8000)); ++ ++ /* Batched SKBs (protocol with high bit set) skip MTU check */ ++ if (!is_batched) { ++ skb->protocol = htons(ETH_P_MCTP); ++ if (skb->len > dst->mtu) { ++ kfree_skb(skb); ++ return -EMSGSIZE; ++ } + } + +- if (cb->ifindex) { +- /* direct route; use the hwaddr we stashed in sendmsg */ +- if (cb->halen != skb->dev->addr_len) { ++ /* direct route; use the hwaddr we stashed in sendmsg (unless tunnel) */ ++ if (dst->halen && !is_tunnel) { ++ if (dst->halen != skb->dev->addr_len) { + /* sanity check, sendmsg should have already caught this */ + kfree_skb(skb); + return -EMSGSIZE; + } +- daddr = cb->haddr; ++ daddr = dst->haddr; + } else { + /* If lookup fails let the device handle daddr==NULL */ +- if (mctp_neigh_lookup(route->dev, hdr->dest, daddr_buf) == 0) ++ if (mctp_neigh_lookup(dst->dev, dst->nexthop, daddr_buf) == 0) + daddr = daddr_buf; + } + +@@ -599,8 +1077,22 @@ static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) + return -EHOSTUNREACH; + } + +- mctp_flow_prepare_output(skb, route->dev); ++ mctp_flow_prepare_output(skb, dst->dev); ++ ++ if (!is_batched) { ++ rc = mctp_nf_egress_check(&skb, dst->dev, hdr); ++ if (!skb) { ++ netdev_dbg( ++ dst->dev->dev, ++ "MCTP nf: egress filter dropped packet src=%u dst=%u rc=%d\n", ++ hdr->src, hdr->dest, rc); ++ return rc; ++ } ++ if (rc) ++ return rc; ++ } + ++ trace_mctp_route_output(skb, skb->dev); + rc = dev_queue_xmit(skb); + if (rc) + rc = net_xmit_errno(rc); +@@ -612,7 +1104,8 @@ static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) + static void mctp_route_release(struct mctp_route *rt) + { + if (refcount_dec_and_test(&rt->refs)) { +- mctp_dev_put(rt->dev); ++ if (rt->dst_type == MCTP_ROUTE_DIRECT) ++ mctp_dev_put(rt->dev); + kfree_rcu(rt, rcu); + } + } +@@ -628,7 +1121,7 @@ static struct mctp_route *mctp_route_alloc(void) + + INIT_LIST_HEAD(&rt->list); + refcount_set(&rt->refs, 1); +- rt->output = mctp_route_discard; ++ rt->output = mctp_dst_discard; + + return rt; + } +@@ -648,13 +1141,13 @@ int mctp_default_net_set(struct net *net, unsigned int index) + + /* tag management */ + static void mctp_reserve_tag(struct net *net, struct mctp_sk_key *key, +- struct mctp_sock *msk) ++ struct mctp_sock *msk, unsigned long lifetime) + { + struct netns_mctp *mns = &net->mctp; + + lockdep_assert_held(&mns->keys_lock); + +- key->expiry = jiffies + mctp_key_lifetime; ++ key->expiry = jiffies + lifetime; + timer_reduce(&msk->key_expiry, key->expiry); + + /* we hold the net->key_lock here, allowing updates to both +@@ -671,7 +1164,8 @@ static void mctp_reserve_tag(struct net *net, struct mctp_sk_key *key, + struct mctp_sk_key *mctp_alloc_local_tag(struct mctp_sock *msk, + unsigned int netid, + mctp_eid_t local, mctp_eid_t peer, +- bool manual, u8 *tagp) ++ bool manual, u8 *tagp, ++ unsigned long lifetime) + { + struct net *net = sock_net(&msk->sk); + struct netns_mctp *mns = &net->mctp; +@@ -735,7 +1229,7 @@ struct mctp_sk_key *mctp_alloc_local_tag(struct mctp_sock *msk, + + if (tagbits) { + key->tag = __ffs(tagbits); +- mctp_reserve_tag(net, key, msk); ++ mctp_reserve_tag(net, key, msk, lifetime); + trace_mctp_key_acquire(key); + + key->manual_alloc = manual; +@@ -801,10 +1295,16 @@ static struct mctp_sk_key *mctp_lookup_prealloc_tag(struct mctp_sock *msk, + } + + /* routing lookups */ ++static unsigned int mctp_route_netid(struct mctp_route *rt) ++{ ++ return rt->dst_type == MCTP_ROUTE_DIRECT ? ++ READ_ONCE(rt->dev->net) : rt->gateway.net; ++} ++ + static bool mctp_rt_match_eid(struct mctp_route *rt, + unsigned int net, mctp_eid_t eid) + { +- return READ_ONCE(rt->dev->net) == net && ++ return mctp_route_netid(rt) == net && + rt->min <= eid && rt->max >= eid; + } + +@@ -813,61 +1313,284 @@ static bool mctp_rt_compare_exact(struct mctp_route *rt1, + struct mctp_route *rt2) + { + ASSERT_RTNL(); +- return rt1->dev->net == rt2->dev->net && +- rt1->min == rt2->min && +- rt1->max == rt2->max; ++ ++ if (mctp_route_netid(rt1) != mctp_route_netid(rt2)) ++ return false; ++ ++ if (rt1->max < rt2->min || rt1->min > rt2->max) ++ return false; ++ ++ /* Allow multiple local routes for same EID on different interfaces */ ++ if (rt1->type == RTN_LOCAL && rt2->type == RTN_LOCAL && ++ rt1->dev != rt2->dev) ++ return false; ++ ++ return true; ++} ++ ++/* must only be called on a direct route, as the final output hop */ ++static void mctp_dst_from_route(struct mctp_dst *dst, mctp_eid_t eid, ++ unsigned int mtu, struct mctp_route *route) ++{ ++ mctp_dev_hold(route->dev); ++ dst->nexthop = eid; ++ dst->dev = route->dev; ++ dst->mtu = READ_ONCE(dst->dev->dev->mtu); ++ if (mtu) ++ dst->mtu = min(dst->mtu, mtu); ++ dst->halen = 0; ++ dst->output = route->output; + } + +-struct mctp_route *mctp_route_lookup(struct net *net, unsigned int dnet, +- mctp_eid_t daddr) ++int mctp_dst_from_extaddr(struct mctp_dst *dst, struct net *net, int ifindex, ++ unsigned char halen, const unsigned char *haddr) + { +- struct mctp_route *tmp, *rt = NULL; ++ struct net_device *netdev; ++ struct mctp_dev *dev; ++ int rc = -ENOENT; ++ ++ if (halen > sizeof(dst->haddr)) ++ return -EINVAL; + + rcu_read_lock(); + +- list_for_each_entry_rcu(tmp, &net->mctp.routes, list) { +- /* TODO: add metrics */ +- if (mctp_rt_match_eid(tmp, dnet, daddr)) { +- if (refcount_inc_not_zero(&tmp->refs)) { +- rt = tmp; +- break; +- } +- } ++ netdev = dev_get_by_index_rcu(net, ifindex); ++ if (!netdev) ++ goto out_unlock; ++ ++ if (netdev->addr_len != halen) { ++ rc = -EINVAL; ++ goto out_unlock; + } + ++ dev = __mctp_dev_get(netdev); ++ if (!dev) ++ goto out_unlock; ++ ++ dst->dev = dev; ++ dst->mtu = READ_ONCE(netdev->mtu); ++ dst->halen = halen; ++ dst->output = mctp_dst_output; ++ dst->nexthop = 0; ++ memcpy(dst->haddr, haddr, halen); ++ ++ rc = 0; ++ ++out_unlock: + rcu_read_unlock(); ++ return rc; ++} + +- return rt; ++void mctp_dst_release(struct mctp_dst *dst) ++{ ++ mctp_dev_put(dst->dev); + } + +-static struct mctp_route *mctp_route_lookup_null(struct net *net, +- struct net_device *dev) ++static struct mctp_route *mctp_route_lookup_single(struct net *net, ++ unsigned int dnet, ++ mctp_eid_t daddr) + { +- struct mctp_route *tmp, *rt = NULL; ++ struct mctp_route *rt; ++ ++ list_for_each_entry_rcu(rt, &net->mctp.routes, list) { ++ if (mctp_rt_match_eid(rt, dnet, daddr)) ++ return rt; ++ } ++ ++ return NULL; ++} ++ ++/* populates *dst on successful lookup, if set */ ++int mctp_route_lookup(struct net *net, unsigned int dnet, ++ mctp_eid_t daddr, struct mctp_dst *dst) ++{ ++ const unsigned int max_depth = 32; ++ unsigned int depth, mtu = 0; ++ int rc = -EHOSTUNREACH; + + rcu_read_lock(); + +- list_for_each_entry_rcu(tmp, &net->mctp.routes, list) { +- if (tmp->dev->dev == dev && tmp->type == RTN_LOCAL && +- refcount_inc_not_zero(&tmp->refs)) { +- rt = tmp; ++ for (depth = 0; depth < max_depth; depth++) { ++ struct mctp_route *rt; ++ ++ rt = mctp_route_lookup_single(net, dnet, daddr); ++ if (!rt) ++ break; ++ ++ /* clamp mtu to the smallest in the path, allowing 0 ++ * to specify no restrictions ++ */ ++ if (mtu && rt->mtu) ++ mtu = min(mtu, rt->mtu); ++ else ++ mtu = mtu ?: rt->mtu; ++ ++ if (rt->dst_type == MCTP_ROUTE_DIRECT) { ++ if (dst) ++ mctp_dst_from_route(dst, daddr, mtu, rt); ++ rc = 0; + break; ++ ++ } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { ++ daddr = rt->gateway.eid; + } + } + + rcu_read_unlock(); + +- return rt; ++ return rc; + } + +-static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, +- unsigned int mtu, u8 tag) ++static int mctp_route_lookup_null(struct net *net, struct net_device *dev, ++ struct mctp_dst *dst) ++{ ++ int rc = -EHOSTUNREACH; ++ struct mctp_route *rt; ++ ++ rcu_read_lock(); ++ ++ list_for_each_entry_rcu(rt, &net->mctp.routes, list) { ++ if (rt->dst_type != MCTP_ROUTE_DIRECT || rt->type != RTN_LOCAL) ++ continue; ++ ++ if (rt->dev->dev != dev) ++ continue; ++ ++ mctp_dst_from_route(dst, 0, 0, rt); ++ rc = 0; ++ break; ++ } ++ ++ rcu_read_unlock(); ++ ++ return rc; ++} ++ ++/* Fragment and batch: pack multiple fragments into a single SKB with space ++ * for transport headers. The transport driver will fill in headers and send. ++ * This function may send multiple batches if the message is large. ++ */ ++static int mctp_do_fragment_route_batch(struct mctp_dst *dst, struct sk_buff *skb, ++ unsigned int mtu, u8 tag, ++ unsigned int batch_hdr_len, ++ unsigned int batch_max_xfer) + { + const unsigned int hlen = sizeof(struct mctp_hdr); + struct mctp_hdr *hdr, *hdr2; ++ struct mctp_skb_cb *cb; ++ struct sk_buff *batch_skb; + unsigned int pos, size, headroom; +- struct sk_buff *skb2; +- int rc; ++ unsigned int total_len, num_frags; ++ unsigned int skb_pos; ++ u8 *batch_data; ++ u8 seq; ++ int rc; ++ ++ hdr = mctp_hdr(skb); ++ seq = 0; ++ headroom = skb_headroom(skb); ++ ++ /* we've got the header */ ++ skb_pull(skb, hlen); ++ ++ skb_pos = 0; ++ rc = 0; ++ ++ while (skb_pos < skb->len) { ++ total_len = 0; ++ num_frags = 0; ++ for (pos = skb_pos; pos < skb->len;) { ++ size = min(mtu - hlen, skb->len - pos); ++ total_len += batch_hdr_len + hlen + size; ++ num_frags++; ++ pos += size; ++ if (total_len + batch_hdr_len + hlen + 1 > batch_max_xfer) ++ break; ++ } ++ ++ batch_skb = alloc_skb(headroom + total_len, GFP_KERNEL); ++ if (!batch_skb) { ++ kfree_skb(skb); ++ return -ENOMEM; ++ } ++ ++ batch_skb->protocol = htons(ETH_P_MCTP | 0x8000); /* Mark as batched */ ++ batch_skb->priority = skb->priority; ++ batch_skb->dev = skb->dev; ++ memcpy(batch_skb->cb, skb->cb, sizeof(batch_skb->cb)); ++ ++ if (skb->sk) ++ skb_set_owner_w(batch_skb, skb->sk); ++ ++ skb_reserve(batch_skb, headroom); ++ skb_reset_network_header(batch_skb); ++ batch_data = skb_put(batch_skb, total_len); ++ ++ cb = mctp_cb(batch_skb); ++ cb->net = mctp_cb(skb)->net; ++ ++ skb_ext_copy(batch_skb, skb); ++ ++ pos = skb_pos; ++ while (num_frags--) { ++ unsigned int pkt_len; ++ bool is_last_fragment_in_message; ++ void *transport_hdr; ++ ++ size = min(mtu - hlen, skb->len - pos); ++ is_last_fragment_in_message = (pos + size >= skb->len); ++ pkt_len = batch_hdr_len + hlen + size; ++ ++ transport_hdr = batch_data; ++ batch_data += batch_hdr_len; ++ ++ hdr2 = (struct mctp_hdr *)batch_data; ++ hdr2->ver = hdr->ver; ++ hdr2->dest = hdr->dest; ++ hdr2->src = hdr->src; ++ hdr2->flags_seq_tag = tag & ++ (MCTP_HDR_TAG_MASK | MCTP_HDR_FLAG_TO); ++ ++ if (pos == 0) ++ hdr2->flags_seq_tag |= MCTP_HDR_FLAG_SOM; ++ if (is_last_fragment_in_message) ++ hdr2->flags_seq_tag |= MCTP_HDR_FLAG_EOM; ++ ++ hdr2->flags_seq_tag |= seq << MCTP_HDR_SEQ_SHIFT; ++ ++ skb_copy_bits(skb, pos, batch_data + hlen, size); ++ ++ if (dst->dev->ops && dst->dev->ops->fill_batch_hdr) ++ dst->dev->ops->fill_batch_hdr(transport_hdr, pkt_len); ++ ++ batch_data += hlen + size; ++ seq = (seq + 1) & MCTP_HDR_SEQ_MASK; ++ pos += size; ++ } ++ ++ skb_pos = pos; ++ ++ rc = dst->output(dst, batch_skb); ++ if (rc) { ++ rc = net_xmit_errno(rc); ++ break; ++ } ++ } ++ ++ consume_skb(skb); ++ return rc; ++} ++ ++static int mctp_do_fragment_route(struct mctp_dst *dst, struct sk_buff *skb, ++ unsigned int mtu, u8 tag) ++{ ++ const unsigned int hlen = sizeof(struct mctp_hdr); ++ struct mctp_hdr *hdr, *hdr2; ++ unsigned int pos, size, headroom; ++ struct sk_buff *skb2; ++ unsigned int batch_hdr_len; ++ unsigned int batch_max_xfer; ++ int rc; + u8 seq; + + hdr = mctp_hdr(skb); +@@ -879,6 +1602,16 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + return -EMSGSIZE; + } + ++ /* If batching is enabled on the device, use the batch path */ ++ if (dst->dev && dst->dev->tx_batching_enabled) { ++ batch_hdr_len = dst->dev->tx_batch_hdr_len; ++ batch_max_xfer = dst->dev->tx_batch_max_xfer; ++ if (batch_hdr_len && batch_max_xfer) ++ return mctp_do_fragment_route_batch(dst, skb, mtu, tag, ++ batch_hdr_len, ++ batch_max_xfer); ++ } ++ + /* keep same headroom as the original skb */ + headroom = skb_headroom(skb); + +@@ -886,8 +1619,11 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + skb_pull(skb, hlen); + + for (pos = 0; pos < skb->len;) { ++ bool is_last_fragment; ++ + /* size of message payload */ + size = min(mtu - hlen, skb->len - pos); ++ is_last_fragment = (pos + size >= skb->len); + + skb2 = alloc_skb(headroom + hlen + size, GFP_KERNEL); + if (!skb2) { +@@ -921,7 +1657,7 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + if (pos == 0) + hdr2->flags_seq_tag |= MCTP_HDR_FLAG_SOM; + +- if (pos + size == skb->len) ++ if (is_last_fragment) + hdr2->flags_seq_tag |= MCTP_HDR_FLAG_EOM; + + hdr2->flags_seq_tag |= seq << MCTP_HDR_SEQ_SHIFT; +@@ -933,7 +1669,7 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + skb_ext_copy(skb2, skb); + + /* do route */ +- rc = rt->output(rt, skb2); ++ rc = dst->output(dst, skb2); + if (rc) + break; + +@@ -945,68 +1681,34 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + return rc; + } + +-int mctp_local_output(struct sock *sk, struct mctp_route *rt, ++int mctp_local_output(struct sock *sk, struct mctp_dst *dst, + struct sk_buff *skb, mctp_eid_t daddr, u8 req_tag) + { + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); +- struct mctp_skb_cb *cb = mctp_cb(skb); +- struct mctp_route tmp_rt = {0}; + struct mctp_sk_key *key; + struct mctp_hdr *hdr; + unsigned long flags; + unsigned int netid; + unsigned int mtu; + mctp_eid_t saddr; +- bool ext_rt; + int rc; + u8 tag; + +- rc = -ENODEV; +- +- if (rt) { +- ext_rt = false; +- if (WARN_ON(!rt->dev)) +- goto out_release; +- +- } else if (cb->ifindex) { +- struct net_device *dev; ++ KUNIT_STATIC_STUB_REDIRECT(mctp_local_output, sk, dst, skb, daddr, ++ req_tag); + +- ext_rt = true; +- rt = &tmp_rt; +- +- rcu_read_lock(); +- dev = dev_get_by_index_rcu(sock_net(sk), cb->ifindex); +- if (!dev) { +- rcu_read_unlock(); +- goto out_free; +- } +- rt->dev = __mctp_dev_get(dev); +- rcu_read_unlock(); +- +- if (!rt->dev) +- goto out_release; +- +- /* establish temporary route - we set up enough to keep +- * mctp_route_output happy +- */ +- rt->output = mctp_route_output; +- rt->mtu = 0; +- +- } else { +- rc = -EINVAL; +- goto out_free; +- } ++ rc = -ENODEV; + +- spin_lock_irqsave(&rt->dev->addrs_lock, flags); +- if (rt->dev->num_addrs == 0) { ++ spin_lock_irqsave(&dst->dev->addrs_lock, flags); ++ if (dst->dev->num_addrs == 0) { + rc = -EHOSTUNREACH; + } else { + /* use the outbound interface's first address as our source */ +- saddr = rt->dev->addrs[0]; ++ saddr = dst->dev->addrs[0]; + rc = 0; + } +- spin_unlock_irqrestore(&rt->dev->addrs_lock, flags); +- netid = READ_ONCE(rt->dev->net); ++ spin_unlock_irqrestore(&dst->dev->addrs_lock, flags); ++ netid = READ_ONCE(dst->dev->net); + + if (rc) + goto out_release; +@@ -1015,9 +1717,15 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, + if (req_tag & MCTP_TAG_PREALLOC) + key = mctp_lookup_prealloc_tag(msk, netid, daddr, + req_tag, &tag); +- else ++ else { ++ unsigned long lifetime = MCTP_DEFAULT_LIFETIME; ++ ++ if (dst->dev) ++ lifetime = dst->dev->key_lifetime; ++ + key = mctp_alloc_local_tag(msk, netid, saddr, daddr, +- false, &tag); ++ false, &tag, lifetime); ++ } + + if (IS_ERR(key)) { + rc = PTR_ERR(key); +@@ -1032,15 +1740,13 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, + tag = req_tag & MCTP_TAG_MASK; + } + ++ skb->pkt_type = PACKET_OUTGOING; + skb->protocol = htons(ETH_P_MCTP); + skb->priority = 0; + skb_reset_transport_header(skb); + skb_push(skb, sizeof(struct mctp_hdr)); + skb_reset_network_header(skb); +- skb->dev = rt->dev->dev; +- +- /* cb->net will have been set on initial ingress */ +- cb->src = saddr; ++ skb->dev = dst->dev->dev; + + /* set up common header fields */ + hdr = mctp_hdr(skb); +@@ -1048,86 +1754,108 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, + hdr->dest = daddr; + hdr->src = saddr; + +- mtu = mctp_route_mtu(rt); ++ /* Capture original message header for error reporting on fragmented messages. ++ * This ensures that if a middle or end fragment fails, we can still report ++ * the original header (PLDM Instance ID, etc.) to the application. ++ */ ++ if (key && skb->len > sizeof(struct mctp_hdr)) { ++ unsigned long flags2; ++ size_t payload_offset, available, capture_len; ++ ++ spin_lock_irqsave(&key->lock, flags2); + ++ /* Capture message type (first byte after MCTP header) */ ++ key->orig_msg_type = *((u8 *)(skb->data + sizeof(struct mctp_hdr))); ++ ++ /* Capture first 32 bytes of payload (after message type) */ ++ payload_offset = sizeof(struct mctp_hdr) + 1; ++ available = skb->len - payload_offset; ++ capture_len = min_t(size_t, available, sizeof(key->orig_payload)); ++ ++ if (capture_len > 0) { ++ memcpy(key->orig_payload, skb->data + payload_offset, capture_len); ++ key->orig_payload_len = capture_len; ++ } else { ++ key->orig_payload_len = 0; ++ } ++ ++ spin_unlock_irqrestore(&key->lock, flags2); ++ } ++ ++ mtu = dst->mtu; ++ ++ trace_mctp_local_output(saddr, daddr, tag, skb->len); + if (skb->len + sizeof(struct mctp_hdr) <= mtu) { + hdr->flags_seq_tag = MCTP_HDR_FLAG_SOM | + MCTP_HDR_FLAG_EOM | tag; +- rc = rt->output(rt, skb); ++ rc = dst->output(dst, skb); + } else { +- rc = mctp_do_fragment_route(rt, skb, mtu, tag); ++ rc = mctp_do_fragment_route(dst, skb, mtu, tag); + } + + /* route output functions consume the skb, even on error */ + skb = NULL; + + out_release: +- if (!ext_rt) +- mctp_route_release(rt); +- +- mctp_dev_put(tmp_rt.dev); +- +-out_free: + kfree_skb(skb); + return rc; + } + + /* route management */ +-static int mctp_route_add(struct mctp_dev *mdev, mctp_eid_t daddr_start, +- unsigned int daddr_extent, unsigned int mtu, +- unsigned char type) ++ ++/* mctp_route_add(): Add the provided route, previously allocated via ++ * mctp_route_alloc(). On success, takes ownership of @rt, which includes a ++ * hold on rt->dev for usage in the route table. On failure a caller will want ++ * to mctp_route_release(). ++ * ++ * We expect that the caller has set rt->type, rt->dst_type, rt->min, rt->max, ++ * rt->mtu and either rt->dev (with a reference held appropriately) or ++ * rt->gateway. Other fields will be populated. ++ */ ++static int mctp_route_add(struct net *net, struct mctp_route *rt) + { +- int (*rtfn)(struct mctp_route *rt, struct sk_buff *skb); +- struct net *net = dev_net(mdev->dev); +- struct mctp_route *rt, *ert; ++ struct mctp_route *ert; + +- if (!mctp_address_unicast(daddr_start)) ++ if (!mctp_address_unicast(rt->min) || !mctp_address_unicast(rt->max)) + return -EINVAL; + +- if (daddr_extent > 0xff || daddr_start + daddr_extent >= 255) ++ if (rt->dst_type == MCTP_ROUTE_DIRECT && !rt->dev) + return -EINVAL; + +- switch (type) { ++ if (rt->dst_type == MCTP_ROUTE_GATEWAY && !rt->gateway.eid) ++ return -EINVAL; ++ ++ switch (rt->type) { + case RTN_LOCAL: +- rtfn = mctp_route_input; ++ rt->output = mctp_dst_input; + break; + case RTN_UNICAST: +- rtfn = mctp_route_output; ++ rt->output = mctp_dst_output; + break; + default: + return -EINVAL; + } + +- rt = mctp_route_alloc(); +- if (!rt) +- return -ENOMEM; +- +- rt->min = daddr_start; +- rt->max = daddr_start + daddr_extent; +- rt->mtu = mtu; +- rt->dev = mdev; +- mctp_dev_hold(rt->dev); +- rt->type = type; +- rt->output = rtfn; +- + ASSERT_RTNL(); ++ + /* Prevent duplicate identical routes. */ + list_for_each_entry(ert, &net->mctp.routes, list) { + if (mctp_rt_compare_exact(rt, ert)) { +- mctp_route_release(rt); + return -EEXIST; + } + } + + list_add_rcu(&rt->list, &net->mctp.routes); + ++ if (rt->dev) ++ trace_mctp_route_add(rt->dev->dev, rt->min, rt->max - rt->min, rt->mtu); + return 0; + } + +-static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, +- unsigned int daddr_extent, unsigned char type) ++static int mctp_route_remove(struct net *net, unsigned int netid, ++ mctp_eid_t daddr_start, unsigned int daddr_extent, ++ unsigned char type) + { +- struct net *net = dev_net(mdev->dev); + struct mctp_route *rt, *tmp; + mctp_eid_t daddr_end; + bool dropped; +@@ -1141,9 +1869,11 @@ static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, + ASSERT_RTNL(); + + list_for_each_entry_safe(rt, tmp, &net->mctp.routes, list) { +- if (rt->dev == mdev && ++ if (mctp_route_netid(rt) == netid && + rt->min == daddr_start && rt->max == daddr_end && + rt->type == type) { ++ if (rt->dev) ++ trace_mctp_route_del(rt->dev->dev, rt->min, rt->max - rt->min); + list_del_rcu(&rt->list); + /* TODO: immediate RTM_DELROUTE */ + mctp_route_release(rt); +@@ -1156,12 +1886,32 @@ static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, + + int mctp_route_add_local(struct mctp_dev *mdev, mctp_eid_t addr) + { +- return mctp_route_add(mdev, addr, 0, 0, RTN_LOCAL); ++ struct mctp_route *rt; ++ int rc; ++ ++ rt = mctp_route_alloc(); ++ if (!rt) ++ return -ENOMEM; ++ ++ rt->min = addr; ++ rt->max = addr; ++ rt->dst_type = MCTP_ROUTE_DIRECT; ++ rt->dev = mdev; ++ rt->type = RTN_LOCAL; ++ ++ mctp_dev_hold(rt->dev); ++ ++ rc = mctp_route_add(dev_net(mdev->dev), rt); ++ if (rc) ++ mctp_route_release(rt); ++ ++ return rc; + } + + int mctp_route_remove_local(struct mctp_dev *mdev, mctp_eid_t addr) + { +- return mctp_route_remove(mdev, addr, 0, RTN_LOCAL); ++ return mctp_route_remove(dev_net(mdev->dev), mdev->net, ++ addr, 0, RTN_LOCAL); + } + + /* removes all entries for a given device */ +@@ -1172,7 +1922,7 @@ void mctp_route_remove_dev(struct mctp_dev *mdev) + + ASSERT_RTNL(); + list_for_each_entry_safe(rt, tmp, &net->mctp.routes, list) { +- if (rt->dev == mdev) { ++ if (rt->dst_type == MCTP_ROUTE_DIRECT && rt->dev == mdev) { + list_del_rcu(&rt->list); + /* TODO: immediate RTM_DELROUTE */ + mctp_route_release(rt); +@@ -1180,6 +1930,37 @@ void mctp_route_remove_dev(struct mctp_dev *mdev) + } + } + ++/* Lookup bound socket for packet delivery when no route exists */ ++static struct mctp_route *mctp_route_lookup_bound_socket(struct net *net, struct sk_buff *skb) ++{ ++ struct mctp_hdr *mh; ++ struct mctp_sock *msk; ++ struct mctp_route *rt = NULL; ++ ++ WARN_ON(!rcu_read_lock_held()); ++ ++ mh = mctp_hdr(skb); ++ ++ if (!skb_headlen(skb)) ++ return NULL; ++ ++ /* Look for bound sockets that match this packet */ ++ msk = mctp_lookup_bind(net, skb); ++ if (msk) { ++ /* Create a temporary route for socket delivery */ ++ rt = mctp_route_alloc(); ++ if (rt) { ++ rt->min = mh->dest; ++ rt->max = mh->dest; ++ rt->type = RTN_LOCAL; ++ rt->output = mctp_dst_input; ++ rt->dev = NULL; ++ } ++ } ++ ++ return rt; ++} ++ + /* Incoming packet-handling */ + + static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, +@@ -1189,8 +1970,9 @@ static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, + struct net *net = dev_net(dev); + struct mctp_dev *mdev; + struct mctp_skb_cb *cb; +- struct mctp_route *rt; ++ struct mctp_dst dst; + struct mctp_hdr *mh; ++ int rc; + + rcu_read_lock(); + mdev = __mctp_dev_get(dev); +@@ -1232,17 +2014,39 @@ static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, + cb->net = READ_ONCE(mdev->net); + cb->ifindex = dev->ifindex; + +- rt = mctp_route_lookup(net, cb->net, mh->dest); ++ if (!mctp_nf_ingress_check(skb, mdev, mh)) { ++ netdev_dbg(dev, "MCTP nf: ingress filter dropped packet src=%u dst=%u\n", ++ mh->src, mh->dest); ++ goto err_drop; ++ } ++ ++ rc = mctp_route_lookup(net, cb->net, mh->dest, &dst); + + /* NULL EID, but addressed to our physical address */ +- if (!rt && mh->dest == MCTP_ADDR_NULL && skb->pkt_type == PACKET_HOST) +- rt = mctp_route_lookup_null(net, dev); ++ if (rc && mh->dest == MCTP_ADDR_NULL && skb->pkt_type == PACKET_HOST) { ++ rc = mctp_route_lookup_null(net, dev, &dst); ++ if (rc) { ++ /* No RTN_LOCAL route: try bound sockets so NULL EID reaches them */ ++ struct mctp_route *rt; ++ ++ rcu_read_lock(); ++ rt = mctp_route_lookup_bound_socket(net, skb); ++ rcu_read_unlock(); ++ if (rt) { ++ rt->dev = mdev; ++ mctp_dev_hold(rt->dev); ++ mctp_dst_from_route(&dst, mh->dest, 0, rt); ++ mctp_route_release(rt); ++ rc = 0; ++ } ++ } ++ } + +- if (!rt) ++ if (rc) + goto err_drop; + +- rt->output(rt, skb); +- mctp_route_release(rt); ++ dst.output(&dst, skb); ++ mctp_dst_release(&dst); + mctp_dev_put(mdev); + + return NET_RX_SUCCESS; +@@ -1264,19 +2068,28 @@ static const struct nla_policy rta_mctp_policy[RTA_MAX + 1] = { + [RTA_DST] = { .type = NLA_U8 }, + [RTA_METRICS] = { .type = NLA_NESTED }, + [RTA_OIF] = { .type = NLA_U32 }, ++ [RTA_GATEWAY] = NLA_POLICY_EXACT_LEN(sizeof(struct mctp_fq_addr)), ++}; ++ ++static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = { ++ [RTAX_MTU] = { .type = NLA_U32 }, + }; + +-/* Common part for RTM_NEWROUTE and RTM_DELROUTE parsing. +- * tb must hold RTA_MAX+1 elements. ++/* base parsing; common to both _lookup and _populate variants. ++ * ++ * For gateway routes (which have a RTA_GATEWAY, and no RTA_OIF), we populate ++ * *gatweayp. for direct routes (RTA_OIF, no RTA_GATEWAY), we populate *mdev. + */ +-static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, +- struct netlink_ext_ack *extack, +- struct nlattr **tb, struct rtmsg **rtm, +- struct mctp_dev **mdev, mctp_eid_t *daddr_start) ++static int mctp_route_nlparse_common(struct net *net, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack, ++ struct nlattr **tb, struct rtmsg **rtm, ++ struct mctp_dev **mdev, ++ struct mctp_fq_addr *gatewayp, ++ mctp_eid_t *daddr_start) + { +- struct net *net = sock_net(skb->sk); ++ struct mctp_fq_addr *gateway = NULL; ++ unsigned int ifindex = 0; + struct net_device *dev; +- unsigned int ifindex; + int rc; + + rc = nlmsg_parse(nlh, sizeof(struct rtmsg), tb, RTA_MAX, +@@ -1292,11 +2105,44 @@ static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, + } + *daddr_start = nla_get_u8(tb[RTA_DST]); + +- if (!tb[RTA_OIF]) { +- NL_SET_ERR_MSG(extack, "ifindex missing"); ++ if (tb[RTA_OIF]) ++ ifindex = nla_get_u32(tb[RTA_OIF]); ++ ++ if (tb[RTA_GATEWAY]) ++ gateway = nla_data(tb[RTA_GATEWAY]); ++ ++ if (ifindex && gateway) { ++ NL_SET_ERR_MSG(extack, ++ "cannot specify both ifindex and gateway"); ++ return -EINVAL; ++ ++ } else if (ifindex) { ++ dev = __dev_get_by_index(net, ifindex); ++ if (!dev) { ++ NL_SET_ERR_MSG(extack, "bad ifindex"); ++ return -ENODEV; ++ } ++ *mdev = mctp_dev_get_rtnl(dev); ++ if (!*mdev) ++ return -ENODEV; ++ gatewayp->eid = 0; ++ ++ } else if (gateway) { ++ if (!mctp_address_unicast(gateway->eid)) { ++ NL_SET_ERR_MSG(extack, "bad gateway"); ++ return -EINVAL; ++ } ++ ++ gatewayp->eid = gateway->eid; ++ gatewayp->net = gateway->net != MCTP_NET_ANY ? ++ gateway->net : ++ READ_ONCE(net->mctp.default_net); ++ *mdev = NULL; ++ ++ } else { ++ NL_SET_ERR_MSG(extack, "no route output provided"); + return -EINVAL; + } +- ifindex = nla_get_u32(tb[RTA_OIF]); + + *rtm = nlmsg_data(nlh); + if ((*rtm)->rtm_family != AF_MCTP) { +@@ -1304,82 +2150,157 @@ static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, + return -EINVAL; + } + +- dev = __dev_get_by_index(net, ifindex); +- if (!dev) { +- NL_SET_ERR_MSG(extack, "bad ifindex"); +- return -ENODEV; +- } +- *mdev = mctp_dev_get_rtnl(dev); +- if (!*mdev) +- return -ENODEV; +- +- if (dev->flags & IFF_LOOPBACK) { +- NL_SET_ERR_MSG(extack, "no routes to loopback"); ++ if ((*rtm)->rtm_type != RTN_UNICAST) { ++ NL_SET_ERR_MSG(extack, "rtm_type must be RTN_UNICAST"); + return -EINVAL; + } + + return 0; + } + +-static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = { +- [RTAX_MTU] = { .type = NLA_U32 }, +-}; +- +-static int mctp_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, +- struct netlink_ext_ack *extack) ++/* Route parsing for lookup operations; we only need the "route target" ++ * components (ie., network and dest-EID range). ++ */ ++static int mctp_route_nlparse_lookup(struct net *net, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack, ++ unsigned char *type, unsigned int *netid, ++ mctp_eid_t *daddr_start, ++ unsigned int *daddr_extent) + { + struct nlattr *tb[RTA_MAX + 1]; ++ struct mctp_fq_addr gw; ++ struct mctp_dev *mdev; ++ struct rtmsg *rtm; ++ int rc; ++ ++ rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, ++ &mdev, &gw, daddr_start); ++ if (rc) ++ return rc; ++ ++ if (mdev) { ++ *netid = mdev->net; ++ } else if (gw.eid) { ++ *netid = gw.net; ++ } else { ++ /* bug: _nlparse_common should not allow this */ ++ return -1; ++ } ++ ++ *type = rtm->rtm_type; ++ *daddr_extent = rtm->rtm_dst_len; ++ ++ return 0; ++} ++ ++/* Full route parse for RTM_NEWROUTE: populate @rt. On success, ++ * MCTP_ROUTE_DIRECT routes (ie, those with a direct dev) will hold a reference ++ * to that dev. ++ */ ++static int mctp_route_nlparse_populate(struct net *net, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack, ++ struct mctp_route *rt) ++{ + struct nlattr *tbx[RTAX_MAX + 1]; ++ struct nlattr *tb[RTA_MAX + 1]; ++ unsigned int daddr_extent; ++ struct mctp_fq_addr gw; + mctp_eid_t daddr_start; +- struct mctp_dev *mdev; ++ struct mctp_dev *dev; + struct rtmsg *rtm; +- unsigned int mtu; ++ u32 mtu = 0; + int rc; + +- rc = mctp_route_nlparse(skb, nlh, extack, tb, +- &rtm, &mdev, &daddr_start); +- if (rc < 0) ++ rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, ++ &dev, &gw, &daddr_start); ++ if (rc) + return rc; + +- if (rtm->rtm_type != RTN_UNICAST) { +- NL_SET_ERR_MSG(extack, "rtm_type must be RTN_UNICAST"); ++ daddr_extent = rtm->rtm_dst_len; ++ ++ if (daddr_extent > 0xff || daddr_extent + daddr_start >= 255) { ++ NL_SET_ERR_MSG(extack, "invalid eid range"); + return -EINVAL; + } + +- mtu = 0; + if (tb[RTA_METRICS]) { + rc = nla_parse_nested(tbx, RTAX_MAX, tb[RTA_METRICS], + rta_metrics_policy, NULL); +- if (rc < 0) ++ if (rc < 0) { ++ NL_SET_ERR_MSG(extack, "incorrect RTA_METRICS format"); + return rc; ++ } + if (tbx[RTAX_MTU]) + mtu = nla_get_u32(tbx[RTAX_MTU]); + } + +- rc = mctp_route_add(mdev, daddr_start, rtm->rtm_dst_len, mtu, +- rtm->rtm_type); ++ rt->type = rtm->rtm_type; ++ rt->min = daddr_start; ++ rt->max = daddr_start + daddr_extent; ++ rt->mtu = mtu; ++ if (gw.eid) { ++ rt->dst_type = MCTP_ROUTE_GATEWAY; ++ rt->gateway.eid = gw.eid; ++ rt->gateway.net = gw.net; ++ } else { ++ rt->dst_type = MCTP_ROUTE_DIRECT; ++ rt->dev = dev; ++ mctp_dev_hold(rt->dev); ++ } ++ ++ return 0; ++} ++ ++static int mctp_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack) ++{ ++ struct net *net = sock_net(skb->sk); ++ struct mctp_route *rt; ++ int rc; ++ ++ rt = mctp_route_alloc(); ++ if (!rt) ++ return -ENOMEM; ++ ++ rc = mctp_route_nlparse_populate(net, nlh, extack, rt); ++ if (rc < 0) ++ goto err_free; ++ ++ if (rt->dst_type == MCTP_ROUTE_DIRECT && ++ rt->dev->dev->flags & IFF_LOOPBACK) { ++ NL_SET_ERR_MSG(extack, "no routes to loopback"); ++ rc = -EINVAL; ++ goto err_free; ++ } ++ ++ rc = mctp_route_add(net, rt); ++ if (!rc) ++ return 0; ++ ++err_free: ++ mctp_route_release(rt); + return rc; + } + + static int mctp_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) + { +- struct nlattr *tb[RTA_MAX + 1]; ++ struct net *net = sock_net(skb->sk); ++ unsigned int netid, daddr_extent; ++ unsigned char type = RTN_UNSPEC; + mctp_eid_t daddr_start; +- struct mctp_dev *mdev; +- struct rtmsg *rtm; + int rc; + +- rc = mctp_route_nlparse(skb, nlh, extack, tb, +- &rtm, &mdev, &daddr_start); ++ rc = mctp_route_nlparse_lookup(net, nlh, extack, &type, &netid, ++ &daddr_start, &daddr_extent); + if (rc < 0) + return rc; + + /* we only have unicast routes */ +- if (rtm->rtm_type != RTN_UNICAST) ++ if (type != RTN_UNICAST) + return -EINVAL; + +- rc = mctp_route_remove(mdev, daddr_start, rtm->rtm_dst_len, RTN_UNICAST); ++ rc = mctp_route_remove(net, netid, daddr_start, daddr_extent, type); + return rc; + } + +@@ -1405,7 +2326,6 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, + hdr->rtm_tos = 0; + hdr->rtm_table = RT_TABLE_DEFAULT; + hdr->rtm_protocol = RTPROT_STATIC; /* everything is user-defined */ +- hdr->rtm_scope = RT_SCOPE_LINK; /* TODO: scope in mctp_route? */ + hdr->rtm_type = rt->type; + + if (nla_put_u8(skb, RTA_DST, rt->min)) +@@ -1422,13 +2342,17 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, + + nla_nest_end(skb, metrics); + +- if (rt->dev) { ++ if (rt->dst_type == MCTP_ROUTE_DIRECT) { ++ hdr->rtm_scope = RT_SCOPE_LINK; + if (nla_put_u32(skb, RTA_OIF, rt->dev->dev->ifindex)) + goto cancel; ++ } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { ++ hdr->rtm_scope = RT_SCOPE_UNIVERSE; ++ if (nla_put(skb, RTA_GATEWAY, ++ sizeof(rt->gateway), &rt->gateway)) ++ goto cancel; + } + +- /* TODO: conditional neighbour physaddr? */ +- + nlmsg_end(skb, nlh); + + return 0; +@@ -1534,6 +2458,602 @@ void mctp_routes_exit(void) + dev_remove_pack(&mctp_packet_type); + } + ++/** ++ * mctp_lookup_sock_by_key - Find socket using MCTP key (tag-based lookup) ++ * @skb: The SKB to look up ++ * @dev: The network device ++ * @found_key: Output parameter to return the matched key (optional, can be NULL) ++ * ++ * Uses MCTP tag to uniquely identify which socket sent the packet. ++ * This properly handles multiple applications sending to the same remote EID. ++ * If found_key is non-NULL, returns the matched key via output parameter. ++ * The returned key is still in the hash table (caller should not free it). ++ * Returns a reference to the sock if found (caller must sock_put), NULL otherwise. ++ */ ++struct sock *mctp_lookup_sock_by_key(struct sk_buff *skb, struct net_device *dev, ++ struct mctp_sk_key **found_key) ++{ ++ struct net *net = dev_net(dev); ++ struct mctp_dev *mdev; ++ struct mctp_hdr *mh; ++ struct mctp_sk_key *key; ++ struct sock *sk = NULL; ++ unsigned long flags; ++ unsigned int netid; ++ u8 tag; ++ ++ /* Initialize output parameter */ ++ if (found_key) ++ *found_key = NULL; ++ ++ if (!skb || skb->len < sizeof(struct mctp_hdr)) { ++ pr_err("MCTP: lookup_sock_by_key: invalid skb (skb=%p len=%u)\n", ++ skb, skb ? skb->len : 0); ++ return NULL; ++ } ++ ++ mh = mctp_hdr(skb); ++ ++ /* Get network ID from device rather than SKB control block. ++ * In URB completion context, skb->cb may not have valid magic, ++ * causing WARN_ON in mctp_cb(). We can safely get netid from ++ * the MCTP device instead. ++ */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (!mdev) { ++ rcu_read_unlock(); ++ pr_err("MCTP: lookup_sock_by_key: no mctp_dev for %s\n", dev->name); ++ return NULL; ++ } ++ netid = READ_ONCE(mdev->net); ++ mctp_dev_put(mdev); ++ rcu_read_unlock(); ++ ++ /* Extract tag from MCTP header (bits 2-0) */ ++ tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ pr_debug("MCTP: lookup_sock_by_key: netid=%u src=%u dest=%u tag=%u\n", ++ netid, mh->src, mh->dest, tag); ++ ++ /* Look up key in global key table */ ++ spin_lock_irqsave(&net->mctp.keys_lock, flags); ++ ++ hlist_for_each_entry(key, &net->mctp.keys, hlist) { ++ /* Match by: network, local EID, peer EID, tag */ ++ if (key->net != netid) ++ continue; ++ ++ /* For TX errors: source = local, dest = peer ++ * Use wildcard matching to handle MCTP_ADDR_ANY in keys. ++ * This is critical for Get Endpoint ID (dest=0/NULL) which ++ * creates keys with peer_addr=MCTP_ADDR_ANY. ++ */ ++ if (!mctp_address_matches(key->local_addr, mh->src)) ++ continue; ++ if (!mctp_address_matches(key->peer_addr, mh->dest)) ++ continue; ++ ++ /* The critical differentiator: TAG */ ++ if (key->tag != tag) ++ continue; ++ ++ /* Found exact match! */ ++ sk = key->sk; ++ if (sk) { ++ sock_hold(sk); ++ /* Return the matched key via output parameter */ ++ if (found_key) ++ *found_key = key; ++ pr_debug("MCTP: lookup_sock_by_key: FOUND socket %p (key: net=%u local=%u peer=%u tag=%u)\n", ++ sk, key->net, key->local_addr, key->peer_addr, key->tag); ++ } ++ break; ++} ++ ++spin_unlock_irqrestore(&net->mctp.keys_lock, flags); ++ ++if (!sk) { ++ pr_debug("MCTP: lookup_sock_by_key: NO MATCH found for netid=%u src=%u dest=%u tag=%u\n", ++ netid, mh->src, mh->dest, tag); ++} ++ ++ return sk; ++} ++EXPORT_SYMBOL_GPL(mctp_lookup_sock_by_key); ++ ++/** ++ * mctp_lookup_sock_for_error - Find socket for error reporting ++ * @skb: Packet that failed (contains addressing info) ++ * @dev: Network device ++ * @key: Existing key (if available, may be NULL) ++ * @found_key: Output parameter to return the matched key (optional, can be NULL) ++ * ++ * Finds which socket should receive an error notification for failed ++ * operations WE initiated (our requests/responses). ++ * ++ * Uses two-tier lookup: ++ * 1. Use socket from existing key (if key provided and valid) ++ * - For RX reassembly errors where key is from reassembly context ++ * 2. Try TX key lookup by tag (for TX errors from drivers) ++ * - TX key existence indicates operation we initiated ++ * - No TX key = not our transaction = don't report ++ * - Returns found key via found_key output parameter ++ * ++ * With BMC-to-device behavior where device uses TO=0 in responses, ++ * TX keys naturally match responses (both have tag value 0-7 only). ++ * ++ * Device-initiated messages won't have TX keys, so errors on those ++ * messages are correctly not reported (not our operations). ++ * ++ * DEADLOCK PREVENTION: Method 2 is skipped if key parameter is provided, ++ * as this indicates call from __mctp_key_remove() which holds keys_lock. ++ * ++ * Returns: Socket with refcount held, or NULL if no socket found or ++ * error should not be reported. Caller must call sock_put() ++ * if non-NULL returned. ++ */ ++struct sock *mctp_lookup_sock_for_error(struct sk_buff *skb, ++ struct net_device *dev, ++ struct mctp_sk_key *key, ++ struct mctp_sk_key **found_key) ++{ ++ struct net *net; ++ struct sock *sk = NULL; ++ struct mctp_hdr *mh; ++ u8 tag; ++ ++ /* Initialize output parameter */ ++ if (found_key) ++ *found_key = NULL; ++ ++ if (!skb || !dev) ++ return NULL; ++ ++ if (skb->len < sizeof(struct mctp_hdr)) ++ return NULL; ++ ++ net = dev_net(dev); ++ mh = mctp_hdr(skb); ++ /* Extract tag value only (0-7), not TO bit. ++ * With BMC-to-device behavior where device uses TO=0 in responses, ++ * this naturally matches TX keys which are also stored without TO bit. ++ */ ++ tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ /* Method 1: Use existing key socket (fastest path) ++ * If caller already has a key (e.g., from RX reassembly), use it directly. ++ */ ++ if (key && key->sk) { ++ struct mctp_sock *check_msk = container_of(key->sk, struct mctp_sock, sk); ++ ++ /* Check if socket is still valid and open */ ++ if (sock_flag(&check_msk->sk, SOCK_DEAD)) { ++ pr_debug("MCTP error: key socket is dead (closed)\n"); ++ return NULL; ++ } ++ ++ /* Skip if this socket doesn't have error queue enabled */ ++ if (!check_msk->enable_errqueue) { ++ pr_debug("MCTP error: key socket found but error queue disabled (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ return NULL; ++ } ++ ++ sk = key->sk; ++ sock_hold(sk); ++ /* Return the key via output parameter */ ++ if (found_key) ++ *found_key = key; ++ pr_debug("MCTP error: socket via key (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ return sk; ++ } ++ ++ /* Method 2: TX key lookup (best effort to get key with orig_payload) ++ * ++ * Try to find TX key first because it provides: ++ * 1. Socket for error reporting ++ * 2. orig_payload for accurate error message content ++ * ++ * TX key existence indicates operation WE initiated (request packet). ++ * This is the preferred method for TX errors because it gives complete context. ++ * ++ * DEADLOCK PREVENTION: Skip this method if key parameter is provided, ++ * as this indicates call from __mctp_key_remove() which holds keys_lock. ++ * mctp_lookup_sock_by_key() also needs keys_lock, causing deadlock. ++ */ ++ if (!key) { ++ struct mctp_sk_key *tx_key = NULL; ++ ++ /* Safe to do key lookup - not called from __mctp_key_remove() */ ++ sk = mctp_lookup_sock_by_key(skb, dev, &tx_key); ++ if (sk) { ++ struct mctp_sock *check_msk = container_of(sk, struct mctp_sock, sk); ++ ++ /* Check if socket is still valid and open */ ++ if (sock_flag(&check_msk->sk, SOCK_DEAD)) { ++ pr_debug("MCTP error: TX key socket is dead (closed)\n"); ++ sock_put(sk); ++ return NULL; ++ } ++ ++ /* Skip if this socket doesn't have error queue enabled */ ++ if (!check_msk->enable_errqueue) { ++ pr_debug("MCTP error: TX key socket found but error queue disabled (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ sock_put(sk); ++ return NULL; ++ } ++ ++ /* Return the found TX key via output parameter */ ++ if (found_key) ++ *found_key = tx_key; ++ ++ pr_debug("MCTP error: socket via TX key lookup (src=%u, dest=%u, tag=%u) with orig_payload\n", ++ mh->src, mh->dest, tag); ++ return sk; ++ } ++ ++ /* No TX key found - fall through to Method 3 (skb->sk fallback) */ ++ pr_debug("MCTP error: No TX key found, trying skb->sk fallback (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ } ++ ++ /* Method 3: Check skb->sk directly (fallback for TX errors without key) ++ * ++ * If TX key lookup failed but SKB has socket pointer, use it as fallback. ++ * This is faster than TX key lookup but provides NO key (no orig_payload). ++ * Only used when TX key doesn't exist (e.g., response packets, old code paths). ++ * ++ * DEADLOCK PREVENTION: Skip this method if key parameter is provided, ++ * as this indicates call from __mctp_key_remove() which holds keys_lock. ++ * ++ * NOTE: found_key will be NULL when returning via this path. ++ * Error reporting will work but without orig_payload context. ++ */ ++ if (!key && skb->sk && skb->sk->sk_family == AF_MCTP) { ++ struct mctp_sock *msk = container_of(skb->sk, struct mctp_sock, sk); ++ ++ /* Check if socket is still valid and open */ ++ if (sock_flag(&msk->sk, SOCK_DEAD)) { ++ pr_debug("MCTP error: skb->sk socket is dead (closed)\n"); ++ return NULL; ++ } ++ ++ /* Skip if this socket doesn't have error queue enabled */ ++ if (!msk->enable_errqueue) { ++ pr_debug("MCTP error: skb->sk socket found but error queue disabled (src=%u, dest=%u)\n", ++ mh->src, mh->dest); ++ return NULL; ++ } ++ ++ /* This is the socket that sent this packet - safe to report error. ++ * NOTE: found_key stays NULL - no orig_payload available. ++ */ ++ sock_hold(skb->sk); ++ pr_debug("MCTP error: socket via skb->sk fallback (src=%u, dest=%u) - no key, no orig_payload\n", ++ mh->src, mh->dest); ++ return skb->sk; ++ } ++ ++ /* If we reach here: ++ * - key parameter provided: called from __mctp_key_remove() ++ * Methods 2 and 3 skipped for deadlock prevention ++ * - OR Method 2 found no valid skb->sk ++ * - OR Method 3 found no TX key: not our transaction ++ * ++ * In any case, don't report error. ++ */ ++ return NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_lookup_sock_for_error); ++ ++/** ++ * mctp_lookup_tx_key_for_rx_error - Look up TX key for RX error reporting ++ * @net: Network namespace ++ * @netid: MCTP network ID ++ * @local_eid: Local EID (from RX packet dest) ++ * @peer_eid: Peer EID (from RX packet src) ++ * @tag: Tag value (without TO bit) ++ * ++ * For RX errors, we need to find the original TX request to report the ++ * correct payload to the application. This function looks up the TX key ++ * by reversing the addressing from the RX packet. ++ * ++ * Returns: TX key with refcount incremented, or NULL if not found ++ */ ++static struct mctp_sk_key *mctp_lookup_tx_key_for_rx_error(struct net *net, ++ unsigned int netid, ++ mctp_eid_t local_eid, ++ mctp_eid_t peer_eid, ++ u8 tag) ++{ ++ struct mctp_sk_key *key, *ret = NULL; ++ unsigned long flags; ++ ++ /* For RX errors, the TX key was created with: ++ * - local_addr = local_eid (our address when we sent request) ++ * - peer_addr = peer_eid (who we sent to) ++ * - tag = tag value (without TO bit, as TX key stores incoming perspective) ++ */ ++ spin_lock_irqsave(&net->mctp.keys_lock, flags); ++ hlist_for_each_entry(key, &net->mctp.keys, hlist) { ++ if (!mctp_key_match(key, netid, local_eid, peer_eid, tag)) ++ continue; ++ ++ spin_lock(&key->lock); ++ if (key->valid && key->orig_payload_len > 0) { ++ refcount_inc(&key->refs); ++ ret = key; ++ spin_unlock(&key->lock); ++ break; ++ } ++ spin_unlock(&key->lock); ++ } ++ spin_unlock_irqrestore(&net->mctp.keys_lock, flags); ++ ++ pr_debug("mctp_lookup_tx_key_for_rx_error: netid=%u local=%u peer=%u tag=%u -> %s\n", ++ netid, local_eid, peer_eid, tag, ret ? "FOUND" : "NOT FOUND"); ++ ++ return ret; ++} ++ ++/** ++ * mctp_queue_error - Queue error to socket error queue ++ * @sk: Socket to report error to ++ * @skb: SKB that failed (contains addressing info) ++ * @error_code: errno value (EPROTO, ETIMEDOUT, EMSGSIZE for RX; EHOSTUNREACH, ENXIO for TX) ++ * @dev: Network device (used to extract MCTP network ID) ++ * @direction: MCTP_DIR_TX or MCTP_DIR_RX ++ * @binding: enum mctp_phys_binding value (MCTP_PHYS_BINDING_USB, MCTP_PHYS_BINDING_SMBUS, etc.) ++ * @key: Key for error context. TX key for TX errors, RX key for RX errors (may be TX key). ++ * ++ * Builds an mctp_error structure and queues it to the socket's error queue. ++ * Applications can read this via recvmsg(MSG_ERRQUEUE). ++ * ++ * Strategy: ++ * - TX errors: Use provided TX key's orig_payload (captured before fragmentation) ++ * - RX errors: Look up TX key using addressing from RX key/SKB, use TX key's orig_payload ++ * (the original request we sent). Only report if TX key found - this ensures ++ * we only report errors for responses to OUR requests, not unsolicited packets. ++ * ++ * This ensures applications always receive the REQUEST payload for both TX and RX errors, ++ * allowing them to identify which transaction failed. ++ * ++ * Common error codes: ++ * RX errors: EPROTO (sequence error), ETIMEDOUT (reassembly timeout), EMSGSIZE (too large) ++ * TX errors: EHOSTUNREACH, ENXIO, ENODEV, ESHUTDOWN, ENOMEM, EPROTO, etc. ++ */ ++void mctp_queue_error(struct sock *sk, struct sk_buff *skb, ++ int error_code, struct net_device *dev, u8 direction, u8 binding, ++ struct mctp_sk_key *key) ++{ ++ struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); ++ struct mctp_sk_key *tx_key = NULL; ++ struct mctp_error *mctp_err; ++ struct sk_buff *err_skb; ++ struct mctp_hdr *mh; ++ struct mctp_dev *mdev; ++ unsigned int netid; ++ size_t capture_len; ++ bool key_found = false; ++ ++ /* Extract network ID from device. ++ * This is safer than using skb->cb which may be corrupted by qdisc. ++ */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (!mdev) { ++ rcu_read_unlock(); ++ pr_debug("MCTP: Failed to get mctp_dev for error reporting\n"); ++ return; ++ } ++ netid = READ_ONCE(mdev->net); ++ mctp_dev_put(mdev); ++ rcu_read_unlock(); ++ ++ if (!msk->enable_errqueue) { ++ pr_debug("MCTP: Error queue not enabled, skipping error report\n"); ++ return; ++ } ++ ++ /* Extract addressing from SKB */ ++ if (!skb || skb->len < sizeof(struct mctp_hdr)) { ++ pr_debug("MCTP: Invalid SKB for error reporting\n"); ++ return; ++ } ++ mh = mctp_hdr(skb); ++ ++ /* Handle TX vs RX errors differently. ++ * ++ * TX errors: Use TX key if available, otherwise extract from SKB ++ * RX errors: Always need TX key to identify our original transaction ++ */ ++ if (direction == MCTP_DIR_TX) { ++ /* ===== TX ERROR PATH ===== ++ * TX errors can occur on: ++ * 1. REQUEST packets (owner=1) - TX key exists with orig_payload ++ * 2. RESPONSE packets (owner=0) - No TX key, extract from SKB ++ * 3. Fragmented packets - Middle/end need key, first has SKB payload ++ */ ++ tx_key = key; /* For TX errors, key parameter holds TX key */ ++ ++ if (tx_key && tx_key->orig_payload_len > 0) { ++ /* Have TX key with payload - use it (fragmented requests) */ ++ key_found = true; ++ pr_debug("mctp_queue_error: TX error - using TX key orig_payload (len=%u)\n", ++ tx_key->orig_payload_len); ++ } else { ++ /* No key or empty key - must extract from SKB. ++ * This handles: ++ * - Unfragmented messages (complete payload in SKB) ++ * - First fragment (SOM=1, has msg_type in SKB) ++ * - Response packets (no TX key created) ++ * ++ * CRITICAL: Only works for first fragment or unfragmented. ++ * Middle/end fragments without key cannot be reported. ++ */ ++ pr_debug("mctp_queue_error: TX error - no TX key, will extract from SKB\n"); ++ key_found = false; ++ } ++ } else { ++ /* ===== RX ERROR PATH ===== ++ * RX errors occur when receiving responses to OUR requests. ++ * We MUST find the TX key to identify our original transaction. ++ * If no TX key exists, this is an unsolicited packet - don't report. ++ */ ++ if (error_code == ETIMEDOUT) { ++ /* RX reassembly timeout: fragments never completed. ++ * RX key IS the TX key (same object, reused for response tracking). ++ * It contains orig_payload from when we sent the original REQUEST. ++ */ ++ tx_key = key; /* For RX timeout, key parameter holds RX key (which is TX key) */ ++ ++ if (tx_key && tx_key->orig_payload_len > 0) { ++ key_found = true; ++ pr_debug("mctp_queue_error: RX timeout - has TX orig_payload (REQUEST, len=%u)\n", ++ tx_key->orig_payload_len); ++ } else { ++ /* No orig_payload = unsolicited request from device, not our transaction */ ++ pr_debug("mctp_queue_error: RX timeout - no TX origin (unsolicited), NOT REPORTING\n"); ++ return; ++ } ++ } else { ++ /* RX sequence/SOM error: look up TX key by reversing addressing. ++ * Response came FROM peer (mh->src) TO us (mh->dest). ++ * Our original request was FROM us (mh->dest) TO peer (mh->src). ++ */ ++ u8 tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ pr_debug("mctp_queue_error: RX error - looking up TX key (local=%u, peer=%u, tag=%u)\n", ++ mh->dest, mh->src, tag); ++ ++ tx_key = mctp_lookup_tx_key_for_rx_error(dev_net(dev), netid, ++ mh->dest, mh->src, tag); ++ if (tx_key && tx_key->orig_payload_len > 0) { ++ key_found = true; ++ pr_debug("mctp_queue_error: RX error - TX key found with payload\n"); ++ } else { ++ if (tx_key) { ++ pr_debug("mctp_queue_error: RX error - TX key has no payload, NOT REPORTING\n"); ++ mctp_key_unref(tx_key); ++ } else { ++ pr_debug("mctp_queue_error: RX error - TX key NOT FOUND (unsolicited), NOT REPORTING\n"); ++ } ++ return; ++ } ++ } ++ } ++ ++ /* Allocate SKB for error */ ++ err_skb = alloc_skb(sizeof(*mctp_err), GFP_ATOMIC); ++ if (!err_skb) { ++ if (direction == MCTP_DIR_RX) ++ mctp_key_unref(tx_key); ++ return; ++ } ++ ++ /* Build error structure */ ++ mctp_err = (struct mctp_error *)skb_put(err_skb, sizeof(*mctp_err)); ++ memset(mctp_err, 0, sizeof(*mctp_err)); ++ ++ /* Fill basic error information */ ++ mctp_err->error_code = error_code; ++ mctp_err->direction = direction; ++ mctp_err->binding = binding; ++ mctp_err->timestamp_ns = ktime_get_ns(); ++ ++ /* Fill addressing from SKB */ ++ mctp_err->src_eid = mh->src; ++ mctp_err->dest_eid = mh->dest; ++ mctp_err->tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ /* Extract payload for error report based on direction and availability. */ ++ if (direction == MCTP_DIR_TX) { ++ /* ===== TX ERROR: Payload Extraction ===== ++ * Try TX key first, fall back to SKB extraction ++ */ ++ if (key_found) { ++ /* Use TX key payload (captured before fragmentation) */ ++ mctp_err->msg_type = tx_key->orig_msg_type; ++ capture_len = min_t(size_t, tx_key->orig_payload_len, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, tx_key->orig_payload, capture_len); ++ mctp_err->payload_len = capture_len; ++ pr_debug("mctp_queue_error: TX - used key payload (len=%u)\n", capture_len); ++ } else { ++ /* No key - extract from SKB (responses, unfragmented, first fragments) */ ++ u8 flags = mh->flags_seq_tag; ++ bool is_first_or_unfragmented = (flags & MCTP_HDR_FLAG_SOM); ++ ++ if (!is_first_or_unfragmented) { ++ /* Middle/end fragment without key - cannot extract payload. ++ * Don't report - first fragment error already queued. ++ */ ++ pr_debug("mctp_queue_error: TX middle/end fragment without key - NOT REPORTING\n"); ++ kfree_skb(err_skb); ++ return; ++ } ++ ++ /* First fragment or unfragmented - SKB has msg_type and payload */ ++ size_t mctp_hdr_size = sizeof(struct mctp_hdr); ++ size_t available = skb->len - mctp_hdr_size; ++ ++ if (available > 0) { ++ u8 *payload_start = skb->data + mctp_hdr_size; ++ ++ /* First byte after MCTP header is message type */ ++ mctp_err->msg_type = *payload_start; ++ ++ /* Capture up to 32 bytes of payload (after message type) */ ++ if (available > 1) { ++ capture_len = min_t(size_t, available - 1, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, payload_start + 1, capture_len); ++ mctp_err->payload_len = capture_len; ++ } else { ++ mctp_err->payload_len = 0; ++ } ++ pr_debug("mctp_queue_error: TX - extracted from SKB (msg_type=%u, len=%u)\n", ++ mctp_err->msg_type, mctp_err->payload_len); ++ } else { ++ pr_debug("mctp_queue_error: TX - SKB too small, no payload\n"); ++ mctp_err->payload_len = 0; ++ } ++ } ++ } else { ++ /* ===== RX ERROR: Payload Extraction ===== ++ * Always use TX key payload (original REQUEST we sent) ++ * We already validated key_found above, so tx_key is valid here. ++ */ ++ mctp_err->msg_type = tx_key->orig_msg_type; ++ capture_len = min_t(size_t, tx_key->orig_payload_len, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, tx_key->orig_payload, capture_len); ++ mctp_err->payload_len = capture_len; ++ pr_debug("mctp_queue_error: RX - used TX key payload (REQUEST, len=%u)\n", capture_len); ++ ++ /* Release TX key if we looked it up (non-timeout RX errors) */ ++ if (error_code != ETIMEDOUT) ++ mctp_key_unref(tx_key); ++ } ++ ++ /* Queue error to socket */ ++ if (sock_queue_err_skb(sk, err_skb) == 0) { ++ /* Successfully queued, trigger error report */ ++ sk_error_report(sk); ++ pr_debug("mctp_queue_error: Error queued successfully (code=%d, %s, src=%u->dest=%u)\n", ++ error_code, direction == MCTP_DIR_TX ? "TX" : "RX", ++ mctp_err->src_eid, mctp_err->dest_eid); ++ } else { ++ /* Failed to queue - free the SKB to avoid memory leak */ ++ kfree_skb(err_skb); ++ pr_debug("mctp_queue_error: Failed to queue error to socket\n"); ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_queue_error); ++ + #if IS_ENABLED(CONFIG_MCTP_TEST) + #include "test/route-test.c" + #endif +diff --git a/net/mctp/test/route-test.c b/net/mctp/test/route-test.c +index 06c1897b6..7a398f41b 100644 +--- a/net/mctp/test/route-test.c ++++ b/net/mctp/test/route-test.c +@@ -2,132 +2,11 @@ + + #include + +-#include "utils.h" +- +-struct mctp_test_route { +- struct mctp_route rt; +- struct sk_buff_head pkts; +-}; +- +-static int mctp_test_route_output(struct mctp_route *rt, struct sk_buff *skb) +-{ +- struct mctp_test_route *test_rt = container_of(rt, struct mctp_test_route, rt); +- +- skb_queue_tail(&test_rt->pkts, skb); +- +- return 0; +-} +- +-/* local version of mctp_route_alloc() */ +-static struct mctp_test_route *mctp_route_test_alloc(void) +-{ +- struct mctp_test_route *rt; +- +- rt = kzalloc(sizeof(*rt), GFP_KERNEL); +- if (!rt) +- return NULL; +- +- INIT_LIST_HEAD(&rt->rt.list); +- refcount_set(&rt->rt.refs, 1); +- rt->rt.output = mctp_test_route_output; +- +- skb_queue_head_init(&rt->pkts); +- +- return rt; +-} +- +-static struct mctp_test_route *mctp_test_create_route(struct net *net, +- struct mctp_dev *dev, +- mctp_eid_t eid, +- unsigned int mtu) +-{ +- struct mctp_test_route *rt; +- +- rt = mctp_route_test_alloc(); +- if (!rt) +- return NULL; +- +- rt->rt.min = eid; +- rt->rt.max = eid; +- rt->rt.mtu = mtu; +- rt->rt.type = RTN_UNSPEC; +- if (dev) +- mctp_dev_hold(dev); +- rt->rt.dev = dev; +- +- list_add_rcu(&rt->rt.list, &net->mctp.routes); +- +- return rt; +-} +- +-static void mctp_test_route_destroy(struct kunit *test, +- struct mctp_test_route *rt) +-{ +- unsigned int refs; +- +- rtnl_lock(); +- list_del_rcu(&rt->rt.list); +- rtnl_unlock(); +- +- skb_queue_purge(&rt->pkts); +- if (rt->rt.dev) +- mctp_dev_put(rt->rt.dev); +- +- refs = refcount_read(&rt->rt.refs); +- KUNIT_ASSERT_EQ_MSG(test, refs, 1, "route ref imbalance"); +- +- kfree_rcu(&rt->rt, rcu); +-} +- +-static void mctp_test_skb_set_dev(struct sk_buff *skb, +- struct mctp_test_dev *dev) +-{ +- struct mctp_skb_cb *cb; +- +- cb = mctp_cb(skb); +- cb->net = READ_ONCE(dev->mdev->net); +- skb->dev = dev->ndev; +-} +- +-static struct sk_buff *mctp_test_create_skb(const struct mctp_hdr *hdr, +- unsigned int data_len) +-{ +- size_t hdr_len = sizeof(*hdr); +- struct sk_buff *skb; +- unsigned int i; +- u8 *buf; +- +- skb = alloc_skb(hdr_len + data_len, GFP_KERNEL); +- if (!skb) +- return NULL; +- +- __mctp_cb(skb); +- memcpy(skb_put(skb, hdr_len), hdr, hdr_len); +- +- buf = skb_put(skb, data_len); +- for (i = 0; i < data_len; i++) +- buf[i] = i & 0xff; +- +- return skb; +-} +- +-static struct sk_buff *__mctp_test_create_skb_data(const struct mctp_hdr *hdr, +- const void *data, +- size_t data_len) +-{ +- size_t hdr_len = sizeof(*hdr); +- struct sk_buff *skb; +- +- skb = alloc_skb(hdr_len + data_len, GFP_KERNEL); +- if (!skb) +- return NULL; +- +- __mctp_cb(skb); +- memcpy(skb_put(skb, hdr_len), hdr, hdr_len); +- memcpy(skb_put(skb, data_len), data, data_len); ++/* keep clangd happy when compiled outside of the route.c include */ ++#include ++#include + +- return skb; +-} ++#include "utils.h" + + #define mctp_test_create_skb_data(h, d) \ + __mctp_test_create_skb_data(h, d, sizeof(*d)) +@@ -141,8 +20,10 @@ struct mctp_frag_test { + static void mctp_test_fragment(struct kunit *test) + { + const struct mctp_frag_test *params; ++ struct mctp_test_pktqueue tpq; + int rc, i, n, mtu, msgsize; +- struct mctp_test_route *rt; ++ struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct sk_buff *skb; + struct mctp_hdr hdr; + u8 seq; +@@ -159,13 +40,15 @@ static void mctp_test_fragment(struct kunit *test) + skb = mctp_test_create_skb(&hdr, msgsize); + KUNIT_ASSERT_TRUE(test, skb); + +- rt = mctp_test_create_route(&init_net, NULL, 10, mtu); +- KUNIT_ASSERT_TRUE(test, rt); ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ ++ mctp_test_dst_setup(test, &dst, dev, &tpq, mtu); + +- rc = mctp_do_fragment_route(&rt->rt, skb, mtu, MCTP_TAG_OWNER); ++ rc = mctp_do_fragment_route(&dst, skb, mtu, MCTP_TAG_OWNER); + KUNIT_EXPECT_FALSE(test, rc); + +- n = rt->pkts.qlen; ++ n = tpq.pkts.qlen; + + KUNIT_EXPECT_EQ(test, n, params->n_frags); + +@@ -178,7 +61,7 @@ static void mctp_test_fragment(struct kunit *test) + first = i == 0; + last = i == (n - 1); + +- skb2 = skb_dequeue(&rt->pkts); ++ skb2 = skb_dequeue(&tpq.pkts); + + if (!skb2) + break; +@@ -216,7 +99,8 @@ static void mctp_test_fragment(struct kunit *test) + kfree_skb(skb2); + } + +- mctp_test_route_destroy(test, rt); ++ mctp_test_dst_release(&dst, &tpq); ++ mctp_test_destroy_dev(dev); + } + + static const struct mctp_frag_test mctp_frag_tests[] = { +@@ -246,25 +130,30 @@ struct mctp_rx_input_test { + static void mctp_test_rx_input(struct kunit *test) + { + const struct mctp_rx_input_test *params; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct sk_buff *skb; + + params = test->param_value; ++ test->priv = &tpq; + + dev = mctp_test_create_dev(); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + +- rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); ++ rt = mctp_test_create_route_direct(&init_net, dev->mdev, 8, 68); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); + + skb = mctp_test_create_skb(¶ms->hdr, 1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); + ++ mctp_test_pktqueue_init(&tpq); ++ + mctp_pkttype_receive(skb, dev->ndev, &mctp_packet_type, NULL); + +- KUNIT_EXPECT_EQ(test, !!rt->pkts.qlen, params->input); ++ KUNIT_EXPECT_EQ(test, !!tpq.pkts.qlen, params->input); + ++ skb_queue_purge(&tpq.pkts); + mctp_test_route_destroy(test, rt); + mctp_test_destroy_dev(dev); + } +@@ -292,12 +181,12 @@ KUNIT_ARRAY_PARAM(mctp_rx_input, mctp_rx_input_tests, + /* set up a local dev, route on EID 8, and a socket listening on type 0 */ + static void __mctp_route_test_init(struct kunit *test, + struct mctp_test_dev **devp, +- struct mctp_test_route **rtp, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket **sockp, + unsigned int netid) + { + struct sockaddr_mctp addr = {0}; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct socket *sock; + int rc; +@@ -307,8 +196,7 @@ static void __mctp_route_test_init(struct kunit *test, + if (netid != MCTP_NET_ANY) + WRITE_ONCE(dev->mdev->net, netid); + +- rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); +- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); ++ mctp_test_dst_setup(test, dst, dev, tpq, 68); + + rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); + KUNIT_ASSERT_EQ(test, rc, 0); +@@ -320,18 +208,18 @@ static void __mctp_route_test_init(struct kunit *test, + rc = kernel_bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + KUNIT_ASSERT_EQ(test, rc, 0); + +- *rtp = rt; + *devp = dev; + *sockp = sock; + } + + static void __mctp_route_test_fini(struct kunit *test, + struct mctp_test_dev *dev, +- struct mctp_test_route *rt, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket *sock) + { + sock_release(sock); +- mctp_test_route_destroy(test, rt); ++ mctp_test_dst_release(dst, tpq); + mctp_test_destroy_dev(dev); + } + +@@ -344,22 +232,24 @@ struct mctp_route_input_sk_test { + static void mctp_test_route_input_sk(struct kunit *test) + { + const struct mctp_route_input_sk_test *params; ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct socket *sock; + int rc; + + params = test->param_value; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + skb = mctp_test_create_skb_data(¶ms->hdr, ¶ms->type); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); + + mctp_test_skb_set_dev(skb, dev); ++ mctp_test_pktqueue_init(&tpq); + +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + + if (params->deliver) { + KUNIT_EXPECT_EQ(test, rc, 0); +@@ -376,7 +266,7 @@ static void mctp_test_route_input_sk(struct kunit *test) + KUNIT_EXPECT_NULL(test, skb2); + } + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + #define FL_S (MCTP_HDR_FLAG_SOM) +@@ -413,16 +303,17 @@ struct mctp_route_input_sk_reasm_test { + static void mctp_test_route_input_sk_reasm(struct kunit *test) + { + const struct mctp_route_input_sk_reasm_test *params; ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct socket *sock; + int i, rc; + u8 c; + + params = test->param_value; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + for (i = 0; i < params->n_hdrs; i++) { + c = i; +@@ -431,7 +322,7 @@ static void mctp_test_route_input_sk_reasm(struct kunit *test) + + mctp_test_skb_set_dev(skb, dev); + +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + } + + skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); +@@ -445,7 +336,7 @@ static void mctp_test_route_input_sk_reasm(struct kunit *test) + KUNIT_EXPECT_NULL(test, skb2); + } + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + #define RX_FRAG(f, s) RX_HDR(1, 10, 8, FL_TO | (f) | ((s) << MCTP_HDR_SEQ_SHIFT)) +@@ -547,7 +438,7 @@ struct mctp_route_input_sk_keys_test { + static void mctp_test_route_input_sk_keys(struct kunit *test) + { + const struct mctp_route_input_sk_keys_test *params; +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; + struct mctp_test_dev *dev; + struct mctp_sk_key *key; +@@ -555,6 +446,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + struct mctp_sock *msk; + struct socket *sock; + unsigned long flags; ++ struct mctp_dst dst; + unsigned int net; + int rc; + u8 c; +@@ -565,8 +457,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + net = READ_ONCE(dev->mdev->net); + +- rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); +- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); ++ mctp_test_dst_setup(test, &dst, dev, &tpq, 68); + + rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); + KUNIT_ASSERT_EQ(test, rc, 0); +@@ -592,7 +483,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + + mctp_test_skb_set_dev(skb, dev); + +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + + /* (potentially) receive message */ + skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); +@@ -606,7 +497,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + skb_free_datagram(sock->sk, skb2); + + mctp_key_unref(key); +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + static const struct mctp_route_input_sk_keys_test mctp_route_input_sk_keys_tests[] = { +@@ -681,7 +572,8 @@ KUNIT_ARRAY_PARAM(mctp_route_input_sk_keys, mctp_route_input_sk_keys_tests, + struct test_net { + unsigned int netid; + struct mctp_test_dev *dev; +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; ++ struct mctp_dst dst; + struct socket *sock; + struct sk_buff *skb; + struct mctp_sk_key *key; +@@ -699,18 +591,20 @@ mctp_test_route_input_multiple_nets_bind_init(struct kunit *test, + + t->msg.data = t->netid; + +- __mctp_route_test_init(test, &t->dev, &t->rt, &t->sock, t->netid); ++ __mctp_route_test_init(test, &t->dev, &t->dst, &t->tpq, &t->sock, ++ t->netid); + + t->skb = mctp_test_create_skb_data(&hdr, &t->msg); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t->skb); + mctp_test_skb_set_dev(t->skb, t->dev); ++ mctp_test_pktqueue_init(&t->tpq); + } + + static void + mctp_test_route_input_multiple_nets_bind_fini(struct kunit *test, + struct test_net *t) + { +- __mctp_route_test_fini(test, t->dev, t->rt, t->sock); ++ __mctp_route_test_fini(test, t->dev, &t->dst, &t->tpq, t->sock); + } + + /* Test that skbs from different nets (otherwise identical) get routed to their +@@ -731,9 +625,9 @@ static void mctp_test_route_input_multiple_nets_bind(struct kunit *test) + mctp_test_route_input_multiple_nets_bind_init(test, &t1); + mctp_test_route_input_multiple_nets_bind_init(test, &t2); + +- rc = mctp_route_input(&t1.rt->rt, t1.skb); ++ rc = mctp_dst_input(&t1.dst, t1.skb); + KUNIT_ASSERT_EQ(test, rc, 0); +- rc = mctp_route_input(&t2.rt->rt, t2.skb); ++ rc = mctp_dst_input(&t2.dst, t2.skb); + KUNIT_ASSERT_EQ(test, rc, 0); + + rx_skb1 = skb_recv_datagram(t1.sock->sk, MSG_DONTWAIT, &rc); +@@ -767,7 +661,8 @@ mctp_test_route_input_multiple_nets_key_init(struct kunit *test, + + t->msg.data = t->netid; + +- __mctp_route_test_init(test, &t->dev, &t->rt, &t->sock, t->netid); ++ __mctp_route_test_init(test, &t->dev, &t->dst, &t->tpq, &t->sock, ++ t->netid); + + msk = container_of(t->sock->sk, struct mctp_sock, sk); + +@@ -790,7 +685,7 @@ mctp_test_route_input_multiple_nets_key_fini(struct kunit *test, + struct test_net *t) + { + mctp_key_unref(t->key); +- __mctp_route_test_fini(test, t->dev, t->rt, t->sock); ++ __mctp_route_test_fini(test, t->dev, &t->dst, &t->tpq, t->sock); + } + + /* test that skbs from different nets (otherwise identical) get routed to their +@@ -812,9 +707,9 @@ static void mctp_test_route_input_multiple_nets_key(struct kunit *test) + mctp_test_route_input_multiple_nets_key_init(test, &t1); + mctp_test_route_input_multiple_nets_key_init(test, &t2); + +- rc = mctp_route_input(&t1.rt->rt, t1.skb); ++ rc = mctp_dst_input(&t1.dst, t1.skb); + KUNIT_ASSERT_EQ(test, rc, 0); +- rc = mctp_route_input(&t2.rt->rt, t2.skb); ++ rc = mctp_dst_input(&t2.dst, t2.skb); + KUNIT_ASSERT_EQ(test, rc, 0); + + rx_skb1 = skb_recv_datagram(t1.sock->sk, MSG_DONTWAIT, &rc); +@@ -843,13 +738,14 @@ static void mctp_test_route_input_multiple_nets_key(struct kunit *test) + static void mctp_test_route_input_sk_fail_single(struct kunit *test) + { + const struct mctp_hdr hdr = RX_HDR(1, 10, 8, FL_S | FL_E | FL_TO); +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct socket *sock; + struct sk_buff *skb; + int rc; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + /* No rcvbuf space, so delivery should fail. __sock_set_rcvbuf will + * clamp the minimum to SOCK_MIN_RCVBUF, so we open-code this. +@@ -865,14 +761,14 @@ static void mctp_test_route_input_sk_fail_single(struct kunit *test) + mctp_test_skb_set_dev(skb, dev); + + /* do route input, which should fail */ +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + KUNIT_EXPECT_NE(test, rc, 0); + + /* we should hold the only reference to skb */ + KUNIT_EXPECT_EQ(test, refcount_read(&skb->users), 1); + kfree_skb(skb); + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + /* Input route to socket, using a fragmented message, where sock delivery fails. +@@ -880,14 +776,15 @@ static void mctp_test_route_input_sk_fail_single(struct kunit *test) + static void mctp_test_route_input_sk_fail_frag(struct kunit *test) + { + const struct mctp_hdr hdrs[2] = { RX_FRAG(FL_S, 0), RX_FRAG(FL_E, 1) }; +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_dev *dev; + struct sk_buff *skbs[2]; ++ struct mctp_dst dst; + struct socket *sock; + unsigned int i; + int rc; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + lock_sock(sock->sk); + WRITE_ONCE(sock->sk->sk_rcvbuf, 0); +@@ -904,11 +801,11 @@ static void mctp_test_route_input_sk_fail_frag(struct kunit *test) + /* first route input should succeed, we're only queueing to the + * frag list + */ +- rc = mctp_route_input(&rt->rt, skbs[0]); ++ rc = mctp_dst_input(&dst, skbs[0]); + KUNIT_EXPECT_EQ(test, rc, 0); + + /* final route input should fail to deliver to the socket */ +- rc = mctp_route_input(&rt->rt, skbs[1]); ++ rc = mctp_dst_input(&dst, skbs[1]); + KUNIT_EXPECT_NE(test, rc, 0); + + /* we should hold the only reference to both skbs */ +@@ -918,7 +815,7 @@ static void mctp_test_route_input_sk_fail_frag(struct kunit *test) + KUNIT_EXPECT_EQ(test, refcount_read(&skbs[1]->users), 1); + kfree_skb(skbs[1]); + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + /* Input route to socket, using a fragmented message created from clones. +@@ -933,23 +830,22 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) + RX_FRAG(FL_S, 0), + RX_FRAG(FL_E, 1), + }; +- struct mctp_test_route *rt; ++ const size_t data_len = 3; /* arbitrary */ ++ u8 compare[3 * ARRAY_SIZE(hdrs)]; ++ u8 flat[3 * ARRAY_SIZE(hdrs)]; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_dev *dev; + struct sk_buff *skb[5]; + struct sk_buff *rx_skb; ++ struct mctp_dst dst; + struct socket *sock; +- size_t data_len; +- u8 compare[100]; +- u8 flat[100]; + size_t total; + void *p; + int rc; + +- /* Arbitrary length */ +- data_len = 3; + total = data_len + sizeof(struct mctp_hdr); + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + /* Create a single skb initially with concatenated packets */ + skb[0] = mctp_test_create_skb(&hdrs[0], 5 * total); +@@ -988,7 +884,7 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) + + /* Feed the fragments into MCTP core */ + for (int i = 0; i < 5; i++) { +- rc = mctp_route_input(&rt->rt, skb[i]); ++ rc = mctp_dst_input(&dst, skb[i]); + KUNIT_EXPECT_EQ(test, rc, 0); + } + +@@ -1026,29 +922,29 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) + kfree_skb(skb[i]); + } + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + #if IS_ENABLED(CONFIG_MCTP_FLOWS) + + static void mctp_test_flow_init(struct kunit *test, + struct mctp_test_dev **devp, +- struct mctp_test_route **rtp, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket **sock, + struct sk_buff **skbp, + unsigned int len) + { +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct sk_buff *skb; + + /* we have a slightly odd routing setup here; the test route + * is for EID 8, which is our local EID. We don't do a routing + * lookup, so that's fine - all we require is a path through +- * mctp_local_output, which will call rt->output on whatever ++ * mctp_local_output, which will call dst->output on whatever + * route we provide + */ +- __mctp_route_test_init(test, &dev, &rt, sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, dst, tpq, sock, MCTP_NET_ANY); + + /* Assign a single EID. ->addrs is freed on mctp netdev release */ + dev->mdev->addrs = kmalloc(sizeof(u8), GFP_KERNEL); +@@ -1061,42 +957,41 @@ static void mctp_test_flow_init(struct kunit *test, + skb_reserve(skb, sizeof(struct mctp_hdr) + 1); + memset(skb_put(skb, len), 0, len); + +- /* take a ref for the route, we'll decrement in local output */ +- refcount_inc(&rt->rt.refs); + + *devp = dev; +- *rtp = rt; + *skbp = skb; + } + + static void mctp_test_flow_fini(struct kunit *test, + struct mctp_test_dev *dev, +- struct mctp_test_route *rt, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket *sock) + { +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, dst, tpq, sock); + } + + /* test that an outgoing skb has the correct MCTP extension data set */ + static void mctp_test_packet_flow(struct kunit *test) + { ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct mctp_flow *flow; + struct socket *sock; +- u8 dst = 8; ++ u8 dst_eid = 8; + int n, rc; + +- mctp_test_flow_init(test, &dev, &rt, &sock, &skb, 30); ++ mctp_test_flow_init(test, &dev, &dst, &tpq, &sock, &skb, 30); + +- rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); ++ rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); + KUNIT_ASSERT_EQ(test, rc, 0); + +- n = rt->pkts.qlen; ++ n = tpq.pkts.qlen; + KUNIT_ASSERT_EQ(test, n, 1); + +- skb2 = skb_dequeue(&rt->pkts); ++ skb2 = skb_dequeue(&tpq.pkts); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb2); + + flow = skb_ext_find(skb2, SKB_EXT_MCTP); +@@ -1105,7 +1000,7 @@ static void mctp_test_packet_flow(struct kunit *test) + KUNIT_ASSERT_PTR_EQ(test, flow->key->sk, sock->sk); + + kfree_skb(skb2); +- mctp_test_flow_fini(test, dev, rt, sock); ++ mctp_test_flow_fini(test, dev, &dst, &tpq, sock); + } + + /* test that outgoing skbs, after fragmentation, all have the correct MCTP +@@ -1113,26 +1008,27 @@ static void mctp_test_packet_flow(struct kunit *test) + */ + static void mctp_test_fragment_flow(struct kunit *test) + { ++ struct mctp_test_pktqueue tpq; + struct mctp_flow *flows[2]; + struct sk_buff *tx_skbs[2]; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct sk_buff *skb; + struct socket *sock; +- u8 dst = 8; ++ u8 dst_eid = 8; + int n, rc; + +- mctp_test_flow_init(test, &dev, &rt, &sock, &skb, 100); ++ mctp_test_flow_init(test, &dev, &dst, &tpq, &sock, &skb, 100); + +- rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); ++ rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); + KUNIT_ASSERT_EQ(test, rc, 0); + +- n = rt->pkts.qlen; ++ n = tpq.pkts.qlen; + KUNIT_ASSERT_EQ(test, n, 2); + + /* both resulting packets should have the same flow data */ +- tx_skbs[0] = skb_dequeue(&rt->pkts); +- tx_skbs[1] = skb_dequeue(&rt->pkts); ++ tx_skbs[0] = skb_dequeue(&tpq.pkts); ++ tx_skbs[1] = skb_dequeue(&tpq.pkts); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tx_skbs[0]); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tx_skbs[1]); +@@ -1148,7 +1044,7 @@ static void mctp_test_fragment_flow(struct kunit *test) + + kfree_skb(tx_skbs[0]); + kfree_skb(tx_skbs[1]); +- mctp_test_flow_fini(test, dev, rt, sock); ++ mctp_test_flow_fini(test, dev, &dst, &tpq, sock); + } + + #else +@@ -1166,15 +1062,16 @@ static void mctp_test_fragment_flow(struct kunit *test) + /* Test that outgoing skbs cause a suitable tag to be created */ + static void mctp_test_route_output_key_create(struct kunit *test) + { ++ const u8 dst_eid = 26, src_eid = 15; ++ struct mctp_test_pktqueue tpq; + const unsigned int netid = 50; +- const u8 dst = 26, src = 15; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct mctp_sk_key *key; + struct netns_mctp *mns; + unsigned long flags; + struct socket *sock; + struct sk_buff *skb; ++ struct mctp_dst dst; + bool empty, single; + const int len = 2; + int rc; +@@ -1183,15 +1080,14 @@ static void mctp_test_route_output_key_create(struct kunit *test) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + WRITE_ONCE(dev->mdev->net, netid); + +- rt = mctp_test_create_route(&init_net, dev->mdev, dst, 68); +- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); ++ mctp_test_dst_setup(test, &dst, dev, &tpq, 68); + + rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); + KUNIT_ASSERT_EQ(test, rc, 0); + + dev->mdev->addrs = kmalloc(sizeof(u8), GFP_KERNEL); + dev->mdev->num_addrs = 1; +- dev->mdev->addrs[0] = src; ++ dev->mdev->addrs[0] = src_eid; + + skb = alloc_skb(sizeof(struct mctp_hdr) + 1 + len, GFP_KERNEL); + KUNIT_ASSERT_TRUE(test, skb); +@@ -1199,8 +1095,6 @@ static void mctp_test_route_output_key_create(struct kunit *test) + skb_reserve(skb, sizeof(struct mctp_hdr) + 1 + len); + memset(skb_put(skb, len), 0, len); + +- refcount_inc(&rt->rt.refs); +- + mns = &sock_net(sock->sk)->mctp; + + /* We assume we're starting from an empty keys list, which requires +@@ -1211,7 +1105,7 @@ static void mctp_test_route_output_key_create(struct kunit *test) + spin_unlock_irqrestore(&mns->keys_lock, flags); + KUNIT_ASSERT_TRUE(test, empty); + +- rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); ++ rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); + KUNIT_ASSERT_EQ(test, rc, 0); + + key = NULL; +@@ -1227,16 +1121,295 @@ static void mctp_test_route_output_key_create(struct kunit *test) + KUNIT_ASSERT_TRUE(test, single); + + KUNIT_EXPECT_EQ(test, key->net, netid); +- KUNIT_EXPECT_EQ(test, key->local_addr, src); +- KUNIT_EXPECT_EQ(test, key->peer_addr, dst); ++ KUNIT_EXPECT_EQ(test, key->local_addr, src_eid); ++ KUNIT_EXPECT_EQ(test, key->peer_addr, dst_eid); + /* key has incoming tag, so inverse of what we sent */ + KUNIT_EXPECT_FALSE(test, key->tag & MCTP_TAG_OWNER); + + sock_release(sock); +- mctp_test_route_destroy(test, rt); ++ mctp_test_dst_release(&dst, &tpq); + mctp_test_destroy_dev(dev); + } + ++static void mctp_test_route_extaddr_input(struct kunit *test) ++{ ++ static const unsigned char haddr[] = { 0xaa, 0x55 }; ++ struct mctp_test_pktqueue tpq; ++ struct mctp_skb_cb *cb, *cb2; ++ const unsigned int len = 40; ++ struct mctp_test_dev *dev; ++ struct sk_buff *skb, *skb2; ++ struct mctp_dst dst; ++ struct mctp_hdr hdr; ++ struct socket *sock; ++ int rc; ++ ++ hdr.ver = 1; ++ hdr.src = 10; ++ hdr.dest = 8; ++ hdr.flags_seq_tag = FL_S | FL_E | FL_TO; ++ ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); ++ ++ skb = mctp_test_create_skb(&hdr, len); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); ++ ++ /* set our hardware addressing data */ ++ cb = mctp_cb(skb); ++ memcpy(cb->haddr, haddr, sizeof(haddr)); ++ cb->halen = sizeof(haddr); ++ ++ mctp_test_skb_set_dev(skb, dev); ++ ++ rc = mctp_dst_input(&dst, skb); ++ KUNIT_ASSERT_EQ(test, rc, 0); ++ ++ mctp_test_dst_release(&dst, &tpq); ++ ++ skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb2); ++ KUNIT_ASSERT_EQ(test, skb2->len, len); ++ ++ cb2 = mctp_cb(skb2); ++ ++ /* Received SKB should have the hardware addressing as set above. ++ * We're likely to have the same actual cb here (ie., cb == cb2), ++ * but it's the comparison that we care about ++ */ ++ KUNIT_EXPECT_EQ(test, cb2->halen, sizeof(haddr)); ++ KUNIT_EXPECT_MEMEQ(test, cb2->haddr, haddr, sizeof(haddr)); ++ ++ skb_free_datagram(sock->sk, skb2); ++ mctp_test_destroy_dev(dev); ++} ++ ++static void mctp_test_route_gw_lookup(struct kunit *test) ++{ ++ struct mctp_test_route *rt1, *rt2; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_test_dev *dev; ++ int rc; ++ ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ ++ /* 8 (local) -> 10 (gateway) via 9 (direct) */ ++ rt1 = mctp_test_create_route_direct(&init_net, dev->mdev, 9, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); ++ rt2 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 10, 9, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); ++ ++ rc = mctp_route_lookup(&init_net, dev->mdev->net, 10, &dst); ++ KUNIT_EXPECT_EQ(test, rc, 0); ++ KUNIT_EXPECT_PTR_EQ(test, dst.dev, dev->mdev); ++ KUNIT_EXPECT_EQ(test, dst.mtu, dev->ndev->mtu); ++ KUNIT_EXPECT_EQ(test, dst.nexthop, 9); ++ KUNIT_EXPECT_EQ(test, dst.halen, 0); ++ ++ mctp_dst_release(&dst); ++ ++ mctp_test_route_destroy(test, rt2); ++ mctp_test_route_destroy(test, rt1); ++ mctp_test_destroy_dev(dev); ++} ++ ++static void mctp_test_route_gw_loop(struct kunit *test) ++{ ++ struct mctp_test_route *rt1, *rt2; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_test_dev *dev; ++ int rc; ++ ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ ++ /* two routes using each other as the gw */ ++ rt1 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 9, 10, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); ++ rt2 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 10, 9, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); ++ ++ /* this should fail, rather than infinite-loop */ ++ rc = mctp_route_lookup(&init_net, dev->mdev->net, 10, &dst); ++ KUNIT_EXPECT_NE(test, rc, 0); ++ ++ mctp_test_route_destroy(test, rt2); ++ mctp_test_route_destroy(test, rt1); ++ mctp_test_destroy_dev(dev); ++} ++ ++struct mctp_route_gw_mtu_test { ++ /* working away from the local stack */ ++ unsigned int dev, neigh, gw, dst; ++ unsigned int exp; ++}; ++ ++static void mctp_route_gw_mtu_to_desc(const struct mctp_route_gw_mtu_test *t, ++ char *desc) ++{ ++ sprintf(desc, "dev %d, neigh %d, gw %d, dst %d -> %d", ++ t->dev, t->neigh, t->gw, t->dst, t->exp); ++} ++ ++static const struct mctp_route_gw_mtu_test mctp_route_gw_mtu_tests[] = { ++ /* no route-specific MTUs */ ++ { 68, 0, 0, 0, 68 }, ++ { 100, 0, 0, 0, 100 }, ++ /* one route MTU (smaller than dev mtu), others unrestricted */ ++ { 100, 68, 0, 0, 68 }, ++ { 100, 0, 68, 0, 68 }, ++ { 100, 0, 0, 68, 68 }, ++ /* smallest applied, regardless of order */ ++ { 100, 99, 98, 68, 68 }, ++ { 99, 100, 98, 68, 68 }, ++ { 98, 99, 100, 68, 68 }, ++ { 68, 98, 99, 100, 68 }, ++}; ++ ++KUNIT_ARRAY_PARAM(mctp_route_gw_mtu, mctp_route_gw_mtu_tests, ++ mctp_route_gw_mtu_to_desc); ++ ++static void mctp_test_route_gw_mtu(struct kunit *test) ++{ ++ const struct mctp_route_gw_mtu_test *mtus = test->param_value; ++ struct mctp_test_route *rt1, *rt2, *rt3; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_test_dev *dev; ++ struct mctp_dev *mdev; ++ unsigned int netid; ++ int rc; ++ ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ dev->ndev->mtu = mtus->dev; ++ mdev = dev->mdev; ++ netid = mdev->net; ++ ++ /* 8 (local) -> 11 (dst) via 10 (gw) via 9 (neigh) */ ++ rt1 = mctp_test_create_route_direct(&init_net, mdev, 9, mtus->neigh); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); ++ ++ rt2 = mctp_test_create_route_gw(&init_net, netid, 10, 9, mtus->gw); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); ++ ++ rt3 = mctp_test_create_route_gw(&init_net, netid, 11, 10, mtus->dst); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt3); ++ ++ rc = mctp_route_lookup(&init_net, dev->mdev->net, 11, &dst); ++ KUNIT_EXPECT_EQ(test, rc, 0); ++ KUNIT_EXPECT_EQ(test, dst.mtu, mtus->exp); ++ ++ mctp_dst_release(&dst); ++ ++ mctp_test_route_destroy(test, rt3); ++ mctp_test_route_destroy(test, rt2); ++ mctp_test_route_destroy(test, rt1); ++ mctp_test_destroy_dev(dev); ++} ++ ++#define MCTP_TEST_LLADDR_LEN 2 ++struct mctp_test_llhdr { ++ unsigned int magic; ++ unsigned char src[MCTP_TEST_LLADDR_LEN]; ++ unsigned char dst[MCTP_TEST_LLADDR_LEN]; ++}; ++ ++static const unsigned int mctp_test_llhdr_magic = 0x5c78339c; ++ ++static int test_dev_header_create(struct sk_buff *skb, struct net_device *dev, ++ unsigned short type, const void *daddr, ++ const void *saddr, unsigned int len) ++{ ++ struct kunit *test = current->kunit_test; ++ struct mctp_test_llhdr *hdr; ++ ++ hdr = skb_push(skb, sizeof(*hdr)); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hdr); ++ skb_reset_mac_header(skb); ++ ++ hdr->magic = mctp_test_llhdr_magic; ++ memcpy(&hdr->src, saddr, sizeof(hdr->src)); ++ memcpy(&hdr->dst, daddr, sizeof(hdr->dst)); ++ ++ return 0; ++} ++ ++/* Test the dst_output path for a gateway-routed skb: we should have it ++ * lookup the nexthop EID in the neighbour table, and call into ++ * header_ops->create to resolve that to a lladdr. Our mock header_ops->create ++ * will just set a synthetic link-layer header, which we check after transmit. ++ */ ++static void mctp_test_route_gw_output(struct kunit *test) ++{ ++ const unsigned char haddr_self[MCTP_TEST_LLADDR_LEN] = { 0xaa, 0x03 }; ++ const unsigned char haddr_peer[MCTP_TEST_LLADDR_LEN] = { 0xaa, 0x02 }; ++ const struct header_ops ops = { ++ .create = test_dev_header_create, ++ }; ++ struct mctp_neigh neigh = { 0 }; ++ struct mctp_test_llhdr *ll_hdr; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_hdr hdr = { 0 }; ++ struct mctp_test_dev *dev; ++ struct sk_buff *skb; ++ unsigned char *buf; ++ int i, rc; ++ ++ dev = mctp_test_create_dev_lladdr(sizeof(haddr_self), haddr_self); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ dev->ndev->header_ops = &ops; ++ ++ dst.dev = dev->mdev; ++ __mctp_dev_get(dst.dev->dev); ++ dst.mtu = 68; ++ dst.nexthop = 9; ++ ++ /* simple mctp_neigh_add for the gateway (not dest!) endpoint */ ++ INIT_LIST_HEAD(&neigh.list); ++ neigh.dev = dev->mdev; ++ mctp_dev_hold(dev->mdev); ++ neigh.eid = 9; ++ neigh.source = MCTP_NEIGH_STATIC; ++ memcpy(neigh.ha, haddr_peer, sizeof(haddr_peer)); ++ list_add_rcu(&neigh.list, &init_net.mctp.neighbours); ++ ++ hdr.ver = 1; ++ hdr.src = 8; ++ hdr.dest = 10; ++ hdr.flags_seq_tag = FL_S | FL_E | FL_TO; ++ ++ /* construct enough for a future link-layer header, the provided ++ * mctp header, and 4 bytes of data ++ */ ++ skb = alloc_skb(sizeof(*ll_hdr) + sizeof(hdr) + 4, GFP_KERNEL); ++ skb->dev = dev->ndev; ++ __mctp_cb(skb); ++ ++ skb_reserve(skb, sizeof(*ll_hdr)); ++ ++ memcpy(skb_put(skb, sizeof(hdr)), &hdr, sizeof(hdr)); ++ buf = skb_put(skb, 4); ++ for (i = 0; i < 4; i++) ++ buf[i] = i; ++ ++ /* extra ref over the dev_xmit */ ++ skb_get(skb); ++ ++ rc = mctp_dst_output(&dst, skb); ++ KUNIT_EXPECT_EQ(test, rc, 0); ++ ++ mctp_dst_release(&dst); ++ list_del_rcu(&neigh.list); ++ mctp_dev_put(dev->mdev); ++ ++ /* check that we have our header created with the correct neighbour */ ++ ll_hdr = (void *)skb_mac_header(skb); ++ KUNIT_EXPECT_EQ(test, ll_hdr->magic, mctp_test_llhdr_magic); ++ KUNIT_EXPECT_MEMEQ(test, ll_hdr->src, haddr_self, sizeof(haddr_self)); ++ KUNIT_EXPECT_MEMEQ(test, ll_hdr->dst, haddr_peer, sizeof(haddr_peer)); ++ kfree_skb(skb); ++} ++ + static struct kunit_case mctp_test_cases[] = { + KUNIT_CASE_PARAM(mctp_test_fragment, mctp_frag_gen_params), + KUNIT_CASE_PARAM(mctp_test_rx_input, mctp_rx_input_gen_params), +@@ -1253,11 +1426,16 @@ static struct kunit_case mctp_test_cases[] = { + KUNIT_CASE(mctp_test_fragment_flow), + KUNIT_CASE(mctp_test_route_output_key_create), + KUNIT_CASE(mctp_test_route_input_cloned_frag), ++ KUNIT_CASE(mctp_test_route_extaddr_input), ++ KUNIT_CASE(mctp_test_route_gw_lookup), ++ KUNIT_CASE(mctp_test_route_gw_loop), ++ KUNIT_CASE_PARAM(mctp_test_route_gw_mtu, mctp_route_gw_mtu_gen_params), ++ KUNIT_CASE(mctp_test_route_gw_output), + {} + }; + + static struct kunit_suite mctp_test_suite = { +- .name = "mctp", ++ .name = "mctp-route", + .test_cases = mctp_test_cases, + }; + +-- +2.34.1 + diff --git a/patches-sonic/0001-mmc-sdhci-cadence-Add-CN10K-eMMC-support.patch b/patches-sonic/0001-mmc-sdhci-cadence-Add-CN10K-eMMC-support.patch new file mode 100644 index 000000000..9776e4650 --- /dev/null +++ b/patches-sonic/0001-mmc-sdhci-cadence-Add-CN10K-eMMC-support.patch @@ -0,0 +1,2892 @@ +From dc877a9ba5783e2e8d2430a2662cf23bb75edddf Mon Sep 17 00:00:00 2001 +From: Renjithkumar Raveendran Nair +Date: Fri, 8 May 2026 00:35:40 +0530 +Subject: [PATCH] mmc: sdhci-cadence: Add CN10K eMMC support + +Bring up and harden the Cadence SDHCI driver for Marvell CN10K +eMMC, including controller-specific timing, ACPI integration, +and HS400-class operation, along with other fixes. + +- Add Cadence SD6 SDHCI controller support for CN10K. +- Enable HS400 / HS400ES emmc modes; +- Set host-side PHY delays and default tuning values when DT + velues are missing or incorrect. +- Add ACPI support for the CN10K eMMC SDHCI instance. +- Implement delay_element handling and Cadence SDHCI tuning + logic, including multi-block read/tuning helpers +- Refresh default tuning parameters and fix DDR, clock, and + strobe corner cases (e.g. enhanced strobe, command delay, + block-gap, interrupt handling workarounds) +- Updates the OTX2_MBOX_VERSION to 0x000b. Extends + struct nix_lf_alloc_rsp with hw_rx_tstamp_en to match the mbox + structure with applications. + +Signed-off-by: Renjithkumar Raveendran Nair +--- + .../devicetree/bindings/mmc/cdns,sdhci.yaml | 1 + + MAINTAINERS | 1 + + drivers/mmc/core/mmc_ops.c | 73 + + drivers/mmc/host/Kconfig | 18 + + drivers/mmc/host/sdhci-cadence.c | 2289 +++++++++++++++-- + .../net/ethernet/marvell/octeontx2/af/mbox.h | 3 +- + include/linux/mmc/host.h | 2 + + include/soc/marvell/octeontx/octeontx_smc.h | 125 + + 8 files changed, 2347 insertions(+), 165 deletions(-) + create mode 100644 include/soc/marvell/octeontx/octeontx_smc.h + +diff --git a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml +index 6c40611405a0..25a5f80fa021 100644 +--- a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml ++++ b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml +@@ -17,6 +17,7 @@ properties: + - microchip,mpfs-sd4hc + - socionext,uniphier-sd4hc + - const: cdns,sd4hc ++ - const: cdns,sd6hc + + reg: + minItems: 1 +diff --git a/MAINTAINERS b/MAINTAINERS +index d0f18fdba068..68d9f14b8b7f 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -13845,6 +13845,7 @@ MARVELL OCTEON CN10K DPI DRIVER + M: Vamsi Attunuru + S: Supported + F: drivers/misc/mrvl_cn10k_dpi.c ++F: include/soc/marvell/octeontx/ + + MARVELL OCTEON ENDPOINT VIRTIO DATA PATH ACCELERATOR + R: schalla@marvell.com +diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c +index 3b3adbddf664..88bf6d0ceef7 100644 +--- a/drivers/mmc/core/mmc_ops.c ++++ b/drivers/mmc/core/mmc_ops.c +@@ -1069,3 +1069,76 @@ int mmc_sanitize(struct mmc_card *card, unsigned int timeout_ms) + return err; + } + EXPORT_SYMBOL_GPL(mmc_sanitize); ++ ++/** ++ * mmc_read_tuning() - read data blocks from the mmc ++ * @host: mmc host doing the read ++ * @blksz: data block size ++ * @blocks: number of blocks to read ++ * ++ * Read one or more blocks of data from the beginning of the mmc. This is a ++ * low-level helper for tuning operation. It is assumed that CMD23 can be used ++ * for multi-block read if the host supports it. ++ * ++ * Note: Allocate and free a temporary buffer to store the data read. The data ++ * is not available outside of the function, only the status of the read ++ * operation. ++ * ++ * Return: 0 in case of success, otherwise -EIO / -ENOMEM / -E2BIG ++ */ ++int mmc_read_tuning(struct mmc_host *host, unsigned int blksz, ++ unsigned int blocks) ++{ ++ struct mmc_request mrq = {}; ++ struct mmc_command sbc = {}; ++ struct mmc_command cmd = {}; ++ struct mmc_command stop = {}; ++ struct mmc_data data = {}; ++ struct scatterlist sg; ++ void *buf; ++ unsigned int len; ++ ++ if (blocks > 1) { ++ if (mmc_host_cmd23(host)) { ++ mrq.sbc = &sbc; ++ sbc.opcode = MMC_SET_BLOCK_COUNT; ++ sbc.arg = blocks; ++ sbc.flags = MMC_RSP_R1 | MMC_CMD_AC; ++ } ++ cmd.opcode = MMC_READ_MULTIPLE_BLOCK; ++ mrq.stop = &stop; ++ stop.opcode = MMC_STOP_TRANSMISSION; ++ stop.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; ++ } else { ++ cmd.opcode = MMC_READ_SINGLE_BLOCK; ++ } ++ ++ mrq.cmd = &cmd; ++ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; ++ ++ mrq.data = &data; ++ data.flags = MMC_DATA_READ; ++ data.blksz = blksz; ++ data.blocks = blocks; ++ data.blk_addr = 0; ++ data.sg = &sg; ++ data.sg_len = 1; ++ data.timeout_ns = 1000000000; ++ ++ if (check_mul_overflow(blksz, blocks, &len)) ++ return -E2BIG; ++ buf = kmalloc(len, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ sg_init_one(&sg, buf, len); ++ ++ mmc_wait_for_req(host, &mrq); ++ kfree(buf); ++ ++ if (sbc.error || cmd.error || data.error) ++ return -EIO; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mmc_read_tuning); +diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig +index 7199cb0bd0b9..4cdf78cc4051 100644 +--- a/drivers/mmc/host/Kconfig ++++ b/drivers/mmc/host/Kconfig +@@ -276,6 +276,24 @@ config MMC_SDHCI_CADENCE + + If unsure, say N. + ++config MMC_SDHCI_CADENCE_DEBUG ++ tristate "Debug support for the Cadence SD/SDIO/eMMC controller" ++ depends on MMC_SDHCI_CADENCE ++ default n ++ help ++ Say Y here to compile extra diagnostic code into the Cadence ++ SDHCI driver. When enabled, the driver can print PHY timing ++ calculations, delay/DLL settings, and host register snapshots ++ (HRS/SRS and PHY registers) at key setup points. This is verbose ++ and not suitable for production kernels because it adds printk ++ traffic and may affect real-time behavior. ++ ++ Use this only while bringing up new silicon or debugging eMMC/SD ++ signal integrity and tuning problems. ++ ++ If you have a controller with this interface, say Y or M here. ++ If unsure, say N. ++ + config MMC_SDHCI_ESDHC_MCF + tristate "SDHCI support for the Freescale eSDHC ColdFire controller" + depends on M5441x +diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c +index be1505e8c536..87022bc6d941 100644 +--- a/drivers/mmc/host/sdhci-cadence.c ++++ b/drivers/mmc/host/sdhci-cadence.c +@@ -4,8 +4,13 @@ + * Author: Masahiro Yamada + */ + ++#include + #include + #include ++#ifdef CONFIG_DMI ++#include ++#include ++#endif + #include + #include + #include +@@ -13,33 +18,164 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "sdhci-pltfm.h" + +-/* HRS - Host Register Set (specific to Cadence) */ +-#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ +-#define SDHCI_CDNS_HRS04_ACK BIT(26) +-#define SDHCI_CDNS_HRS04_RD BIT(25) +-#define SDHCI_CDNS_HRS04_WR BIT(24) +-#define SDHCI_CDNS_HRS04_RDATA GENMASK(23, 16) +-#define SDHCI_CDNS_HRS04_WDATA GENMASK(15, 8) +-#define SDHCI_CDNS_HRS04_ADDR GENMASK(5, 0) +- +-#define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ +-#define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) +-#define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8) +-#define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0) +-#define SDHCI_CDNS_HRS06_MODE_SD 0x0 +-#define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 +-#define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 +-#define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 +-#define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 +-#define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 ++#ifdef CONFIG_MMC_SDHCI_CADENCE_DEBUG ++#define DEBUG_DRV(...) pr_info(__VA_ARGS__) ++#else ++#define DEBUG_DRV(...) ++#endif ++ ++#define SDMCLK_MAX_FREQ 200000000 ++ ++#define DEFAULT_CMD_DELAY 16 ++#define SDHCI_CDNS_TUNE_START 16 ++#define SDHCI_CDNS_TUNE_STEP 6 ++#define SDHCI_CDNS_TUNE_ITERATIONS 40 ++#define SDHCI_CDNS_TUNE_MAX_VALUE 256 ++#define SDHCI_CDNS_SD6_DEFAULT_DELAY_ELEMENT 8 ++#define DEFAULT_READ_DQS_CMD_DELAY 64 ++#define DEFAULT_CLK_WRDQS_DELAY 0 ++#define DEFAULT_CLK_WR_DELAY 0 ++#define DEFAULT_READ_DQS_DELAY 64 ++ ++#define SDHCI_CDNS_HRS00 0x00 ++#define SDHCI_CDNS_HRS00_SWR BIT(0) ++ ++#define SDHCI_CDNS_HRS02 0x08 /* PHY access port */ ++#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ ++ ++/* SD 4.0 Controller HRS - Host Register Set (specific to Cadence) */ ++#define SDHCI_CDNS_SD4_HRS04_ACK BIT(26) ++#define SDHCI_CDNS_SD4_HRS04_RD BIT(25) ++#define SDHCI_CDNS_SD4_HRS04_WR BIT(24) ++#define SDHCI_CDNS_SD4_HRS04_RDATA GENMASK(23, 16) ++#define SDHCI_CDNS_SD4_HRS04_WDATA GENMASK(15, 8) ++#define SDHCI_CDNS_SD4_HRS04_ADDR GENMASK(5, 0) ++ ++#define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ ++#define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) ++#define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8) ++#define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0) ++#define SDHCI_CDNS_HRS06_MODE_SD 0x0 ++#define SDHCI_CDNS_HRS06_MODE_LEGACY 0x1 ++#define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 ++#define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 ++#define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 ++#define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 ++#define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 ++ ++/* SD 6.0 Controller HRS - Host Register Set (Specific to Cadence) */ ++#define SDHCI_CDNS_SD6_HRS04_ADDR GENMASK(15, 0) ++ ++#define SDHCI_CDNS_HRS05 0x14 ++ ++#define SDHCI_CDNS_HRS07 0x1C ++#define SDHCI_CDNS_HRS07_RW_COMPENSATE GENMASK(20, 16) ++#define SDHCI_CDNS_HRS07_IDELAY_VAL GENMASK(4, 0) ++ ++#define SDHCI_CDNS_HRS09 0x24 ++#define SDHCI_CDNS_HRS09_RDDATA_EN BIT(16) ++#define SDHCI_CDNS_HRS09_RDCMD_EN BIT(15) ++#define SDHCI_CDNS_HRS09_EXTENDED_WR_MODE BIT(3) ++#define SDHCI_CDNS_HRS09_EXTENDED_RD_MODE BIT(2) ++#define SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE BIT(1) ++#define SDHCI_CDNS_HRS09_PHY_SW_RESET BIT(0) ++ ++#define SDHCI_CDNS_HRS10 0x28 ++#define SDHCI_CDNS_HRS10_HCSDCLKADJ GENMASK(19, 16) ++ ++#define SDHCI_CDNS_HRS11 0x2c ++/*Reset related*/ ++#define SDHCI_CDNS_SRS11_SW_RESET_ALL BIT(24) ++#define SDHCI_CDNS_SRS11_SW_RESET_CMD BIT(25) ++#define SDHCI_CDNS_SRS11_SW_RESET_DAT BIT(26) ++ ++#define SDHCI_CDNS_HRS16 0x40 ++#define SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY GENMASK(31, 28) ++#define SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY GENMASK(27, 24) ++#define SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY GENMASK(23, 20) ++#define SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY GENMASK(19, 16) ++#define SDHCI_CDNS_HRS16_WRDATA1_DLY GENMASK(15, 12) ++#define SDHCI_CDNS_HRS16_WRDATA0_DLY GENMASK(11, 8) ++#define SDHCI_CDNS_HRS16_WRCMD1_DLY GENMASK(7, 4) ++#define SDHCI_CDNS_HRS16_WRCMD0_DLY GENMASK(3, 0) ++ ++/* PHY registers for SD6 controller */ ++#define SDHCI_CDNS_SD6_PHY_DQ_TIMING 0x2000 ++#define SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON BIT(31) ++#define SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END GENMASK(29, 27) ++#define SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START GENMASK(26, 24) ++#define SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END GENMASK(2, 0) ++ ++#define SDHCI_CDNS_SD6_PHY_DQS_TIMING 0x2004 ++#define SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS BIT(22) ++#define SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS BIT(21) ++#define SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS BIT(20) ++#define SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD BIT(19) ++ ++#define SDHCI_CDNS_SD6_PHY_GATE_LPBK 0x2008 ++#define SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD BIT(31) ++#define SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT BIT(28) ++#define SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL GENMASK(24, 19) ++#define SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON BIT(6) ++ ++#define SDHCI_CDNS_SD6_PHY_DLL_MASTER 0x200C ++#define SDHCI_CDNS_SD6_PHY_DLL_MASTER_BYPASS_MODE BIT(23) ++#define SDHCI_CDNS_SD6_PHY_DLL_MASTER_PHASE_DETECT_SEL GENMASK(22, 20) ++#define SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_LOCK_NUM GENMASK(18, 16) ++#define SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_START_POINT GENMASK(7, 0) ++ ++#define SDHCI_CDNS_SD6_PHY_DLL_SLAVE 0x2010 ++#define SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_CMD_DELAY GENMASK(31, 24) ++#define SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WRDQS_DELAY GENMASK(23, 16) ++#define SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WR_DELAY GENMASK(15, 8) ++#define SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_DELAY GENMASK(7, 0) ++ ++#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0 0x201C ++#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK BIT(0) ++#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK_MODE GENMASK(2, 1) ++#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_UNLOCK_CNT GENMASK(7, 3) ++#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK_VALUE GENMASK(15, 8) ++ ++#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG1 0x2020 ++#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG2 0x2024 ++ ++#define SDHCI_CDNS_SD6_PHY_CTRL 0x2080 ++#define SDHCI_CDNS_SD6_PHY_CTRL_PHONY_DQS_TIMING GENMASK(9, 4) ++ ++#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0 0x2088 ++#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV GENMASK(6, 5) ++#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV_OVR_EN BIT(4) ++#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW GENMASK(2, 1) ++#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW_OVR_EN BIT(0) ++ ++/* Read block gap */ ++#define SDHCI_CDNS_HRS37 0x94 /* interface mode select */ ++#define SDHCI_CDNS_HRS37_MODE_DS 0x0 ++#define SDHCI_CDNS_HRS37_MODE_HS 0x1 ++#define SDHCI_CDNS_HRS37_MODE_UDS_SDR12 0x8 ++#define SDHCI_CDNS_HRS37_MODE_UDS_SDR25 0x9 ++#define SDHCI_CDNS_HRS37_MODE_UDS_SDR50 0xa ++#define SDHCI_CDNS_HRS37_MODE_UDS_SDR104 0xb ++#define SDHCI_CDNS_HRS37_MODE_UDS_DDR50 0xc ++#define SDHCI_CDNS_HRS37_MODE_MMC_LEGACY 0x20 ++#define SDHCI_CDNS_HRS37_MODE_MMC_SDR 0x21 ++#define SDHCI_CDNS_HRS37_MODE_MMC_DDR 0x22 ++#define SDHCI_CDNS_HRS37_MODE_MMC_HS200 0x23 ++#define SDHCI_CDNS_HRS37_MODE_MMC_HS400 0x24 ++#define SDHCI_CDNS_HRS37_MODE_MMC_HS400ES 0x25 ++#define SDHCI_CDNS_HRS38 0x98 /* Read block gap coefficient */ ++#define SDHCI_CDNS_HRS38_BLKGAP_MAX 0xf + + /* SRS - Slot Register Set (SDHCI-compatible) */ + #define SDHCI_CDNS_SRS_BASE 0x200 + +-/* PHY */ ++/* PHY registers for SD4 controller */ + #define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 + #define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 + #define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 +@@ -53,6 +189,8 @@ + #define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c + #define SDHCI_CDNS_PHY_DLY_STROBE 0x0d + ++#define CN10K_MSIX_INTR 0x718 ++ + /* + * The tuned val register is 6 bit-wide, but not the whole of the range is + * available. The range 0-42 seems to be available (then 43 wraps around to 0) +@@ -60,11 +198,24 @@ + */ + #define SDHCI_CDNS_MAX_TUNING_LOOP 40 + +-struct sdhci_cdns_phy_param { ++static int cn10k_irq_workaround; ++ ++static int tune_val_start = SDHCI_CDNS_TUNE_START; ++static int tune_val_step = SDHCI_CDNS_TUNE_STEP; ++static int max_tune_iter = SDHCI_CDNS_TUNE_ITERATIONS; ++ ++struct sdhci_cdns_priv; ++ ++struct sdhci_cdns_sd4_phy_param { + u8 addr; + u8 data; + }; + ++struct sdhci_cdns_data { ++ int (*phy_init)(struct sdhci_cdns_priv *priv); ++ int (*set_tune_val)(struct sdhci_host *host, unsigned int val); ++}; ++ + struct sdhci_cdns_priv { + void __iomem *hrs_addr; + void __iomem *ctl_addr; /* write control */ +@@ -72,11 +223,13 @@ struct sdhci_cdns_priv { + bool enhanced_strobe; + void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); + struct reset_control *rst_hw; ++ const struct sdhci_cdns_data *cdns_data; ++ void *phy; + unsigned int nr_phy_params; +- struct sdhci_cdns_phy_param phy_params[]; ++ struct sdhci_cdns_sd4_phy_param phy_params[]; + }; + +-struct sdhci_cdns_phy_cfg { ++struct sdhci_cdns_sd4_phy_cfg { + const char *property; + u8 addr; + }; +@@ -84,9 +237,10 @@ struct sdhci_cdns_phy_cfg { + struct sdhci_cdns_drv_data { + int (*init)(struct platform_device *pdev); + const struct sdhci_pltfm_data pltfm_data; ++ const struct sdhci_cdns_data *cdns_data; + }; + +-static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { ++static const struct sdhci_cdns_sd4_phy_cfg sdhci_cdns_sd4_phy_cfgs[] = { + { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, + { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, + { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, +@@ -100,125 +254,1724 @@ static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { + { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, + }; + +-static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val, +- void __iomem *reg) +-{ +- writel(val, reg); ++enum sdhci_cdns_sd6_phy_lock_mode { ++ SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK = 0, ++ SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK = 2, ++ SDHCI_CDNS_SD6_PHY_LOCK_MODE_SATURATION = 3, ++ SDHCI_CDNS_SD6_PHY_LOCK_MODE_UNITIALIZED = 0xff, ++}; ++ ++struct sdhci_cdns_sd6_phy_timings { ++ u32 t_cmd_output_min; ++ u32 t_cmd_output_max; ++ u32 t_dat_output_min; ++ u32 t_dat_output_max; ++ u32 t_cmd_input_min; ++ u32 t_cmd_input_max; ++ u32 t_dat_input_min; ++ u32 t_dat_input_max; ++ u32 t_sdclk_min; ++ u32 t_sdclk_max; ++}; ++ ++struct sdhci_cdns_sd6_phy_delays { ++ u32 phy_sdclk_delay; ++ u32 phy_cmd_o_delay; ++ u32 phy_dat_o_delay; ++ u32 iocell_input_delay; ++ u32 iocell_output_delay; ++ u32 delay_element_org; ++ u32 delay_element; ++}; ++ ++struct sdhci_cdns_sd6_phy_settings { ++ /* SDHCI_CDNS_SD6_PHY_DLL_SLAVE */ ++ u32 cp_read_dqs_cmd_delay; ++ u32 cp_read_dqs_delay; ++ u32 cp_clk_wr_delay; ++ u32 cp_clk_wrdqs_delay; ++ ++ /* SDHCI_CDNS_SD6_PHY_DLL_MASTER */ ++ u32 cp_dll_bypass_mode; ++ u32 cp_dll_start_point; ++ ++ /* SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0 */ ++ u32 cp_dll_locked_mode; ++ ++ /* SDHCI_CDNS_SD6_PHY_GATE_LPBK */ ++ u32 cp_gate_cfg_always_on; ++ u32 cp_sync_method; ++ u32 cp_rd_del_sel; ++ u32 cp_sw_half_cycle_shift; ++ u32 cp_underrun_suppress; ++ ++ /* SDHCI_CDNS_SD6_PHY_DQ_TIMING */ ++ u32 cp_io_mask_always_on; ++ u32 cp_io_mask_end; ++ u32 cp_io_mask_start; ++ u32 cp_data_select_oe_end; ++ ++ /* SDHCI_CDNS_SD6_PHY_DQS_TIMING */ ++ u32 cp_use_ext_lpbk_dqs; ++ u32 cp_use_lpbk_dqs; ++ u8 cp_use_phony_dqs; ++ u8 cp_use_phony_dqs_cmd; ++ ++ /* HRS 09 */ ++ u8 sdhc_extended_rd_mode; ++ u8 sdhc_extended_wr_mode; ++ u32 sdhc_rdcmd_en; ++ u32 sdhc_rddata_en; ++ ++ /* HRS10 */ ++ u32 sdhc_hcsdclkadj; ++ ++ /* HRS 07 */ ++ u32 sdhc_idelay_val; ++ u32 sdhc_rw_compensate; ++ ++ /* SRS 11 */ ++ u32 sdhc_sdcfsh; ++ u32 sdhc_sdcfsl; ++ ++ /* HRS 16 */ ++ u32 sdhc_wrcmd0_dly; ++ u32 sdhc_wrcmd0_sdclk_dly; ++ u32 sdhc_wrcmd1_dly; ++ u32 sdhc_wrcmd1_sdclk_dly; ++ u32 sdhc_wrdata0_dly; ++ u32 sdhc_wrdata0_sdclk_dly; ++ u32 sdhc_wrdata1_dly; ++ u32 sdhc_wrdata1_sdclk_dly; ++ ++ u32 hs200_tune_val; ++ u32 drive; ++ u32 slew; ++}; ++ ++struct sdhci_cdns_sd6_phy_intermediate_results { ++ u32 t_sdmclk_calc; ++ u32 dll_max_value; ++}; ++ ++struct sdhci_cdns_sd6_phy { ++ struct sdhci_cdns_sd6_phy_timings t; ++ struct sdhci_cdns_sd6_phy_delays d; ++ u32 t_sdmclk; ++ struct sdhci_cdns_sd6_phy_settings settings; ++ struct sdhci_cdns_sd6_phy_intermediate_results vars; ++ bool ddr; ++ bool tune_cmd; ++ bool tune_dat; ++ bool strobe_cmd; ++ bool strobe_dat; ++ int mode; ++ int t_sdclk; ++}; ++ ++static int sdhci_cdns_sd6_get_dll_parameters(struct sdhci_cdns_priv *priv, ++ enum sdhci_cdns_sd6_phy_lock_mode *lock_mode, ++ bool *locked, ++ u8 *dll_lock_value); ++ ++static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val, ++ void __iomem *reg) ++{ ++ writel(val, reg); ++} ++ ++static void init_hs(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 2000, .t_cmd_output_max = t_sdclk - 6000, ++ .t_dat_output_min = 2000, .t_dat_output_max = t_sdclk - 6000, ++ .t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 2500, ++ .t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 2500, ++ .t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_uhs_sdr12(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000, ++ .t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000, ++ .t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 1500, ++ .t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 1500, ++ .t_sdclk_min = 1000000 / 25, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_uhs_sdr25(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000, ++ .t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000, ++ .t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 1500, ++ .t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 1500, ++ .t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_uhs_sdr50(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000, ++ .t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000, ++ .t_cmd_input_min = 7500, .t_cmd_input_max = t_sdclk + 1500, ++ .t_dat_input_min = 7500, .t_dat_input_max = t_sdclk + 1500, ++ .t_sdclk_min = 1000000 / 100, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_uhs_sdr104(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400, ++ .t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 1400, ++ .t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000, ++ .t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000, ++ .t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100 ++ }; ++} ++ ++static void init_uhs_ddr50(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000, ++ .t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000, ++ .t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 1500, ++ .t_dat_input_min = 7000, .t_dat_input_max = t_sdclk + 1500, ++ .t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_emmc_legacy(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000, ++ .t_dat_output_min = 3000, .t_dat_output_max = t_sdclk - 3000, ++ .t_cmd_input_min = 11700, .t_cmd_input_max = t_sdclk + 8300, ++ .t_dat_input_min = 11700, .t_dat_input_max = t_sdclk + 8300, ++ .t_sdclk_min = 1000000 / 25, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_emmc_sdr(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000, ++ .t_dat_output_min = 3000, .t_dat_output_max = t_sdclk - 3000, ++ .t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 2500, ++ .t_dat_input_min = 13700, .t_dat_input_max = t_sdclk + 2500, ++ .t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_emmc_ddr(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000, ++ .t_dat_output_min = 2500, ++ .t_dat_output_max = (t_sdclk / 2) - 2500, ++ .t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 2500, ++ .t_dat_input_min = 7000, .t_dat_input_max = t_sdclk + 1500, ++ .t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4 ++ }; ++} ++ ++static void init_emmc_hs200(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400, ++ .t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 1400, ++ .t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000, ++ .t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000, ++ .t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100 ++ }; ++} ++ ++/* HS400 and HS400ES */ ++static void init_emmc_hs400(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk) ++{ ++ *t = (struct sdhci_cdns_sd6_phy_timings){ ++ .t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400, ++ .t_dat_output_min = 400, ++ .t_dat_output_max = (t_sdclk / 2) - 400, ++ .t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000, ++ .t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000, ++ .t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100 ++ }; ++} ++ ++static void (*init_timings[])(struct sdhci_cdns_sd6_phy_timings*, int) = { ++ &init_hs, &init_emmc_legacy, &init_emmc_sdr, ++ &init_emmc_ddr, &init_emmc_hs200, &init_emmc_hs400, ++ &init_uhs_sdr12, &init_uhs_sdr25, &init_uhs_sdr50, ++ &init_uhs_sdr104, &init_uhs_ddr50 ++}; ++ ++static u32 read_dqs_cmd_delay, clk_wrdqs_delay, clk_wr_delay, read_dqs_delay; ++static u32 read_dqs_cmd_delay_hs400, read_dqs_delay_hs400; ++static u32 clk_wrdqs_delay_hs400, clk_wr_delay_hs400; ++ ++static u32 sdhci_cdns_sd6_get_mode(struct sdhci_host *host, ++ unsigned int timing); ++ ++#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS ++static u32 sdhci_cdns_sd6_readl(struct sdhci_host *host, int reg) ++{ ++ return readl(host->ioaddr + reg); ++} ++ ++static void sdhci_cdns_sd6_writel(struct sdhci_host *host, u32 val, int reg) ++{ ++ writel(val, host->ioaddr + reg); ++} ++ ++static u16 sdhci_cdns_sd6_readw(struct sdhci_host *host, int reg) ++{ ++ u32 val, regoff; ++ ++ regoff = reg & ~3; ++ ++ val = readl(host->ioaddr + regoff); ++ if ((reg & 0x3) == 0) ++ return (val & 0xFFFF); ++ else ++ return ((val >> 16) & 0xFFFF); ++} ++ ++static void sdhci_cdns_sd6_writew(struct sdhci_host *host, u16 val, int reg) ++{ ++ writew(val, host->ioaddr + reg); ++} ++ ++static u8 sdhci_cdns_sd6_readb(struct sdhci_host *host, int reg) ++{ ++ u32 val, regoff; ++ ++ regoff = reg & ~3; ++ ++ val = readl(host->ioaddr + regoff); ++ switch (reg & 3) { ++ case 0: ++ return (val & 0xFF); ++ case 1: ++ return ((val >> 8) & 0xFF); ++ case 2: ++ return ((val >> 16) & 0xFF); ++ case 3: ++ return ((val >> 24) & 0xFF); ++ } ++ return 0; ++} ++ ++static void sdhci_cdns_sd6_writeb(struct sdhci_host *host, u8 val, int reg) ++{ ++ writeb(val, host->ioaddr + reg); ++} ++#endif ++ ++static void *sdhci_cdns_priv(struct sdhci_host *host) ++{ ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ ++ return sdhci_pltfm_priv(pltfm_host); ++} ++ ++static int sdhci_cdns_sd6_phy_lock_dll(struct sdhci_cdns_priv *priv) ++{ ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ u32 delay_element = phy->d.delay_element; ++ u32 delay_elements_in_sdmclk; ++ int ret; ++ enum sdhci_cdns_sd6_phy_lock_mode mode; ++ bool locked = false; ++ u8 dll_lock_value = 0xff; ++ ++ ret = sdhci_cdns_sd6_get_dll_parameters(priv, &mode, &locked, ++ &dll_lock_value); ++ if (ret) { ++ pr_err("%s: Could not get DLL parameters\n", __func__); ++ return ret; ++ } ++ ++ if (locked) { ++ /* ++ * If we have lock then we use units of 1/256 or 1/128 ++ * depending if we're in full or half clock mode. ++ * For now, saturation mode is treated like full clock mode ++ * though this may be incorrect. ++ */ ++ if (mode == SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK) { ++ DEBUG_DRV("%s: PHY DLL locked in full clock mode\n", ++ __func__); ++ delay_elements_in_sdmclk = 256; ++ phy->vars.dll_max_value = 255; ++ tune_val_step = SDHCI_CDNS_TUNE_STEP; ++ } else if (mode == SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK) { ++ DEBUG_DRV("%s: PHY DLL locked in half clock mode\n", ++ __func__); ++ delay_elements_in_sdmclk = 128; ++ phy->vars.dll_max_value = 127; ++ tune_val_step = SDHCI_CDNS_TUNE_STEP / 2; ++ } else { ++ DEBUG_DRV("%s: PHY DLL locked in saturation mode.\n", ++ __func__); ++ delay_elements_in_sdmclk = 256; ++ phy->vars.dll_max_value = 255; ++ tune_val_step = SDHCI_CDNS_TUNE_STEP; ++ } ++ /* No bypass if we're locked */ ++ phy->settings.cp_dll_bypass_mode = 0; ++ phy->vars.t_sdmclk_calc = phy->t_sdmclk; ++ DEBUG_DRV("%s: t_sdmclk_calc: %u\n", ++ __func__, phy->vars.t_sdmclk_calc); ++ } else { ++ /* ++ * Since we do not have a DLL lock we must use delay elements. ++ * The delay element count for a 250MHz clock period was ++ * determined earlier, so we calculate the delay in terms of ++ * the number of delay elements. ++ */ ++ DEBUG_DRV("%s: PHY DLL not locked, using delay element %u\n", ++ __func__, delay_element); ++ delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, ++ delay_element); ++ DEBUG_DRV("%s: delay elements in sdmclk: %u\n", ++ __func__, delay_elements_in_sdmclk); ++ if (delay_elements_in_sdmclk > 256) { ++ DEBUG_DRV("%s: delay elem in sdmclk %u > 256, delay elem %u\n", ++ __func__, delay_elements_in_sdmclk, ++ delay_element); ++ delay_element *= 2; ++ DEBUG_DRV("%s: doubling delay_element to %u\n", ++ __func__, delay_element); ++ delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, ++ delay_element); ++ ++ if (delay_elements_in_sdmclk > 256) { ++ DEBUG_DRV("%s: WARN sdmclk delay elems %u > 256; clamp it\n", ++ __func__, delay_elements_in_sdmclk); ++ delay_elements_in_sdmclk = 256; ++ } ++ mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK; ++ phy->vars.dll_max_value = 127; ++ DEBUG_DRV("%s: delay_element_in_sdmclk now %u\n", ++ __func__, delay_elements_in_sdmclk); ++ } else { ++ mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK; ++ phy->vars.dll_max_value = 255; ++ } ++ /* Bypass if no lock */ ++ phy->settings.cp_dll_bypass_mode = 1; ++ phy->vars.t_sdmclk_calc = delay_element * ++ delay_elements_in_sdmclk; ++ phy->d.delay_element = delay_element; ++ } ++ delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element); ++ if (delay_elements_in_sdmclk > 256) { ++ delay_element *= 2; ++ delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, ++ delay_element); ++ ++ if (delay_elements_in_sdmclk > 256) ++ return -1; ++ ++ mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK; ++ phy->vars.dll_max_value = 127; ++ } else { ++ mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK; ++ phy->vars.dll_max_value = 255; ++ } ++ ++ phy->vars.t_sdmclk_calc = delay_element * delay_elements_in_sdmclk; ++ phy->d.delay_element = delay_element; ++ phy->settings.cp_dll_locked_mode = mode; ++ phy->settings.cp_dll_bypass_mode = 0; ++ ++ return 0; ++} ++ ++static void sdhci_cdns_sd6_phy_dll_bypass(struct sdhci_cdns_sd6_phy *phy) ++{ ++ phy->vars.dll_max_value = 256; ++ phy->settings.cp_dll_bypass_mode = 1; ++ phy->settings.cp_dll_locked_mode = ++ SDHCI_CDNS_SD6_PHY_LOCK_MODE_SATURATION; ++} ++ ++static void sdhci_cdns_sd6_phy_configure_dll(struct sdhci_cdns_priv *priv) ++{ ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ ++ if (phy->settings.sdhc_extended_wr_mode == 0) { ++ if (sdhci_cdns_sd6_phy_lock_dll(priv) == 0) ++ return; ++ } ++ sdhci_cdns_sd6_phy_dll_bypass(phy); ++} ++ ++static void sdhci_cdns_sd6_phy_calc_out(struct sdhci_cdns_sd6_phy *phy, ++ bool cmd_not_dat) ++{ ++ u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay, ++ clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0; ++ bool data_ddr = phy->ddr && !cmd_not_dat; ++ int t; ++ ++ if (cmd_not_dat) { ++ output_min = phy->t.t_cmd_output_min; ++ output_max = phy->t.t_cmd_output_max; ++ phy_o_delay = phy->d.phy_cmd_o_delay; ++ } else { ++ output_min = phy->t.t_dat_output_min; ++ output_max = phy->t.t_dat_output_max; ++ phy_o_delay = phy->d.phy_dat_o_delay; ++ } ++ ++ clk_wr_delay = 0; ++ if (data_ddr) { ++ wr0_sdclk_dly = 1; ++ wr1_sdclk_dly = 1; ++ } ++ ++ t = phy_o_delay - phy->d.phy_sdclk_delay - output_min; ++ if (t < 0 && phy->settings.sdhc_extended_wr_mode == 1) { ++ u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk); ++ ++ wr0_dly = (n_half_cycle + 1) / 2; ++ if (data_ddr) ++ wr1_dly = (n_half_cycle + 1) / 2; ++ else ++ wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1; ++ } ++ ++ if (phy->settings.sdhc_extended_wr_mode == 0) { ++ u32 out_hold, out_setup, out_hold_margin; ++ u32 n; ++ ++ if (!data_ddr) ++ wr0_dly = 1; ++ ++ out_setup = output_max; ++ out_hold = output_min; ++ out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4); ++ out_hold += out_hold_margin; ++ ++ if (phy->settings.cp_dll_bypass_mode == 0) ++ n = DIV_ROUND_UP(256 * out_hold, ++ phy->vars.t_sdmclk_calc); ++ else ++ n = DIV_ROUND_UP(out_hold, phy->d.delay_element) - 1; ++ ++ if (n <= phy->vars.dll_max_value) ++ clk_wr_delay = n; ++ else ++ clk_wr_delay = 255; ++ } else { ++ /* sdhc_extended_wr_mode = 1 - PHY IO cell work in SDR mode */ ++ clk_wr_delay = 0; ++ } ++ ++ if (cmd_not_dat) { ++ phy->settings.sdhc_wrcmd0_dly = wr0_dly; ++ phy->settings.sdhc_wrcmd1_dly = wr1_dly; ++ phy->settings.cp_clk_wrdqs_delay = clk_wr_delay; ++ phy->settings.sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly; ++ phy->settings.sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly; ++ } else { ++ phy->settings.sdhc_wrdata0_dly = wr0_dly; ++ phy->settings.sdhc_wrdata1_dly = wr1_dly; ++ phy->settings.cp_clk_wr_delay = clk_wr_delay; ++ phy->settings.sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly; ++ phy->settings.sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly; ++ } ++ ++ if (phy->mode == MMC_TIMING_MMC_HS400 && cmd_not_dat) { ++ phy->settings.cp_clk_wrdqs_delay = ++ clk_wrdqs_delay_hs400 ? clk_wrdqs_delay_hs400 : ++ clk_wr_delay; ++ DEBUG_DRV("%s:cp_clk_wrdqs_delay %d clk_wrdqs_delay_hs400 %d\n", ++ __func__, phy->settings.cp_clk_wrdqs_delay, ++ clk_wrdqs_delay_hs400); ++ } else if (phy->mode == MMC_TIMING_MMC_HS400 && !cmd_not_dat) { ++ phy->settings.cp_clk_wr_delay = ++ clk_wr_delay_hs400 ? clk_wr_delay_hs400 : clk_wr_delay; ++ DEBUG_DRV("%s:cp_clk_wr_delay %d clk_wr_delay_hs400 %d\n", ++ __func__, phy->settings.cp_clk_wr_delay, ++ clk_wr_delay_hs400); ++ } ++} ++ ++static void sdhci_cdns_sd6_phy_calc_cmd_out(struct sdhci_cdns_sd6_phy *phy) ++{ ++ sdhci_cdns_sd6_phy_calc_out(phy, true); ++} ++ ++static void sdhci_cdns_sd6_phy_calc_cmd_in(struct sdhci_cdns_sd6_phy *phy) ++{ ++ phy->settings.cp_io_mask_end = ++ ((phy->d.iocell_output_delay + phy->d.iocell_input_delay) * 2) ++ / phy->t_sdmclk; ++ ++ if (phy->settings.cp_io_mask_end >= 8) ++ phy->settings.cp_io_mask_end = 7; ++ ++ if (phy->strobe_cmd && phy->settings.cp_io_mask_end > 0) ++ phy->settings.cp_io_mask_end--; ++ ++ if (phy->strobe_cmd) { ++ phy->settings.cp_use_phony_dqs_cmd = 0; ++ phy->settings.cp_read_dqs_cmd_delay = read_dqs_cmd_delay_hs400; ++ } else { ++ phy->settings.cp_use_phony_dqs_cmd = 1; ++ phy->settings.cp_read_dqs_cmd_delay = 0; ++ } ++ ++ if ((phy->mode == MMC_TIMING_MMC_HS400 && !phy->strobe_cmd) || ++ phy->mode == MMC_TIMING_MMC_HS200) ++ phy->settings.cp_read_dqs_cmd_delay = ++ phy->settings.hs200_tune_val; ++} ++ ++static void sdhci_cdns_sd6_phy_calc_dat_in(struct sdhci_cdns_sd6_phy *phy) ++{ ++ u32 hcsdclkadj = 0; ++ ++ if (phy->strobe_dat) { ++ phy->settings.cp_use_phony_dqs = 0; ++ phy->settings.cp_read_dqs_delay = read_dqs_delay_hs400; ++ } else { ++ phy->settings.cp_use_phony_dqs = 1; ++ phy->settings.cp_read_dqs_delay = 0; ++ } ++ ++ if (phy->mode == MMC_TIMING_MMC_HS200) ++ phy->settings.cp_read_dqs_delay = ++ phy->settings.hs200_tune_val; ++ ++ if (phy->strobe_dat) { ++ /* dqs loopback input via IO cell */ ++ hcsdclkadj += phy->d.iocell_input_delay; ++ /* ++ * dfi_dqs_in: mem_dqs -> clean_dqs_mod; ++ * hic_dll_dqs_nand2 delay ++ */ ++ hcsdclkadj += phy->d.delay_element / 2; ++ /* delay line */ ++ hcsdclkadj += phy->t_sdclk / 2; ++ /* PHY FIFO write pointer */ ++ hcsdclkadj += phy->t_sdclk / 2 + phy->d.delay_element; ++ /* 1st synchronizer */ ++ hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk) ++ * phy->t_sdmclk - hcsdclkadj; ++ /* ++ * 2nd synchronizer + PHY FIFO read pointer + PHY rddata ++ * + PHY rddata registered, + FIFO 1st ciu_en ++ */ ++ hcsdclkadj += 5 * phy->t_sdmclk; ++ /* FIFO 2st ciu_en */ ++ hcsdclkadj += phy->t_sdclk; ++ ++ hcsdclkadj /= phy->t_sdclk; ++ } else { ++ u32 n; ++ ++ /* rebar PHY delay */ ++ hcsdclkadj += 2 * phy->t_sdmclk; ++ /* rebar output via IO cell */ ++ hcsdclkadj += phy->d.iocell_output_delay; ++ /* dqs loopback input via IO cell */ ++ hcsdclkadj += phy->d.iocell_input_delay; ++ /* ++ * dfi_dqs_in: mem_dqs -> clean_dqs_mod, ++ * hic_dll_dqs_nand2 delay ++ */ ++ hcsdclkadj += phy->d.delay_element / 2; ++ /* dll: one delay element between SIGI_0 and SIGO_0 */ ++ hcsdclkadj += phy->d.delay_element; ++ /* ++ * dfi_dqs_in: mem_dqs_delayed -> clk_dqs, ++ * hic_dll_dqs_nand2 delay ++ */ ++ hcsdclkadj += phy->d.delay_element / 2; ++ /* deskew DLL: clk_dqs -> clk_dqN: one delay element */ ++ hcsdclkadj += phy->d.delay_element; ++ ++ if (phy->t_sdclk == phy->t_sdmclk) ++ n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk; ++ else ++ n = hcsdclkadj / phy->t_sdclk; ++ ++ /* phase shift in one t_sdclk from rebar/lbk dqs delay */ ++ hcsdclkadj = hcsdclkadj % phy->t_sdclk; ++ /* PHY FIFO write pointer */ ++ hcsdclkadj += phy->t_sdclk / 2; ++ /* 1st synchronizer */ ++ hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk) ++ * phy->t_sdmclk - hcsdclkadj; ++ /* ++ * 2nd synchronizer + PHY FIFO read pointer + PHY rddata ++ * + PHY rddata registered ++ */ ++ hcsdclkadj += 4 * phy->t_sdmclk; ++ ++ if ((phy->t_sdclk / phy->t_sdmclk) > 1) { ++ u32 tmp1, tmp2; ++ ++ tmp1 = hcsdclkadj; ++ tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk ++ + phy->t_sdclk - phy->t_sdmclk; ++ if (tmp1 == tmp2) ++ tmp2 += phy->t_sdclk; ++ ++ /* FIFO aligns to clock cycle before ciu_en */ ++ hcsdclkadj += tmp2 - tmp1; ++ } ++ ++ /* FIFO 1st ciu_en */ ++ hcsdclkadj += phy->t_sdmclk; ++ /* FIFO 2nd ciu_en */ ++ hcsdclkadj += phy->t_sdclk; ++ ++ hcsdclkadj /= phy->t_sdclk; ++ ++ hcsdclkadj += n; ++ ++ if ((phy->t_sdclk / phy->t_sdmclk) >= 2) { ++ if (phy->mode == MMC_TIMING_UHS_DDR50 || ++ phy->mode == MMC_TIMING_MMC_DDR52) ++ hcsdclkadj -= 2; ++ else ++ hcsdclkadj -= 1; ++ } else if ((phy->t_sdclk / phy->t_sdmclk) == 1) { ++ hcsdclkadj += 2; ++ } ++ ++ if (phy->tune_dat) ++ hcsdclkadj -= 1; ++ } ++ ++ if (hcsdclkadj > 15) ++ hcsdclkadj = 15; ++ ++ phy->settings.sdhc_hcsdclkadj = hcsdclkadj; ++} ++ ++static void sdhci_cdns_sd6_phy_calc_dat_out(struct sdhci_cdns_sd6_phy *phy) ++{ ++ sdhci_cdns_sd6_phy_calc_out(phy, false); ++} ++ ++static void sdhci_cdns_sd6_phy_calc_io(struct sdhci_cdns_sd6_phy *phy) ++{ ++ u32 rw_compensate; ++ ++ rw_compensate = (phy->d.iocell_input_delay + phy->d.iocell_output_delay) ++ / phy->t_sdmclk + phy->settings.sdhc_wrdata0_dly + 5 + 3; ++ ++ phy->settings.sdhc_idelay_val = (2 * phy->d.iocell_input_delay) ++ / phy->t_sdmclk; ++ ++ phy->settings.cp_io_mask_start = 0; ++ if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10) ++ phy->settings.cp_io_mask_start = 2 * (rw_compensate - 10); ++ ++ if (phy->mode == MMC_TIMING_UHS_SDR104) ++ phy->settings.cp_io_mask_start++; ++ ++ if (phy->t_sdclk == phy->t_sdmclk && phy->mode == MMC_TIMING_UHS_SDR50) ++ phy->settings.cp_io_mask_start++; ++ ++ phy->settings.sdhc_rw_compensate = rw_compensate; ++} ++ ++static void sdhci_cdns_sd6_phy_calc_settings(struct sdhci_cdns_sd6_phy *phy) ++{ ++ sdhci_cdns_sd6_phy_calc_cmd_out(phy); ++ sdhci_cdns_sd6_phy_calc_cmd_in(phy); ++ sdhci_cdns_sd6_phy_calc_dat_out(phy); ++ sdhci_cdns_sd6_phy_calc_dat_in(phy); ++ sdhci_cdns_sd6_phy_calc_io(phy); ++} ++ ++static int sdhci_cdns_sd4_write_phy_reg(struct sdhci_cdns_priv *priv, ++ u8 addr, u8 data) ++{ ++ void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; ++ u32 tmp; ++ int ret; ++ ++ ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_SD4_HRS04_ACK), ++ 0, 10); ++ if (ret) ++ return ret; ++ ++ tmp = FIELD_PREP(SDHCI_CDNS_SD4_HRS04_WDATA, data) | ++ FIELD_PREP(SDHCI_CDNS_SD4_HRS04_ADDR, addr); ++ priv->priv_writel(priv, tmp, reg); ++ ++ tmp |= SDHCI_CDNS_SD4_HRS04_WR; ++ priv->priv_writel(priv, tmp, reg); ++ ++ ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_SD4_HRS04_ACK, ++ 0, 10); ++ if (ret) ++ return ret; ++ ++ tmp &= ~SDHCI_CDNS_SD4_HRS04_WR; ++ priv->priv_writel(priv, tmp, reg); ++ ++ ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_SD4_HRS04_ACK), ++ 0, 10); ++ ++ return ret; ++} ++ ++static unsigned int sdhci_cdns_sd4_phy_param_count(struct device *dev) ++{ ++ unsigned int count = 0; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(sdhci_cdns_sd4_phy_cfgs); i++) ++ if (device_property_read_bool(dev, ++ sdhci_cdns_sd4_phy_cfgs[i].property)) ++ count++; ++ ++ return count; ++} ++ ++static void sdhci_cdns_sd4_phy_param_parse(struct device *dev, ++ struct sdhci_cdns_priv *priv) ++{ ++ struct sdhci_cdns_sd4_phy_param *p = priv->phy_params; ++ u32 val; ++ int ret, i; ++ ++ for (i = 0; i < ARRAY_SIZE(sdhci_cdns_sd4_phy_cfgs); i++) { ++ ret = device_property_read_u32(dev, ++ sdhci_cdns_sd4_phy_cfgs[i].property, ++ &val); ++ if (ret) ++ continue; ++ ++ p->addr = sdhci_cdns_sd4_phy_cfgs[i].addr; ++ p->data = val; ++ p++; ++ } ++} ++ ++static int sdhci_cdns_sd4_phy_init(struct sdhci_cdns_priv *priv) ++{ ++ int ret, i; ++ ++ for (i = 0; i < priv->nr_phy_params; i++) { ++ ret = sdhci_cdns_sd4_write_phy_reg(priv, ++ priv->phy_params[i].addr, ++ priv->phy_params[i].data); ++ if (ret) ++ return ret; ++ } ++ return 0; ++} ++ ++static u32 sdhci_cdns_sd6_read_phy_reg(struct sdhci_cdns_priv *priv, ++ u32 addr) ++{ ++ writel(FIELD_PREP(SDHCI_CDNS_SD6_HRS04_ADDR, addr), ++ priv->hrs_addr + SDHCI_CDNS_HRS04); ++ return readl(priv->hrs_addr + SDHCI_CDNS_HRS05); ++} ++ ++static void sdhci_cdns_sd6_write_phy_reg(struct sdhci_cdns_priv *priv, ++ u32 addr, u32 data) ++{ ++ writel(FIELD_PREP(SDHCI_CDNS_SD6_HRS04_ADDR, addr), ++ priv->hrs_addr + SDHCI_CDNS_HRS04); ++ writel(data, priv->hrs_addr + SDHCI_CDNS_HRS05); ++} ++ ++static int sdhci_cdns_sd6_dll_reset(struct sdhci_cdns_priv *priv, bool reset) ++{ ++ u32 reg; ++ int ret = 0; ++ ++ reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09); ++ if (reset) ++ reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET; ++ else ++ reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET; ++ ++ writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09); ++ ++ if (!reset) ++ ret = readl_poll_timeout(priv->hrs_addr + SDHCI_CDNS_HRS09, ++ reg, ++ (reg & ++ SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE), ++ 0, 0); ++ ++ return ret; ++} ++ ++static void sdhci_cdns_sd6_calc_phy(struct sdhci_cdns_sd6_phy *phy) ++{ ++ if (phy->mode == MMC_TIMING_MMC_HS) { ++ phy->settings.cp_clk_wr_delay = 0; ++ phy->settings.cp_clk_wrdqs_delay = 0; ++ phy->settings.cp_data_select_oe_end = 1; ++ phy->settings.cp_dll_bypass_mode = 1; ++ phy->settings.cp_dll_locked_mode = 3; ++ phy->settings.cp_dll_start_point = 4; ++ phy->settings.cp_gate_cfg_always_on = 1; ++ phy->settings.cp_io_mask_always_on = 0; ++ phy->settings.cp_io_mask_end = 0; ++ phy->settings.cp_io_mask_start = 0; ++ phy->settings.cp_rd_del_sel = 52; ++ phy->settings.cp_read_dqs_cmd_delay = 0; ++ phy->settings.cp_read_dqs_delay = 0; ++ phy->settings.cp_sw_half_cycle_shift = 0; ++ phy->settings.cp_sync_method = 1; ++ phy->settings.cp_underrun_suppress = 1; ++ phy->settings.cp_use_ext_lpbk_dqs = 1; ++ phy->settings.cp_use_lpbk_dqs = 1; ++ phy->settings.cp_use_phony_dqs = 1; ++ phy->settings.cp_use_phony_dqs_cmd = 1; ++ phy->settings.sdhc_extended_rd_mode = 1; ++ phy->settings.sdhc_extended_wr_mode = 1; ++ phy->settings.sdhc_hcsdclkadj = 2; ++ phy->settings.sdhc_idelay_val = 0; ++ phy->settings.sdhc_rdcmd_en = 1; ++ phy->settings.sdhc_rddata_en = 1; ++ phy->settings.sdhc_rw_compensate = 9; ++ phy->settings.sdhc_sdcfsh = 0; ++ phy->settings.sdhc_sdcfsl = 4; ++ phy->settings.sdhc_wrcmd0_dly = 1; ++ phy->settings.sdhc_wrcmd0_sdclk_dly = 0; ++ phy->settings.sdhc_wrcmd1_dly = 0; ++ phy->settings.sdhc_wrcmd1_sdclk_dly = 0; ++ phy->settings.sdhc_wrdata0_dly = 1; ++ phy->settings.sdhc_wrdata0_sdclk_dly = 0; ++ phy->settings.sdhc_wrdata1_dly = 0; ++ phy->settings.sdhc_wrdata1_sdclk_dly = 0; ++ } ++} ++ ++#ifdef CONFIG_MMC_SDHCI_CADENCE_DEBUG ++static void sdhci_cdns_sd6_phy_dump(struct sdhci_cdns_sd6_phy *phy) ++{ ++ DEBUG_DRV("PHY Timings\n"); ++ DEBUG_DRV("mode %d t_sdclk %d\n", phy->mode, phy->t_sdclk); ++ ++ DEBUG_DRV("cp_clk_wr_delay %d\n", phy->settings.cp_clk_wr_delay); ++ DEBUG_DRV("cp_clk_wrdqs_delay %d\n", ++ phy->settings.cp_clk_wrdqs_delay); ++ DEBUG_DRV("cp_data_select_oe_end %d\n", ++ phy->settings.cp_data_select_oe_end); ++ DEBUG_DRV("cp_dll_bypass_mode %d\n", ++ phy->settings.cp_dll_bypass_mode); ++ DEBUG_DRV("cp_dll_locked_mode %d\n", ++ phy->settings.cp_dll_locked_mode); ++ DEBUG_DRV("cp_dll_start_point %d\n", ++ phy->settings.cp_dll_start_point); ++ DEBUG_DRV("cp_io_mask_always_on %d\n", ++ phy->settings.cp_io_mask_always_on); ++ DEBUG_DRV("cp_io_mask_end %d\n", phy->settings.cp_io_mask_end); ++ DEBUG_DRV("cp_io_mask_start %d\n", phy->settings.cp_io_mask_start); ++ DEBUG_DRV("cp_rd_del_sel %d\n", phy->settings.cp_rd_del_sel); ++ DEBUG_DRV("cp_read_dqs_cmd_delay %d\n", ++ phy->settings.cp_read_dqs_cmd_delay); ++ DEBUG_DRV("cp_read_dqs_delay %d\n", ++ phy->settings.cp_read_dqs_delay); ++ DEBUG_DRV("cp_sw_half_cycle_shift %d\n", ++ phy->settings.cp_sw_half_cycle_shift); ++ DEBUG_DRV("cp_sync_method %d\n", phy->settings.cp_sync_method); ++ DEBUG_DRV("cp_use_ext_lpbk_dqs %d\n", ++ phy->settings.cp_use_ext_lpbk_dqs); ++ DEBUG_DRV("cp_use_lpbk_dqs %d\n", phy->settings.cp_use_lpbk_dqs); ++ DEBUG_DRV("cp_use_phony_dqs %d\n", phy->settings.cp_use_phony_dqs); ++ DEBUG_DRV("cp_use_phony_dqs_cmd %d\n", ++ phy->settings.cp_use_phony_dqs_cmd); ++ DEBUG_DRV("sdhc_extended_rd_mode %d\n", ++ phy->settings.sdhc_extended_rd_mode); ++ DEBUG_DRV("sdhc_extended_wr_mode %d\n", ++ phy->settings.sdhc_extended_wr_mode); ++ ++ DEBUG_DRV("sdhc_hcsdclkadj %d\n", phy->settings.sdhc_hcsdclkadj); ++ DEBUG_DRV("sdhc_idelay_val %d\n", phy->settings.sdhc_idelay_val); ++ DEBUG_DRV("sdhc_rdcmd_en %d\n", phy->settings.sdhc_rdcmd_en); ++ DEBUG_DRV("sdhc_rddata_en %d\n", phy->settings.sdhc_rddata_en); ++ DEBUG_DRV("sdhc_rw_compensate %d\n", phy->settings.sdhc_rw_compensate); ++ DEBUG_DRV("sdhc_sdcfsh %d\n", phy->settings.sdhc_sdcfsh); ++ DEBUG_DRV("sdhc_sdcfsl %d\n", phy->settings.sdhc_sdcfsl); ++ DEBUG_DRV("sdhc_wrcmd0_dly %d %d\n", phy->settings.sdhc_wrcmd0_dly, ++ phy->settings.sdhc_wrcmd0_sdclk_dly); ++ DEBUG_DRV("sdhc_wrcmd1_dly %d %d\n", phy->settings.sdhc_wrcmd1_dly, ++ phy->settings.sdhc_wrcmd1_sdclk_dly); ++ DEBUG_DRV("sdhc_wrdata0_dly %d %d\n", phy->settings.sdhc_wrdata0_dly, ++ phy->settings.sdhc_wrdata0_sdclk_dly); ++ DEBUG_DRV("sdhc_wrdata1_dly %d %d\n", phy->settings.sdhc_wrdata1_dly, ++ phy->settings.sdhc_wrdata1_sdclk_dly); ++ DEBUG_DRV("hs200_tune_val %d\n", phy->settings.hs200_tune_val); ++} ++ ++void sdhci_cdns_sd6_dump(struct sdhci_cdns_priv *priv) ++{ ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ int id; ++ ++ sdhci_cdns_sd6_phy_dump(phy); ++ ++ DEBUG_DRV("Host controller Register Dump\n"); ++ for (id = 0; id < 14; id++) ++ DEBUG_DRV("HRS%d 0x%x\n", id, readl(priv->hrs_addr + (id * 4))); ++ ++ id = 29; ++ DEBUG_DRV("HRS%d 0x%x\n", id, readl(priv->hrs_addr + (id * 4))); ++ id = 30; ++ DEBUG_DRV("HRS%d 0x%x\n", id, readl(priv->hrs_addr + (id * 4))); ++ ++ for (id = 0; id < 27; id++) ++ DEBUG_DRV("SRS%d 0x%x\n", id, ++ readl(priv->hrs_addr + 0x200 + (id * 4))); ++ ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_DQS_TIMING 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQS_TIMING)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_GATE_LPBK 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_GATE_LPBK)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_DLL_MASTER 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_MASTER)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_DLL_SLAVE 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_SLAVE)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_CTRL 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_CTRL)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_GPIO_CTRL0 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_DQ_TIMING 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQ_TIMING)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_DLL_OBS_REG1 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_OBS_REG1)); ++ DEBUG_DRV("SDHCI_CDNS_SD6_PHY_DLL_OBS_REG2 0x%x\n", ++ sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_OBS_REG2)); ++} ++#endif ++ ++static int sdhci_cdns_sd6_get_dll_parameters(struct sdhci_cdns_priv *priv, ++ enum sdhci_cdns_sd6_phy_lock_mode *lock_mode, ++ bool *locked, ++ u8 *dll_lock_value) ++{ ++ int timeout_value = 1000; ++ int reg; ++ u8 tmp_lock_value = 0; ++ ++ *dll_lock_value = 0xff; ++ ++ do { ++ reg = sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0); ++ *locked = !!FIELD_GET(SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK, reg); ++ *lock_mode = FIELD_GET(SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK_MODE, reg); ++ tmp_lock_value = FIELD_GET(SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK_VALUE, reg); ++ ++ if (*dll_lock_value != tmp_lock_value) ++ *dll_lock_value = tmp_lock_value; ++ else ++ return 0; ++ } while (timeout_value-- > 0); ++ ++ return -1; ++} ++ ++/* ++ * Determine the number of delay elements to calculate timing delays when ++ * the DLL is not able to be locked. The DLL should always lock at ++ * 250MHz so we count the number of delay elements within the clock ++ * period. If for some reason there is no lock then we return a default ++ * value. ++ * ++ * The delay element value should only be used when cp_dll_bypass_mode=1, ++ * otherwise units of 1/256 clock period are used. ++ */ ++static int sdhci_cdns_sd6_get_delay_element(struct sdhci_host *host) ++{ ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ int timeout_counter = 1000; ++ u32 delay_element = 0xff; ++ enum sdhci_cdns_sd6_phy_lock_mode lock_mode = ++ SDHCI_CDNS_SD6_PHY_LOCK_MODE_UNITIALIZED; ++ bool locked = false; ++ u32 reg = 0; ++ u8 dll_lock_value = 0xff; ++ ++ phy->settings.cp_dll_bypass_mode = 0; ++ ++ /* Setup HS400 @250MHz regs; sample DLL lock value */ ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQS_TIMING, ++ 0x780000); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_GATE_LPBK, ++ 0x81a00040); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_MASTER, ++ 0x200004); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_SLAVE, ++ 0x404000); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_CTRL, 0x4000); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQ_TIMING, 0x1); ++ ++ /* turn on internal clock */ ++ reg = sdhci_cdns_sd6_readw(host, SDHCI_CLOCK_CONTROL); ++ reg |= SDHCI_CLOCK_INT_EN; ++ sdhci_cdns_sd6_writew(host, reg, SDHCI_CLOCK_CONTROL); ++ /* wait for it to stabilize */ ++ do { ++ reg = sdhci_cdns_sd6_readw(host, SDHCI_CLOCK_CONTROL); ++ udelay(1); ++ } while (!(reg & SDHCI_CLOCK_INT_STABLE) && (timeout_counter-- > 0)); ++ ++ if (!timeout_counter) { /* should never happen */ ++ pr_err("SDHCI internal clock unstable\n"); ++ return -1; ++ } ++ reg = sdhci_cdns_sd6_readw(host, SDHCI_CLOCK_CONTROL); ++ reg &= ~0xffc0; ++ reg |= 0x100; /* Set SDCFSL clock divider to 1 */ ++ sdhci_cdns_sd6_writew(host, reg, SDHCI_CLOCK_CONTROL); ++ ++ /* 8-bit burst, 4 transfers */ ++ writel(0x30004, priv->hrs_addr + SDHCI_CDNS_HRS02); ++ /* HS400 mode */ ++ writel(SDHCI_CDNS_HRS06_MODE_MMC_HS400, ++ priv->hrs_addr + SDHCI_CDNS_HRS06); ++ ++ /* Reset PHY */ ++ writel(0xf1c00003, priv->hrs_addr + SDHCI_CDNS_HRS09); ++ /* Wait for init to complete */ ++ do { ++ reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09); ++ } while (!(reg & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE)); ++ ++ mdelay(1); ++ if (sdhci_cdns_sd6_get_dll_parameters(priv, &lock_mode, &locked, ++ &dll_lock_value)) { ++ pr_err("%s: Error getting DLL parameters\n", __func__); ++ return -1; ++ } ++ ++ if (locked) { ++ if (lock_mode == SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK) { ++ delay_element = DIV_ROUND_UP(4000, dll_lock_value + 1); ++ tune_val_step = SDHCI_CDNS_TUNE_STEP; ++ } else if (lock_mode == SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK) { ++ delay_element = DIV_ROUND_UP(2000, dll_lock_value + 1); ++ tune_val_step = SDHCI_CDNS_TUNE_STEP / 2; ++ } else { ++ delay_element = SDHCI_CDNS_SD6_DEFAULT_DELAY_ELEMENT; ++ tune_val_step = SDHCI_CDNS_TUNE_STEP; ++ phy->settings.cp_dll_bypass_mode = 1; ++ } ++ } else { ++ delay_element = SDHCI_CDNS_SD6_DEFAULT_DELAY_ELEMENT; ++ tune_val_step = SDHCI_CDNS_TUNE_STEP; ++ phy->settings.cp_dll_bypass_mode = 1; ++ } ++ return delay_element; ++} ++ ++static int sdhci_cdns_sd6_get_delay_params(struct device *dev, ++ struct sdhci_cdns_priv *priv) ++{ ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ int ret; ++ ++ ret = device_property_read_u32(dev, "cdns,read_dqs_cmd_delay", ++ &phy->settings.cp_read_dqs_cmd_delay); ++ if (ret) ++ phy->settings.cp_read_dqs_cmd_delay = DEFAULT_CMD_DELAY; ++ ++ ret = device_property_read_u32(dev, "cdns,read_dqs_delay", ++ &phy->settings.cp_read_dqs_delay); ++ if (ret) ++ phy->settings.cp_read_dqs_delay = DEFAULT_CMD_DELAY; ++ ++ ret = device_property_read_u32(dev, "cdns,read_dqs_cmd_delay_hs400", ++ &read_dqs_cmd_delay_hs400); ++ DEBUG_DRV("read_dqs_cmd_delay_hs400 %d\n", read_dqs_cmd_delay_hs400); ++ if (ret) ++ read_dqs_cmd_delay_hs400 = DEFAULT_READ_DQS_CMD_DELAY; ++ ++ ret = device_property_read_u32(dev, "cdns,clk_wrdqs_delay_hs400", ++ &clk_wrdqs_delay_hs400); ++ DEBUG_DRV("clk_wrdqs_delay_hs400 %d\n", clk_wrdqs_delay_hs400); ++ if (ret) ++ clk_wrdqs_delay_hs400 = DEFAULT_CLK_WRDQS_DELAY; ++ ++ ret = device_property_read_u32(dev, "cdns,clk_wr_delay_hs400", ++ &clk_wr_delay_hs400); ++ DEBUG_DRV("clk_wr_delay_hs400 %d\n", clk_wr_delay_hs400); ++ if (ret) ++ clk_wr_delay_hs400 = DEFAULT_CLK_WR_DELAY; ++ ++ ret = device_property_read_u32(dev, "cdns,read_dqs_delay_hs400", ++ &read_dqs_delay_hs400); ++ DEBUG_DRV("read_dqs_delay_hs400 %d\n", read_dqs_delay_hs400); ++ if (ret) ++ read_dqs_delay_hs400 = DEFAULT_READ_DQS_DELAY; ++ ++ ret = device_property_read_u32(dev, "cdns,tune_val_start", ++ &tune_val_start); ++ if (ret) ++ tune_val_start = SDHCI_CDNS_TUNE_START; ++ ++ ret = device_property_read_u32(dev, "cdns,tune_val_step", ++ &tune_val_step); ++ if (ret) ++ tune_val_step = SDHCI_CDNS_TUNE_STEP; ++ ++ ret = device_property_read_u32(dev, "cdns,max_tune_iter", ++ &max_tune_iter); ++ if (ret) ++ max_tune_iter = SDHCI_CDNS_TUNE_ITERATIONS; ++ ++ /* ++ * Device tree contains values for the tuning parameters ++ * Per JESD84-B51, a valid 8-bit tune range satisfies: ++ * 'tune_start_value + (tune_step * tune_iterations) <= 256' ++ * If the DT-populated values cross 256, then these values may be ++ * re-adjusted to default values, accordingly. ++ */ ++ pr_info("%s: Get Tuning params: Start[%d], Step[%d], Iterations[%d].\n", ++ __func__, tune_val_start, tune_val_step, max_tune_iter); ++ if (WARN_ON((tune_val_start + (max_tune_iter * tune_val_step)) > ++ SDHCI_CDNS_TUNE_MAX_VALUE)) { ++ tune_val_start = SDHCI_CDNS_TUNE_START; ++ tune_val_step = SDHCI_CDNS_TUNE_STEP; ++ max_tune_iter = SDHCI_CDNS_TUNE_ITERATIONS; ++ pr_info("%s: EMMC tuning params out-of-range; check device tree\n", ++ __func__); ++ } ++ pr_info("%s: Selected Tuning params: Start[%d], Step[%d], Iterations[%d].\n", ++ __func__, tune_val_start, tune_val_step, max_tune_iter); ++ ++ read_dqs_cmd_delay = phy->settings.cp_read_dqs_cmd_delay; ++ clk_wrdqs_delay = phy->settings.cp_clk_wrdqs_delay; ++ clk_wr_delay = phy->settings.cp_clk_wr_delay; ++ read_dqs_delay = phy->settings.cp_read_dqs_delay; ++ return 0; ++} ++ ++static int sdhci_cdns_sd6_phy_init(struct sdhci_cdns_priv *priv) ++{ ++ int ret; ++ u32 reg; ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ ++ sdhci_cdns_sd6_dll_reset(priv, true); ++ ++ reg = sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQS_TIMING); ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS; ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS; ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS; ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD; ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS, ++ phy->settings.cp_use_ext_lpbk_dqs); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS, ++ phy->settings.cp_use_lpbk_dqs); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS, ++ phy->settings.cp_use_phony_dqs); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD, ++ phy->settings.cp_use_phony_dqs_cmd); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQS_TIMING, reg); ++ ++ reg = sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_GATE_LPBK); ++ reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD; ++ reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT; ++ reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL; ++ reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON; ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD, ++ phy->settings.cp_sync_method); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT, ++ phy->settings.cp_sw_half_cycle_shift); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL, ++ phy->settings.cp_rd_del_sel); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON, ++ phy->settings.cp_gate_cfg_always_on); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_GATE_LPBK, reg); ++ ++ reg = 0x0; ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_BYPASS_MODE, ++ phy->settings.cp_dll_bypass_mode); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_PHASE_DETECT_SEL, 2); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_LOCK_NUM, 0); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_START_POINT, ++ phy->settings.cp_dll_start_point); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_MASTER, reg); ++ ++ reg = 0x0; ++ reg = FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_CMD_DELAY, ++ phy->settings.cp_read_dqs_cmd_delay); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WRDQS_DELAY, ++ phy->settings.cp_clk_wrdqs_delay); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WR_DELAY, ++ phy->settings.cp_clk_wr_delay); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_DELAY, ++ phy->settings.cp_read_dqs_delay); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DLL_SLAVE, reg); ++ ++ reg = sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_CTRL); ++ reg &= ~SDHCI_CDNS_SD6_PHY_CTRL_PHONY_DQS_TIMING; ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_CTRL, reg); ++ ++ reg = sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0); ++ reg &= ~0x77; ++ reg |= SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV_OVR_EN | ++ SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW_OVR_EN; ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV, ++ phy->settings.drive); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW, ++ phy->settings.slew); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0, reg); ++ ++ ret = sdhci_cdns_sd6_dll_reset(priv, false); ++ if (ret) ++ return ret; ++ ++ reg = sdhci_cdns_sd6_read_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQ_TIMING); ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON; ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END; ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START; ++ reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END; ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON, ++ phy->settings.cp_io_mask_always_on); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END, ++ phy->settings.cp_io_mask_end); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START, ++ phy->settings.cp_io_mask_start); ++ reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END, ++ phy->settings.cp_data_select_oe_end); ++ sdhci_cdns_sd6_write_phy_reg(priv, SDHCI_CDNS_SD6_PHY_DQ_TIMING, reg); ++ ++ reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09); ++ if (phy->settings.sdhc_extended_wr_mode) ++ reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE; ++ else ++ reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE; ++ ++ if (phy->settings.sdhc_extended_rd_mode) ++ reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE; ++ else ++ reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE; ++ ++ if (phy->settings.sdhc_rddata_en) ++ reg |= SDHCI_CDNS_HRS09_RDDATA_EN; ++ else ++ reg &= ~SDHCI_CDNS_HRS09_RDDATA_EN; ++ ++ if (phy->settings.sdhc_rdcmd_en) ++ reg |= SDHCI_CDNS_HRS09_RDCMD_EN; ++ else ++ reg &= ~SDHCI_CDNS_HRS09_RDCMD_EN; ++ ++ writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09); ++ ++ writel(0x30004, priv->hrs_addr + SDHCI_CDNS_HRS02); ++ ++ reg = 0x0; ++ reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, ++ phy->settings.sdhc_hcsdclkadj); ++ writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS10); ++ ++ if (phy->mode != MMC_TIMING_MMC_HS && ++ phy->mode != MMC_TIMING_MMC_DDR52) { ++ reg = 0x0; ++ reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY, ++ phy->settings.sdhc_wrdata1_sdclk_dly); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY, ++ phy->settings.sdhc_wrdata0_sdclk_dly); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY, ++ phy->settings.sdhc_wrcmd1_sdclk_dly); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY, ++ phy->settings.sdhc_wrcmd0_sdclk_dly); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY, ++ phy->settings.sdhc_wrdata1_dly); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY, ++ phy->settings.sdhc_wrdata0_dly); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY, ++ phy->settings.sdhc_wrcmd1_dly); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY, ++ phy->settings.sdhc_wrcmd0_dly); ++ } else { ++ reg = 0x202; ++ } ++ ++ writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS16); ++ ++ reg = 0x0; ++ reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE, ++ phy->settings.sdhc_rw_compensate); ++ reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL, ++ phy->settings.sdhc_idelay_val); ++ writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS07); ++ return 0; ++} ++ ++static int sdhci_cdns_sd6_set_tune_val(struct sdhci_host *host, ++ unsigned int val) ++{ ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ ++ phy->settings.hs200_tune_val = val; ++ phy->settings.cp_read_dqs_cmd_delay = read_dqs_cmd_delay; ++ phy->settings.cp_read_dqs_delay = val; ++ ++ return sdhci_cdns_sd6_phy_init(priv); ++} ++ ++static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) ++{ ++ /* ++ * Cadence's spec says the Timeout Clock Frequency is the same as the ++ * Base Clock Frequency. ++ */ ++ return host->max_clk; ++} ++ ++static unsigned int sdhci_cdns_get_max_clock(struct sdhci_host *host) ++{ ++ return SDMCLK_MAX_FREQ; ++} ++ ++static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode) ++{ ++ u32 tmp; ++ ++ /* The speed mode for eMMC is selected by HRS06 register */ ++ tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); ++ tmp &= ~SDHCI_CDNS_HRS06_MODE; ++ tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); ++ priv->priv_writel(priv, tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); ++} ++ ++static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv) ++{ ++ u32 tmp; ++ ++ tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); ++ return FIELD_GET(SDHCI_CDNS_HRS06_MODE, tmp); ++} ++ ++static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, ++ unsigned int timing) ++{ ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); ++ u32 mode; ++ ++ switch (timing) { ++ case MMC_TIMING_MMC_HS: ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; ++ break; ++ case MMC_TIMING_MMC_DDR52: ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; ++ break; ++ case MMC_TIMING_MMC_HS200: ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; ++ break; ++ case MMC_TIMING_MMC_HS400: ++ if (priv->enhanced_strobe) ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES; ++ else ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; ++ break; ++ case MMC_TIMING_SD_HS: ++ mode = SDHCI_CDNS_HRS06_MODE_SD; ++ break; ++ default: ++ mode = SDHCI_CDNS_HRS06_MODE_LEGACY; ++ break; ++ } ++ ++ pr_debug("%s mode %d timing %d\n", __func__, mode, timing); ++ sdhci_cdns_set_emmc_mode(priv, mode); ++ ++ /* For SD, fall back to the default handler */ ++ if (mode == SDHCI_CDNS_HRS06_MODE_SD) ++ sdhci_set_uhs_signaling(host, timing); ++} ++ ++static int sdhci_cdns_sd6_phy_update_timings(struct sdhci_host *host) ++{ ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; ++ int t_sdmclk = phy->t_sdmclk; ++ int mode; ++ ++ mode = sdhci_cdns_sd6_get_mode(host, host->mmc->ios.timing); ++ /* initialize input */ ++ init_timings[mode](&phy->t, phy->t_sdclk); ++ ++ phy->mode = host->mmc->ios.timing; ++ phy->strobe_dat = false; ++ phy->strobe_cmd = false; ++ ++ switch (phy->mode) { ++ case MMC_TIMING_UHS_SDR104: ++ phy->tune_cmd = true; ++ phy->tune_dat = true; ++ break; ++ case MMC_TIMING_UHS_DDR50: ++ phy->ddr = true; ++ break; ++ case MMC_TIMING_MMC_DDR52: ++ phy->ddr = true; ++ break; ++ case MMC_TIMING_MMC_HS200: ++ phy->tune_dat = true; ++ phy->tune_cmd = true; ++ break; ++ case MMC_TIMING_MMC_HS400: ++ phy->tune_cmd = true; ++ phy->ddr = true; ++ phy->strobe_dat = true; ++ if (priv->enhanced_strobe) ++ phy->strobe_cmd = true; ++ break; ++ } ++ ++ phy->d.phy_sdclk_delay = 2 * t_sdmclk; ++ phy->d.phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2; ++ phy->d.phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2; ++ ++ if (phy->t_sdclk == phy->t_sdmclk) { ++ phy->settings.sdhc_extended_wr_mode = 0; ++ phy->settings.sdhc_extended_rd_mode = 0; ++ } else { ++ phy->settings.sdhc_extended_wr_mode = 1; ++ phy->settings.sdhc_extended_rd_mode = 1; ++ } ++ ++ phy->settings.cp_gate_cfg_always_on = 1; ++ ++ sdhci_cdns_sd6_phy_configure_dll(priv); ++ ++ sdhci_cdns_sd6_phy_calc_settings(phy); ++ ++ return 0; ++} ++ ++static u32 sdhci_cdns_sd6_get_mode(struct sdhci_host *host, ++ unsigned int timing) ++{ ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); ++ u32 mode; ++ ++ switch (timing) { ++ case MMC_TIMING_MMC_HS: ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; ++ break; ++ case MMC_TIMING_MMC_DDR52: ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; ++ break; ++ case MMC_TIMING_MMC_HS200: ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; ++ break; ++ case MMC_TIMING_MMC_HS400: ++ if (priv->enhanced_strobe) ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES; ++ else ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; ++ break; ++ case MMC_TIMING_SD_HS: ++ mode = SDHCI_CDNS_HRS06_MODE_SD; ++ break; ++ default: ++ mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; ++ break; ++ } ++ ++ return mode; + } + +-static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, +- u8 addr, u8 data) ++static u32 sdhci_cdns_sd6_irq(struct sdhci_host *host, u32 intmask) + { +- void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; +- u32 tmp; +- int ret; ++ struct sdhci_cdns_priv *priv; ++ u64 reg; + +- ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), +- 0, 10); +- if (ret) +- return ret; ++ /* If errata workaround is not required, return */ ++ if (!cn10k_irq_workaround) ++ return intmask; + +- tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) | +- FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); +- priv->priv_writel(priv, tmp, reg); ++ priv = sdhci_cdns_priv(host); ++ reg = readq(priv->hrs_addr + CN10K_MSIX_INTR); + +- tmp |= SDHCI_CDNS_HRS04_WR; +- priv->priv_writel(priv, tmp, reg); ++ if (intmask) ++ sdhci_cdns_sd6_writel(host, intmask, SDHCI_INT_STATUS); + +- ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 0, 10); +- if (ret) +- return ret; ++ writeq(reg, priv->hrs_addr + CN10K_MSIX_INTR); ++ reg = readq(priv->hrs_addr + CN10K_MSIX_INTR); + +- tmp &= ~SDHCI_CDNS_HRS04_WR; +- priv->priv_writel(priv, tmp, reg); ++ return intmask; ++} + +- ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), +- 0, 10); ++static void sdhci_cdns_sd6_set_uhs_signaling(struct sdhci_host *host, ++ unsigned int timing) ++{ ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; + +- return ret; ++ sdhci_cdns_set_uhs_signaling(host, timing); ++ ++ if ((phy->mode == -1) || (phy->t_sdclk == -1)) ++ return; ++ ++ if (sdhci_cdns_sd6_phy_update_timings(host)) ++ pr_debug("%s: update timings failed\n", __func__); ++ ++ if (sdhci_cdns_sd6_phy_init(priv)) ++ pr_debug("%s: phy init failed\n", __func__); + } + +-static unsigned int sdhci_cdns_phy_param_count(struct device_node *np) ++static void sdhci_cdns_sd6_set_clock(struct sdhci_host *host, ++ unsigned int clock) + { +- unsigned int count = 0; +- int i; ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); ++ struct sdhci_cdns_sd6_phy *phy = priv->phy; + +- for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) +- if (of_property_read_bool(np, sdhci_cdns_phy_cfgs[i].property)) +- count++; ++ if (clock == 0) ++ return sdhci_set_clock(host, clock); + +- return count; ++ phy->t_sdclk = DIV_ROUND_DOWN_ULL(1e12, clock); ++ ++ pr_debug("%s %d %d\n", __func__, phy->mode, clock); ++ ++ if (sdhci_cdns_sd6_phy_update_timings(host)) ++ pr_debug("%s: update timings failed\n", __func__); ++ ++ if (sdhci_cdns_sd6_phy_init(priv)) ++ pr_debug("%s: phy init failed\n", __func__); ++ ++ sdhci_set_clock(host, clock); ++ ++#ifdef CONFIG_MMC_SDHCI_CADENCE_DEBUG ++ sdhci_cdns_sd6_dump(priv); ++#endif + } + +-static void sdhci_cdns_phy_param_parse(struct device_node *np, +- struct sdhci_cdns_priv *priv) ++static int sdhci_cdns_sd6_phy_probe(struct platform_device *pdev) + { +- struct sdhci_cdns_phy_param *p = priv->phy_params; ++ struct device *dev = &pdev->dev; ++ struct sdhci_cdns_sd6_phy *phy; ++ struct sdhci_host *host = platform_get_drvdata(pdev); ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + u32 val; +- int ret, i; ++ struct clk *clk; ++ int ret; ++ const char *mode_name; + +- for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { +- ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property, +- &val); +- if (ret) +- continue; ++ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); ++ if (!phy) ++ return -ENOMEM; + +- p->addr = sdhci_cdns_phy_cfgs[i].addr; +- p->data = val; +- p++; +- } +-} ++ priv->phy = phy; + +-static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) +-{ +- int ret, i; ++ if (!has_acpi_companion(dev)) { ++ clk = devm_clk_get(dev, "sdmclk"); ++ if (IS_ERR(clk)) { ++ dev_err(dev, "sdmclk get error\n"); ++ return PTR_ERR(clk); ++ } + +- for (i = 0; i < priv->nr_phy_params; i++) { +- ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr, +- priv->phy_params[i].data); +- if (ret) +- return ret; ++ val = clk_get_rate(clk); ++ } else { ++ if (device_property_read_u32(dev, "clock-frequency", &val)) ++ // Property not found, but it's mandatory for ACPI case ++ return -ENODEV; + } ++ phy->t_sdmclk = DIV_ROUND_DOWN_ULL(1e12, val); + +- return 0; +-} ++ ret = device_property_read_u32(dev, "cdns,host_slew", ++ &phy->settings.slew); ++ if (ret) ++ phy->settings.slew = 3; + +-static void *sdhci_cdns_priv(struct sdhci_host *host) +-{ +- struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ ret = device_property_read_u32(dev, "cdns,host_drive", ++ &phy->settings.drive); ++ if (ret) ++ phy->settings.drive = 2; + +- return sdhci_pltfm_priv(pltfm_host); +-} ++ ret = device_property_read_u32(dev, "cdns,iocell_input_delay", ++ &phy->d.iocell_input_delay); ++ if (ret) ++ phy->d.iocell_input_delay = 2500; + +-static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) +-{ +- /* +- * Cadence's spec says the Timeout Clock Frequency is the same as the +- * Base Clock Frequency. +- */ +- return host->max_clk; +-} ++ ret = device_property_read_u32(dev, "cdns,iocell_output_delay", ++ &phy->d.iocell_output_delay); ++ if (ret) ++ phy->d.iocell_output_delay = 2500; + +-static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode) +-{ +- u32 tmp; ++ ret = device_property_read_u32(dev, "cdns,delay_element", ++ &phy->d.delay_element); ++ if (ret) { ++ ret = sdhci_cdns_sd6_get_delay_element(host); ++ if (ret < 0) ++ return ret; ++ phy->d.delay_element = ret; ++ } + +- /* The speed mode for eMMC is selected by HRS06 register */ +- tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); +- tmp &= ~SDHCI_CDNS_HRS06_MODE; +- tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); +- priv->priv_writel(priv, tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); +-} ++ ret = device_property_read_string(dev, "cdns,mode", &mode_name); ++ if (!ret) { ++ if (!strcmp("emmc_sdr", mode_name)) ++ phy->mode = MMC_TIMING_MMC_HS; ++ else if (!strcmp("emmc_ddr", mode_name)) ++ phy->mode = MMC_TIMING_MMC_DDR52; ++ else if (!strcmp("emmc_hs200", mode_name)) ++ phy->mode = MMC_TIMING_MMC_HS200; ++ else if (!strcmp("emmc_hs400", mode_name)) ++ phy->mode = MMC_TIMING_MMC_HS400; ++ else if (!strcmp("sd_hs", mode_name)) ++ phy->mode = MMC_TIMING_SD_HS; ++ else ++ phy->mode = MMC_TIMING_MMC_HS; ++ } else { ++ phy->mode = MMC_TIMING_MMC_HS; ++ } + +-static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv) +-{ +- u32 tmp; ++ phy->d.delay_element_org = phy->d.delay_element; ++ phy->d.iocell_input_delay = 650; ++ phy->d.iocell_output_delay = 1800; + +- tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); +- return FIELD_GET(SDHCI_CDNS_HRS06_MODE, tmp); ++ switch (phy->mode) { ++ case MMC_TIMING_MMC_HS: ++ phy->t_sdclk = 10000; ++ break; ++ case MMC_TIMING_MMC_DDR52: ++ phy->t_sdclk = 10000; ++ break; ++ case MMC_TIMING_MMC_HS200: ++ phy->t_sdclk = 5000; ++ break; ++ case MMC_TIMING_MMC_HS400: ++ phy->t_sdclk = 5000; ++ break; ++ case MMC_TIMING_SD_HS: ++ phy->t_sdclk = 100000; ++ break; ++ default: ++ phy->t_sdclk = 10000; ++ break; ++ } ++ ++ sdhci_cdns_sd6_get_delay_params(dev, priv); ++ ++ sdhci_cdns_sd6_calc_phy(phy); ++ return 0; + } + +-static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) ++static int sdhci_cdns_sd4_set_tune_val(struct sdhci_host *host, ++ unsigned int val) + { + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06; +@@ -251,16 +2004,57 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) + return 0; + } + ++/** ++ * sdhci_cdns_tune_blkgap() - tune multi-block read gap ++ * @mmc: MMC host ++ * ++ * Tune delay used in multi block read. To do so, ++ * try sending multi-block read command with incremented gap, unless ++ * it succeeds. ++ * ++ * Return: error code ++ */ ++static int sdhci_cdns_tune_blkgap(struct mmc_host *mmc) ++{ ++ struct sdhci_host *host = mmc_priv(mmc); ++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); ++ struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host); ++ void __iomem *hrs37_reg = priv->hrs_addr + SDHCI_CDNS_HRS37; ++ void __iomem *hrs38_reg = priv->hrs_addr + SDHCI_CDNS_HRS38; ++ int ret; ++ u32 gap; ++ ++ /* Currently only needed in HS200 mode */ ++ if (host->timing != MMC_TIMING_MMC_HS200) ++ return 0; ++ ++ writel(SDHCI_CDNS_HRS37_MODE_MMC_HS200, hrs37_reg); ++ ++ for (gap = 0; gap <= SDHCI_CDNS_HRS38_BLKGAP_MAX; gap++) { ++ writel(gap, hrs38_reg); ++ ret = mmc_read_tuning(mmc, 512, 32); ++ if (!ret) ++ break; ++ } ++ ++ dev_dbg(mmc_dev(mmc), "read block gap tune %s, gap %d\n", ++ ret ? "failed" : "OK", gap); ++ return ret; ++} ++ + /* + * In SD mode, software must not use the hardware tuning and instead perform + * an almost identical procedure to eMMC. + */ +-static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) ++static int sdhci_cdns_sd6_execute_tuning(struct sdhci_host *host, u32 opcode) + { ++ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + int cur_streak = 0; + int max_streak = 0; + int end_of_streak = 0; + int i; ++ int ret; ++ int midpoint, iter = 0; + + /* + * Do not execute tuning for UHS_SDR50 or UHS_DDR50. +@@ -270,15 +2064,26 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) + host->timing != MMC_TIMING_UHS_SDR104) + return 0; + +- for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { +- if (sdhci_cdns_set_tune_val(host, i) || ++ for (i = tune_val_start; iter < max_tune_iter; ++ iter++, i += tune_val_step) { ++ if (priv->cdns_data->set_tune_val(host, i) || + mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ + cur_streak = 0; + } else { /* good */ ++ int span_start; + cur_streak++; + if (cur_streak > max_streak) { + max_streak = cur_streak; + end_of_streak = i; ++ span_start = end_of_streak - ++ ((cur_streak - 1) * tune_val_step); ++ pr_debug("%s (%d-%d = %d)", __func__, ++ span_start, end_of_streak, cur_streak); ++ } else { ++ span_start = i - ((cur_streak - 1) * ++ tune_val_step); ++ ++ pr_debug("%s (%d-%d)", __func__, span_start, i); + } + } + } +@@ -287,42 +2092,67 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) + dev_err(mmc_dev(host->mmc), "no tuning point found\n"); + return -EIO; + } ++ { ++ int span_start = end_of_streak - ++ ((max_streak - 1) * tune_val_step); ++ ++ pr_debug("max_streak: %d-%d", span_start, end_of_streak); ++ } ++ ++ midpoint = end_of_streak - (((max_streak - 1) * tune_val_step) / 2); ++ pr_info("%s: Tuning Midpoint value = %d\n", __func__, midpoint); ++ ++ ret = priv->cdns_data->set_tune_val(host, midpoint); ++ if (ret) ++ return ret; ++ ++ return sdhci_cdns_tune_blkgap(host->mmc); + +- return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); + } + +-static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, +- unsigned int timing) ++/* ++ * In SD mode, software must not use the hardware tuning and instead perform ++ * an almost identical procedure to eMMC. ++ */ ++static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) + { +- struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); +- u32 mode; ++ int cur_streak = 0; ++ int max_streak = 0; ++ int end_of_streak = 0; ++ int i; ++ int ret; + +- switch (timing) { +- case MMC_TIMING_MMC_HS: +- mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; +- break; +- case MMC_TIMING_MMC_DDR52: +- mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; +- break; +- case MMC_TIMING_MMC_HS200: +- mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; +- break; +- case MMC_TIMING_MMC_HS400: +- if (priv->enhanced_strobe) +- mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES; +- else +- mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; +- break; +- default: +- mode = SDHCI_CDNS_HRS06_MODE_SD; +- break; ++ /* ++ * Do not execute tuning for UHS_SDR50 or UHS_DDR50. ++ * The delay is set by probe, based on the DT properties. ++ */ ++ if (host->timing != MMC_TIMING_MMC_HS200 && ++ host->timing != MMC_TIMING_UHS_SDR104) ++ return 0; ++ ++ for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { ++ if (sdhci_cdns_sd4_set_tune_val(host, i) || ++ mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ ++ cur_streak = 0; ++ } else { /* good */ ++ cur_streak++; ++ if (cur_streak > max_streak) { ++ max_streak = cur_streak; ++ end_of_streak = i; ++ } ++ } + } + +- sdhci_cdns_set_emmc_mode(priv, mode); ++ if (!max_streak) { ++ dev_err(mmc_dev(host->mmc), "no tuning point found\n"); ++ return -EIO; ++ } + +- /* For SD, fall back to the default handler */ +- if (mode == SDHCI_CDNS_HRS06_MODE_SD) +- sdhci_set_uhs_signaling(host, timing); ++ ret = sdhci_cdns_sd4_set_tune_val(host, end_of_streak - max_streak / 2); ++ if (ret) ++ return ret; ++ ++ return sdhci_cdns_tune_blkgap(host->mmc); + } + + /* Elba control register bits [6:3] are byte-lane enables */ +@@ -410,7 +2240,7 @@ static int elba_drv_init(struct platform_device *pdev) + return 0; + } + +-static const struct sdhci_ops sdhci_cdns_ops = { ++static const struct sdhci_ops sdhci_cdns_sd4_ops = { + .set_clock = sdhci_set_clock, + .get_timeout_clock = sdhci_cdns_get_timeout_clock, + .set_bus_width = sdhci_set_bus_width, +@@ -419,11 +2249,31 @@ static const struct sdhci_ops sdhci_cdns_ops = { + .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, + }; + ++static const struct sdhci_ops sdhci_cdns_sd6_ops = { ++#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS ++ .read_l = sdhci_cdns_sd6_readl, ++ .write_l = sdhci_cdns_sd6_writel, ++ .read_w = sdhci_cdns_sd6_readw, ++ .write_w = sdhci_cdns_sd6_writew, ++ .read_b = sdhci_cdns_sd6_readb, ++ .write_b = sdhci_cdns_sd6_writeb, ++#endif ++ .get_max_clock = sdhci_cdns_get_max_clock, ++ .set_clock = sdhci_cdns_sd6_set_clock, ++ .get_timeout_clock = sdhci_cdns_get_timeout_clock, ++ .set_bus_width = sdhci_set_bus_width, ++ .reset = sdhci_reset, ++ .platform_execute_tuning = sdhci_cdns_sd6_execute_tuning, ++ .set_uhs_signaling = sdhci_cdns_sd6_set_uhs_signaling, ++ .irq = sdhci_cdns_sd6_irq, ++}; ++ + static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = { + .pltfm_data = { +- .ops = &sdhci_cdns_ops, ++ .ops = &sdhci_cdns_sd4_ops, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }, ++ .cdns_data = NULL, + }; + + static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = { +@@ -431,19 +2281,40 @@ static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = { + .pltfm_data = { + .ops = &sdhci_elba_ops, + }, ++ .cdns_data = NULL, ++}; ++ ++static const struct sdhci_cdns_data sdhci_cdns_sd4_data = { ++ .phy_init = sdhci_cdns_sd4_phy_init, ++ .set_tune_val = sdhci_cdns_sd4_set_tune_val, ++}; ++ ++static const struct sdhci_cdns_data sdhci_cdns_sd6_data = { ++ .phy_init = sdhci_cdns_sd6_phy_init, ++ .set_tune_val = sdhci_cdns_sd6_set_tune_val, ++}; ++ ++static const struct sdhci_cdns_drv_data sdhci_cdns_sd4_drv_data = { ++ .pltfm_data = { ++ .ops = &sdhci_cdns_sd4_ops, ++ }, ++ .cdns_data = &sdhci_cdns_sd4_data, + }; + + static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = { + .pltfm_data = { +- .ops = &sdhci_cdns_ops, ++ .ops = &sdhci_cdns_sd4_ops, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }, ++ .cdns_data = NULL, + }; + +-static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = { ++static const struct sdhci_cdns_drv_data sdhci_cdns_sd6_drv_data = { ++ .init = sdhci_cdns_sd6_phy_probe, + .pltfm_data = { +- .ops = &sdhci_cdns_ops, ++ .ops = &sdhci_cdns_sd6_ops, + }, ++ .cdns_data = &sdhci_cdns_sd6_data, + }; + + static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, +@@ -475,6 +2345,51 @@ static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc) + usleep_range(300, 1000); + } + ++#ifdef CONFIG_DMI ++ ++static char *part_no; ++#define DMI_ENTRY_PROCESSOR_MIN_LENGTH 48 ++#define DMI_PROC_PART_NUMBER 0x22 ++#define DMI_MAX_STRLEN 80 ++ ++static void find_proc_part(const struct dmi_header *dm, void *private) ++{ ++ const u8 *dmi_data = (const u8 *)dm; ++ char *ptr; ++ int i, idx; ++ ++ if (dm->type == DMI_ENTRY_PROCESSOR && ++ dm->length >= DMI_ENTRY_PROCESSOR_MIN_LENGTH) { ++ idx = (int)get_unaligned((const u8 *) ++ (dmi_data + DMI_PROC_PART_NUMBER)); ++ ptr = (char *)(dmi_data + dm->length); ++ for (i = 1; i < idx; i++) { ++ while (*ptr) ++ ptr++; ++ ptr++; ++ } ++ strscpy(private, ptr, DMI_MAX_STRLEN); ++ } ++} ++#endif ++ ++static int dmi_check_part_no(void) ++{ ++#ifdef CONFIG_DMI ++ part_no = kcalloc(DMI_MAX_STRLEN, sizeof(char), GFP_KERNEL); ++ if ((part_no) && !dmi_walk(find_proc_part, part_no)) { ++ if (!strncmp(part_no, "MV-CN10624-A", 12) || ++ !strncmp(part_no, "MV-CN10624SA", 12) || ++ !strncmp(part_no, "MV-CN10518-A", 12)) { ++ kfree(part_no); ++ return 1; ++ } ++ kfree(part_no); ++ } ++#endif ++ return 0; ++} ++ + static int sdhci_cdns_probe(struct platform_device *pdev) + { + struct sdhci_host *host; +@@ -483,31 +2398,46 @@ static int sdhci_cdns_probe(struct platform_device *pdev) + struct sdhci_cdns_priv *priv; + struct clk *clk; + unsigned int nr_phy_params; ++ bool has_acpi; + int ret; + struct device *dev = &pdev->dev; + static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT; + +- clk = devm_clk_get_enabled(dev, NULL); +- if (IS_ERR(clk)) +- return PTR_ERR(clk); ++ has_acpi = has_acpi_companion(dev); ++ ++ if (!has_acpi) { ++ clk = devm_clk_get_enabled(dev, NULL); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ } + +- data = of_device_get_match_data(dev); +- if (!data) +- data = &sdhci_cdns_drv_data; ++ data = device_get_match_data(dev); ++ if (!data) { ++ ret = PTR_ERR(data); ++ goto disable_clk; ++ } + +- nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node); ++ nr_phy_params = sdhci_cdns_sd4_phy_param_count(dev); + host = sdhci_pltfm_init(pdev, &data->pltfm_data, + struct_size(priv, phy_params, nr_phy_params)); +- if (IS_ERR(host)) +- return PTR_ERR(host); ++ if (IS_ERR(host)) { ++ ret = PTR_ERR(host); ++ goto disable_clk; ++ } + + pltfm_host = sdhci_priv(host); +- pltfm_host->clk = clk; ++ if (!has_acpi) ++ pltfm_host->clk = clk; + ++ host->clk_mul = 0; ++ host->max_clk = SDMCLK_MAX_FREQ; ++ host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; ++ host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN; + priv = sdhci_pltfm_priv(pltfm_host); + priv->nr_phy_params = nr_phy_params; + priv->hrs_addr = host->ioaddr; + priv->enhanced_strobe = false; ++ priv->cdns_data = data->cdns_data; + priv->priv_writel = cdns_writel; + host->ioaddr += SDHCI_CDNS_SRS_BASE; + host->mmc_host_ops.hs400_enhanced_strobe = +@@ -517,8 +2447,11 @@ static int sdhci_cdns_probe(struct platform_device *pdev) + if (ret) + goto free; + } +- sdhci_enable_v4_mode(host); +- __sdhci_read_caps(host, &version, NULL, NULL); ++ ++ if (has_acpi) ++ cn10k_irq_workaround = dmi_check_part_no(); ++ else if (is_soc_cn10ka_ax() || is_soc_cnf10ka_ax()) ++ cn10k_irq_workaround = 1; + + sdhci_get_of_property(pdev); + +@@ -526,12 +2459,19 @@ static int sdhci_cdns_probe(struct platform_device *pdev) + if (ret) + goto free; + +- sdhci_cdns_phy_param_parse(dev->of_node, priv); ++ if (of_device_is_compatible(dev->of_node, "cdns,sd6hc")) { ++ ret = sdhci_cdns_sd6_phy_init(priv); ++ } else { ++ sdhci_cdns_sd4_phy_param_parse(dev, priv); ++ ret = sdhci_cdns_sd4_phy_init(priv); ++ } + +- ret = sdhci_cdns_phy_init(priv); + if (ret) + goto free; + ++ sdhci_enable_v4_mode(host); ++ __sdhci_read_caps(host, &version, NULL, NULL); ++ + if (host->mmc->caps & MMC_CAP_HW_RESET) { + priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(priv->rst_hw)) { +@@ -550,6 +2490,10 @@ static int sdhci_cdns_probe(struct platform_device *pdev) + return 0; + free: + sdhci_pltfm_free(pdev); ++disable_clk: ++ if (!has_acpi) ++ clk_disable_unprepare(clk); ++ + return ret; + } + +@@ -559,13 +2503,22 @@ static int sdhci_cdns_resume(struct device *dev) + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host); ++ bool has_acpi; + int ret; + +- ret = clk_prepare_enable(pltfm_host->clk); +- if (ret) +- return ret; ++ has_acpi = has_acpi_companion(dev); ++ ++ if (!has_acpi) { ++ ret = clk_prepare_enable(pltfm_host->clk); ++ if (ret) ++ return ret; ++ } ++ ++ if (priv->cdns_data->phy_init(priv)) ++ ret = priv->cdns_data->phy_init(priv); ++ else ++ ret = sdhci_cdns_sd4_phy_init(priv); + +- ret = sdhci_cdns_phy_init(priv); + if (ret) + goto disable_clk; + +@@ -576,7 +2529,8 @@ static int sdhci_cdns_resume(struct device *dev) + return 0; + + disable_clk: +- clk_disable_unprepare(pltfm_host->clk); ++ if (!has_acpi) ++ clk_disable_unprepare(pltfm_host->clk); + + return ret; + } +@@ -606,7 +2549,14 @@ static const struct of_device_id sdhci_cdns_match[] = { + .compatible = "mobileye,eyeq-sd4hc", + .data = &sdhci_eyeq_drv_data, + }, +- { .compatible = "cdns,sd4hc" }, ++ { ++ .compatible = "cdns,sd4hc", ++ .data = &sdhci_cdns_sd4_drv_data, ++ }, ++ { ++ .compatible = "cdns,sd6hc", ++ .data = &sdhci_cdns_sd6_drv_data, ++ }, + { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(of, sdhci_cdns_match); +diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +index 6ea2f3071fe8..83aa6740d735 100644 +--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h ++++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +@@ -87,7 +87,7 @@ struct mbox_msghdr { + #define OTX2_MBOX_REQ_SIG (0xdead) + #define OTX2_MBOX_RSP_SIG (0xbeef) + u16 sig; /* Signature, for validating corrupted msgs */ +-#define OTX2_MBOX_VERSION (0x000a) ++#define OTX2_MBOX_VERSION (0x000b) + u16 ver; /* Version of msg's structure for this ID */ + u16 next_msgoff; /* Offset of next msg within mailbox region */ + int rc; /* Msg process'ed response code */ +@@ -897,6 +897,7 @@ struct nix_lf_alloc_rsp { + u8 lf_tx_stats; /* NIX_AF_CONST1::LF_TX_STATS */ + u16 cints; /* NIX_AF_CONST2::CINTS */ + u16 qints; /* NIX_AF_CONST2::QINTS */ ++ u8 hw_rx_tstamp_en; + u8 cgx_links; /* No. of CGX links present in HW */ + u8 lbk_links; /* No. of LBK links present in HW */ + u8 sdp_links; /* No. of SDP links present in HW */ +diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h +index 8fc2b328ec4d..76f5d5a4ec9f 100644 +--- a/include/linux/mmc/host.h ++++ b/include/linux/mmc/host.h +@@ -655,5 +655,7 @@ int mmc_send_status(struct mmc_card *card, u32 *status); + int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error); + int mmc_send_abort_tuning(struct mmc_host *host, u32 opcode); + int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd); ++int mmc_read_tuning(struct mmc_host *host, unsigned int blksz, ++ unsigned int blocks); + + #endif /* LINUX_MMC_HOST_H */ +diff --git a/include/soc/marvell/octeontx/octeontx_smc.h b/include/soc/marvell/octeontx/octeontx_smc.h +new file mode 100644 +index 000000000000..530a21981480 +--- /dev/null ++++ b/include/soc/marvell/octeontx/octeontx_smc.h +@@ -0,0 +1,125 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++ ++/* ++ * Copyright (C) 2021 Marvell ++ * ++ */ ++ ++#ifndef _SOC_MRVL_OCTEONTX_SMC_H ++#define _SOC_MRVL_OCTEONTX_SMC_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* Data and defines for SMC call */ ++#define OCTEONTX_ARM_SMC_SVC_UID 0xc200ff01 ++ ++/* This is expected OCTEONTX response for SVC UID command */ ++/** Check software version and compatibility of ATF ++ * ++ * The call verifies ATF instance running on the system. ++ * ++ * @return ++ * 0 (T9x) and 2 (cn10k) on success ++ * error code on failure ++ * ++ */ ++static inline int octeontx_soc_check_smc(void) ++{ ++#define CPU_MODEL_CN10KX_PART 0xd49 ++ ++ const int octeontx_svc_uuid[] = { ++ 0x6ff498cf, ++ 0x5a4e9cfa, ++ 0x2f2a3aa4, ++ 0x5945b105, ++ }; ++ ++ struct arm_smccc_res res; ++ ++ /* Is it OCTEONTX on the other side of SMC monitor? */ ++ arm_smccc_smc(OCTEONTX_ARM_SMC_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); ++ if (res.a0 != octeontx_svc_uuid[0] || res.a1 != octeontx_svc_uuid[1] || ++ res.a2 != octeontx_svc_uuid[2] || res.a3 != octeontx_svc_uuid[3]) ++ return -EPERM; ++ ++ if (MIDR_PARTNUM(read_cpuid_id()) == CPU_MODEL_CN10KX_PART) ++ return 2; ++ ++ return 0; ++} ++ ++static inline bool is_soc_cn9x(void) ++{ ++ u32 partnum; ++ ++ partnum = MIDR_PARTNUM(read_cpuid_id()); ++ ++ /* checking partnum between cn9xx and cn8xx series */ ++ if (!((partnum & 0xF0) ^ (0xb0)) || !((partnum & 0xFF) ^ (0xaf))) ++ return true; ++ ++ return false; ++} ++ ++static inline bool is_soc_cn10kx(void) ++{ ++ if (MIDR_PARTNUM(read_cpuid_id()) == CPU_MODEL_CN10KX_PART) ++ return 1; ++ return 0; ++} ++ ++static inline bool is_soc_cnf10kb(void) ++{ ++ static const struct soc_device_attribute cnf10kb_socinfo[] = { ++ {.soc_id = "jep106:0369:00bc", }, ++ {}, ++ }; ++ const struct soc_device_attribute *attr; ++ ++ attr = soc_device_match(cnf10kb_socinfo); ++ ++ if (attr) ++ return true; ++ ++ return false; ++} ++ ++static inline bool is_soc_cn10ka_ax(void) ++{ ++ static const struct soc_device_attribute cn10ka_socinfo[] = { ++ {.soc_id = "jep106:0369:00b9", .revision = "0x00000000", }, ++ {.soc_id = "jep106:0369:00b9", .revision = "0x00000001", }, ++ {}, ++ }; ++ const struct soc_device_attribute *attr; ++ ++ attr = soc_device_match(cn10ka_socinfo); ++ ++ if (attr) ++ return true; ++ ++ return false; ++} ++ ++static inline bool is_soc_cnf10ka_ax(void) ++{ ++ static const struct soc_device_attribute cnf10ka_socinfo[] = { ++ {.soc_id = "jep106:0369:00ba", .revision = "0x00000000", }, ++ {.soc_id = "jep106:0369:00ba", .revision = "0x00000001", }, ++ {}, ++ }; ++ const struct soc_device_attribute *attr; ++ ++ attr = soc_device_match(cnf10ka_socinfo); ++ ++ if (attr) ++ return true; ++ ++ return false; ++} ++ ++#endif /* _SOC_MRVL_OCTEONTX_SMC_H */ +-- +2.43.0 + diff --git a/patches-sonic/0001-psample-define-the-macro-PSAMPLE_MD_EXTENDED_ATTR.patch b/patches-sonic/0001-psample-define-the-macro-PSAMPLE_MD_EXTENDED_ATTR.patch new file mode 100644 index 000000000..64d591cb4 --- /dev/null +++ b/patches-sonic/0001-psample-define-the-macro-PSAMPLE_MD_EXTENDED_ATTR.patch @@ -0,0 +1,26 @@ +From 7dbf2689eb841c51dca4dad51b0941c06aa09e26 Mon Sep 17 00:00:00 2001 +From: Vadym Hlushko +Date: Mon, 11 Apr 2022 15:41:46 +0000 +Subject: psample: Add Nvidia-specific wrapper function for the psample driver + +Add a variable to notify the psample driver to use a wrapper function, +which does preprocess before sending the sample packet to the userspace application. + +Signed-off-by: Vadym Hlushko +--- + include/net/psample.h | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/include/net/psample.h b/include/net/psample.h +index 0509d2d..c66e325 100644 +--- a/include/net/psample.h ++++ b/include/net/psample.h +@@ -14,6 +14,8 @@ struct psample_group { + struct rcu_head rcu; + }; + ++#define PSAMPLE_MD_EXTENDED_ATTR 1 ++ + struct psample_metadata { + u32 trunc_size; + int in_ifindex; diff --git a/patches-sonic/0001-regmap-i2c-add-SMBus-byte-word-reg16-bus-for-adapter.patch b/patches-sonic/0001-regmap-i2c-add-SMBus-byte-word-reg16-bus-for-adapter.patch new file mode 100755 index 000000000..f9659ab0c --- /dev/null +++ b/patches-sonic/0001-regmap-i2c-add-SMBus-byte-word-reg16-bus-for-adapter.patch @@ -0,0 +1,119 @@ +From 1493f1d6dc733be76faa07ad7ab86c91dfe86309 Mon Sep 17 00:00:00 2001 +From: Nishanth Sampath Kumar +Date: Fri, 8 May 2026 14:29:23 -0700 +Subject: [PATCH] regmap-i2c: add SMBus byte/word reg16 bus for adapters + lacking I2C_FUNC_I2C + +[ upstream bad4bd28abf4d7cb2adcb39cc0de789729d2cd69 ] + +AMD PIIX4 SMBus adapters, present on AMD SP5/EPYC-based platforms +(including Cisco 8000 series routers), support SMBUS_BYTE_DATA and +SMBUS_WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK. + +When at24 (or any driver) requests a regmap with reg_bits=16 and +val_bits=8 on such an adapter, regmap_get_i2c_bus() finds no matching +bus and returns -ENOTSUPP. The existing regmap_i2c_smbus_i2c_block_reg16 +bus type already implements 16-bit addressed reads using only +write_byte_data() + read_byte() primitives, but its selection is gated +on I2C_FUNC_SMBUS_I2C_BLOCK which these adapters lack. + +Add a new regmap_smbus_byte_word_reg16 bus that: + + READ: reuses regmap_i2c_smbus_i2c_read_reg16() -- sets the 16-bit + address via write_byte_data(addr_lo, addr_hi), then reads + bytes sequentially via read_byte() (EEPROM auto-increments). + Requires only SMBUS_BYTE_DATA. + + WRITE: uses write_word_data(addr_hi, (data << 8) | addr_lo) to + encode one data byte per SMBus WORD transaction. + Requires only SMBUS_WORD_DATA. Single-byte writes only. + +The new bus is selected in regmap_get_i2c_bus() when reg_bits=16, +val_bits=8, and the adapter has SMBUS_BYTE_DATA | SMBUS_WORD_DATA but +not I2C_FUNC_I2C or SMBUS_I2C_BLOCK. The branch is placed after the +existing I2C_BLOCK_reg16 check so adapters with full block support +continue to use the faster path. + +This fixes at24 EEPROM probe failures on PIIX4: + at24 3-0055: probe with driver at24 failed with error -524 + +No driver changes are required -- at24 already passes reg_bits=16 to +devm_regmap_init_i2c(), which now succeeds. + +Link: https://patch.msgid.link/20260407233927.498932-1-nissampa@cisco.com +Signed-off-by: Nishanth Sampath Kumar +Signed-off-by: Mark Brown +--- + drivers/base/regmap/regmap-i2c.c | 49 ++++++++++++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + +diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c +index c9b39a02278e..31e30dfced19 100644 +--- a/drivers/base/regmap/regmap-i2c.c ++++ b/drivers/base/regmap/regmap-i2c.c +@@ -303,6 +303,50 @@ static const struct regmap_bus regmap_i2c_smbus_i2c_block_reg16 = { + .max_raw_write = I2C_SMBUS_BLOCK_MAX - 2, + }; + ++/* ++ * SMBus byte/word reg16 support for adapters that have SMBUS_BYTE_DATA ++ * and SMBUS_WORD_DATA but lack I2C_FUNC_I2C and I2C_FUNC_SMBUS_I2C_BLOCK, ++ * such as the AMD PIIX4. ++ * ++ * READ: set 16-bit EEPROM address via write_byte_data(addr_lo, addr_hi), ++ * then sequentially read bytes via read_byte() (EEPROM auto- ++ * increments the address pointer). Same as the I2C-block reg16 ++ * read path above. ++ * ++ * WRITE: encode the low address byte and data into a word transaction: ++ * write_word_data(addr_hi, (data_byte << 8) | addr_lo). ++ * Only single-byte writes are supported (one value per transaction). ++ */ ++static int regmap_smbus_word_write_reg16(void *context, const void *data, ++ size_t count) ++{ ++ struct device *dev = context; ++ struct i2c_client *i2c = to_i2c_client(dev); ++ u8 addr_hi, addr_lo, val; ++ ++ /* ++ * data layout: [addr_hi, addr_lo, val0, val1, ...]. ++ * Only single-byte value writes are supported; multi-byte would ++ * require raw I2C (or repeated word writes with incrementing address). ++ */ ++ if (count != 3) ++ return -EINVAL; ++ ++ addr_hi = ((u8 *)data)[0]; ++ addr_lo = ((u8 *)data)[1]; ++ val = ((u8 *)data)[2]; ++ ++ return i2c_smbus_write_word_data(i2c, addr_hi, ++ cpu_to_le16(((u16)val << 8) | addr_lo)); ++} ++ ++static const struct regmap_bus regmap_smbus_byte_word_reg16 = { ++ .write = regmap_smbus_word_write_reg16, ++ .read = regmap_i2c_smbus_i2c_read_reg16, ++ .max_raw_read = I2C_SMBUS_BLOCK_MAX - 2, ++ .max_raw_write = 1, ++}; ++ + static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c, + const struct regmap_config *config) + { +@@ -321,6 +365,11 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c, + i2c_check_functionality(i2c->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)) + bus = ®map_i2c_smbus_i2c_block_reg16; ++ else if (config->val_bits == 8 && config->reg_bits == 16 && ++ i2c_check_functionality(i2c->adapter, ++ I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA)) ++ bus = ®map_smbus_byte_word_reg16; + else if (config->val_bits == 16 && config->reg_bits == 8 && + i2c_check_functionality(i2c->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) +-- +2.25.1 + diff --git a/patches-sonic/0001-tty-8250-HSUART-DMA-be-deactivated-for-DNV-CPU.patch b/patches-sonic/0001-tty-8250-HSUART-DMA-be-deactivated-for-DNV-CPU.patch new file mode 100644 index 000000000..d6845a6c3 --- /dev/null +++ b/patches-sonic/0001-tty-8250-HSUART-DMA-be-deactivated-for-DNV-CPU.patch @@ -0,0 +1,45 @@ +From b63977b0e1421c641495940fafab9502a2a6d659 Mon Sep 17 00:00:00 2001 +From: Philo Shao +Date: Thu, 17 Apr 2025 02:17:18 -0500 +Subject: tty/8250: Deactivate the HSUART DMA for the DNV CPU + +Intel DNV CPU reports an error, indicating that there is a possibility of abnormal serial port functionality and the CPU may hang. +The HSUART DMA will be deactivated for the DNV CPU. + +Signed-off-by: Philo Shao +--- + drivers/tty/serial/8250/8250_mid.c | 18 +++++++++++------- + 1 file changed, 11 insertions(+), 7 deletions(-) + +diff --git a/drivers/tty/serial/8250/8250_mid.c b/drivers/tty/serial/8250/8250_mid.c +index 2cc78a4bf..e4a5d6358 100644 +--- a/drivers/tty/serial/8250/8250_mid.c ++++ b/drivers/tty/serial/8250/8250_mid.c +@@ -321,13 +321,17 @@ static int mid8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) + if (!uart.port.membase) + return -ENOMEM; + +- ret = mid->board->setup(mid, &uart.port); +- if (ret) +- return ret; +- +- ret = mid8250_dma_setup(mid, &uart); +- if (ret) +- goto err; ++ if (id->device != PCI_DEVICE_ID_INTEL_DNV_UART) { ++ ret = mid->board->setup(mid, &uart.port); ++ if (ret) ++ return ret; ++ ++ ret = mid8250_dma_setup(mid, &uart); ++ if (ret) ++ goto err; ++ } else { ++ uart.port.handle_irq = dnv_handle_irq; ++ } + + ret = serial8250_register_8250_port(&uart); + if (ret < 0) +-- +2.25.1 + diff --git a/patches-sonic/0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch b/patches-sonic/0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch new file mode 100644 index 000000000..5e45001d1 --- /dev/null +++ b/patches-sonic/0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch @@ -0,0 +1,525 @@ +From ef2205ef9ff492a8b83dc0ff2727f941c3385e2b Mon Sep 17 00:00:00 2001 +From: "Mike RE Mallin" +Date: Fri, 13 Feb 2026 14:47:17 -0800 +Subject: [PATCH] vxlan/bridge: Add NDA_FLAGS_EXT support with + NTF_EXT_MH_PEER_SYNC + +Add support for NDA_FLAGS_EXT attribute in VXLAN and bridge FDB entries +to support multi-homing peer synchronization. + +This patch adds: +- ext_flags field to vxlan_fdb structure +- NTF_EXT_MH_PEER_SYNC flag and associated infrastructure +- Propagation of ext_flags through vxlan_fdb_create/update/parse +- NEIGH_UPDATE_F_EXT_MH_PEER_SYNC for neighbor updates +--- + drivers/net/vxlan/vxlan_core.c | 59 ++++++++++++++++++++++++++----------- + drivers/net/vxlan/vxlan_private.h | 6 ++-- + drivers/net/vxlan/vxlan_vnifilter.c | 2 +- + include/net/neighbour.h | 4 ++- + include/uapi/linux/neighbour.h | 1 + + net/bridge/br.c | 2 +- + net/bridge/br_fdb.c | 35 +++++++++++++++++----- + net/bridge/br_private.h | 3 +- + net/core/neighbour.c | 13 ++++++++ + 9 files changed, 94 insertions(+), 31 deletions(-) + +diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c +index 1a70770..ffb0b61 100644 +--- a/drivers/net/vxlan/vxlan_core.c ++++ b/drivers/net/vxlan/vxlan_core.c +@@ -227,6 +227,9 @@ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan, + be32_to_cpu(fdb->vni))) + goto nla_put_failure; + ++ if (fdb->ext_flags && nla_put_u32(skb, NDA_FLAGS_EXT, fdb->ext_flags)) ++ goto nla_put_failure; ++ + ci.ndm_used = jiffies_to_clock_t(now - READ_ONCE(fdb->used)); + ci.ndm_confirmed = 0; + ci.ndm_updated = jiffies_to_clock_t(now - READ_ONCE(fdb->updated)); +@@ -252,6 +255,7 @@ static inline size_t vxlan_nlmsg_size(void) + + nla_total_size(sizeof(__be32)) /* NDA_VNI */ + + nla_total_size(sizeof(__u32)) /* NDA_IFINDEX */ + + nla_total_size(sizeof(__s32)) /* NDA_LINK_NETNSID */ ++ + nla_total_size(sizeof(__u32)) /* NDA_FLAGS_EXT */ + + nla_total_size(sizeof(struct nda_cacheinfo)); + } + +@@ -791,7 +795,7 @@ static int vxlan_gpe_gro_complete(struct sock *sk, struct sk_buff *skb, int nhof + + static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac, + __u16 state, __be32 src_vni, +- __u16 ndm_flags) ++ __u16 ndm_flags, __u32 ext_flags) + { + struct vxlan_fdb *f; + +@@ -803,6 +807,7 @@ static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac, + f->updated = f->used = jiffies; + f->vni = src_vni; + f->nh = NULL; ++ f->ext_flags = ext_flags; + RCU_INIT_POINTER(f->vdev, vxlan); + INIT_LIST_HEAD(&f->nh_list); + INIT_LIST_HEAD(&f->remotes); +@@ -886,7 +891,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, + __u16 state, __be16 port, __be32 src_vni, + __be32 vni, __u32 ifindex, __u16 ndm_flags, + u32 nhid, struct vxlan_fdb **fdb, +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + struct vxlan_rdst *rd = NULL; + struct vxlan_fdb *f; +@@ -897,7 +902,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, + return -ENOSPC; + + netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip); +- f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags); ++ f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags, ext_flags); + if (!f) + return -ENOMEM; + +@@ -981,7 +986,7 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + __u32 ifindex, __u16 ndm_flags, + struct vxlan_fdb *f, u32 nhid, + bool swdev_notify, +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + __u16 fdb_flags = (ndm_flags & ~NTF_USE); + struct vxlan_rdst *rd = NULL; +@@ -989,6 +994,7 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + int notify = 0; + int rc = 0; + int err; ++ u32 old_ext_flags = f->ext_flags; + + if (nhid && !rcu_access_pointer(f->nh)) { + NL_SET_ERR_MSG(extack, +@@ -1019,6 +1025,14 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + } + } + ++ if ((old_ext_flags ^ ext_flags) & NTF_EXT_MH_PEER_SYNC) { ++ notify = 1; ++ if (ext_flags & NTF_EXT_MH_PEER_SYNC) ++ f->ext_flags |= NTF_EXT_MH_PEER_SYNC; ++ else ++ f->ext_flags &= ~NTF_EXT_MH_PEER_SYNC; ++ } ++ + if ((flags & NLM_F_REPLACE)) { + /* Only change unicasts */ + if (!(is_multicast_ether_addr(f->eth_addr) || +@@ -1080,7 +1094,7 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, + __be16 port, __be32 src_vni, __be32 vni, + __u32 ifindex, __u16 ndm_flags, u32 nhid, + bool swdev_notify, +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + __u16 fdb_flags = (ndm_flags & ~NTF_USE); + struct vxlan_fdb *f; +@@ -1093,7 +1107,8 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, + + netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip); + rc = vxlan_fdb_create(vxlan, mac, ip, state, port, src_vni, +- vni, ifindex, fdb_flags, nhid, &f, extack); ++ vni, ifindex, fdb_flags, nhid, &f, extack, ++ ext_flags); + if (rc < 0) + return rc; + +@@ -1116,8 +1131,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + __u16 state, __u16 flags, + __be16 port, __be32 src_vni, __be32 vni, + __u32 ifindex, __u16 ndm_flags, u32 nhid, +- bool swdev_notify, +- struct netlink_ext_ack *extack) ++ bool swdev_notify, struct netlink_ext_ack *extack, ++ u32 ext_flags) + { + struct vxlan_fdb *f; + +@@ -1131,7 +1146,7 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + + return vxlan_fdb_update_existing(vxlan, ip, state, flags, port, + vni, ifindex, ndm_flags, f, +- nhid, swdev_notify, extack); ++ nhid, swdev_notify, extack, ext_flags); + } else { + if (!(flags & NLM_F_CREATE)) + return -ENOENT; +@@ -1139,7 +1154,7 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + return vxlan_fdb_update_create(vxlan, mac, ip, state, flags, + port, src_vni, vni, ifindex, + ndm_flags, nhid, swdev_notify, +- extack); ++ extack, ext_flags); + } + } + +@@ -1154,7 +1169,7 @@ static void vxlan_fdb_dst_destroy(struct vxlan_dev *vxlan, struct vxlan_fdb *f, + static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan, + union vxlan_addr *ip, __be16 *port, __be32 *src_vni, + __be32 *vni, u32 *ifindex, u32 *nhid, +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 *ext_flags) + { + struct net *net = dev_net(vxlan->dev); + int err; +@@ -1237,6 +1252,12 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan, + else + *nhid = 0; + ++ if (tb[NDA_FLAGS_EXT]) { ++ *ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); ++ } else { ++ *ext_flags = 0; ++ } ++ + return 0; + } + +@@ -1253,6 +1274,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + __be32 src_vni, vni; + u32 ifindex, nhid; + u32 hash_index; ++ u32 ext_flags; + int err; + + if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_REACHABLE))) { +@@ -1265,7 +1287,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + return -EINVAL; + + err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex, +- &nhid, extack); ++ &nhid, extack, &ext_flags); + if (err) + return err; + +@@ -1277,7 +1299,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + err = vxlan_fdb_update(vxlan, addr, &ip, ndm->ndm_state, flags, + port, src_vni, vni, ifindex, + ndm->ndm_flags | NTF_VXLAN_ADDED_BY_USER, +- nhid, true, extack); ++ nhid, true, extack, ext_flags); + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +@@ -1327,11 +1349,12 @@ static int vxlan_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], + __be32 src_vni, vni; + u32 ifindex, nhid; + u32 hash_index; ++ u32 ext_flags; + __be16 port; + int err; + + err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex, +- &nhid, extack); ++ &nhid, extack, &ext_flags); + if (err) + return err; + +@@ -1493,7 +1516,7 @@ static bool vxlan_snoop(struct net_device *dev, + vxlan->cfg.dst_port, + vni, + vxlan->default_dst.remote_vni, +- ifindex, NTF_SELF, 0, true, NULL); ++ ifindex, NTF_SELF, 0, true, NULL, 0); + spin_unlock(&vxlan->hash_lock[hash_index]); + } + +@@ -3948,7 +3971,7 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev, + dst->remote_vni, + dst->remote_vni, + dst->remote_ifindex, +- NTF_SELF, 0, &f, extack); ++ NTF_SELF, 0, &f, extack, 0); + if (err) + return err; + } +@@ -4382,7 +4405,7 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], + vxlan->cfg.dst_port, + conf.vni, conf.vni, + conf.remote_ifindex, +- NTF_SELF, 0, true, extack); ++ NTF_SELF, 0, true, extack, 0); + if (err) { + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + netdev_adjacent_change_abort(dst->remote_dev, +@@ -4738,7 +4761,7 @@ vxlan_fdb_external_learn_add(struct net_device *dev, + fdb_info->remote_vni, + fdb_info->remote_ifindex, + NTF_USE | NTF_SELF | NTF_EXT_LEARNED, +- 0, false, extack); ++ 0, false, extack, 0); + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +diff --git a/drivers/net/vxlan/vxlan_private.h b/drivers/net/vxlan/vxlan_private.h +index 76a351a..e0878e0 100644 +--- a/drivers/net/vxlan/vxlan_private.h ++++ b/drivers/net/vxlan/vxlan_private.h +@@ -35,6 +35,7 @@ struct vxlan_fdb { + u16 state; /* see ndm_state */ + __be32 vni; + u16 flags; /* see ndm_flags and below */ ++ u32 ext_flags; + struct list_head nh_list; + struct nexthop __rcu *nh; + struct vxlan_dev __rcu *vdev; +@@ -180,7 +181,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, + __u16 state, __be16 port, __be32 src_vni, + __be32 vni, __u32 ifindex, __u16 ndm_flags, + u32 nhid, struct vxlan_fdb **fdb, +- struct netlink_ext_ack *extack); ++ struct netlink_ext_ack *extack, u32 ext_flags); + int __vxlan_fdb_delete(struct vxlan_dev *vxlan, + const unsigned char *addr, union vxlan_addr ip, + __be16 port, __be32 src_vni, __be32 vni, +@@ -192,7 +193,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + __u16 state, __u16 flags, + __be16 port, __be32 src_vni, __be32 vni, + __u32 ifindex, __u16 ndm_flags, u32 nhid, +- bool swdev_notify, struct netlink_ext_ack *extack); ++ bool swdev_notify, struct netlink_ext_ack *extack, ++ u32 ext_flags); + void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, + __be32 default_vni, struct vxlan_rdst *rdst, bool did_rsc); + int vxlan_vni_in_use(struct net *src_net, struct vxlan_dev *vxlan, +diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c +index 06d19e9..af38b5b 100644 +--- a/drivers/net/vxlan/vxlan_vnifilter.c ++++ b/drivers/net/vxlan/vxlan_vnifilter.c +@@ -497,7 +497,7 @@ static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni, + vni, + vni, + dst->remote_ifindex, +- NTF_SELF, 0, true, extack); ++ NTF_SELF, 0, true, extack, 0); + if (err) { + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + return err; +diff --git a/include/net/neighbour.h b/include/net/neighbour.h +index a44f262..5804014 100644 +--- a/include/net/neighbour.h ++++ b/include/net/neighbour.h +@@ -266,13 +266,15 @@ static inline void *neighbour_priv(const struct neighbour *n) + #define NEIGH_UPDATE_F_EXT_LEARNED BIT(5) + #define NEIGH_UPDATE_F_ISROUTER BIT(6) + #define NEIGH_UPDATE_F_ADMIN BIT(7) ++#define NEIGH_UPDATE_F_EXT_MH_PEER_SYNC BIT(8) + + /* In-kernel representation for NDA_FLAGS_EXT flags: */ + #define NTF_OLD_MASK 0xff + #define NTF_EXT_SHIFT 8 +-#define NTF_EXT_MASK (NTF_EXT_MANAGED) ++#define NTF_EXT_MASK (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC) + + #define NTF_MANAGED (NTF_EXT_MANAGED << NTF_EXT_SHIFT) ++#define NTF_MH_PEER_SYNC (NTF_EXT_MH_PEER_SYNC << NTF_EXT_SHIFT) + + extern const struct nla_policy nda_policy[]; + +diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h +index 5e67a7e..12412eb 100644 +--- a/include/uapi/linux/neighbour.h ++++ b/include/uapi/linux/neighbour.h +@@ -54,6 +54,7 @@ enum { + /* Extended flags under NDA_FLAGS_EXT: */ + #define NTF_EXT_MANAGED (1 << 0) + #define NTF_EXT_LOCKED (1 << 1) ++#define NTF_EXT_MH_PEER_SYNC (1 << 3) + + /* + * Neighbor Cache Entry States. +diff --git a/net/bridge/br.c b/net/bridge/br.c +index 2cab878..72b3b54 100644 +--- a/net/bridge/br.c ++++ b/net/bridge/br.c +@@ -167,7 +167,7 @@ static int br_switchdev_event(struct notifier_block *unused, + fdb_info = ptr; + err = br_fdb_external_learn_add(br, p, fdb_info->addr, + fdb_info->vid, +- fdb_info->locked, false); ++ fdb_info->locked, false, 0); + if (err) { + err = notifier_from_errno(err); + break; +diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c +index 642b8cc..202d791 100644 +--- a/net/bridge/br_fdb.c ++++ b/net/bridge/br_fdb.c +@@ -125,6 +125,8 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, + ndm->ndm_flags |= NTF_STICKY; + if (test_bit(BR_FDB_LOCKED, &fdb->flags)) + ext_flags |= NTF_EXT_LOCKED; ++ if (test_bit(BR_FDB_REMOTE_SYNC, &fdb->flags)) ++ ext_flags |= NTF_EXT_MH_PEER_SYNC; + + if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr)) + goto nla_put_failure; +@@ -920,9 +922,12 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, + fdb_modified = true; + /* Take over HW learned entry */ + if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN, +- &fdb->flags))) ++ &fdb->flags))) { + clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, + &fdb->flags); ++ clear_bit(BR_FDB_REMOTE_SYNC, ++ &fdb->flags); ++ } + /* Clear locked flag when roaming to an + * unlocked port. + */ +@@ -1159,7 +1164,7 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, + static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br, + struct net_bridge_port *p, const unsigned char *addr, + u16 nlh_flags, u16 vid, struct nlattr *nfea_tb[], +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + int err = 0; + +@@ -1183,7 +1188,8 @@ static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br, + "FDB entry towards bridge must be permanent"); + return -EINVAL; + } +- err = br_fdb_external_learn_add(br, p, addr, vid, false, true); ++ err = br_fdb_external_learn_add(br, p, addr, vid, false, true, ++ ext_flags); + } else { + spin_lock_bh(&br->hash_lock); + err = fdb_add_entry(br, p, addr, ndm, nlh_flags, vid, nfea_tb); +@@ -1246,6 +1252,9 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + return -EINVAL; + } + ++ if (tb[NDA_FLAGS_EXT]) ++ ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); ++ + if (tb[NDA_FDB_EXT_ATTRS]) { + attr = tb[NDA_FDB_EXT_ATTRS]; + err = nla_parse_nested(nfea_tb, NFEA_MAX, attr, +@@ -1265,10 +1274,10 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + + /* VID was specified, so use it. */ + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid, nfea_tb, +- extack); ++ extack, ext_flags); + } else { + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, nfea_tb, +- extack); ++ extack, ext_flags); + if (err || !vg || !vg->num_vlans) + goto out; + +@@ -1280,7 +1289,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + if (!br_vlan_should_use(v)) + continue; + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, v->vid, +- nfea_tb, extack); ++ nfea_tb, extack, ext_flags); + if (err) + goto out; + } +@@ -1422,7 +1431,7 @@ void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p) + + int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + const unsigned char *addr, u16 vid, bool locked, +- bool swdev_notify) ++ bool swdev_notify, u32 ext_flags) + { + struct net_bridge_fdb_entry *fdb; + bool modified = false; +@@ -1448,6 +1457,9 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + if (locked) + flags |= BIT(BR_FDB_LOCKED); + ++ if (ext_flags & NTF_EXT_MH_PEER_SYNC) ++ flags |= BIT(BR_FDB_REMOTE_SYNC); ++ + fdb = fdb_create(br, p, addr, vid, flags); + if (!fdb) { + err = -ENOMEM; +@@ -1481,6 +1493,15 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + modified = true; + } + ++ if (test_bit(BR_FDB_REMOTE_SYNC, &fdb->flags) != ++ !!(ext_flags & NTF_EXT_MH_PEER_SYNC)) { ++ modified = true; ++ if (ext_flags & NTF_EXT_MH_PEER_SYNC) ++ set_bit(BR_FDB_REMOTE_SYNC, &fdb->flags); ++ else ++ clear_bit(BR_FDB_REMOTE_SYNC, &fdb->flags); ++ } ++ + if (swdev_notify) + set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags); + +diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h +index 6a1bce8..bb431db 100644 +--- a/net/bridge/br_private.h ++++ b/net/bridge/br_private.h +@@ -277,6 +277,7 @@ enum { + BR_FDB_NOTIFY_INACTIVE, + BR_FDB_LOCKED, + BR_FDB_DYNAMIC_LEARNED, ++ BR_FDB_REMOTE_SYNC, + }; + + struct net_bridge_fdb_key { +@@ -869,7 +870,7 @@ int br_fdb_sync_static(struct net_bridge *br, struct net_bridge_port *p); + void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p); + int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + const unsigned char *addr, u16 vid, +- bool locked, bool swdev_notify); ++ bool locked, bool swdev_notify, u32 ext_flags); + int br_fdb_external_learn_del(struct net_bridge *br, struct net_bridge_port *p, + const unsigned char *addr, u16 vid, + bool swdev_notify); +diff --git a/net/core/neighbour.c b/net/core/neighbour.c +index 8082cc6..019aec2 100644 +--- a/net/core/neighbour.c ++++ b/net/core/neighbour.c +@@ -186,6 +186,9 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + + ndm_flags = (flags & NEIGH_UPDATE_F_EXT_LEARNED) ? NTF_EXT_LEARNED : 0; + ndm_flags |= (flags & NEIGH_UPDATE_F_MANAGED) ? NTF_MANAGED : 0; ++ ndm_flags |= (flags & NEIGH_UPDATE_F_EXT_MH_PEER_SYNC) ? ++ NTF_MH_PEER_SYNC : ++ 0; + + if ((old_flags ^ ndm_flags) & NTF_EXT_LEARNED) { + if (ndm_flags & NTF_EXT_LEARNED) +@@ -203,6 +206,14 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + *notify = 1; + *managed_update = true; + } ++ ++ if ((old_flags ^ ndm_flags) & NTF_MH_PEER_SYNC) { ++ if (ndm_flags & NTF_MH_PEER_SYNC) ++ neigh->flags |= NTF_MH_PEER_SYNC; ++ else ++ neigh->flags &= ~NTF_MH_PEER_SYNC; ++ *notify = 1; ++ } + } + + static bool neigh_del(struct neighbour *n, struct neighbour __rcu **np, +@@ -2104,6 +2115,8 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + flags |= NEIGH_UPDATE_F_MANAGED; + if (ndm_flags & NTF_USE) + flags |= NEIGH_UPDATE_F_USE; ++ if (ndm_flags & NTF_MH_PEER_SYNC) ++ flags |= NEIGH_UPDATE_F_EXT_MH_PEER_SYNC; + + err = __neigh_update(neigh, lladdr, ndm->ndm_state, flags, + NETLINK_CB(skb).portid, extack); +-- +2.34.1 diff --git a/patches-sonic/0001-x86-CPU-AMD-Print-the-reason-for-the-last-reset.patch b/patches-sonic/0001-x86-CPU-AMD-Print-the-reason-for-the-last-reset.patch new file mode 100644 index 000000000..7f247882d --- /dev/null +++ b/patches-sonic/0001-x86-CPU-AMD-Print-the-reason-for-the-last-reset.patch @@ -0,0 +1,519 @@ +From d0dc75304bc26dfb821f710ddf0d428484620af1 Mon Sep 17 00:00:00 2001 +From: Yazen Ghannam +Date: Tue, 22 Apr 2025 18:48:30 -0500 +Subject: [PATCH 1/2] x86/CPU/AMD: Print the reason for the last reset + +[ Upstream commit ab8131028710d009ab93d6bffd2a2749ade909b0 ] + +The following register contains bits that indicate the cause for the +previous reset. + + PMx000000C0 (FCH::PM::S5_RESET_STATUS) + +This is useful for debug. The reasons for reset are broken into 6 high level +categories. Decode it by category and print during boot. + +Specifics within a category are split off into debugging documentation. + +The register is accessed indirectly through a "PM" port in the FCH. Use +MMIO access in order to avoid restrictions with legacy port access. + +Use a late_initcall() to ensure that MMIO has been set up before trying to +access the register. + +This register was introduced with AMD Family 17h, so avoid access on older +families. There is no CPUID feature bit for this register. + + [ bp: Simplify the reason dumping loop. + - merge a fix to not access an array element after the last one: + https://lore.kernel.org/r/20250505133609.83933-1-superm1@kernel.org + Reported-by: James Dutton + ] + + [ mingo: + - Use consistent .rst formatting + - Fix 'Sleep' class field to 'ACPI-State' + - Standardize pin messages around the 'tripped' verbiage + - Remove reference to ring-buffer printing & simplify the wording + - Use curly braces for multi-line conditional statements ] + +Signed-off-by: Yazen Ghannam +Co-developed-by: Mario Limonciello +Signed-off-by: Mario Limonciello +Signed-off-by: Borislav Petkov (AMD) +Signed-off-by: Ingo Molnar +Signed-off-by: Borislav Petkov (AMD) +Link: https://lore.kernel.org/20250422234830.2840784-6-superm1@kernel.org +--- + Documentation/arch/x86/amd-debugging.rst | 368 +++++++++++++++++++++++ + arch/x86/include/asm/amd/fch.h | 13 + + arch/x86/kernel/cpu/amd.c | 54 ++++ + 3 files changed, 435 insertions(+) + create mode 100644 Documentation/arch/x86/amd-debugging.rst + create mode 100644 arch/x86/include/asm/amd/fch.h + +diff --git a/Documentation/arch/x86/amd-debugging.rst b/Documentation/arch/x86/amd-debugging.rst +new file mode 100644 +index 000000000000..d92bf59d62c7 +--- /dev/null ++++ b/Documentation/arch/x86/amd-debugging.rst +@@ -0,0 +1,368 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++Debugging AMD Zen systems +++++++++++++++++++++++++++ ++ ++Introduction ++============ ++ ++This document describes techniques that are useful for debugging issues with ++AMD Zen systems. It is intended for use by developers and technical users ++to help identify and resolve issues. ++ ++S3 vs s2idle ++============ ++ ++On AMD systems, it's not possible to simultaneously support suspend-to-RAM (S3) ++and suspend-to-idle (s2idle). To confirm which mode your system supports you ++can look at ``cat /sys/power/mem_sleep``. If it shows ``s2idle [deep]`` then ++*S3* is supported. If it shows ``[s2idle]`` then *s2idle* is ++supported. ++ ++On systems that support *S3*, the firmware will be utilized to put all hardware into ++the appropriate low power state. ++ ++On systems that support *s2idle*, the kernel will be responsible for transitioning devices ++into the appropriate low power state. When all devices are in the appropriate low ++power state, the hardware will transition into a hardware sleep state. ++ ++After a suspend cycle you can tell how much time was spent in a hardware sleep ++state by looking at ``cat /sys/power/suspend_stats/last_hw_sleep``. ++ ++This flowchart explains how the AMD s2idle suspend flow works. ++ ++.. kernel-figure:: suspend.svg ++ ++This flowchart explains how the amd s2idle resume flow works. ++ ++.. kernel-figure:: resume.svg ++ ++s2idle debugging tool ++===================== ++ ++As there are a lot of places that problems can occur, a debugging tool has been ++created at ++`amd-debug-tools `_ ++that can help test for common problems and offer suggestions. ++ ++If you have an s2idle issue, it's best to start with this and follow instructions ++from its findings. If you continue to have an issue, raise a bug with the ++report generated from this script to ++`drm/amd gitlab `_. ++ ++Spurious s2idle wakeups from an IRQ ++=================================== ++ ++Spurious wakeups will generally have an IRQ set to ``/sys/power/pm_wakeup_irq``. ++This can be matched to ``/proc/interrupts`` to determine what device woke the system. ++ ++If this isn't enough to debug the problem, then the following sysfs files ++can be set to add more verbosity to the wakeup process: :: ++ ++ # echo 1 | sudo tee /sys/power/pm_debug_messages ++ # echo 1 | sudo tee /sys/power/pm_print_times ++ ++After making those changes, the kernel will display messages that can ++be traced back to kernel s2idle loop code as well as display any active ++GPIO sources while waking up. ++ ++If the wakeup is caused by the ACPI SCI, additional ACPI debugging may be ++needed. These commands can enable additional trace data: :: ++ ++ # echo enable | sudo tee /sys/module/acpi/parameters/trace_state ++ # echo 1 | sudo tee /sys/module/acpi/parameters/aml_debug_output ++ # echo 0x0800000f | sudo tee /sys/module/acpi/parameters/debug_level ++ # echo 0xffff0000 | sudo tee /sys/module/acpi/parameters/debug_layer ++ ++Spurious s2idle wakeups from a GPIO ++=================================== ++ ++If a GPIO is active when waking up the system ideally you would look at the ++schematic to determine what device it is associated with. If the schematic ++is not available, another tactic is to look at the ACPI _EVT() entry ++to determine what device is notified when that GPIO is active. ++ ++For a hypothetical example, say that GPIO 59 woke up the system. You can ++look at the SSDT to determine what device is notified when GPIO 59 is active. ++ ++First convert the GPIO number into hex. :: ++ ++ $ python3 -c "print(hex(59))" ++ 0x3b ++ ++Next determine which ACPI table has the ``_EVT`` entry. For example: :: ++ ++ $ sudo grep EVT /sys/firmware/acpi/tables/SSDT* ++ grep: /sys/firmware/acpi/tables/SSDT27: binary file matches ++ ++Decode this table:: ++ ++ $ sudo cp /sys/firmware/acpi/tables/SSDT27 . ++ $ sudo iasl -d SSDT27 ++ ++Then look at the table and find the matching entry for GPIO 0x3b. :: ++ ++ Case (0x3B) ++ { ++ M000 (0x393B) ++ M460 (" Notify (\\_SB.PCI0.GP17.XHC1, 0x02)\n", Zero, Zero, Zero, Zero, Zero, Zero) ++ Notify (\_SB.PCI0.GP17.XHC1, 0x02) // Device Wake ++ } ++ ++You can see in this case that the device ``\_SB.PCI0.GP17.XHC1`` is notified ++when GPIO 59 is active. It's obvious this is an XHCI controller, but to go a ++step further you can figure out which XHCI controller it is by matching it to ++ACPI.:: ++ ++ $ grep "PCI0.GP17.XHC1" /sys/bus/acpi/devices/*/path ++ /sys/bus/acpi/devices/device:2d/path:\_SB_.PCI0.GP17.XHC1 ++ /sys/bus/acpi/devices/device:2e/path:\_SB_.PCI0.GP17.XHC1.RHUB ++ /sys/bus/acpi/devices/device:2f/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT1 ++ /sys/bus/acpi/devices/device:30/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT1.CAM0 ++ /sys/bus/acpi/devices/device:31/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT1.CAM1 ++ /sys/bus/acpi/devices/device:32/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT2 ++ /sys/bus/acpi/devices/LNXPOWER:0d/path:\_SB_.PCI0.GP17.XHC1.PWRS ++ ++Here you can see it matches to ``device:2d``. Look at the ``physical_node`` ++to determine what PCI device that actually is. :: ++ ++ $ ls -l /sys/bus/acpi/devices/device:2d/physical_node ++ lrwxrwxrwx 1 root root 0 Feb 12 13:22 /sys/bus/acpi/devices/device:2d/physical_node -> ../../../../../pci0000:00/0000:00:08.1/0000:c2:00.4 ++ ++So there you have it: the PCI device associated with this GPIO wakeup was ``0000:c2:00.4``. ++ ++The ``amd_s2idle.py`` script will capture most of these artifacts for you. ++ ++s2idle PM debug messages ++======================== ++ ++During the s2idle flow on AMD systems, the ACPI LPS0 driver is responsible ++to check all uPEP constraints. Failing uPEP constraints does not prevent ++s0i3 entry. This means that if some constraints are not met, it is possible ++the kernel may attempt to enter s2idle even if there are some known issues. ++ ++To activate PM debugging, either specify ``pm_debug_messagess`` kernel ++command-line option at boot or write to ``/sys/power/pm_debug_messages``. ++Unmet constraints will be displayed in the kernel log and can be ++viewed by logging tools that process kernel ring buffer like ``dmesg`` or ++``journalctl``." ++ ++If the system freezes on entry/exit before these messages are flushed, a ++useful debugging tactic is to unbind the ``amd_pmc`` driver to prevent ++notification to the platform to start s0i3 entry. This will stop the ++system from freezing on entry or exit and let you view all the failed ++constraints. :: ++ ++ cd /sys/bus/platform/drivers/amd_pmc ++ ls | grep AMD | sudo tee unbind ++ ++After doing this, run the suspend cycle and look specifically for errors around: :: ++ ++ ACPI: LPI: Constraint not met; min power state:%s current power state:%s ++ ++Historical examples of s2idle issues ++==================================== ++ ++To help understand the types of issues that can occur and how to debug them, ++here are some historical examples of s2idle issues that have been resolved. ++ ++Core offlining ++-------------- ++An end user had reported that taking a core offline would prevent the system ++from properly entering s0i3. This was debugged using internal AMD tools ++to capture and display a stream of metrics from the hardware showing what changed ++when a core was offlined. It was determined that the hardware didn't get ++notification the offline cores were in the deepest state, and so it prevented ++CPU from going into the deepest state. The issue was debugged to a missing ++command to put cores into C3 upon offline. ++ ++`commit d6b88ce2eb9d2 ("ACPI: processor idle: Allow playing dead in C3 state") `_ ++ ++Corruption after resume ++----------------------- ++A big problem that occurred with Rembrandt was that there was graphical ++corruption after resume. This happened because of a misalignment of PSP ++and driver responsibility. The PSP will save and restore DMCUB, but the ++driver assumed it needed to reset DMCUB on resume. ++This actually was a misalignment for earlier silicon as well, but was not ++observed. ++ ++`commit 79d6b9351f086 ("drm/amd/display: Don't reinitialize DMCUB on s0ix resume") `_ ++ ++Back to Back suspends fail ++-------------------------- ++When using a wakeup source that triggers the IRQ to wakeup, a bug in the ++pinctrl-amd driver may capture the wrong state of the IRQ and prevent the ++system going back to sleep properly. ++ ++`commit b8c824a869f22 ("pinctrl: amd: Don't save/restore interrupt status and wake status bits") `_ ++ ++Spurious timer based wakeup after 5 minutes ++------------------------------------------- ++The HPET was being used to program the wakeup source for the system, however ++this was causing a spurious wakeup after 5 minutes. The correct alarm to use ++was the ACPI alarm. ++ ++`commit 3d762e21d5637 ("rtc: cmos: Use ACPI alarm for non-Intel x86 systems too") `_ ++ ++Disk disappears after resume ++---------------------------- ++After resuming from s2idle, the NVME disk would disappear. This was due to the ++BIOS not specifying the _DSD StorageD3Enable property. This caused the NVME ++driver not to put the disk into the expected state at suspend and to fail ++on resume. ++ ++`commit e79a10652bbd3 ("ACPI: x86: Force StorageD3Enable on more products") `_ ++ ++Spurious IRQ1 ++------------- ++A number of Renoir, Lucienne, Cezanne, & Barcelo platforms have a ++platform firmware bug where IRQ1 is triggered during s0i3 resume. ++ ++This was fixed in the platform firmware, but a number of systems didn't ++receive any more platform firmware updates. ++ ++`commit 8e60615e89321 ("platform/x86/amd: pmc: Disable IRQ1 wakeup for RN/CZN") `_ ++ ++Hardware timeout ++---------------- ++The hardware performs many actions besides accepting the values from ++amd-pmc driver. As the communication path with the hardware is a mailbox, ++it's possible that it might not respond quickly enough. ++This issue manifested as a failure to suspend: :: ++ ++ PM: dpm_run_callback(): acpi_subsys_suspend_noirq+0x0/0x50 returns -110 ++ amd_pmc AMDI0005:00: PM: failed to suspend noirq: error -110 ++ ++The timing problem was identified by comparing the values of the idle mask. ++ ++`commit 3c3c8e88c8712 ("platform/x86: amd-pmc: Increase the response register timeout") `_ ++ ++Failed to reach hardware sleep state with panel on ++-------------------------------------------------- ++On some Strix systems certain panels were observed to block the system from ++entering a hardware sleep state if the internal panel was on during the sequence. ++ ++Even though the panel got turned off during suspend it exposed a timing problem ++where an interrupt caused the display hardware to wake up and block low power ++state entry. ++ ++`commit 40b8c14936bd2 ("drm/amd/display: Disable unneeded hpd interrupts during dm_init") `_ ++ ++Runtime power consumption issues ++================================ ++ ++Runtime power consumption is influenced by many factors, including but not ++limited to the configuration of the PCIe Active State Power Management (ASPM), ++the display brightness, the EPP policy of the CPU, and the power management ++of the devices. ++ ++ASPM ++---- ++For the best runtime power consumption, ASPM should be programmed as intended ++by the BIOS from the hardware vendor. To accomplish this the Linux kernel ++should be compiled with ``CONFIG_PCIEASPM_DEFAULT`` set to ``y`` and the ++sysfs file ``/sys/module/pcie_aspm/parameters/policy`` should not be modified. ++ ++Most notably, if L1.2 is not configured properly for any devices, the SoC ++will not be able to enter the deepest idle state. ++ ++EPP Policy ++---------- ++The ``energy_performance_preference`` sysfs file can be used to set a bias ++of efficiency or performance for a CPU. This has a direct relationship on ++the battery life when more heavily biased towards performance. ++ ++ ++BIOS debug messages ++=================== ++ ++Most OEM machines don't have a serial UART for outputting kernel or BIOS ++debug messages. However BIOS debug messages are useful for understanding ++both BIOS bugs and bugs with the Linux kernel drivers that call BIOS AML. ++ ++As the BIOS on most OEM AMD systems are based off an AMD reference BIOS, ++the infrastructure used for exporting debugging messages is often the same ++as AMD reference BIOS. ++ ++Manually Parsing ++---------------- ++There is generally an ACPI method ``\M460`` that different paths of the AML ++will call to emit a message to the BIOS serial log. This method takes ++7 arguments, with the first being a string and the rest being optional ++integers:: ++ ++ Method (M460, 7, Serialized) ++ ++Here is an example of a string that BIOS AML may call out using ``\M460``:: ++ ++ M460 (" OEM-ASL-PCIe Address (0x%X)._REG (%d %d) PCSA = %d\n", DADR, Arg0, Arg1, PCSA, Zero, Zero) ++ ++Normally when executed, the ``\M460`` method would populate the additional ++arguments into the string. In order to get these messages from the Linux ++kernel a hook has been added into ACPICA that can capture the *arguments* ++sent to ``\M460`` and print them to the kernel ring buffer. ++For example the following message could be emitted into kernel ring buffer:: ++ ++ extrace-0174 ex_trace_args : " OEM-ASL-PCIe Address (0x%X)._REG (%d %d) PCSA = %d\n", ec106000, 2, 1, 1, 0, 0 ++ ++In order to get these messages, you need to compile with ``CONFIG_ACPI_DEBUG`` ++and then turn on the following ACPICA tracing parameters. ++This can be done either on the kernel command line or at runtime: ++ ++* ``acpi.trace_method_name=\M460`` ++* ``acpi.trace_state=method`` ++ ++NOTE: These can be very noisy at bootup. If you turn these parameters on ++the kernel command, please also consider turning up ``CONFIG_LOG_BUF_SHIFT`` ++to a larger size such as 17 to avoid losing early boot messages. ++ ++Tool assisted Parsing ++--------------------- ++As mentioned above, parsing by hand can be tedious, especially with a lot of ++messages. To help with this, a tool has been created at ++`amd-debug-tools `_ ++to help parse the messages. ++ ++Random reboot issues ++==================== ++ ++When a random reboot occurs, the high-level reason for the reboot is stored ++in a register that will persist onto the next boot. ++ ++There are 6 classes of reasons for the reboot: ++ * Software induced ++ * Power state transition ++ * Pin induced ++ * Hardware induced ++ * Remote reset ++ * Internal CPU event ++ ++.. csv-table:: ++ :header: "Bit", "Type", "Reason" ++ :align: left ++ ++ "0", "Pin", "thermal pin BP_THERMTRIP_L was tripped" ++ "1", "Pin", "power button was pressed for 4 seconds" ++ "2", "Pin", "shutdown pin was tripped" ++ "4", "Remote", "remote ASF power off command was received" ++ "9", "Internal", "internal CPU thermal limit was tripped" ++ "16", "Pin", "system reset pin BP_SYS_RST_L was tripped" ++ "17", "Software", "software issued PCI reset" ++ "18", "Software", "software wrote 0x4 to reset control register 0xCF9" ++ "19", "Software", "software wrote 0x6 to reset control register 0xCF9" ++ "20", "Software", "software wrote 0xE to reset control register 0xCF9" ++ "21", "ACPI-state", "ACPI power state transition occurred" ++ "22", "Pin", "keyboard reset pin KB_RST_L was tripped" ++ "23", "Internal", "internal CPU shutdown event occurred" ++ "24", "Hardware", "system failed to boot before failed boot timer expired" ++ "25", "Hardware", "hardware watchdog timer expired" ++ "26", "Remote", "remote ASF reset command was received" ++ "27", "Internal", "an uncorrected error caused a data fabric sync flood event" ++ "29", "Internal", "FCH and MP1 failed warm reset handshake" ++ "30", "Internal", "a parity error occurred" ++ "31", "Internal", "a software sync flood event occurred" ++ ++This information is read by the kernel at bootup and printed into ++the syslog. When a random reboot occurs this message can be helpful ++to determine the next component to debug. +diff --git a/arch/x86/include/asm/amd/fch.h b/arch/x86/include/asm/amd/fch.h +new file mode 100644 +index 000000000000..2cf5153edbc2 +--- /dev/null ++++ b/arch/x86/include/asm/amd/fch.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef _ASM_X86_AMD_FCH_H_ ++#define _ASM_X86_AMD_FCH_H_ ++ ++#define FCH_PM_BASE 0xFED80300 ++ ++/* Register offsets from PM base: */ ++#define FCH_PM_DECODEEN 0x00 ++#define FCH_PM_DECODEEN_SMBUS0SEL GENMASK(20, 19) ++#define FCH_PM_SCRATCH 0x80 ++#define FCH_PM_S5_RESET_STATUS 0xC0 ++ ++#endif /* _ASM_X86_AMD_FCH_H_ */ +diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c +index 823f44f7bc94..c3194101a92a 100644 +--- a/arch/x86/kernel/cpu/amd.c ++++ b/arch/x86/kernel/cpu/amd.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1216,3 +1217,56 @@ void amd_check_microcode(void) + if (cpu_feature_enabled(X86_FEATURE_ZEN2)) + on_each_cpu(zenbleed_check_cpu, NULL, 1); + } ++ ++static const char * const s5_reset_reason_txt[] = { ++ [0] = "thermal pin BP_THERMTRIP_L was tripped", ++ [1] = "power button was pressed for 4 seconds", ++ [2] = "shutdown pin was tripped", ++ [4] = "remote ASF power off command was received", ++ [9] = "internal CPU thermal limit was tripped", ++ [16] = "system reset pin BP_SYS_RST_L was tripped", ++ [17] = "software issued PCI reset", ++ [18] = "software wrote 0x4 to reset control register 0xCF9", ++ [19] = "software wrote 0x6 to reset control register 0xCF9", ++ [20] = "software wrote 0xE to reset control register 0xCF9", ++ [21] = "ACPI power state transition occurred", ++ [22] = "keyboard reset pin KB_RST_L was tripped", ++ [23] = "internal CPU shutdown event occurred", ++ [24] = "system failed to boot before failed boot timer expired", ++ [25] = "hardware watchdog timer expired", ++ [26] = "remote ASF reset command was received", ++ [27] = "an uncorrected error caused a data fabric sync flood event", ++ [29] = "FCH and MP1 failed warm reset handshake", ++ [30] = "a parity error occurred", ++ [31] = "a software sync flood event occurred", ++}; ++ ++static __init int print_s5_reset_status_mmio(void) ++{ ++ unsigned long value; ++ void __iomem *addr; ++ int i; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_ZEN)) ++ return 0; ++ ++ addr = ioremap(FCH_PM_BASE + FCH_PM_S5_RESET_STATUS, sizeof(value)); ++ if (!addr) ++ return 0; ++ ++ value = ioread32(addr); ++ iounmap(addr); ++ ++ for (i = 0; i < ARRAY_SIZE(s5_reset_reason_txt); i++) { ++ if (!(value & BIT(i))) ++ continue; ++ ++ if (s5_reset_reason_txt[i]) { ++ pr_info("x86/amd: Previous system reset reason [0x%08lx]: %s\n", ++ value, s5_reset_reason_txt[i]); ++ } ++ } ++ ++ return 0; ++} ++late_initcall(print_s5_reset_status_mmio); +-- +2.43.0 + diff --git a/patches-sonic/0002-Adding-support-LTC3888.patch b/patches-sonic/0002-Adding-support-LTC3888.patch new file mode 100644 index 000000000..c0d023cc0 --- /dev/null +++ b/patches-sonic/0002-Adding-support-LTC3888.patch @@ -0,0 +1,122 @@ +From 1f7f624747ff66df7b9bf02e98541ebb81447f8c Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 08:29:14 +0000 +Subject: [PATCH 02/27] Adding support LTC3888 + +This patch adds support for the LTC3888 VRM. + +Datasheet: +https://www.analog.com/media/en/technical-documentation/data-sheets/ltc3888-3888-1-3888-2.pdf + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + drivers/hwmon/pmbus/ltc2978.c | 43 ++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 42 insertions(+), 1 deletion(-) + +diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c +index 73a86f4..6ad6d0d 100644 +--- a/drivers/hwmon/pmbus/ltc2978.c ++++ b/drivers/hwmon/pmbus/ltc2978.c +@@ -23,7 +23,7 @@ enum chips { + /* Managers */ + ltc2972, ltc2974, ltc2975, ltc2977, ltc2978, ltc2979, ltc2980, + /* Controllers */ +- ltc3880, ltc3882, ltc3883, ltc3884, ltc3886, ltc3887, ltc3889, ltc7132, ltc7880, ++ ltc3880, ltc3882, ltc3883, ltc3884, ltc3886, ltc3887, ltc3888, ltc3889, ltc7132, ltc7880, + /* Modules */ + ltm2987, ltm4664, ltm4675, ltm4676, ltm4677, ltm4678, ltm4680, ltm4686, + ltm4700, +@@ -53,6 +53,9 @@ enum chips { + /* LTC3883, LTC3884, LTC3886, LTC3889, LTC7132, LTC7880 */ + #define LTC3883_MFR_IIN_PEAK 0xe1 + ++/* LTC3888 only */ ++#define LTC3888_MFR_TOTAL_IOUT 0xe1 ++ + /* LTC2975 only */ + #define LTC2975_MFR_IIN_PEAK 0xc4 + #define LTC2975_MFR_IIN_MIN 0xc5 +@@ -78,6 +81,8 @@ enum chips { + #define LTC3884_ID 0x4C00 + #define LTC3886_ID 0x4600 + #define LTC3887_ID 0x4700 ++#define LTC3888_ID 0x4800 ++#define LTC3888_ID_D1 0x4880 /* Dash 1 */ + #define LTC3889_ID 0x4900 + #define LTC7132_ID 0x4CE0 + #define LTC7880_ID 0x49E0 +@@ -467,6 +472,26 @@ static int ltc3883_read_word_data(struct i2c_client *client, int page, + return ret; + } + ++static int ltc3888_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct ltc2978_data *data = to_ltc2978_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIRT_READ_IOUT_AVG: ++ ret = ltc_read_word_data(client, page, 0xff, ++ LTC3888_MFR_TOTAL_IOUT); ++ data->iout_max[page] = lin11_to_val(ret); ++ break; ++ default: ++ ret = ltc3880_read_word_data(client, page, phase, reg); ++ break; ++ } ++ return ret; ++} ++ + static int ltc2978_clear_peaks(struct ltc2978_data *data, + struct i2c_client *client, int page) + { +@@ -546,6 +571,7 @@ static int ltc2978_write_word_data(struct i2c_client *client, int page, + {"ltc3884", ltc3884}, + {"ltc3886", ltc3886}, + {"ltc3887", ltc3887}, ++ {"ltc3888", ltc3888}, + {"ltc3889", ltc3889}, + {"ltc7132", ltc7132}, + {"ltc7880", ltc7880}, +@@ -650,6 +676,8 @@ static int ltc2978_get_id(struct i2c_client *client) + return ltc3886; + else if (chip_id == LTC3887_ID) + return ltc3887; ++ else if (chip_id == LTC3888_ID || chip_id == LTC3888_ID_D1) ++ return ltc3888; + else if (chip_id == LTC3889_ID) + return ltc3889; + else if (chip_id == LTC7132_ID) +@@ -854,6 +882,18 @@ static int ltc2978_probe(struct i2c_client *client) + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; ++ case ltc3888: ++ data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; ++ info->read_word_data = ltc3888_read_word_data; ++ info->pages = LTC3880_NUM_PAGES; ++ info->func[0] = PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT ++ | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP ++ | PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT ++ | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2; ++ info->func[1] = PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT ++ | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT ++ | PMBUS_HAVE_TEMP; ++ break; + default: + return -ENODEV; + } +@@ -905,6 +945,7 @@ static int ltc2978_probe(struct i2c_client *client) + { .compatible = "lltc,ltc3884" }, + { .compatible = "lltc,ltc3886" }, + { .compatible = "lltc,ltc3887" }, ++ { .compatible = "lltc,ltc3888" }, + { .compatible = "lltc,ltc3889" }, + { .compatible = "lltc,ltc7132" }, + { .compatible = "lltc,ltc7880" }, +-- +1.8.3.1 + diff --git a/patches-sonic/0002-DTS-Aspeed-Nvidia-spc6-bmc-dts.patch b/patches-sonic/0002-DTS-Aspeed-Nvidia-spc6-bmc-dts.patch new file mode 100644 index 000000000..af194c3e8 --- /dev/null +++ b/patches-sonic/0002-DTS-Aspeed-Nvidia-spc6-bmc-dts.patch @@ -0,0 +1,683 @@ +From c49814b1f798db5713b04f09ff89b5b9c2907330 Mon Sep 17 00:00:00 2001 +From: Oleksandr Ivantsiv +Date: Fri, 8 May 2026 00:20:22 +0300 +Subject: [PATCH 2/2] DTS: Aspeed: Nvidia spc6 bmc dts + +Add DTS for AST2700 A2 for Nvidia. + +Signed-off-by: Oleksandr Ivantsiv +--- + arch/arm64/boot/dts/aspeed/Makefile | 3 +- + .../aspeed/aspeed-nvidia-spc6-bmc-core.dtsi | 125 +++++ + .../dts/aspeed/aspeed-nvidia-spc6-bmc.dts | 467 ++++++++++++++++++ + .../boot/dts/aspeed/nvidia-ast27xx-irot.dtsi | 25 + + .../boot/dts/aspeed/nvidia-ast27xx-sunda.dtsi | 8 + + 5 files changed, 627 insertions(+), 1 deletion(-) + create mode 100644 arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-core.dtsi + create mode 100644 arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts + create mode 100644 arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot.dtsi + create mode 100644 arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda.dtsi + +diff --git a/arch/arm64/boot/dts/aspeed/Makefile b/arch/arm64/boot/dts/aspeed/Makefile +index a481a41b5..410778816 100644 +--- a/arch/arm64/boot/dts/aspeed/Makefile ++++ b/arch/arm64/boot/dts/aspeed/Makefile +@@ -33,4 +33,5 @@ dtb-$(CONFIG_ARCH_ASPEED) += \ + ### + + dtb-$(CONFIG_ARCH_ASPEED) += \ +- aspeed-nvidia-spc6-a1-bmc.dtb ++ aspeed-nvidia-spc6-a1-bmc.dtb \ ++ aspeed-nvidia-spc6-bmc.dtb +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-core.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-core.dtsi +new file mode 100644 +index 000000000..d3b9eb76a +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-core.dtsi +@@ -0,0 +1,125 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++ ++&gpio1 { ++ gpio-line-names = ++ ++ /* gpio-line-names are the combination of - , "" is the placeholder for the unused pins*/ ++ ++ /* 208 (26*8) 3.3V GPIOs */ ++ ++ /*A0-A7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*B0-B7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*C0-C7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*D0-D7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*E0-E7*/ ++ "", "GP_BMC_STBY_PWR_EN", "GP_STBY_PG", "GP_BMC_PRESNT0_L", "", "", "", "", ++ ++ /*F0-F7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*G0-G7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*H0-H7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*I0-I7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*J0-J7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*K0-K7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*L0-L7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*M0-M7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*N0-N7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*O0-O7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*P0-P7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*Q0-Q7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*R0-R7*/ ++ "GP_BMC_EXP_INT_L", "", "", "GP_BMC_READY", "GP_APML_SMB_ALERT_L", "", "", "GP_MGMT_CPLD_BMC_INT_L", ++ ++ /*S0-S7*/ ++ "GP_BMC_GPIO_EXP_RESET_L", "GP_BMC_PWR_CYCLE_BMC", "", "", "", "", "", "", ++ ++ /*T0-T7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*U0-U7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*V0-V7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*W0-W7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*X0-X7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*Y0-Y7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*Z0-Z7*/ ++ "", "", "", "", "", "", "", ""; ++ ++ bmc_ready { ++ gpio-hog; ++ gpios = ; ++ output-high; ++ line-name = "gpio-bmc-ready"; ++ }; ++ ++ stby_enable { ++ gpio-hog; ++ gpios = ; ++ output-high; ++ line-name = "gpio-stby-enable"; ++ }; ++ ++}; ++ ++&gpio0 { ++ /* 36 1.8V GPIOs */ ++ gpio-line-names = ++ /*A0-A7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*B0-B7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*C0-C7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*D0-D7*/ ++ "", "", "", "", "", "", "", "", ++ ++ /*E0-E7*/ ++ "", "", "", "", "", "", "", ""; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +new file mode 100644 +index 000000000..81f4beeb8 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +@@ -0,0 +1,467 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/dts-v1/; ++ ++#include "aspeed-nvidia-spc6-bmc-core.dtsi" ++#include ++#include ++#include ++#include "nvidia-ast27xx-sunda.dtsi" ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700-A2 SPC6 CPU BMC"; ++ compatible = "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = &uart12; ++ bootargs = "console=ttyS12,115200 earlyprintk"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ }; ++}; ++ ++/* ++ * SPI0: BIOS recovery (CS0, CPLD selects which flash) and TPM (CS1, AST2700 only). ++ * AST2700 uses &spi0; AST2600 uses &spi1raw. ++ * Remove default flash@0/flash@1 from dtsi so spidev and tpm are the only children. ++ */ ++&spi0 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ /delete-node/ flash@0; ++ /delete-node/ flash@1; ++ spidev@0 { ++ /* BIOS flash: CPLD (spi_chnl_select) selects which of two flashes */ ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ status = "okay"; ++ }; ++ /* TPM on chip select 1; both ST33 and Infineon SLB9672 use tpm_tis_spi */ ++ tpm@1 { ++ compatible = "st,st33htpm-spi", "infineon,slb9670", "tcg,tpm_tis-spi"; ++ reg = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++#if 0 ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x300000>; // 3MB ++ label = "u-boot"; ++ }; ++ ++ /* TODO: move to eMMC */ ++ u-boot-env@300000 { ++ reg = <0x300000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ ++ kernel@320000 { ++ reg = <0x320000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ rofs@c20000 { ++ reg = <0xc20000 0x3000000>; // 48MB ++ label = "rofs"; ++ }; ++ rwfs@3c20000 { ++ reg = <0x3C20000 0x3E0000>; // ~4MB ++ label = "rwfs"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++#endif ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ // BMC Debug Console ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "disabled"; ++}; ++ ++&mdio1 { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "disabled"; ++}; ++ ++&mac1 { ++ status = "disabled"; ++}; ++ ++&sgmii { ++ status = "okay"; ++ #phy-cells = <0>; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ reset-gpios = <&exp0 10 GPIO_ACTIVE_LOW>; ++ reset-delay-us = <30000>; ++ reset-post-delay-us = <30000>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mac2 { ++ status = "okay"; ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++ phys = <&sgmii>; ++ phy-names = "sgmii-phy"; ++}; ++ ++/* ++ * Enable port A as device (via the virtual hub) and port B as ++ * host by default on the eval board. This can be easily changed ++ * by replacing the override below with &ehci0 { ... } to enable ++ * host on both ports. ++ */ ++&vhuba0 { ++ status = "okay"; ++}; ++ ++/* USB PORT B - MCU and Power bank */ ++/* This is an assembly option */ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++/* I2C bus connecting BMC to CPU for IPMI */ ++/* Note : On Chameleon SPC6 on 2600 it will be i2c-0, on 2700 i2c-16 */ ++&i2c1 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz standard mode */ ++ /* BMC as I2C slave for IPMB */ ++ ipmb@20 { ++ compatible = "ipmb-dev"; ++ reg = <0x20>; /* BMC slave address (7-bit) */ ++ slave-dev; /* This is a slave device */ ++ }; ++}; ++ ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz */ ++ aspeed,hw-timeout-ms = <1000>; ++ multi-master; ++}; ++ ++&i2c4 { ++ status = "okay"; ++/* ++ // To be added later ++ rtc@6f { ++ compatible = "nxp,pcf85053a"; ++ reg = <0x6f>; ++ }; ++*/ ++}; ++ ++&i2c5 { ++ status = "okay"; ++ exp0: pcal6524@20 { ++ status = "okay"; ++ compatible = "nxp,pcal6524"; ++ reg = <0x20>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ gpio-line-names = ++ "RSRVD_0", "GP_EROT_RST_IN_L", ++ "GP_3V3_BMC_PHY_PG", "GP_BMC_PWR_CYCLE_3V3_MASK_L", ++ "GP_BMC_SPI_BUF_OE_L", "GP_BMC_SPI_TPM_BUF_OE_L", ++ "SE_CPLD_EROT_BOOT_COMPLETE", "GP_BMC_WP_CTRL_L", ++ "GP_BMC_REC_SPI_MUX1_SEL", "RSRVD_1", ++ "S_BMC_GPIO_PHY_RST_L", "GP_JTAG_BMC_CPLD_BURN_EN_L", ++ "GP_PWR_BANK_PRESENT_L", "RSRVD_2", ++ "RSRVD_3", "RSRVD_4", ++ "RSRVD_5", "RSRVD_6", ++ "RSRVD_7", "GP_PROD_CS_FLASH0_EN", ++ "RSRVD_8", "GP_PROD_CS_FLASH1_EN", ++ "RSRVD_9", "RSRVD_10"; ++ }; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c8 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++/* Used by I3C */ ++&i2c9 { ++ status = "disabled"; ++}; ++ ++&i2c10 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c11 { ++ status = "disabled"; ++}; ++ ++&i2c12 { ++ status = "disabled"; ++}; ++ ++/* CPU master to BMC slave recovery interface */ ++/* Note: On Chameleon SPC6 multi - on 2600 it will be i2c-9, on 2700 - i2c-13 */ ++&i2c13 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++ /* ++ * BMC slave device (slave-24c02 at 0x42) is to be instantiated via ++ * bmc-i2c-slave-setup.service at boot. This approach ensures the ++ * I2C_CLIENT_SLAVE flag (0x1042) is properly set, which is required ++ * for the Aspeed I2C controller to register the hardware slave address. ++ * ++ * DTS-based instantiation doesn't set the I2C_CLIENT_SLAVE flag, causing: ++ * "i2c_slave_register: client slave flag not set. You might see address collisions" ++ * and preventing proper slave address registration in hardware. ++ * ++ * Note: "multi-master" is not required. ++ * ++ * Manual instantiation: echo slave-24c02 0x1042 > /sys/bus/i2c/devices/i2c-9/new_device ++ * Consider to provide service: /etc/systemd/system/bmc-i2c-slave-setup.service ++ */ ++}; ++ ++&i2c14 { ++ status = "okay"; ++ cpld: nvsw_bmc_hid189@31 { ++ compatible = "nvidia,hid189"; ++ reg = <0x31>; ++ interrupt-parent = <&gpio1>; ++ interrupts = ; ++ }; ++}; ++ ++&i2c15 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++// Enable emmc ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++ non-removable; ++ bus-width = <4>; ++ max-frequency = <52000000>; ++}; ++ ++/* eSPI Controller - Communication with host CPU */ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++/* RTC over eSPI - Time synchronization with host */ ++&rtc_over_espi0 { ++ status = "okay"; ++}; ++ ++// TODO: FIXME probably N/A for in 2700 ++#if 0 ++&lpc_ctrl { ++ status = "okay"; ++ memory-region = <&espi_memory>; ++}; ++ ++&lpc_reset { ++ status = "okay"; ++}; ++#endif ++ ++/* ++ * LPC SNOOP for POST code capture (simpler alternative to PCC) ++ * ++ * SNOOP driver benefits: ++ * - Mainline Linux driver (well-tested, stable) ++ * - Two independent channels for monitoring multiple ports ++ * - Simple FIFO-based interface via /dev/aspeed-lpc-snoop0/1 ++ * ++ * Usage: ++ * modprobe aspeed-lpc-snoop ++ * hexdump -C /dev/aspeed-lpc-snoop0 # Monitor port 0x80 ++ * hexdump -C /dev/aspeed-lpc-snoop1 # Monitor port 0x81 (if configured) ++ */ ++&pcie_lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>; /* Capture POST codes from port 0x80 */ ++ /* ++ * Optional: Add second port for dual-channel monitoring ++ * snoop-ports = <0x80>, <0x81>; ++ */ ++}; ++ ++/* ++ * PCC disabled - cannot run simultaneously with SNOOP ++ * (both use same hardware registers: SNPWADR, HICR5, HICR6) ++ */ ++&pcie_lpc0_pcc { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <1>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <2>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ /* Primary IPMI interface */ ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <3>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++// TODO: FIXME ++#if 0 ++&pinctrl0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_espi0_default>, ++ <&pinctrl_espialt_default>; pinctrl_salt0_default ???? ++}; ++#endif ++ ++/* ++ * I3C Controller for MCTP communication ++ * Note: pinctrl is already configured in aspeed-g7.dtsi base DTS ++ */ ++&i3c1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ i3c-scl-hz = <12500000>; ++ mctp-controller; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot.dtsi b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot.dtsi +new file mode 100644 +index 000000000..7cc319e40 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot.dtsi +@@ -0,0 +1,25 @@ ++/ { ++ // reserved memory for RAM loaded firmware from bali ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ rofs_memory: ramrofs@403920000 { ++ reg = <0x00000004 0x03920000 0x0 0x030A0000>; ++ no-map; ++ }; ++ }; ++ ++ // Add a platform device node for IRoT. ++ mctpirot0 { ++ compatible = "nvidia,ast27xx,irot"; ++ mboxes = <&mbox0 0>; ++ mbox-names = "irot"; ++ status = "okay"; ++ }; ++}; ++ ++&mbox0 { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda.dtsi b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda.dtsi +new file mode 100644 +index 000000000..b23cec8d9 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda.dtsi +@@ -0,0 +1,8 @@ ++ ++#include "nvidia-ast27xx-irot.dtsi" ++//#include "nvidia-optee-vrot.dtsi" ++ ++// enable OTP for testing (might remove later) ++&otp { ++ status = "okay"; ++}; +-- +2.49.0 + diff --git a/patches-sonic/0002-PCI-AER-Update-statistics-before-ratelimiting.patch b/patches-sonic/0002-PCI-AER-Update-statistics-before-ratelimiting.patch new file mode 100644 index 000000000..262800624 --- /dev/null +++ b/patches-sonic/0002-PCI-AER-Update-statistics-before-ratelimiting.patch @@ -0,0 +1,68 @@ +From dfcd0e7d5008dffb55517a8207d455cab69dc70c Mon Sep 17 00:00:00 2001 +From: Bjorn Helgaas +Date: Thu, 22 May 2025 18:21:16 -0500 +Subject: [PATCH 02/12] PCI/AER: Update statistics before ratelimiting +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +There are two AER logging entry points: + + - aer_print_error() is used by DPC (dpc_process_error()) and native AER + handling (aer_process_err_devices()). + + - pci_print_aer() is used by GHES (aer_recover_work_func()) and CXL + (cxl_handle_rdport_errors()) + +Both use __aer_print_error() to print the AER error bits. Previously +__aer_print_error() also incremented the AER statistics via +pci_dev_aer_stats_incr(). + +Call pci_dev_aer_stats_incr() early in the entry points instead of in +__aer_print_error() so we update the statistics even if the actual printing +of error bits is rate limited by a future change. + +Signed-off-by: Bjorn Helgaas +Tested-by: Krzysztof Wilczyński +Reviewed-by: Ilpo Järvinen +Reviewed-by: Kuppuswamy Sathyanarayanan +Reviewed-by: Jonathan Cameron +Link: https://patch.msgid.link/20250522232339.1525671-11-helgaas@kernel.org +(cherry picked from commit 88a7765e62b9e4c79c7ca2c7b749ae04f54a5668) +--- + drivers/pci/pcie/aer.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 7d7fc4a9fec2..48a33151d145 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -694,7 +694,6 @@ static void __aer_print_error(struct pci_dev *dev, + pci_printk(level, dev, " [%2d] %-22s%s\n", i, errmsg, + info->first_error == i ? " (First)" : ""); + } +- pci_dev_aer_stats_incr(dev, info); + } + + void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) +@@ -703,6 +702,8 @@ void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) + int id = pci_dev_id(dev); + const char *level; + ++ pci_dev_aer_stats_incr(dev, info); ++ + if (!info->status) { + pci_err(dev, "PCIe Bus Error: severity=%s, type=Inaccessible, (Unregistered Agent ID)\n", + aer_error_severity_string[info->severity]); +@@ -783,6 +784,8 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + info.status = status; + info.mask = mask; + ++ pci_dev_aer_stats_incr(dev, &info); ++ + layer = AER_GET_LAYER_ERROR(aer_severity, status); + agent = AER_GET_AGENT(aer_severity, status); + +-- +2.47.0 + diff --git a/patches-sonic/0002-armhf-armada38x-disable-HW-UIE.patch b/patches-sonic/0002-armhf-armada38x-disable-HW-UIE.patch new file mode 100644 index 000000000..b9200f5cc --- /dev/null +++ b/patches-sonic/0002-armhf-armada38x-disable-HW-UIE.patch @@ -0,0 +1,81 @@ +From c80d67f9d7d8693b26f9c34e7395ab2c8d028e1a Mon Sep 17 00:00:00 2001 +From: Ioannis Fountzoulas +Date: Fri, 3 Jul 2026 08:24:45 -0400 +Subject: [PATCH] rtc: armada38x: do not advertise update interrupt (UIE) + support + +Problem: +chrony enables RTC update interrupts via the RTC_UIE_ON ioctl to track +RTC drift. On the armada38x driver this request is served by the RTC +core's native path, which arms a 1 second periodic timer that is +re-programmed on the alarm and serviced by rtc_timer_do_work(). +If the RTC time is then stepped forward by a large amount while this +timer is active, its scheduled expiry ends up far in the past compared +to the freshly read time. + +Why the CPU hangs: +When rtc_timer_do_work() runs, it expires every timer whose expiry is +not in the future, advancing periodic timers by one period each pass: + while ((next = timerqueue_getnext(&rtc->timerqueue))) { + if (next->expires > now) + break; + ... + timer->node.expires += timer->period; /* += 1s */ + timerqueue_add(&rtc->timerqueue, &timer->node); + } +With a large forward step (seen when the RTC starts far in the past and +chrony corrects it after the first NTP sync), the periodic UIE timer is +overdue by the size of the jump, so this loop must run one iteration per +elapsed second before it can exit. +It never yields in that time, so the workqueue worker pins the CPU and +the watchdog reports a soft lockup / RCU stall, after which the +board reboots: + watchdog: BUG: soft lockup - CPU#1 stuck for 48s! [kworker/1:3:432] + Kernel panic - not syncing: softlockup: hung tasks + Workqueue: events rtc_timer_do_work + rtc_handle_legacy_irq from rtc_timer_do_work + rtc_timer_do_work from process_one_work + process_one_work from worker_thread + +Fix: +Clear RTC_FEATURE_UPDATE_INTERRUPT at probe time so the driver stops +advertising native UIE. RTC_UIE_ON is then served by the poll-based UIE +emulation in rtc-dev (CONFIG_RTC_INTF_DEV_UIE_EMUL), which delivers the +1 Hz update notifications chrony needs without ever queuing the runaway +periodic timer. + +Testing: +Tested on a Marvell Armada 38x board (ARM Cortex-A9). Before the change, +stepping the clock forward while UIE was enabled reproduced the soft +lockup (about 1 in 4 boots). After the change, rtc_timer_do_work() runs +zero loop iterations and returns immediately, chrony still receives +update interrupts via emulation, and no lockups occur over repeated +reboot cycles. + +Signed-off-by: Ioannis Fountzoulas +--- + drivers/rtc/rtc-armada38x.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/drivers/rtc/rtc-armada38x.c b/drivers/rtc/rtc-armada38x.c +index 245290ae1a8d..da036d819649 100644 +--- a/drivers/rtc/rtc-armada38x.c ++++ b/drivers/rtc/rtc-armada38x.c +@@ -526,6 +526,14 @@ static __init int armada38x_rtc_probe(struct platform_device *pdev) + else + clear_bit(RTC_FEATURE_ALARM, rtc->rtc_dev->features); + ++ /* ++ * A large forward step of the RTC time makes ++ * rtc_timer_do_work() replay one period per elapsed second and can ++ * loop long enough to trigger a soft lockup. Do not advertise ++ * native UIE; RTC_UIE_ON then uses the poll-based emulation. ++ */ ++ clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtc_dev->features); ++ + /* Update RTC-MBUS bridge timing parameters */ + rtc->data->update_mbus_timing(rtc); + +-- +2.34.1 + diff --git a/patches-sonic/0002-aspeed-ast2700-non-aspeed-deps-update-to-6.12.94-00.07.04.patch b/patches-sonic/0002-aspeed-ast2700-non-aspeed-deps-update-to-6.12.94-00.07.04.patch new file mode 100644 index 000000000..e749ebe4e --- /dev/null +++ b/patches-sonic/0002-aspeed-ast2700-non-aspeed-deps-update-to-6.12.94-00.07.04.patch @@ -0,0 +1,8038 @@ +From: Chandrasekaran Swaminathan +Date: Wed, 09 Sep 2026 15:51:49 +0000 +Subject: [PATCH] Aspeed AST2700 support: new non-Aspeed dependency subsystems + +New, self-contained non-Aspeed subsystems the Aspeed image depends +on (i3c/mctp, jtag, dwc TRNG, mctp-pcie-vdm, xdma trace), from a +fresh Aspeed merge at v00.07.04 on the 6.12.94 SONiC kernel. + +Signed-off-by: Chandrasekaran Swaminathan +--- +diff --git a/drivers/char/hw_random/dwc/Kconfig b/drivers/char/hw_random/dwc/Kconfig +--- a/drivers/char/hw_random/dwc/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/Kconfig 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,18 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++# ++# DWC Hardware Random Number Generator (RNG) configuration ++# ++ ++config HW_RANDOM_DWC ++ tristate "DesignWare Cores HW Random Number Generator support" ++ depends on HW_RANDOM ++ depends on ARCH_ASPEED || COMPILE_TEST ++ help ++ This driver provides kernel-side support for the DWC ++ Random Number Generator hardware found on Aspeed SoCs. ++ ++ To compile this driver as a module, choose M here. the ++ module will be called dwc-rng. ++ ++ If unsure, say Y. ++ +diff --git a/drivers/char/hw_random/dwc/Makefile b/drivers/char/hw_random/dwc/Makefile +--- a/drivers/char/hw_random/dwc/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/Makefile 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,22 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++ccflags-y := -I $(srctree)/drivers/char/hw_random/dwc/src/trng/include \ ++ -I $(srctree)/drivers/char/hw_random/dwc/src/pdu/linux/include ++ ++obj-$(CONFIG_HW_RANDOM_DWC) += elppdu.o ++elppdu-objs := src/pdu/linux/kernel/pdu.o \ ++ src/pdu/common/pdu/pdu_dev32.o \ ++ ++obj-$(CONFIG_HW_RANDOM_DWC) += elpmem.o ++elpmem-objs := src/pdu/linux/kernel/spacc_mem.o \ ++ ++obj-$(CONFIG_HW_RANDOM_DWC) += nisttrng.o ++nisttrng-objs := src/trng/kernel/nist_trng.o \ ++ src/trng/trng/nist_trng.o \ ++ src/trng/trng/nist_trng_private.o \ ++ ++clean: ++ @find \( -name '*.o' \ ++ -o -name '*.a' \ ++ -o -name '*.order' \ ++ \) -type f -print | xargs rm -rvf +diff --git a/drivers/char/hw_random/dwc/src/pdu/common/include/elppdu_error.h b/drivers/char/hw_random/dwc/src/pdu/common/include/elppdu_error.h +--- a/drivers/char/hw_random/dwc/src/pdu/common/include/elppdu_error.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/pdu/common/include/elppdu_error.h 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,96 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2011-2017 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef SYNPDU_ERROR_H_ ++#define SYNPDU_ERROR_H_ ++ ++/* ++ * Common error definitions. Be sure to update pdu_error_code when changing ++ * anything in this list. ++ */ ++ ++#define CRYPTO_OK (0) ++#define CRYPTO_FAILED (-1) ++#define CRYPTO_INPROGRESS (-2) ++#define CRYPTO_INVALID_HANDLE (-3) ++#define CRYPTO_INVALID_CONTEXT (-4) ++#define CRYPTO_INVALID_SIZE (-5) ++#define CRYPTO_NOT_INITIALIZED (-6) ++#define CRYPTO_NO_MEM (-7) ++#define CRYPTO_INVALID_ALG (-8) ++#define CRYPTO_INVALID_KEY_SIZE (-9) ++#define CRYPTO_INVALID_ARGUMENT (-10) ++#define CRYPTO_MODULE_DISABLED (-11) ++#define CRYPTO_NOT_IMPLEMENTED (-12) ++#define CRYPTO_INVALID_BLOCK_ALIGNMENT (-13) ++#define CRYPTO_INVALID_MODE (-14) ++#define CRYPTO_INVALID_KEY (-15) ++#define CRYPTO_AUTHENTICATION_FAILED (-16) ++#define CRYPTO_INVALID_IV_SIZE (-17) ++#define CRYPTO_MEMORY_ERROR (-18) ++#define CRYPTO_LAST_ERROR (-19) ++#define CRYPTO_HALTED (-20) ++#define CRYPTO_TIMEOUT (-21) ++#define CRYPTO_SRM_FAILED (-22) ++#define CRYPTO_COMMON_ERROR_MAX (-100) ++#define CRYPTO_INVALID_ICV_KEY_SIZE (-100) ++#define CRYPTO_INVALID_PARAMETER_SIZE (-101) ++#define CRYPTO_SEQUENCE_OVERFLOW (-102) ++#define CRYPTO_DISABLED (-103) ++#define CRYPTO_INVALID_VERSION (-104) ++#define CRYPTO_FATAL (-105) ++#define CRYPTO_INVALID_PAD (-106) ++#define CRYPTO_FIFO_FULL (-107) ++#define CRYPTO_INVALID_SEQUENCE (-108) ++#define CRYPTO_INVALID_FIRMWARE (-109) ++#define CRYPTO_NOT_FOUND (-110) ++#define CRYPTO_CMD_FIFO_INACTIVE (-111) ++#define CRYPTO_INVALID_PROTOCOL (-112) ++#define CRYPTO_REPLAY (-113) ++#define CRYPTO_NOT_INSTANTIATED (-114) ++#define CRYPTO_RESEED_REQUIRED (-115) ++ ++#endif +diff --git a/drivers/char/hw_random/dwc/src/pdu/common/pdu/pdu_dev32.c b/drivers/char/hw_random/dwc/src/pdu/common/pdu/pdu_dev32.c +--- a/drivers/char/hw_random/dwc/src/pdu/common/pdu/pdu_dev32.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/pdu/common/pdu/pdu_dev32.c 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,165 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2011-2017 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "elppdu.h" ++ ++void pdu_to_dev32(void *addr_, u32 *src, unsigned long nword) ++{ ++ unsigned char *addr = addr_; ++ ++ while (nword--) { ++ pdu_io_write32(addr, *src++); ++ addr += 4; ++ } ++} ++EXPORT_SYMBOL(pdu_to_dev32); ++ ++void pdu_from_dev32(u32 *dst, void *addr_, unsigned long nword) ++{ ++ unsigned char *addr = addr_; ++ ++ while (nword--) { ++ *dst++ = pdu_io_read32(addr); ++ addr += 4; ++ } ++} ++EXPORT_SYMBOL(pdu_from_dev32); ++ ++void pdu_to_dev32_big(void *addr_, const unsigned char *src, ++ unsigned long nword) ++{ ++ unsigned char *addr = addr_; ++ unsigned long v; ++ ++ while (nword--) { ++ v = 0; ++ v = (v << 8) | ((unsigned long)*src++); ++ v = (v << 8) | ((unsigned long)*src++); ++ v = (v << 8) | ((unsigned long)*src++); ++ v = (v << 8) | ((unsigned long)*src++); ++ pdu_io_write32(addr, v); ++ addr += 4; ++ } ++} ++EXPORT_SYMBOL(pdu_to_dev32_big); ++ ++void pdu_from_dev32_big(unsigned char *dst, void *addr_, unsigned long nword) ++{ ++ unsigned char *addr = addr_; ++ unsigned long v; ++ ++ while (nword--) { ++ v = pdu_io_read32(addr); ++ addr += 4; ++ *dst++ = (v >> 24) & 0xFF; ++ v <<= 8; ++ *dst++ = (v >> 24) & 0xFF; ++ v <<= 8; ++ *dst++ = (v >> 24) & 0xFF; ++ v <<= 8; ++ *dst++ = (v >> 24) & 0xFF; ++ v <<= 8; ++ } ++} ++EXPORT_SYMBOL(pdu_from_dev32_big); ++ ++void pdu_to_dev32_little(void *addr_, const unsigned char *src, ++ unsigned long nword) ++{ ++ unsigned char *addr = addr_; ++ unsigned long v; ++ ++ while (nword--) { ++ v = 0; ++ v = (v >> 8) | ((unsigned long)*src++ << 24UL); ++ v = (v >> 8) | ((unsigned long)*src++ << 24UL); ++ v = (v >> 8) | ((unsigned long)*src++ << 24UL); ++ v = (v >> 8) | ((unsigned long)*src++ << 24UL); ++ pdu_io_write32(addr, v); ++ addr += 4; ++ } ++} ++EXPORT_SYMBOL(pdu_to_dev32_little); ++ ++void pdu_from_dev32_little(unsigned char *dst, void *addr_, unsigned long nword) ++{ ++ unsigned char *addr = addr_; ++ unsigned long v; ++ ++ while (nword--) { ++ v = pdu_io_read32(addr); ++ addr += 4; ++ *dst++ = v & 0xFF; ++ v >>= 8; ++ *dst++ = v & 0xFF; ++ v >>= 8; ++ *dst++ = v & 0xFF; ++ v >>= 8; ++ *dst++ = v & 0xFF; ++ v >>= 8; ++ } ++} ++EXPORT_SYMBOL(pdu_from_dev32_little); ++ ++void pdu_to_dev32_s(void *addr, const unsigned char *src, unsigned long nword, ++ int endian) ++{ ++ if (endian) ++ pdu_to_dev32_big(addr, src, nword); ++ else ++ pdu_to_dev32_little(addr, src, nword); ++} ++EXPORT_SYMBOL(pdu_to_dev32_s); ++ ++void pdu_from_dev32_s(unsigned char *dst, void *addr, unsigned long nword, ++ int endian) ++{ ++ if (endian) ++ pdu_from_dev32_big(dst, addr, nword); ++ else ++ pdu_from_dev32_little(dst, addr, nword); ++} ++EXPORT_SYMBOL(pdu_from_dev32_s); +diff --git a/drivers/char/hw_random/dwc/src/pdu/linux/include/elppdu.h b/drivers/char/hw_random/dwc/src/pdu/linux/include/elppdu.h +--- a/drivers/char/hw_random/dwc/src/pdu/linux/include/elppdu.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/pdu/linux/include/elppdu.h 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,125 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2011-2017 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef SYNPDU_H_ ++#define SYNPDU_H_ ++ ++/* Platform Specific */ ++#include /* printk() */ ++#include /* size_t */ ++#include /* memcpy()/etc */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifndef PDU_BASE_ADDR ++#define PDU_BASE_ADDR 0x14c3b000 ++#endif ++ ++#ifndef PDU_BASE_IRQ ++#define PDU_BASE_IRQ 91 ++#endif ++ ++#define PDU_SINGLE_CORE 1 ++#define PDU_SINGLE_NIST_TRNG 1 ++ ++#if 1 ++#define SYNHW_PRINT printk ++#else ++#define SYNHW_PRINT(...) ++#endif ++ ++#define CPU_YIELD ++#define SYNHW_MEMCPY memcpy ++ ++// Debug modifier for printing, in linux adding KERN_DEBUG makes the output only show up in debug logs (avoids /var/log/messages) ++#define SYNHW_PRINT_DEBUG KERN_DEBUG ++ ++// Locking ++#define PDU_LOCK_TYPE spinlock_t ++#define PDU_INIT_LOCK(lock) spin_lock_init(lock) ++ ++// these are for IRQ contexts ++#define PDU_LOCK(lock, flags) spin_lock_irqsave(lock, flags) ++#define PDU_UNLOCK(lock, flags) spin_unlock_irqrestore(lock, flags) ++ ++// these are for bottom half BH contexts ++#define PDU_LOCK_TYPE_BH struct mutex ++#define PDU_INIT_LOCK_BH(lock) mutex_init(lock) ++#define PDU_LOCK_BH(lock) mutex_lock(lock) ++#define PDU_UNLOCK_BH(lock) mutex_unlock(lock) ++ ++#include "../../common/include/elppdu_error.h" ++ ++void *pdu_linux_map_regs(struct device *dev, struct resource *regs); ++ ++void pdu_io_write32(void *addr, unsigned long val); ++void pdu_io_cached_write32(void *addr, unsigned long val, u32 *cache); ++unsigned long pdu_io_read32(void *addr); ++ ++void pdu_to_dev32(void *addr, u32 *src, unsigned long nword); ++void pdu_from_dev32(u32 *dst, void *addr, unsigned long nword); ++void pdu_to_dev32_big(void *addr, const unsigned char *src, unsigned long nword); ++void pdu_from_dev32_big(unsigned char *dst, void *addr, unsigned long nword); ++void pdu_to_dev32_little(void *addr, const unsigned char *src, unsigned long nword); ++void pdu_from_dev32_little(unsigned char *dst, void *addr, unsigned long nword); ++void pdu_from_dev32_s(unsigned char *dst, void *addr, unsigned long nword, int endian); ++void pdu_to_dev32_s(void *addr, const unsigned char *src, unsigned long nword, int endian); ++ ++void *pdu_malloc(unsigned long n); ++void pdu_free(void *p); ++ ++int pdu_error_code(int code); ++ ++#endif ++ +diff --git a/drivers/char/hw_random/dwc/src/pdu/linux/kernel/pdu.c b/drivers/char/hw_random/dwc/src/pdu/linux/kernel/pdu.c +--- a/drivers/char/hw_random/dwc/src/pdu/linux/kernel/pdu.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/pdu/linux/kernel/pdu.c 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2011-2017 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++ ++#include "elppdu.h" ++ ++static bool trace_io; ++module_param(trace_io, bool, 0600); ++MODULE_PARM_DESC(trace_io, "Trace MMIO reads/writes"); ++ ++void *pdu_linux_map_regs(struct device *dev, struct resource *regs) ++{ ++ return devm_ioremap_resource(dev, regs); ++} ++EXPORT_SYMBOL(pdu_linux_map_regs); ++ ++void pdu_io_write32(void *addr, unsigned long val) ++{ ++ if (trace_io) ++ SYNHW_PRINT("PDU: write %.8lx -> %p\n", val, addr); ++ ++ writel(val, addr); ++} ++EXPORT_SYMBOL(pdu_io_write32); ++ ++void pdu_io_cached_write32(void *addr, unsigned long val, uint32_t *cache) ++{ ++ if (*cache == val) { ++ if (trace_io) { ++ SYNHW_PRINT("PDU: write %.8lx -> %p (cached)\n", val, ++ addr); ++ } ++ return; ++ } ++ ++ *cache = val; ++ pdu_io_write32(addr, val); ++} ++EXPORT_SYMBOL(pdu_io_cached_write32); ++ ++unsigned long pdu_io_read32(void *addr) ++{ ++ unsigned long val; ++ ++ val = readl(addr); ++ ++ if (trace_io) ++ SYNHW_PRINT("PDU: read %.8lx <- %p\n", val, addr); ++ ++ return val; ++} ++EXPORT_SYMBOL(pdu_io_read32); ++ ++/* Platform specific memory allocation */ ++void *pdu_malloc(unsigned long n) ++{ ++ return vmalloc(n); ++} ++ ++void pdu_free(void *p) ++{ ++ vfree(p); ++} ++ ++/* Convert SDK error codes to corresponding kernel error codes. */ ++int pdu_error_code(int code) ++{ ++ switch (code) { ++ case CRYPTO_INPROGRESS: ++ return -EINPROGRESS; ++ case CRYPTO_INVALID_HANDLE: ++ case CRYPTO_INVALID_CONTEXT: ++ return -ENXIO; ++ case CRYPTO_NOT_INITIALIZED: ++ return -ENODATA; ++ case CRYPTO_INVALID_SIZE: ++ case CRYPTO_INVALID_ALG: ++ case CRYPTO_INVALID_KEY_SIZE: ++ case CRYPTO_INVALID_ARGUMENT: ++ case CRYPTO_INVALID_BLOCK_ALIGNMENT: ++ case CRYPTO_INVALID_MODE: ++ case CRYPTO_INVALID_KEY: ++ case CRYPTO_INVALID_IV_SIZE: ++ case CRYPTO_INVALID_ICV_KEY_SIZE: ++ case CRYPTO_INVALID_PARAMETER_SIZE: ++ case CRYPTO_REPLAY: ++ case CRYPTO_INVALID_PROTOCOL: ++ case CRYPTO_RESEED_REQUIRED: ++ return -EINVAL; ++ case CRYPTO_NOT_IMPLEMENTED: ++ case CRYPTO_MODULE_DISABLED: ++ return -ENOTSUPP; ++ case CRYPTO_NO_MEM: ++ return -ENOMEM; ++ case CRYPTO_INVALID_PAD: ++ case CRYPTO_INVALID_SEQUENCE: ++ return -EILSEQ; ++ case CRYPTO_MEMORY_ERROR: ++ return -EIO; ++ case CRYPTO_TIMEOUT: ++ return -ETIMEDOUT; ++ case CRYPTO_HALTED: ++ return -ECANCELED; ++ case CRYPTO_AUTHENTICATION_FAILED: ++ case CRYPTO_SEQUENCE_OVERFLOW: ++ case CRYPTO_INVALID_VERSION: ++ return -EPROTO; ++ case CRYPTO_FIFO_FULL: ++ return -EBUSY; ++ case CRYPTO_SRM_FAILED: ++ case CRYPTO_DISABLED: ++ case CRYPTO_LAST_ERROR: ++ return -EAGAIN; ++ case CRYPTO_FAILED: ++ case CRYPTO_FATAL: ++ return -EIO; ++ case CRYPTO_INVALID_FIRMWARE: ++ return -ENOEXEC; ++ case CRYPTO_NOT_FOUND: ++ return -ENOENT; ++ } ++ ++ /* ++ * Any unrecognized code is either success (i.e., zero) or a negative ++ * error code, which may be meaningless but at least will still be ++ * recognized as an error. ++ */ ++ return code; ++} ++EXPORT_SYMBOL(pdu_error_code); ++ ++static int __init pdu_mod_init(void) ++{ ++ return 0; ++} ++ ++static void __exit pdu_mod_exit(void) ++{ ++} ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Synopsys, Inc."); ++module_init(pdu_mod_init); ++module_exit(pdu_mod_exit); +diff --git a/drivers/char/hw_random/dwc/src/pdu/linux/kernel/spacc_mem.c b/drivers/char/hw_random/dwc/src/pdu/linux/kernel/spacc_mem.c +--- a/drivers/char/hw_random/dwc/src/pdu/linux/kernel/spacc_mem.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/pdu/linux/kernel/spacc_mem.c 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,191 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2011-2016 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "elppdu.h" ++ ++static unsigned long vex_baseaddr = PDU_BASE_ADDR; ++module_param_named(baseaddr, vex_baseaddr, ulong, 0); ++MODULE_PARM_DESC(baseaddr, "Hardware base address (default " __stringify(PDU_BASE_ADDR) ")"); ++ ++// max of 16 devices ++#define MAX_DEV 16 ++ ++static struct platform_device *devices[MAX_DEV]; ++static int dev_id; ++ ++static void register_device(const char *name, int id, ++ const struct resource *res, unsigned int num) ++{ ++ char suffix[16] = ""; ++ struct platform_device_info pdevinfo = { ++ .name = name, ++ .id = id, ++ .res = res, ++ .num_res = num, ++ .dma_mask = 0xffffffff, ++ }; ++ ++ if (dev_id >= MAX_DEV) { ++ pr_err("Too many devices; increase MAX_DEV.\n"); ++ return; ++ } ++ ++ devices[dev_id] = platform_device_register_full(&pdevinfo); ++ if (IS_ERR(devices[dev_id])) { ++ if (id >= 0) ++ snprintf(suffix, sizeof(suffix), ".%d", id); ++ pr_err("Failed to register %s%s\n", name, suffix); ++ ++ devices[dev_id] = NULL; ++ return; ++ } ++ ++ dev_id++; ++} ++ ++static int __init get_irq_num(unsigned int irq_num) ++{ ++ if (IS_ENABLED(CONFIG_ARCH_ZYNQ)) { ++ struct of_phandle_args args = { 0 }; ++ ++ /* ++ * Since this driver is for non-DT use but Zynq uses DT to setup IRQs, ++ * find the GIC by searching for its DT node then manually create the ++ * IRQ mappings. ++ */ ++ ++ do { ++ args.np = of_find_node_with_property(args.np, ++ "interrupt-controller"); ++ if (!args.np) { ++ pr_err("cannot find IRQ controller"); ++ return -ENODEV; ++ } ++ } while (!of_device_is_compatible(args.np, "arm,cortex-a9-gic")); ++ ++ if (irq_num < 32 || irq_num >= 96) { ++ pr_err("SPI interrupts must be in the range [32,96) on Zynq\n"); ++ return -EINVAL; ++ } ++ ++ args.args_count = 3; ++ args.args[0] = 0; /* SPI */ ++ args.args[1] = irq_num - 32; ++ args.args[2] = 4; /* Active high, level-sensitive */ ++ ++ irq_num = irq_create_of_mapping(&args); ++ of_node_put(args.np); ++ if (irq_num == 0) ++ return -EINVAL; ++ } ++ ++ if (irq_num > INT_MAX) ++ return -EINVAL; ++ ++ return irq_num; ++} ++ ++static int __init pdu_vex_mod_init(void) ++{ ++ int irq_num = get_irq_num(PDU_BASE_IRQ); ++ struct resource res[2]; ++#ifndef PDU_SINGLE_CORE ++ void *pdu_mem; ++ int i, rc; ++#endif ++ ++ if (irq_num >= 0) { ++ res[1] = (struct resource){ ++ .start = irq_num, ++ .end = irq_num, ++ .flags = IORESOURCE_IRQ, ++ }; ++ } else { ++ res[1] = (struct resource){ 0 }; ++ pr_err("IRQ setup failed (error %d), not using IRQs\n", ++ irq_num); ++ } ++ ++#ifdef PDU_SINGLE_BASIC_TRNG ++ res[0] = (struct resource){ ++ .start = vex_baseaddr, ++ .end = vex_baseaddr + 0x80 - 1, ++ .flags = IORESOURCE_MEM, ++ }; ++ register_device("basic_trng", -1, res, 2); ++#endif ++ ++#ifdef PDU_SINGLE_NIST_TRNG ++ res[0] = (struct resource){ ++ .start = vex_baseaddr, ++ .end = vex_baseaddr + 0x800 - 1, ++ .flags = IORESOURCE_MEM, ++ }; ++ register_device("nist_trng", -1, res, 2); ++#endif ++ ++ return 0; ++} ++module_init(pdu_vex_mod_init); ++ ++static void __exit pdu_vex_mod_exit(void) ++{ ++ int i; ++ ++ for (i = 0; i < MAX_DEV; i++) ++ platform_device_unregister(devices[i]); ++} ++module_exit(pdu_vex_mod_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Synopsys, Inc."); +diff --git a/drivers/char/hw_random/dwc/src/trng/include/nisttrng.h b/drivers/char/hw_random/dwc/src/trng/include/nisttrng.h +--- a/drivers/char/hw_random/dwc/src/trng/include/nisttrng.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/include/nisttrng.h 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,63 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2012-2016 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef NISTTRNG_H ++#define NISTTRNG_H ++ ++#include "synversion.h" ++#include "elppdu.h" ++#include "nisttrng_hw.h" ++#include "nisttrng_common.h" ++#include "nisttrng_private.h" ++ ++int nisttrng_init(struct nist_trng_state *state, u32 *base); ++int nisttrng_instantiate(struct nist_trng_state *state, int req_sec_strength, int pred_resist, void *personal_str); ++int nisttrng_uninstantiate(struct nist_trng_state *state); ++int nisttrng_reseed(struct nist_trng_state *state, int pred_resist, void *addin_str); ++int nisttrng_generate(struct nist_trng_state *state, void *random_bits, unsigned long req_num_bytes, int req_sec_strength, int pred_resist, void *addin_str); ++int nisttrng_rbc(struct nist_trng_state *state, int enable, int rbc_num, int rate, int urun_blnk); ++int nisttrng_generate_public_vtrng(struct nist_trng_state *state, void *random_bits, unsigned long req_num_bytes, int vtrng); ++#endif +diff --git a/drivers/char/hw_random/dwc/src/trng/include/nisttrng_common.h b/drivers/char/hw_random/dwc/src/trng/include/nisttrng_common.h +--- a/drivers/char/hw_random/dwc/src/trng/include/nisttrng_common.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/include/nisttrng_common.h 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,144 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++// ------------------------------------------------------------------------ ++// ++// (C) COPYRIGHT 2012 - 2016 SYNOPSYS, INC. ++// ALL RIGHTS RESERVED ++// ++// (C) COPYRIGHT 2012-2016 Synopsys, Inc. ++// This Synopsys software and all associated documentation are ++// proprietary to Synopsys, Inc. and may only be used pursuant ++// to the terms and conditions of a written license agreement ++// with Synopsys, Inc. All other use, reproduction, modification, ++// or distribution of the Synopsys software or the associated ++// documentation is strictly prohibited. ++// ++// ------------------------------------------------------------------------ ++ ++#ifndef NISTTRNG_COMMON_H ++#define NISTTRNG_COMMON_H ++ ++#define NIST_TRNG_RETRY_MAX 5000000UL ++ ++#define NIST_DFLT_MAX_BITS_PER_REQ BIT(19) ++#define NIST_DFLT_MAX_REQ_PER_SEED BIT(48) ++ ++/* Do not change the following parameters */ ++#define NIST_TRNG_DFLT_MAX_REJECTS 10 ++ ++#define DEBUG(...) ++//#define DEBUG(...) printk(__VA_ARGS__) ++ ++enum nisttrng_sec_strength { ++ SEC_STRNT_AES128 = 0, ++ SEC_STRNT_AES256 = 1 ++}; ++ ++enum nisttrng_drbg_arch { ++ AES128 = 0, ++ AES256 = 1 ++}; ++ ++enum nisttrng_current_state { ++ NIST_TRNG_STATE_INITIALIZE = 0, ++ NIST_TRNG_STATE_UNINSTANTIATE, ++ NIST_TRNG_STATE_INSTANTIATE, ++ NIST_TRNG_STATE_RESEED, ++ NIST_TRNG_STATE_GENERATE ++}; ++ ++struct nist_trng_state { ++ u32 *base; ++ ++ /* Hardware features and build ID */ ++ struct { ++ struct { ++ enum nisttrng_drbg_arch drbg_arch; ++ unsigned int extra_ps_present, ++ secure_rst_state, ++ diag_level_basic_trng, ++ diag_level_stat_hlt, ++ diag_level_ns; ++ } features; ++ ++ struct { ++ unsigned int ext_enum, ++ ext_ver, ++ rel_num; ++ } corekit_rel; ++ ++ struct { ++ unsigned int core_type, ++ bg8, ++ cdc_synch_depth, ++ background_noise, ++ edu_present, ++ aes_datapath, ++ aes_max_key_size, ++ personilzation_str; ++ } build_cfg0; ++ ++ struct { ++ unsigned int num_raw_noise_blks, ++ sticky_startup, ++ auto_correlation_test, ++ mono_bit_test, ++ run_test, ++ poker_test, ++ raw_ht_adap_test, ++ raw_ht_rep_test, ++ ent_src_rep_smpl_size, ++ ent_src_rep_test, ++ ent_src_rep_min_entropy; ++ } build_cfg1; ++ ++ struct { ++ unsigned int rbc2_rate_width, ++ rbc1_rate_width, ++ rbc0_rate_width, ++ public_vtrng_channels, ++ esm_channel, ++ rbc_channels, ++ fifo_depth; ++ } edu_build_cfg0; ++ } config; ++ ++ /* status */ ++ struct { ++ //nist_trng_current_state current_state; ++ enum nisttrng_current_state current_state; // old for now ++ unsigned int nonce_mode, ++ secure_mode, ++ pred_resist; ++ //nist_trng_sec_strength sec_strength; ++ enum nisttrng_sec_strength sec_strength; ++ unsigned int pad_ps_addin; ++ unsigned int alarm_code; ++ // Private VTRNG STAT, all the public trng will have the same STAT as public TRNG in terms of ++ // rnc_enabled and seed_enum ++ struct { ++ unsigned int seed_enum, ++ rnc_enabled; ++ } edu_vstat; ++ } status; ++ ++ /* reminders and alarms */ ++ struct { ++ unsigned long max_bits_per_req; ++ unsigned long long max_req_per_seed; ++ unsigned long bits_per_req_left; ++ unsigned long long req_per_seed_left; ++ } counters; ++}; ++ ++#define nist_trng_zero_status(x) \ ++ memset(&((x)->status), 0, sizeof((x)->status)) ++ ++#define DRBG_INSTANTIATED(cs) \ ++ ((((cs) == NIST_TRNG_STATE_INSTANTIATE) || \ ++ ((cs) == NIST_TRNG_STATE_RESEED) || \ ++ ((cs) == NIST_TRNG_STATE_GENERATE)) ? 1 : 0) ++ ++#define REQ_SEC_STRENGTH_IS_VALID(sec_st) \ ++ ((((sec_st) > 0) && ((sec_st) <= 256)) ? 1 : 0) ++ ++#endif +diff --git a/drivers/char/hw_random/dwc/src/trng/include/nisttrng_hw.h b/drivers/char/hw_random/dwc/src/trng/include/nisttrng_hw.h +--- a/drivers/char/hw_random/dwc/src/trng/include/nisttrng_hw.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/include/nisttrng_hw.h 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,457 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2012-2016 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef NISTTRNG_HW_H ++#define NISTTRNG_HW_H ++ ++/* HW related Parameters */ ++#define NIST_TRNG_RAND_BLK_SIZE_BITS 128 ++#define CHX_URUN_BLANK_AFTER_RESET 0x3 ++ ++/* registers */ ++#define NIST_TRNG_REG_CTRL 0x00 ++#define NIST_TRNG_REG_MODE 0x01 ++#define NIST_TRNG_REG_SMODE 0x02 ++#define NIST_TRNG_REG_STAT 0x03 ++#define NIST_TRNG_REG_IE 0x04 ++#define NIST_TRNG_REG_ISTAT 0x05 ++#define NIST_TRNG_REG_ALARM 0x06 ++#define NIST_TRNG_REG_COREKIT_REL 0x07 ++#define NIST_TRNG_REG_FEATURES 0x08 ++#define NIST_TRNG_REG_RAND0 0x09 ++#define NIST_TRNG_REG_RAND1 0x0A ++#define NIST_TRNG_REG_RAND2 0x0B ++#define NIST_TRNG_REG_RAND3 0x0C ++#define NIST_TRNG_REG_NPA_DATA0 0x0D ++#define NIST_TRNG_REG_NPA_DATA1 0x0E ++#define NIST_TRNG_REG_NPA_DATA2 0x0F ++#define NIST_TRNG_REG_NPA_DATA3 0x10 ++#define NIST_TRNG_REG_NPA_DATA4 0x11 ++#define NIST_TRNG_REG_NPA_DATA5 0x12 ++#define NIST_TRNG_REG_NPA_DATA6 0x13 ++#define NIST_TRNG_REG_NPA_DATA7 0x14 ++#define NIST_TRNG_REG_NPA_DATA8 0x15 ++#define NIST_TRNG_REG_NPA_DATA9 0x16 ++#define NIST_TRNG_REG_NPA_DATA10 0x17 ++#define NIST_TRNG_REG_NPA_DATA11 0x18 ++#define NIST_TRNG_REG_NPA_DATA12 0x19 ++#define NIST_TRNG_REG_NPA_DATA13 0x1A ++#define NIST_TRNG_REG_NPA_DATA14 0x1B ++#define NIST_TRNG_REG_NPA_DATA15 0x1C ++#define NIST_TRNG_REG_SEED0 0x1D ++#define NIST_TRNG_REG_SEED1 0x1E ++#define NIST_TRNG_REG_SEED2 0x1F ++#define NIST_TRNG_REG_SEED3 0x20 ++#define NIST_TRNG_REG_SEED4 0x21 ++#define NIST_TRNG_REG_SEED5 0x22 ++#define NIST_TRNG_REG_SEED6 0x23 ++#define NIST_TRNG_REG_SEED7 0x24 ++#define NIST_TRNG_REG_SEED8 0x25 ++#define NIST_TRNG_REG_SEED9 0x26 ++#define NIST_TRNG_REG_SEED10 0x27 ++#define NIST_TRNG_REG_SEED11 0x28 ++#define NIST_TRNG_REG_TIME_TO_SEED 0x34 ++#define NIST_TRNG_REG_IA_RDATA 0x38 ++#define NIST_TRNG_REG_IA_WDATA 0x39 ++#define NIST_TRNG_REG_IA_ADDR 0x3A ++#define NIST_TRNG_REG_IA_CMD 0x3B ++#define NIST_TRNG_REG_BUILD_CFG0 0x3C ++#define NIST_TRNG_REG_BUILD_CFG1 0x3D ++ ++/* nist edu registers */ ++#define NIST_TRNG_EDU_RNC_CTRL 0x100 ++#define NIST_TRNG_EDU_FLUSH_CTRL 0x101 ++#define NIST_TRNG_EDU_RESEED_CNTR 0x102 ++#define NIST_TRNG_EDU_RBC_CTRL 0x104 ++#define NIST_TRNG_EDU_STAT 0x106 ++#define NIST_TRNG_EDU_IE 0x108 ++#define NIST_TRNG_EDU_ISTAT 0x109 ++#define NIST_TRNG_EDU_BUILD_CFG0 0x12C ++#define NIST_TRNG_EDU_VCTRL 0x138 ++#define NIST_TRNG_EDU_VSTAT 0x139 ++#define NIST_TRNG_EDU_VIE 0x13A ++#define NIST_TRNG_EDU_VISTAT 0x13B ++#define NIST_TRNG_EDU_VRAND_0 0x13C ++#define NIST_TRNG_EDU_VRAND_1 0x13D ++#define NIST_TRNG_EDU_VRAND_2 0x13E ++#define NIST_TRNG_EDU_VRAND_3 0x13F ++ ++/* edu vtrng registers */ ++#define NIST_TRNG_EDU_VTRNG_VCTRL0 0x180 ++#define NIST_TRNG_EDU_VTRNG_VSTAT0 0x181 ++#define NIST_TRNG_EDU_VTRNG_VIE0 0x182 ++#define NIST_TRNG_EDU_VTRNG_VISTAT0 0x183 ++#define NIST_TRNG_EDU_VTRNG_VRAND0_0 0x184 ++#define NIST_TRNG_EDU_VTRNG_VRAND0_1 0x185 ++#define NIST_TRNG_EDU_VTRNG_VRAND0_2 0x186 ++#define NIST_TRNG_EDU_VTRNG_VRAND0_3 0x187 ++#define NIST_TRNG_EDU_VTRNG_VCTRL1 0x188 ++#define NIST_TRNG_EDU_VTRNG_VSTAT1 0x189 ++#define NIST_TRNG_EDU_VTRNG_VIE1 0x18A ++#define NIST_TRNG_EDU_VTRNG_VISTAT1 0x18B ++#define NIST_TRNG_EDU_VTRNG_VRAND1_0 0x18C ++#define NIST_TRNG_EDU_VTRNG_VRAND1_1 0x18D ++#define NIST_TRNG_EDU_VTRNG_VRAND1_2 0x18E ++#define NIST_TRNG_EDU_VTRNG_VRAND1_3 0x18F ++#define NIST_TRNG_EDU_VTRNG_VCTRL2 0x190 ++#define NIST_TRNG_EDU_VTRNG_VSTAT2 0x191 ++#define NIST_TRNG_EDU_VTRNG_VIE2 0x192 ++#define NIST_TRNG_EDU_VTRNG_VISTAT2 0x193 ++#define NIST_TRNG_EDU_VTRNG_VRAND2_0 0x194 ++#define NIST_TRNG_EDU_VTRNG_VRAND2_1 0x195 ++#define NIST_TRNG_EDU_VTRNG_VRAND2_2 0x196 ++#define NIST_TRNG_EDU_VTRNG_VRAND2_3 0x197 ++#define NIST_TRNG_EDU_VTRNG_VCTRL3 0x198 ++#define NIST_TRNG_EDU_VTRNG_VSTAT3 0x199 ++#define NIST_TRNG_EDU_VTRNG_VIE3 0x19A ++#define NIST_TRNG_EDU_VTRNG_VISTAT3 0x19B ++#define NIST_TRNG_EDU_VTRNG_VRAND3_0 0x19C ++#define NIST_TRNG_EDU_VTRNG_VRAND3_1 0x19D ++#define NIST_TRNG_EDU_VTRNG_VRAND3_2 0x19E ++#define NIST_TRNG_EDU_VTRNG_VRAND3_3 0x19F ++#define NIST_TRNG_EDU_VTRNG_VCTRL4 0x1A0 ++#define NIST_TRNG_EDU_VTRNG_VSTAT4 0x1A1 ++#define NIST_TRNG_EDU_VTRNG_VIE4 0x1A2 ++#define NIST_TRNG_EDU_VTRNG_VISTAT4 0x1A3 ++#define NIST_TRNG_EDU_VTRNG_VRAND4_0 0x1A4 ++#define NIST_TRNG_EDU_VTRNG_VRAND4_1 0x1A5 ++#define NIST_TRNG_EDU_VTRNG_VRAND4_2 0x1A6 ++#define NIST_TRNG_EDU_VTRNG_VRAND4_3 0x1A7 ++#define NIST_TRNG_EDU_VTRNG_VCTRL5 0x1A8 ++#define NIST_TRNG_EDU_VTRNG_VSTAT5 0x1A9 ++#define NIST_TRNG_EDU_VTRNG_VIE5 0x1AA ++#define NIST_TRNG_EDU_VTRNG_VISTAT5 0x1AB ++#define NIST_TRNG_EDU_VTRNG_VRAND5_0 0x1AC ++#define NIST_TRNG_EDU_VTRNG_VRAND5_1 0x1AD ++#define NIST_TRNG_EDU_VTRNG_VRAND5_2 0x1AE ++#define NIST_TRNG_EDU_VTRNG_VRAND5_3 0x1AF ++#define NIST_TRNG_EDU_VTRNG_VCTRL6 0x1B0 ++#define NIST_TRNG_EDU_VTRNG_VSTAT6 0x1B1 ++#define NIST_TRNG_EDU_VTRNG_VIE6 0x1B2 ++#define NIST_TRNG_EDU_VTRNG_VISTAT6 0x1B3 ++#define NIST_TRNG_EDU_VTRNG_VRAND6_0 0x1B4 ++#define NIST_TRNG_EDU_VTRNG_VRAND6_1 0x1B5 ++#define NIST_TRNG_EDU_VTRNG_VRAND6_2 0x1B6 ++#define NIST_TRNG_EDU_VTRNG_VRAND6_3 0x1B7 ++#define NIST_TRNG_EDU_VTRNG_VCTRL7 0x1B8 ++#define NIST_TRNG_EDU_VTRNG_VSTAT7 0x1B9 ++#define NIST_TRNG_EDU_VTRNG_VIE7 0x1BA ++#define NIST_TRNG_EDU_VTRNG_VISTAT7 0x1BB ++#define NIST_TRNG_EDU_VTRNG_VRAND7_0 0x1BC ++#define NIST_TRNG_EDU_VTRNG_VRAND7_1 0x1BD ++#define NIST_TRNG_EDU_VTRNG_VRAND7_2 0x1BE ++#define NIST_TRNG_EDU_VTRNG_VRAND7_3 0x1BF ++ ++#define NIST_TRNG_EDU_VTRNG_VCTRL_CMD_NOP 0x0 ++#define NIST_TRNG_EDU_VTRNG_VCTRL_CMD_GET_RANDOM 0x1 ++#define NIST_TRNG_EDU_VTRNG_VCTRL_CMD_INIT 0x2 ++ ++#define NIST_TRNG_EDU_VTRNG_VCTRL_CMD_MASK 0x3Ul ++#define NIST_TRNG_EDU_VTRNG_VCTRL_CMD_SET(y, x) (((y) & ~(NIST_TRNG_EDU_VTRNG_VCTRL_CMD_MASK)) | ((x))) ++ ++/* CTRL */ ++#define NIST_TRNG_REG_CTRL_CMD_NOP 0 ++#define NIST_TRNG_REG_CTRL_CMD_GEN_NOISE 1 ++#define NIST_TRNG_REG_CTRL_CMD_GEN_NONCE 2 ++#define NIST_TRNG_REG_CTRL_CMD_CREATE_STATE 3 ++#define NIST_TRNG_REG_CTRL_CMD_RENEW_STATE 4 ++#define NIST_TRNG_REG_CTRL_CMD_REFRESH_ADDIN 5 ++#define NIST_TRNG_REG_CTRL_CMD_GEN_RANDOM 6 ++#define NIST_TRNG_REG_CTRL_CMD_ADVANCE_STATE 7 ++#define NIST_TRNG_REG_CTRL_CMD_KAT 8 ++#define NIST_TRNG_REG_CTRL_CMD_ZEROIZE 15 ++ ++/* EDU CTRL */ ++#define NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_DISABLE_TO_HOLD 0 ++#define NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_ENABLE 1 ++#define NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_DISABLE_TO_IDLE 2 ++#define NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_FINISH_TO_IDLE 3 ++ ++#define NIST_TRNG_EDU_RNC_CTRL_CMD_MASK 0x3Ul ++#define NIST_TRNG_EDU_RNC_CTRL_CMD_SET(y, x) (((y) & ~(NIST_TRNG_EDU_RNC_CTRL_CMD_MASK)) | ((x))) ++ ++/* EDU_FLUSH_CTRL */ ++#define _NIST_TRNG_EDU_FLUSH_CTRL_CH2_RBC 3 ++#define _NIST_TRNG_EDU_FLUSH_CTRL_CH1_RBC 2 ++#define _NIST_TRNG_EDU_FLUSH_CTRL_CH0_RBC 1 ++#define _NIST_TRNG_EDU_FLUSH_CTRL_FIFO 0 ++ ++#define NIST_TRNG_EDU_FLUSH_CTRL_CH2_RBC BIT(_NIST_TRNG_EDU_FLUSH_CTRL_CH2_RBC) ++#define NIST_TRNG_EDU_FLUSH_CTRL_CH1_RBC BIT(_NIST_TRNG_EDU_FLUSH_CTRL_CH1_RBC) ++#define NIST_TRNG_EDU_FLUSH_CTRL_CH0_RBC BIT(_NIST_TRNG_EDU_FLUSH_CTRL_CH0_RBC) ++#define NIST_TRNG_EDU_FLUSH_CTRL_FIFO BIT(_NIST_TRNG_EDU_FLUSH_CTRL_FIFO) ++ ++/*EDU_RBC_CTRL*/ ++#define _NIST_TRNG_EDU_RBC_CTRL_CH2_URUN_BLANK 28 ++#define _NIST_TRNG_EDU_RBC_CTRL_CH1_URUN_BLANK 26 ++#define _NIST_TRNG_EDU_RBC_CTRL_CH0_URUN_BLANK 24 ++#define _NIST_TRNG_EDU_RBC_CTRL_CH2_RATE 16 ++#define _NIST_TRNG_EDU_RBC_CTRL_CH1_RATE 8 ++#define _NIST_TRNG_EDU_RBC_CTRL_CH0_RATE 0 ++ ++#define _NIST_TRNG_EDU_RBC_CTRL_CH_RATE_MASK 0xFUL ++#define _NIST_TRNG_EDU_RBC_CTRL_CH_URUN_BLANK_MASK 0x3UL ++ ++#define NISTTRNG_EDU_RBC_CTRL_SET_CH_RATE(z, y, x) (((y) & ~(_NIST_TRNG_EDU_RBC_CTRL_CH_RATE_MASK << (x))) | ((z) << (x))) ++#define NISTTRNG_EDU_RBC_CTRL_SET_CH_URUN_BLANK(z, y, x) (((y) & ~(_NIST_TRNG_EDU_RBC_CTRL_CH_URUN_BLANK_MASK << (x))) | ((z) << (x))) ++ ++#define NISTTRNG_EDU_RBC_CTRL_GET_CH_RATE(y, x) ((_NIST_TRNG_EDU_RBC_CTRL_CH_RATE_MASK) & ((y) >> (x))) ++#define NISTTRNG_EDU_RBC_CTRL_GET_CH_URUN_BLANK(y, x) ((_NIST_TRNG_EDU_RBC_CTRL_CH_URUN_BLANK_MASK) & ((y) >> (x))) ++ ++#define NISTTRNG_EDU_RBC_CTRL_GET_CH_RATE_AFTER_RESET 0x0 ++#define NISTTRNG_EDU_RBC_CTRL_SET_CH_URUN_BLANK_AFTER_RESET 0x3 ++ ++/* MODE */ ++#define _NIST_TRNG_REG_MODE_KAT_SEL 7 ++#define _NIST_TRNG_REG_MODE_KAT_VEC 5 ++#define _NIST_TRNG_REG_MODE_ADDIN_PRESENT 4 ++#define _NIST_TRNG_REG_MODE_PRED_RESIST 3 ++#define _NIST_TRNG_REG_MODE_SEC_ALG 0 ++ ++#define NIST_TRNG_REG_MODE_ADDIN_PRESENT BIT(_NIST_TRNG_REG_MODE_ADDIN_PRESENT) ++#define NIST_TRNG_REG_MODE_PRED_RESIST BIT(_NIST_TRNG_REG_MODE_PRED_RESIST) ++#define NIST_TRNG_REG_MODE_SEC_ALG BIT(_NIST_TRNG_REG_MODE_SEC_ALG) ++ ++/* SMODE */ ++#define _NIST_TRNG_REG_SMODE_NOISE_COLLECT 31 ++#define _NIST_TRNG_REG_SMODE_INDIV_HT_DISABLE 16 ++#define _NIST_TRNG_REG_SMODE_MAX_REJECTS 2 ++#define _NIST_TRNG_REG_SMODE_MISSION_MODE 1 ++#define _NIST_TRNG_REG_SMODE_SECURE_EN _NIST_TRNG_REG_SMODE_MISSION_MODE ++#define _NIST_TRNG_REG_SMODE_NONCE 0 ++ ++#define NIST_TRNG_REG_SMODE_MAX_REJECTS(x) ((x) << _NIST_TRNG_REG_SMODE_MAX_REJECTS) ++#define NIST_TRNG_REG_SMODE_SECURE_EN(x) ((x) << _NIST_TRNG_REG_SMODE_SECURE_EN) ++#define NIST_TRNG_REG_SMODE_NONCE BIT(_NIST_TRNG_REG_SMODE_NONCE) ++ ++/* STAT */ ++#define _NIST_TRNG_REG_STAT_BUSY 31 ++#define _NIST_TRNG_REG_STAT_STARTUP_TEST_IN_PROG 10 ++#define _NIST_TRNG_REG_STAT_STARTUP_TEST_STUCK 9 ++#define _NIST_TRNG_REG_STAT_DRBG_STATE 7 ++#define _NIST_TRNG_REG_STAT_SECURE 6 ++#define _NIST_TRNG_REG_STAT_NONCE_MODE 5 ++#define _NIST_TRNG_REG_STAT_SEC_ALG 4 ++#define _NIST_TRNG_REG_STAT_LAST_CMD 0 ++ ++#define NIST_TRNG_REG_STAT_BUSY BIT(_NIST_TRNG_REG_STAT_BUSY) ++//#define NIST_TRNG_REG_STAT_DRBG_STATE (1UL<<_NIST_TRNG_REG_STAT_DRBG_STATE) ++//#define NIST_TRNG_REG_STAT_SECURE (1UL << _NIST_TRNG_REG_STAT_SECURE) ++//#define NIST_TRNG_REG_STAT_NONCE_MODE (1UL << _NIST_TRNG_REG_STAT_NONCE_MODE) ++//#define NIST_TRNG_REG_STAT_SEC_ALG (1UL << _NIST_TRNG_REG_STAT_SEC_ALG) ++//#define NIST_TRNG_REG_STAT_LAST_CMD(x) (((x) >> _NIST_TRNG_REG_STAT_LAST_CMD)&0xF) ++ ++/*EDU_STAT*/ ++ ++#define NIST_TRNG_EDU_STAT_FIFO_LEVEL(x) (((x) >> 24) & 255) ++#define NIST_TRNG_EDU_STAT_TTT_INDEX(x) (((x) >> 16) & 255) ++#define NIST_TRNG_EDU_STAT_RNC_BUSY(x) (((x) >> 3) & 7) ++#define NIST_TRNG_EDU_STAT_RNC_ENABLED(x) (((x) >> 2) & 1) ++#define NIST_TRNG_EDU_STAT_FIFO_EMPTY(x) (((x) >> 1) & 1) ++#define NIST_TRNG_EDU_STAT_FIFO_FULL(x) ((x) & 1) ++ ++/* IE */ ++#define _NIST_TRNG_REG_IE_GLBL 31 ++#define _NIST_TRNG_REG_IE_DONE 4 ++#define _NIST_TRNG_REG_IE_ALARMS 3 ++#define _NIST_TRNG_REG_IE_NOISE_RDY 2 ++#define _NIST_TRNG_REG_IE_KAT_COMPLETE 1 ++#define _NIST_TRNG_REG_IE_ZEROIZE 0 ++ ++#define NIST_TRNG_REG_IE_GLBL BIT(_NIST_TRNG_REG_IE_GLBL) ++#define NIST_TRNG_REG_IE_DONE BIT(_NIST_TRNG_REG_IE_DONE) ++#define NIST_TRNG_REG_IE_ALARMS BIT(_NIST_TRNG_REG_IE_ALARMS) ++#define NIST_TRNG_REG_IE_NOISE_RDY BIT(_NIST_TRNG_REG_IE_NOISE_RDY) ++#define NIST_TRNG_REG_IE_KAT_COMPLETE BIT(_NIST_TRNG_REG_IE_KAT_COMPLETE) ++#define NIST_TRNG_REG_IE_ZEROIZE BIT(_NIST_TRNG_REG_IE_ZEROIZE) ++ ++/* ISTAT */ ++#define _NIST_TRNG_REG_ISTAT_DONE 4 ++#define _NIST_TRNG_REG_ISTAT_ALARMS 3 ++#define _NIST_TRNG_REG_ISTAT_NOISE_RDY 2 ++#define _NIST_TRNG_REG_ISTAT_KAT_COMPLETE 1 ++#define _NIST_TRNG_REG_ISTAT_ZEROIZE 0 ++ ++#define NIST_TRNG_REG_ISTAT_DONE BIT(_NIST_TRNG_REG_ISTAT_DONE) ++#define NIST_TRNG_REG_ISTAT_ALARMS BIT(_NIST_TRNG_REG_ISTAT_ALARMS) ++#define NIST_TRNG_REG_ISTAT_NOISE_RDY BIT(_NIST_TRNG_REG_ISTAT_NOISE_RDY) ++#define NIST_TRNG_REG_ISTAT_KAT_COMPLETE BIT(_NIST_TRNG_REG_ISTAT_KAT_COMPLETE) ++#define NIST_TRNG_REG_ISTAT_ZEROIZE BIT(_NIST_TRNG_REG_ISTAT_ZEROIZE) ++ ++/*EDU_ISTAT*/ ++ ++#define _NIST_TRNG_EDU_ISTAT_CH2_RBC_URUN 8 ++#define _NIST_TRNG_EDU_ISTAT_CH1_RBC_URUN 7 ++#define _NIST_TRNG_EDU_ISTAT_CH0_RBC_URUN 6 ++#define _NIST_TRNG_EDU_ISTAT_PRIVATE_VTRNG 5 ++#define _NIST_TRNG_EDU_ISTAT_WAIT_EXP_TIMEOUT 4 ++#define _NIST_TRNG_EDU_ISTAT_RNC_DRVN_OFFLINE 3 ++#define _NIST_TRNG_EDU_ISTAT_FIFO_URUN 2 ++#define _NIST_TRNG_EDU_ISTAT_ACCESS_VIOL 1 ++#define _NIST_TRNG_EDU_ISTAT_RESEED_REMINDER 0 ++ ++#define NIST_TRNG_EDU_ISTAT_CH2_RBC_URUN BIT(_NIST_TRNG_EDU_ISTAT_CH2_RBC_URUN) ++#define NIST_TRNG_EDU_ISTAT_CH1_RBC_URUN BIT(_NIST_TRNG_EDU_ISTAT_CH1_RBC_URUN) ++#define NIST_TRNG_EDU_ISTAT_CH0_RBC_URUN BIT(_NIST_TRNG_EDU_ISTAT_CH0_RBC_URUN) ++#define NIST_TRNG_EDU_ISTAT_PRIVATE_VTRNG BIT(_NIST_TRNG_EDU_ISTAT_PRIVATE_VTRNG) ++#define NIST_TRNG_EDU_ISTAT_WAIT_EXP_TIMEOUT BIT(_NIST_TRNG_EDU_ISTAT_WAIT_EXP_TIMEOUT) ++#define NIST_TRNG_EDU_ISTAT_RNC_DRVN_OFFLINE BIT(_NIST_TRNG_EDU_ISTAT_RNC_DRVN_OFFLINE) ++#define NIST_TRNG_EDU_ISTAT_FIFO_URUN BIT(_NIST_TRNG_EDU_ISTAT_FIFO_URUN) ++#define NIST_TRNG_EDU_ISTAT_ACCESS_VIOL BIT(_NIST_TRNG_EDU_ISTAT_ACCESS_VIOL) ++#define NIST_TRNG_EDU_ISTAT_RESEED_REMINDER BIT(_NIST_TRNG_EDU_ISTAT_RESEED_REMINDER) ++ ++/* ALARMS */ ++#define NIST_TRNG_REG_ALARM_ILLEGAL_CMD_SEQ BIT(4) ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_OK 0 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_KAT_STAT 1 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_KAT 2 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_MONOBIT 3 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_RUN 4 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_LONGRUN 5 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_AUTOCORRELATION 6 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_POKER 7 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_REPETITION_COUNT 8 ++#define NIST_TRNG_REG_ALARM_FAILED_TEST_ID_ADAPATIVE_PROPORTION 9 ++ ++/* COREKIT_REL */ ++#define NIST_TRNG_REG_EXT_ENUM(x) (((x) >> 28) & 0xF) ++#define NIST_TRNG_REG_EXT_VER(x) (((x) >> 23) & 0xFF) ++#define NIST_TRNG_REG_REL_NUM(x) ((x) & 0xFFFF) ++ ++// This will be deleted ?? per comments in hw details. ie use CFG ++/* FEATURES */ ++#define NIST_TRNG_REG_FEATURES_AES_256(x) (((x) >> 9) & 1) ++#define NIST_TRNG_REG_FEATURES_EXTRA_PS_PRESENT(x) (((x) >> 8) & 1) ++#define NIST_TRNG_REG_FEATURES_DIAG_LEVEL_NS(x) (((x) >> 7) & 1) ++#define NIST_TRNG_REG_FEATURES_DIAG_LEVEL_BASIC_TRNG(x) (((x) >> 4) & 7) ++#define NIST_TRNG_REG_FEATURES_DIAG_LEVEL_ST_HLT(x) (((x) >> 1) & 7) ++#define NIST_TRNG_REG_FEATURES_SECURE_RST_STATE(x) ((x) & 1) ++ ++/* build_CFG0 */ ++#define NIST_TRNG_REG_CFG0_PERSONILIZATION_STR(x) (((x) >> 14) & 1) ++#define NIST_TRNG_REG_CFG0_AES_MAX_KEY_SIZE(x) (((x) >> 13) & 1) ++#define NIST_TRNG_REG_CFG0_AES_DATAPATH(x) (((x) >> 12) & 1) ++#define NIST_TRNG_REG_CFG0_EDU_PRESENT(x) (((x) >> 11) & 1) ++#define NIST_TRNG_REG_CFG0_BACGROUND_NOISE(x) (((x) >> 10) & 1) ++#define NIST_TRNG_REG_CFG0_CDC_SYNCH_DEPTH(x) (((x) >> 8) & 3) ++#define NIST_TRNG_REG_CFG0_BG8(x) (((x) >> 7) & 1) ++#define NIST_TRNG_REG_CFG0_CORE_TYPE(x) ((x) & 3) ++ ++/* build_CFG1 */ ++#define NIST_TRNG_REG_CFG1_ENT_SRC_REP_MIN_ENTROPY(x) (((x) >> 24) & 255) ++#define NIST_TRNG_REG_CFG1_ENT_SRC_REP_TEST(x) (((x) >> 23) & 1) ++#define NIST_TRNG_REG_CFG1_ENT_SRC_REP_SMPL_SIZE(x) (((x) >> 20) & 7) ++#define NIST_TRNG_REG_CFG1_RAW_HT_REP_TEST(x) (((x) >> 19) & 1) ++#define NIST_TRNG_REG_CFG1_RAW_HT_ADAP_TEST(x) (((x) >> 16) & 7) ++#define NIST_TRNG_REG_CFG1_POKER_TEST(x) (((x) >> 15) & 1) ++#define NIST_TRNG_REG_CFG1_RUN_TEST(x) (((x) >> 14) & 1) ++#define NIST_TRNG_REG_CFG1_MONO_BIT_TEST(x) (((x) >> 13) & 1) ++#define NIST_TRNG_REG_CFG1_AUTO_CORRELATION_TEST(x) (((x) >> 12) & 1) ++#define NIST_TRNG_REG_CFG1_STICKY_STARTUP(x) (((x) >> 8) & 1) ++#define NIST_TRNG_REG_CFG1_NUM_RAW_NOISE_BLKS(x) ((x) & 255) ++ ++/* EDU_BUILD_CFG0 */ ++#define NIST_TRNG_REG_EDU_CFG0_RBC2_RATE_WIDTH(x) (((x) >> 20) & 7) ++#define NIST_TRNG_REG_EDU_CFG0_RBC1_RATE_WIDTH(x) (((x) >> 16) & 7) ++#define NIST_TRNG_REG_EDU_CFG0_RBC0_RATE_WIDTH(x) (((x) >> 12) & 7) ++#define NIST_TRNG_REG_EDU_CFG0_PUBLIC_VTRNG_CHANNELS(x) (((x) >> 8) & 15) ++#define NIST_TRNG_REG_EDU_CFG0_ESM_CHANNEL(x) (((x) >> 6) & 1) ++#define NIST_TRNG_REG_EDU_CFG0_RBC_CHANNELS(x) (((x) >> 4) & 3) ++#define NIST_TRNG_REG_EDU_CFG0_FIFO_DEPTH(x) (((x) >> 2) & 7) ++ ++/* EDU_VSTAT */ ++#define NIST_TRNG_REG_EDU_VSTAT_BUSY(x) (((x) >> 31) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_RNC_ENABLED(x) (((x) >> 30) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SEED_ENUM(x) (((x) >> 28) & 3) ++#define NIST_TRNG_REG_EDU_VSTAT_RWUE(x) (((x) >> 27) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_RWNE(x) (((x) >> 26) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SRWE(x) (((x) >> 25) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_ANY_RW1(x) (((x) >> 24) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_BCKGRND_NOISE(x) (((x) >> 23) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_RNC_FIFO_EMPTY(x) (((x) >> 22) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_RWI3(x) (((x) >> 15) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_RWI2(x) (((x) >> 14) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_RWI1(x) (((x) >> 13) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_RWI0(x) (((x) >> 12) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD3(x) (((x) >> 11) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD2(x) (((x) >> 10) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD1(x) (((x) >> 9) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD0(x) (((x) >> 8) & 1) ++#define NIST_TRNG_REG_EDU_VSTAT_CURRENT_CMD(x) (((x) >> 4) & 15) ++#define NIST_TRNG_REG_EDU_VSTAT_LAST_CMD(x) ((x) & 15) ++ ++#define _NIST_TRNG_REG_SMODE_MAX_REJECTS_MASK 255UL ++#define _NIST_TRNG_REG_SMODE_SECURE_EN_MASK 1UL ++#define _NIST_TRNG_REG_SMODE_NONCE_MASK 1UL ++#define _NIST_TRNG_REG_MODE_SEC_ALG_MASK 1UL ++#define _NIST_TRNG_REG_MODE_ADDIN_PRESENT_MASK 1UL ++#define _NIST_TRNG_REG_MODE_PRED_RESIST_MASK 1UL ++#define _NIST_TRNG_REG_MODE_KAT_SEL_MASK 3UL ++#define _NIST_TRNG_REG_MODE_KAT_VEC_MASK 3UL ++#define _NIST_TRNG_REG_STAT_DRBG_STATE_MASK 3UL ++#define _NIST_TRNG_REG_STAT_SECURE_MASK 1UL ++#define _NIST_TRNG_REG_STAT_NONCE_MASK 1UL ++ ++#define NIST_TRNG_REG_SMODE_SET_MAX_REJECTS(y, x) (((y) & ~(_NIST_TRNG_REG_SMODE_MAX_REJECTS_MASK << _NIST_TRNG_REG_SMODE_MAX_REJECTS)) | ((x) << _NIST_TRNG_REG_SMODE_MAX_REJECTS)) ++#define NIST_TRNG_REG_SMODE_SET_SECURE_EN(y, x) (((y) & ~(_NIST_TRNG_REG_SMODE_SECURE_EN_MASK << _NIST_TRNG_REG_SMODE_SECURE_EN)) | ((x) << _NIST_TRNG_REG_SMODE_SECURE_EN)) ++#define NIST_TRNG_REG_SMODE_SET_NONCE(y, x) (((y) & ~(_NIST_TRNG_REG_SMODE_NONCE_MASK << _NIST_TRNG_REG_SMODE_NONCE)) | ((x) << _NIST_TRNG_REG_SMODE_NONCE)) ++#define NIST_TRNG_REG_SMODE_GET_MAX_REJECTS(x) (((x) >> _NIST_TRNG_REG_SMODE_MAX_REJECTS) & _NIST_TRNG_REG_SMODE_MAX_REJECTS_MASK) ++#define NIST_TRNG_REG_SMODE_GET_SECURE_EN(x) (((x) >> _NIST_TRNG_REG_SMODE_SECURE_EN) & _NIST_TRNG_REG_SMODE_SECURE_EN_MASK) ++#define NIST_TRNG_REG_SMODE_GET_NONCE(x) (((x) >> _NIST_TRNG_REG_SMODE_NONCE) & _NIST_TRNG_REG_SMODE_NONCE_MASK) ++ ++#define NIST_TRNG_REG_MODE_SET_SEC_ALG(y, x) (((y) & ~(_NIST_TRNG_REG_MODE_SEC_ALG_MASK << _NIST_TRNG_REG_MODE_SEC_ALG)) | ((x) << _NIST_TRNG_REG_MODE_SEC_ALG)) ++#define NIST_TRNG_REG_MODE_SET_PRED_RESIST(y, x) (((y) & ~(_NIST_TRNG_REG_MODE_PRED_RESIST_MASK << _NIST_TRNG_REG_MODE_PRED_RESIST)) | ((x) << _NIST_TRNG_REG_MODE_PRED_RESIST)) ++#define NIST_TRNG_REG_MODE_SET_ADDIN_PRESENT(y, x) (((y) & ~(_NIST_TRNG_REG_MODE_ADDIN_PRESENT_MASK << _NIST_TRNG_REG_MODE_ADDIN_PRESENT)) | ((x) << _NIST_TRNG_REG_MODE_ADDIN_PRESENT)) ++#define NIST_TRNG_REG_MODE_SET_KAT_SEL(y, x) (((y) & ~(_NIST_TRNG_REG_MODE_KAT_SEL_MASK << _NIST_TRNG_REG_MODE_KAT_SEL)) | ((x) << _NIST_TRNG_REG_MODE_KAT_SEL)) ++#define NIST_TRNG_REG_MODE_SET_KAT_VEC(y, x) (((y) & ~(_NIST_TRNG_REG_MODE_KAT_VEC_MASK << _NIST_TRNG_REG_MODE_KAT_VEC)) | ((x) << _NIST_TRNG_REG_MODE_KAT_VEC)) ++#define NIST_TRNG_REG_MODE_GET_SEC_ALG(x) (((x) >> _NIST_TRNG_REG_MODE_SEC_ALG) & _NIST_TRNG_REG_MODE_SEC_ALG_MASK) ++#define NIST_TRNG_REG_MODE_GET_PRED_RESIST(x) (((x) >> _NIST_TRNG_REG_MODE_PRED_RESIST) & _NIST_TRNG_REG_MODE_PRED_RESIST_MASK) ++#define NIST_TRNG_REG_MODE_GET_ADDIN_PRESENT(x) (((x) >> _NIST_TRNG_REG_MODE_ADDIN_PRESENT) & _NIST_TRNG_REG_MODE_ADDIN_PRESENT_MASK) ++#define NIST_TRNG_REG_STAT_GET_DRBG_STATE(x) (((x) >> _NIST_TRNG_REG_STAT_DRBG_STATE) & _NIST_TRNG_REG_STAT_DRBG_STATE_MASK) ++#define NIST_TRNG_REG_STAT_GET_SECURE(x) (((x) >> _NIST_TRNG_REG_STAT_SECURE) & _NIST_TRNG_REG_STAT_SECURE_MASK) ++#define NIST_TRNG_REG_STAT_GET_NONCE(x) (((x) >> _NIST_TRNG_REG_STAT_NONCE_MODE) & _NIST_TRNG_REG_STAT_NONCE_MASK) ++ ++#endif +diff --git a/drivers/char/hw_random/dwc/src/trng/include/nisttrng_private.h b/drivers/char/hw_random/dwc/src/trng/include/nisttrng_private.h +--- a/drivers/char/hw_random/dwc/src/trng/include/nisttrng_private.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/include/nisttrng_private.h 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2012-2016 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef NISTTRNG_PRIVATE_H ++#define NISTTRNG_PRIVATE_H ++ ++#include "elppdu.h" ++#include "nisttrng_hw.h" ++#include "nisttrng_common.h" ++ ++int nisttrng_wait_on_busy(struct nist_trng_state *state); ++int nisttrng_wait_on_done(struct nist_trng_state *state); ++int nisttrng_wait_on_noise_rdy(struct nist_trng_state *state); ++int nisttrng_get_alarms(struct nist_trng_state *state); ++int nisttrng_reset_state(struct nist_trng_state *state); ++ ++/* ---------- Reminders ---------- */ ++int nisttrng_reset_counters(struct nist_trng_state *state); ++int nisttrng_set_reminder_max_bits_per_req(struct nist_trng_state *state, unsigned long max_bits_per_req); ++int nisttrng_set_reminder_max_req_per_seed(struct nist_trng_state *state, unsigned long long max_req_per_seed); ++int nisttrng_check_seed_lifetime(struct nist_trng_state *state); ++ ++/* ---------- Set field APIs ---------- */ ++int nisttrng_set_sec_strength(struct nist_trng_state *state, int req_sec_strength); ++int nisttrng_set_addin_present(struct nist_trng_state *state, int addin_present); ++int nisttrng_set_pred_resist(struct nist_trng_state *state, int pred_resist); ++int nisttrng_set_secure_mode(struct nist_trng_state *state, int secure_mode); ++int nisttrng_set_nonce_mode(struct nist_trng_state *state, int nonce_mode); ++ ++/* ---------- Load data APIs ---------- */ ++int nisttrng_load_ps_addin(struct nist_trng_state *state, void *input_str); ++ ++/* ---------- Command APIs ---------- */ ++int nisttrng_get_entropy_input(struct nist_trng_state *state, void *input_nonce, int nonce_operation); ++int nisttrng_refresh_addin(struct nist_trng_state *state, void *addin_str); ++int nisttrng_gen_random(struct nist_trng_state *state, void *random_bits, unsigned long req_num_bytes); ++int nisttrng_advance_state(struct nist_trng_state *state); ++int nisttrng_kat(struct nist_trng_state *state, int kat_sel, int kat_vec); ++int nisttrng_full_kat(struct nist_trng_state *state); ++int nisttrng_zeroize(struct nist_trng_state *state); ++ ++/* ---------- edu related ---------- */ ++ ++int nisttrng_rnc(struct nist_trng_state *state, int rnc_ctrl_cmd); ++int nisttrng_wait_fifo_full(struct nist_trng_state *state); ++#endif +diff --git a/drivers/char/hw_random/dwc/src/trng/include/synversion.h b/drivers/char/hw_random/dwc/src/trng/include/synversion.h +--- a/drivers/char/hw_random/dwc/src/trng/include/synversion.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/include/synversion.h 2026-09-09 15:51:42.909265407 +0000 +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2012-2016 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef VERSION_H ++#define VERSION_H ++ ++#define TRNG_VERSION "1.00a" ++ ++#endif +diff --git a/drivers/char/hw_random/dwc/src/trng/kernel/nist_trng.c b/drivers/char/hw_random/dwc/src/trng/kernel/nist_trng.c +--- a/drivers/char/hw_random/dwc/src/trng/kernel/nist_trng.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/kernel/nist_trng.c 2026-09-09 15:51:42.910265392 +0000 +@@ -0,0 +1,2170 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2012-2017 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "nisttrng.h" ++ ++#define SYNOPSYS_HWRNG_DRIVER_NAME "hwrng-nist_trng" ++ ++#define num_gen_bytes 64 ++static unsigned long max_reads = 128; ++ ++struct synopsys_nisttrng_driver { ++ struct nist_trng_state nisttrng; ++ void *hwrng_drv; ++ void *crypto_drv; ++ unsigned char rand_out[num_gen_bytes]; ++}; ++ ++static unsigned int xxd_vtrng; ++ ++static void nisttrng_reinit(struct nist_trng_state *nist_trng) ++{ ++ int err; ++ ++ err = nisttrng_uninstantiate(nist_trng); ++ if (err && err != CRYPTO_NOT_INSTANTIATED) ++ goto ERR; ++ ++ err = nisttrng_instantiate(nist_trng, 128, 1, NULL); ++ if (err) ++ goto ERR; ++ ++ERR: ++ DEBUG("NIST_TRNG: Trying to reinitialize after a fatal alarm: %d\n", ++ err); ++} ++ ++static int nisttrng_platform_driver_read(struct platform_device *pdev, ++ void *buf, size_t max, bool wait) ++{ ++ struct synopsys_nisttrng_driver *data = 0; ++ int nisttrng_error = -1; ++ u32 *out = kmalloc(max, GFP_KERNEL); ++ unsigned int vtrng; ++ ++ if (!out) { ++ SYNHW_PRINT("memory not allocated\n"); ++ return -1; ++ } ++ ++ if (!pdev || !buf || !max) ++ return nisttrng_error; ++ ++ data = platform_get_drvdata(pdev); ++ if (data == 0) ++ return nisttrng_error; ++ ++ if (data->nisttrng.config.build_cfg0.edu_present) { ++ vtrng = xxd_vtrng % ((data->nisttrng.config.edu_build_cfg0 ++ .public_vtrng_channels) + ++ 1); ++ if (vtrng == 0) { ++ /* private vtrng */ ++ nisttrng_error = nisttrng_generate(&data->nisttrng, out, max, ++ data->nisttrng.status.sec_strength ? 256 : 128, ++ data->nisttrng.status.pred_resist, NULL); ++ } else { ++ /* public vtrng */ ++ nisttrng_error = nisttrng_generate_public_vtrng(&data->nisttrng, out, max, vtrng - 1); ++ } ++ xxd_vtrng++; ++ } else { ++ /* nist core vtrng */ ++ nisttrng_error = nisttrng_generate(&data->nisttrng, out, max, ++ data->nisttrng.status.sec_strength ? 256 : 128, ++ data->nisttrng.status.pred_resist, NULL); ++ } ++ if (nisttrng_error < 0) { ++ if (data->nisttrng.status.alarm_code) ++ nisttrng_reinit(&data->nisttrng); ++ ++ return nisttrng_error; ++ } ++ ++ memcpy(buf, out, max); ++ kfree(out); ++ ++ return max; ++} ++ ++static int nisttrng_hwrng_driver_read(struct hwrng *rng, void *buf, size_t max, ++ bool wait) ++{ ++ struct platform_device *pdev = 0; ++ ++ if (rng == 0) ++ return -1; ++ ++ pdev = (struct platform_device *)rng->priv; ++ return nisttrng_platform_driver_read(pdev, buf, max, wait); ++} ++ ++static ssize_t ckr_show(struct device *dev, struct device_attribute *devattr, ++ char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "rel_num=%u, ext_ver=%u, ext_enum=%u\n", ++ priv->nisttrng.config.corekit_rel.rel_num, ++ priv->nisttrng.config.corekit_rel.ext_ver, ++ priv->nisttrng.config.corekit_rel.ext_enum); ++} ++ ++static ssize_t features_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, ++ "drbg_arch = %u, diag_basic_trng=%u, diag_st_hlt=%u, diag_ns=%u, secure_rst_state=%u, extra_ps_present=%u\n", ++ priv->nisttrng.config.features.drbg_arch, ++ priv->nisttrng.config.features.diag_level_basic_trng, ++ priv->nisttrng.config.features.diag_level_stat_hlt, ++ priv->nisttrng.config.features.diag_level_ns, ++ priv->nisttrng.config.features.secure_rst_state, ++ priv->nisttrng.config.features.extra_ps_present); ++} ++ ++static ssize_t secure_show(struct device *dev, struct device_attribute *devattr, ++ char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%s\n", NIST_TRNG_REG_SMODE_GET_SECURE_EN(pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_SMODE)) ? "on" : "off"); ++} ++ ++static ssize_t secure_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = nisttrng_set_secure_mode(&priv->nisttrng, ++ sysfs_streq(buf, "on") ? 1 : 0); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t nonce_show(struct device *dev, struct device_attribute *devattr, ++ char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%s\n", NIST_TRNG_REG_SMODE_GET_NONCE(pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_SMODE)) ? "on" : "off"); ++} ++ ++static ssize_t nonce_store(struct device *dev, struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = nisttrng_set_nonce_mode(&priv->nisttrng, ++ sysfs_streq(buf, "on") ? 1 : 0); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t sec_strength_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%s\n", ++ priv->nisttrng.status.sec_strength ? "256" : "128"); ++} ++ ++static ssize_t sec_strength_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ int tmp; ++ int ret; ++ ++ if (count > 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtoint(foo, 10, &tmp); ++ if (ret) ++ return ret; ++ ++ ret = nisttrng_set_sec_strength(&priv->nisttrng, tmp); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t rand_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ unsigned int x; ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ for (x = 0; x < 4; x++) { ++ sprintf(buf + 8 * x, "%08lx", ++ pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_RAND0 + 3 - x)); ++ } ++ ++ strcat(buf, "\n"); ++ return strlen(buf); ++} ++ ++static ssize_t seed_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ unsigned int x; ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ for (x = 0; x < 12; x++) { ++ sprintf(buf + 8 * x, "%08lx", ++ pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_SEED0 + 11 - x)); ++ } ++ strcat(buf, "\n"); ++ return strlen(buf); ++} ++ ++static ssize_t seed_reg_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int x, tmp; ++ int ret; ++ ++ // string must be at least 12 32-bit words long in 0 padded hex ++ if (count < (2 * 12 * 4)) ++ return -1; ++ ++ foo[8] = 0; ++ for (x = 0; x < 12; x++) { ++ memcpy(foo, buf + x * 8, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_SEED0 + x, ++ tmp); ++ } ++ ++ return count; ++} ++ ++static ssize_t npa_data_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ unsigned int x; ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ for (x = 0; x < 16; x++) { ++ sprintf(buf + 8 * x, "%08lx", ++ pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_NPA_DATA0 + 15 - x)); ++ } ++ ++ strcat(buf, "\n"); ++ return strlen(buf); ++} ++ ++static ssize_t npa_data_reg_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int x, tmp; ++ int ret; ++ ++ // string must be at least 16 32-bit words long in 0 padded hex ++ if (count < (2 * 16 * 4)) ++ return -1; ++ ++ foo[8] = 0; ++ for (x = 0; x < 16; x++) { ++ memcpy(foo, buf + x * 8, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_NPA_DATA0 + x, tmp); ++ } ++ ++ return count; ++} ++ ++static ssize_t ctrl_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_CTRL)); ++} ++ ++static ssize_t ctrl_reg_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_CTRL, tmp); ++ return count; ++} ++ ++static ssize_t istat_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_ISTAT)); ++} ++ ++static ssize_t istat_reg_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_ISTAT, tmp); ++ return count; ++} ++ ++static ssize_t mode_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_MODE)); ++} ++ ++static ssize_t mode_reg_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_MODE, tmp); ++ ++ return count; ++} ++ ++static ssize_t smode_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_SMODE)); ++} ++ ++static ssize_t smode_reg_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_SMODE, tmp); ++ return count; ++} ++ ++static ssize_t alarm_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_ALARM)); ++} ++ ++static ssize_t alarm_reg_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_ALARM, tmp); ++ return count; ++} ++ ++static ssize_t stat_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_STAT)); ++} ++ ++static ssize_t ia_wdata_reg_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_WDATA, tmp); ++ return count; ++} ++ ++static ssize_t ia_wdata_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_IA_WDATA)); ++} ++ ++static ssize_t ia_rdata_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_IA_RDATA)); ++} ++ ++static ssize_t ia_addr_reg_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_ADDR, tmp); ++ return count; ++} ++ ++static ssize_t ia_addr_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_IA_ADDR)); ++} ++ ++static ssize_t ia_cmd_reg_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_CMD, tmp); ++ return count; ++} ++ ++static ssize_t ia_cmd_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_IA_CMD)); ++} ++ ++static ssize_t rnc_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_EDU_RNC_CTRL)); ++} ++ ++static ssize_t rnc_reg_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned int tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtouint(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_EDU_RNC_CTRL, tmp); ++ ++ return count; ++} ++ ++static ssize_t rbc_reg_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ pdu_io_read32(priv->nisttrng.base + NIST_TRNG_EDU_RBC_CTRL)); ++} ++ ++static ssize_t rbc_reg_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ char opts_str[5]; ++ unsigned int opts_int; ++ int enable, rbc_num, rate, urun_blnk, ret; ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ opts_str[4] = 0; ++ memcpy(opts_str, buf, 4); ++ ret = kstrtouint(opts_str, 16, &opts_int); ++ if (ret) ++ return ret; ++ ++ SYNHW_PRINT("%s %x\n", __func__, opts_int); ++ ++ enable = (opts_int >> 12 & 0xf); ++ if (enable > 1) { ++ SYNHW_PRINT("incorrect enable %x\n", enable); ++ return -1; ++ } ++ ++ rbc_num = (opts_int >> 8 & 0xf); ++ if (rbc_num > priv->nisttrng.config.edu_build_cfg0.rbc_channels - 1) { ++ SYNHW_PRINT("incorrect rbc_num %x\n", rbc_num); ++ return -1; ++ } ++ ++ rate = (opts_int >> 4 & 0xf); ++ if (rate > 8) { ++ SYNHW_PRINT("incorrect rate %x\n", rate); ++ return -1; ++ } ++ ++ urun_blnk = (opts_int & 0xf); ++ if (urun_blnk > 3) { ++ SYNHW_PRINT("incorrect urun_blnk %x\n", urun_blnk); ++ return -1; ++ } ++ ++ SYNHW_PRINT("enable %x rbc_num %x rate %x urun_blnk %x\n", enable, ++ rbc_num, rate, urun_blnk); ++ ++ ret = nisttrng_rbc(&priv->nisttrng, enable, rbc_num, rate, ++ urun_blnk); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t hw_state_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ u32 addr; ++ int i; ++ int tot_char; ++ ++ addr = 0x20; ++ tot_char = sprintf(buf, "Key = "); ++ for (i = 0; i < 8; i++) { ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_ADDR, ++ addr + 7 - i); ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_CMD, ++ 0x80000000); ++ tot_char += sprintf(buf + tot_char, "%08lx", ++ pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_IA_RDATA)); ++ } ++ tot_char += sprintf(buf + tot_char, "\n"); ++ ++ addr = 0x28; ++ tot_char += sprintf(buf + tot_char, "V = "); ++ for (i = 0; i < 4; i++) { ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_ADDR, ++ addr + 3 - i); ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_CMD, ++ 0x80000000); ++ tot_char += sprintf(buf + tot_char, "%08lx", ++ pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_IA_RDATA)); ++ } ++ ++ tot_char += sprintf(buf + tot_char, "\n"); ++ ++ return tot_char; ++} ++ ++static ssize_t max_bits_per_req_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ unsigned long tmp; ++ int ret; ++ ++ // string must be at least a 32-bit word in 0 padded hex ++ if (count < 8) ++ return -1; ++ ++ foo[8] = 0; ++ memcpy(foo, buf, 8); ++ ret = kstrtoul(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ ret = nisttrng_set_reminder_max_bits_per_req(&priv->nisttrng, ++ tmp); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t max_bits_per_req_show(struct device *dev, ++ struct device_attribute *devattr, ++ char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08lx\n", ++ priv->nisttrng.counters.max_bits_per_req); ++} ++ ++static ssize_t max_req_per_seed_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[17]; ++ unsigned long long tmp; ++ int ret; ++ ++ // string must be at least a 64-bit word in 0 padded hex ++ if (count < 16) ++ return -1; ++ ++ foo[16] = 0; ++ memcpy(foo, buf, 16); ++ ret = kstrtoull(foo, 16, &tmp); ++ if (ret) ++ return ret; ++ ++ ret = nisttrng_set_reminder_max_req_per_seed(&priv->nisttrng, ++ tmp); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t max_req_per_seed_show(struct device *dev, ++ struct device_attribute *devattr, ++ char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%08llx\n", ++ priv->nisttrng.counters.max_req_per_seed); ++} ++ ++static ssize_t collect_ent_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ int rep; ++ int i, j; ++ int ret; ++ u32 tmp; ++ int t; ++ ++ t = NIST_TRNG_RETRY_MAX; ++ ++ // Change to TEST mode ++ DEBUG("Change to TEST mode\n"); ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_SMODE, 0x00000028); ++ // Turn on the noise collect mode ++ DEBUG("Turn on the noise collect mode\n"); ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_SMODE, 0x80000028); ++ ++ // issue generate entropy command ++ DEBUG("Issue a GEN_NOISE command\n"); ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_GEN_NOISE); ++ ++ // read raw noise ++ // 2 reads if sec_strength is 128 and 3 reads if it is 256 ++ if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES128) ++ rep = 2; ++ else if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES256) ++ rep = 3; ++ ++ for (i = 0; i < rep; i++) { ++ t = NIST_TRNG_RETRY_MAX; ++ tmp = 0; ++ DEBUG("Wait for NOISE_RDY interrupt.\n"); ++ do { ++ tmp = pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_ISTAT); ++ } while (!(tmp & (NIST_TRNG_REG_ISTAT_NOISE_RDY | ++ NIST_TRNG_REG_ISTAT_ALARMS)) && ++ --t); ++ ++ DEBUG("Read NPA_DATAx\n"); ++ for (j = 0; j < 16; j++) { ++ sprintf(buf + 128 * i + 8 * j, "%08lx", ++ pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_NPA_DATA0 + j)); ++ } ++ ++ // clear NOISE_RDY IRQ ++ DEBUG("Clear NOISE_RDY interrupt.\n"); ++ ret = nisttrng_wait_on_noise_rdy(&priv->nisttrng); ++ if (ret) ++ return -1; ++ } ++ ++ DEBUG("Wait for DONE\n"); ++ ret = nisttrng_wait_on_done(&priv->nisttrng); ++ if (ret) ++ return -1; ++ ++ strcat(buf, "\n"); ++ return strlen(buf); ++} ++ ++static ssize_t collect_ent_nsout_show(struct device *dev, ++ struct device_attribute *devattr, ++ char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ int rep; ++ int i; ++ int ret; ++ ++ // generate entropy ++ ret = nisttrng_get_entropy_input(&priv->nisttrng, NULL, 0); ++ if (ret) ++ return -1; ++ ++ // read NS_OUTPUTx ++ // 32 reads if sec_strength is 128 and 48 reads if it is 256 ++ if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES128) ++ rep = 32; ++ else if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES256) ++ rep = 48; ++ ++ for (i = 0; i < rep; i++) { ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_ADDR, ++ 0x70 + rep - 1 - i); ++ pdu_io_write32(priv->nisttrng.base + NIST_TRNG_REG_IA_CMD, ++ 0x80000000); ++ sprintf(buf + 8 * i, "%08lx", ++ pdu_io_read32(priv->nisttrng.base + ++ NIST_TRNG_REG_IA_RDATA)); ++ } ++ ++ strcat(buf, "\n"); ++ return strlen(buf); ++} ++ ++static ssize_t nonce_seed_with_df_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ u32 seed[48] = { 0 }; ++ int rep; ++ int i; ++ int ret; ++ ++ if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES128) ++ rep = 2; ++ else if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES256) ++ rep = 3; ++ ++ DEBUG("Number of char in input = %zu\n", count); ++ if (count != (rep * 128)) ++ return -1; ++ ++ foo[8] = 0; ++ for (i = 0; i < (rep * 16); i++) { ++ memcpy(foo, buf + i * 8, 8); ++ ret = kstrtouint(foo, 16, (seed + (rep * 16 - 1) - i)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = nisttrng_get_entropy_input(&priv->nisttrng, seed, 1); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t nonce_seed_direct_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char foo[9]; ++ u32 seed[12] = { 0 }; ++ int rep; ++ int i; ++ int ret; ++ ++ if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES128) ++ rep = 2; ++ else if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES256) ++ rep = 3; ++ ++ DEBUG("Number of char in input = %zu\n", count); ++ if (count != (rep * 32)) ++ return -1; ++ ++ foo[8] = 0; ++ for (i = 0; i < (rep * 4); i++) { ++ memcpy(foo, buf + i * 8, 8); ++ ret = kstrtouint(foo, 16, (seed + (rep * 4 - 1) - i)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = nisttrng_get_entropy_input(&priv->nisttrng, seed, 0); ++ if (ret) ++ return -1; ++ ++ return count; ++} ++ ++static ssize_t instantiate_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char opts_str[101]; ++ unsigned int opts_int; ++ int req_sec_strength = 256; ++ int pred_resist = 1; ++ bool ps_exists = 0; ++ u32 ps[12]; ++ unsigned int ps_length; ++ int i; ++ int ret; ++ ++ /* First 3 digits: ++ * they have to be 0 or 1 ++ * 2-1-0 --> 2: predictoin resistance, 1: security strength, 0: personilizatoin string existence ++ */ ++ opts_str[3] = 0; ++ memcpy(opts_str, buf, 3); ++ ret = kstrtouint(opts_str, 2, &opts_int); ++ if (ret) ++ return ret; ++ ++ if (((opts_str[0] != '0') && (opts_str[0] != '1')) || ++ ((opts_str[1] != '0') && (opts_str[1] != '1')) || ++ ((opts_str[2] != '0') && (opts_str[2] != '1'))) { ++ SYNHW_PRINT("Invalid input options: First 3 digits can only be 1 or 0\n"); ++ return -1; ++ } ++ ++ if (opts_int & 1) ++ ps_exists = 1; ++ else ++ ps_exists = 0; ++ ++ if (opts_int & 2) ++ req_sec_strength = 256; ++ else ++ req_sec_strength = 128; ++ ++ if (opts_int & 4) ++ pred_resist = 1; ++ else ++ pred_resist = 0; ++ ++ /* check input option length */ ++ if (!ps_exists) { ++ if (count != 3) { ++ SYNHW_PRINT("Invalid input options: If personilization string does not exist, options has to be 3 char.\n"); ++ return -1; ++ } ++ } else { ++ if (req_sec_strength == 128) { ++ if (count != 64 + 4) { // +4 for options and "-" ++ SYNHW_PRINT("Invalid input options: If personilization string exists and security strength is 128-bit, options has to be 68 char (not %zu char).\n", ++ count); ++ return -1; ++ } ++ } else if (req_sec_strength == 256) { ++ if (count != ++ 96 + 4) { // +4 for options and "-", +1 because of the termination char that count includes ++ SYNHW_PRINT("Invalid input options: If personilization string exists and security strength is 256-bit, options has to be 100 char (not %zu char).\n", ++ count); ++ return -1; ++ } ++ } else { ++ SYNHW_PRINT("Invalid input options\n"); ++ return -1; ++ } ++ } ++ ++ /* Personilization string */ ++ for (i = 0; i < 12; i++) ++ ps[i] = 0; ++ ++ if (req_sec_strength == 128) ++ ps_length = 64; ++ else if (req_sec_strength == 256) ++ ps_length = 96; ++ else ++ SYNHW_PRINT("Invalid security strength\n"); ++ ++ if (ps_exists) { ++ opts_str[1] = 0; ++ memcpy(opts_str, buf + 3, 1); ++ ++ if (opts_str[0] == '-') { ++ opts_str[8] = 0; ++ for (i = 0; i < ps_length / 8; i++) { ++ memcpy(opts_str, buf + 4 + i * 8, 8); ++ ret = kstrtouint(opts_str, 16, ++ ps + (ps_length / 8 - 1) - i); ++ if (ret) ++ return ret; ++ } ++ } else { ++ SYNHW_PRINT("4th character of input has to be \"-\" when personilization string exists\n"); ++ } ++ ++ ret = nisttrng_instantiate(&priv->nisttrng, ++ req_sec_strength, pred_resist, ++ ps); ++ if (ret) ++ return -1; ++ ++ } else { ++ ret = nisttrng_instantiate(&priv->nisttrng, ++ req_sec_strength, pred_resist, ++ NULL); ++ if (ret) ++ return -1; ++ } ++ ++ return count; ++} ++ ++static ssize_t uninstantiate_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ ++ nisttrng_uninstantiate(&priv->nisttrng); ++ ++ return count; ++} ++ ++static ssize_t reseed_store(struct device *dev, struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char opts_str[100]; ++ unsigned int opts_int; ++ int pred_resist = 1; ++ bool addin_exists = 0; ++ u32 addin[12]; ++ unsigned int addin_length; ++ int i; ++ int ret; ++ ++ /* First 2 digits: ++ * they have to be 0 or 1 ++ * 1-0 --> 1: predictoin resistance, 0: additional input string existence ++ */ ++ opts_str[2] = 0; ++ memcpy(opts_str, buf, 2); ++ ret = kstrtouint(opts_str, 2, &opts_int); ++ if (ret) ++ return ret; ++ ++ if (((opts_str[0] != '0') && (opts_str[0] != '1')) || ++ ((opts_str[1] != '0') && (opts_str[1] != '1'))) { ++ SYNHW_PRINT("Invalid input options: First 2 digits can only be 1 or 0\n"); ++ return -1; ++ } ++ ++ if (opts_int & 1) ++ addin_exists = 1; ++ else ++ addin_exists = 0; ++ ++ if (opts_int & 2) ++ pred_resist = 1; ++ else ++ pred_resist = 0; ++ ++ /* check input option length */ ++ if (!addin_exists) { ++ if (count != 2) { ++ SYNHW_PRINT("Invalid input options: If additional input does not exist, options has to be 2 char.\n"); ++ return -1; ++ } ++ } else { ++ if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES128) { ++ if (count != 64 + 3) { // +3 for options and "-" ++ SYNHW_PRINT("Invalid input options: If additional input exists and security strength is 128-bit, options has to be 67 char.\n"); ++ return -1; ++ } ++ } else if (priv->nisttrng.status.sec_strength == ++ SEC_STRNT_AES256) { ++ if (count != 96 + 3) { // +3 for options and "-" ++ SYNHW_PRINT("Invalid input options: If additional input exists and security strength is 256-bit, options has to be 99 char.\n"); ++ return -1; ++ } ++ } else { ++ SYNHW_PRINT("Invalid input options\n"); ++ return -1; ++ } ++ } ++ ++ /* Additional input */ ++ for (i = 0; i < 12; i++) ++ addin[i] = 0; ++ ++ if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES128) ++ addin_length = 64; ++ else if (priv->nisttrng.status.sec_strength == SEC_STRNT_AES256) ++ addin_length = 96; ++ else ++ SYNHW_PRINT("Invalid security strength\n"); ++ ++ if (addin_exists) { ++ opts_str[1] = 0; ++ memcpy(opts_str, buf + 2, 1); ++ ++ if (opts_str[0] == '-') { ++ opts_str[8] = 0; ++ for (i = 0; i < addin_length / 8; i++) { ++ memcpy(opts_str, buf + 3 + i * 8, 8); ++ ret = kstrtouint(opts_str, 16, addin + (addin_length / 8 - 1) - i); ++ if (ret) ++ return ret; ++ } ++ } else { ++ SYNHW_PRINT("3rd character of input has to be \"-\" when additional input exists\n"); ++ } ++ ++ ret = nisttrng_reseed(&priv->nisttrng, pred_resist, ++ addin); ++ if (ret) ++ return -1; ++ ++ } else { ++ ret = nisttrng_reseed(&priv->nisttrng, pred_resist, ++ NULL); ++ if (ret) ++ return -1; ++ } ++ ++ return count; ++} ++ ++static ssize_t generate_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char opts_str[101]; ++ unsigned int opts_int; ++ int req_sec_strength = 128; ++ int pred_resist = 1; ++ bool addin_exists = 0; ++ unsigned char out[num_gen_bytes]; ++ u32 addin[12]; ++ unsigned int addin_length; ++ int i; ++ int ret; ++ ++ /* First 3 digits: ++ * they have to be 0 or 1 ++ * 2-1-0 --> 2: predictoin resistance, 1: security strength, 0: additional input string existence ++ */ ++ opts_str[3] = 0; ++ memcpy(opts_str, buf, 3); ++ ret = kstrtouint(opts_str, 2, &opts_int); ++ if (ret) ++ return ret; ++ ++ if (((opts_str[0] != '0') && (opts_str[0] != '1')) || ++ ((opts_str[1] != '0') && (opts_str[1] != '1')) || ++ ((opts_str[2] != '0') && (opts_str[2] != '1'))) { ++ SYNHW_PRINT("Invalid input options: First 3 digits can only be 1 or 0\n"); ++ return -1; ++ } ++ ++ if (opts_int & 1) ++ addin_exists = 1; ++ else ++ addin_exists = 0; ++ ++ if (opts_int & 2) ++ req_sec_strength = 256; ++ else ++ req_sec_strength = 128; ++ ++ if (opts_int & 4) ++ pred_resist = 1; ++ else ++ pred_resist = 0; ++ ++ /* check input option length */ ++ if (!addin_exists) { ++ if (count != 3) { ++ SYNHW_PRINT("Invalid input options: If additional input does not exist, options has to be 3 char.\n"); ++ return -1; ++ } ++ } else { ++ if (req_sec_strength == 128) { ++ if (count != 64 + 4) { // +4 for options and "-" ++ SYNHW_PRINT("Invalid input options: If additional input exists and security strength is 128-bit, options has to be 68 char.\n"); ++ return -1; ++ } ++ } else if (req_sec_strength == 256) { ++ if (count != 96 + 4) { // +4 for options and "-" ++ SYNHW_PRINT("Invalid input options: If additional input exists and security strength is 256-bit, options has to be 100 char.\n"); ++ return -1; ++ } ++ } else { ++ SYNHW_PRINT("Invalid input options\n"); ++ return -1; ++ } ++ } ++ ++ /* Additional input */ ++ for (i = 0; i < 12; i++) ++ addin[i] = 0; ++ ++ if (req_sec_strength == 128) ++ addin_length = 64; ++ else if (req_sec_strength == 256) ++ addin_length = 96; ++ else ++ SYNHW_PRINT("Invalid security strength\n"); ++ ++ if (addin_exists) { ++ opts_str[1] = 0; ++ memcpy(opts_str, buf + 3, 1); ++ ++ if (opts_str[0] == '-') { ++ opts_str[8] = 0; ++ for (i = 0; i < addin_length / 8; i++) { ++ memcpy(opts_str, buf + 4 + i * 8, 8); ++ ret = kstrtouint(opts_str, 16, addin + (addin_length / 8 - 1) - i); ++ if (ret) ++ return ret; ++ } ++ } else { ++ SYNHW_PRINT("4th character of input has to be \"-\" when additional input exists\n"); ++ } ++ ++ ret = nisttrng_generate(&priv->nisttrng, (u32 *)out, ++ num_gen_bytes, req_sec_strength, ++ pred_resist, addin); ++ if (ret) ++ return -1; ++ ++ } else { ++ ret = nisttrng_generate(&priv->nisttrng, (u32 *)out, ++ num_gen_bytes, req_sec_strength, ++ pred_resist, NULL); ++ if (ret) ++ return -1; ++ } ++ ++ /* store the result */ ++ memcpy(priv->rand_out, out, sizeof(out)); ++ ++ return count; ++} ++ ++static ssize_t generate_pub_vtrng_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ char opts_str[2]; ++ unsigned int opts_int; ++ unsigned char out[num_gen_bytes]; ++ int ret; ++ ++ opts_str[1] = 0; ++ memcpy(opts_str, buf, 1); ++ ret = kstrtouint(opts_str, 16, &opts_int); ++ if (ret) ++ return ret; ++ ++ SYNHW_PRINT("%s %d %d %d %d\n", __func__, opts_str[0], ++ priv->nisttrng.config.edu_build_cfg0.public_vtrng_channels, ++ opts_str[1], opts_int); ++ ++ ret = nisttrng_generate_public_vtrng(&priv->nisttrng, ++ (u32 *)out, ++ num_gen_bytes, opts_int); ++ if (ret) ++ return -1; ++ ++ memcpy(priv->rand_out, out, sizeof(out)); ++ ++ return count; ++} ++ ++/* rand_out_show displays last generated random number (num_gen_bytes number of bytes), not just the last block. */ ++static ssize_t rand_out_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ unsigned int i, j; ++ unsigned long rand; ++ bool all_zero = true; ++ ++ /* If all bits of the rand_reg register are 0, display 0 */ ++ for (i = 0; i < 4; i++) { ++ rand = pdu_io_read32(priv->nisttrng.base + NIST_TRNG_REG_RAND0 + ++ (3 - i)); ++ if (rand != 0) { ++ all_zero = false; ++ break; ++ } ++ } ++ ++ if (all_zero) { ++ sprintf(buf + 2 * i, "%02x", 0); ++ } else { ++ for (i = 0; i < (num_gen_bytes / 16); i++) { ++ for (j = 0; j < 16; j++) { ++ sprintf(buf + 2 * (i * 16 + j), "%02x", ++ priv->rand_out[(i + 1) * 16 - 1 - j]); ++ } ++ } ++ j = 0; ++ while (i * 16 + j < num_gen_bytes) { ++ sprintf(buf + 2 * (i * 16 + j), "%02x", ++ priv->rand_out[num_gen_bytes - 1 - j]); ++ j++; ++ } ++ } ++ ++ strcat(buf, "\n"); ++ return strlen(buf); ++} ++ ++/* rand_out_vtrng_show displays last generated random number (num_gen_bytes number of bytes), not just the last block. */ ++static ssize_t rand_out_vtrng_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ unsigned int i, j; ++ ++ /* If all bits of the rand_reg register are 0, display 0 */ ++ ++ for (i = 0; i < (num_gen_bytes / 16); i++) { ++ for (j = 0; j < 16; j++) { ++ sprintf(buf + 2 * (i * 16 + j), "%02x", ++ priv->rand_out[(i + 1) * 16 - 1 - j]); ++ } ++ } ++ ++ j = 0; ++ while (i * 16 + j < num_gen_bytes) { ++ sprintf(buf + 2 * (i * 16 + j), "%02x", ++ priv->rand_out[num_gen_bytes - 1 - j]); ++ j++; ++ } ++ ++ strcat(buf, "\n"); ++ return strlen(buf); ++} ++ ++static ssize_t kat_store(struct device *dev, struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ int ret; ++ ++ if (sysfs_streq(buf, "full")) { ++ ret = nisttrng_full_kat(&priv->nisttrng); ++ if (ret) ++ return -1; ++ ++ } else if (sysfs_streq(buf, "00")) { ++ ret = nisttrng_kat(&priv->nisttrng, 0, 0); ++ if (ret) ++ return -1; ++ ++ } else if (sysfs_streq(buf, "01")) { ++ ret = nisttrng_kat(&priv->nisttrng, 0, 1); ++ if (ret) ++ return -1; ++ ++ } else if (sysfs_streq(buf, "10")) { ++ ret = nisttrng_kat(&priv->nisttrng, 1, 0); ++ if (ret) ++ return -1; ++ ++ } else if (sysfs_streq(buf, "11")) { ++ ret = nisttrng_kat(&priv->nisttrng, 1, 1); ++ if (ret) ++ return -1; ++ ++ } else { ++ ret = nisttrng_full_kat(&priv->nisttrng); ++ if (ret) ++ return -1; ++ } ++ ++ return count; ++} ++ ++static void str_to_384_bit(char *buf, u32 *out) ++{ ++ char foo[9]; ++ int i; ++ int ret; ++ ++ foo[8] = 0; ++ for (i = 0; i < 12; i++) { ++ memcpy(foo, buf + i * 8, 8); ++ ret = kstrtouint(foo, 16, out + 11 - i); ++ } ++} ++ ++/* This attribute is only for test purpuses */ ++static ssize_t test_attr_store(struct device *dev, ++ struct device_attribute *devattr, const char *buf, ++ size_t count) ++{ ++ struct synopsys_nisttrng_driver *priv = dev_get_drvdata(dev); ++ int i; ++ int err; ++ u32 addin[12]; ++ u32 ps[12]; ++ char *out; ++ ++ char buf_seed1[96] = ++ "c54805274bde00aa5289e0513579019707666d2fa7a1c8908865891c87c0c652335a4d3cc415bc30742b164647f8820f"; ++ char buf_ps1[96] = ++ "d63fb5afa2101fa4b8a6c3b89d9c250ac728fc1ddad0e7585b5d54728ed20c2f940e89155596e3b963635b6d6088164b"; ++ char buf_addin1[96] = ++ "744bfae3c23a5cc9a3b373b6c50795068d35eb8a339746ac810d16f864e880061082edf9d2687c211960aa83400f85f9"; ++ char buf_seed2[96] = ++ "b2ad31d1f20dcf30dd526ec9156c07f270216bdb59197325bab180675929888ab699c54fb21819b7d921d6346bff2f7f"; ++ char buf_addin2[96] = ++ "ad55c682962aa4fe9ebc227c9402e79b0aa7874844d33eaee7e2d15baf81d9d33936e4d93f28ad109657b512aee115a5"; ++ char buf_seed3[96] = ++ "eca449048d26fd38f8ca435237dce66eadec7069ee5dd0b70084b819a711c0820a7556bbd0ae20f06e5169278b593b71"; ++ u32 tmp[12]; ++ ++ for (i = 0; i < 12; i++) ++ addin[i] = i; ++ ++ for (i = 0; i < 12; i++) ++ ps[i] = i + 100; ++ ++ /* SDK doc example - Prediction Resistance not available, no Reseed */ ++ err = nisttrng_uninstantiate(&priv->nisttrng); ++ if (err && err != CRYPTO_NOT_INSTANTIATED) ++ return -1; ++ ++ if (nisttrng_instantiate(&priv->nisttrng, 128, 0, ps) < 0) ++ return -1; ++ ++ out = kmalloc(10, GFP_KERNEL); ++ if (nisttrng_generate(&priv->nisttrng, out, 10, 128, 0, addin) < 0) ++ return -1; ++ ++ DEBUG("----- Generate 10 bytes\n"); ++ for (i = 0; i < 10; i++) ++ DEBUG("%02x", out[i]); ++ ++ DEBUG("\n"); ++ kfree(out); ++ ++ out = kmalloc(512, GFP_KERNEL); ++ if (nisttrng_generate(&priv->nisttrng, out, 512, 128, 0, addin) < 0) ++ return -1; ++ ++ DEBUG("----- Generate 512 bytes\n"); ++ for (i = 0; i < 512; i++) ++ DEBUG("%02x", out[i]); ++ ++ DEBUG("\n"); ++ kfree(out); ++ ++ out = kmalloc(41, GFP_KERNEL); ++ if (nisttrng_generate(&priv->nisttrng, out, 41, 128, 0, addin) < 0) ++ return -1; ++ ++ DEBUG("----- Generate 41 bytes\n"); ++ for (i = 0; i < 41; i++) ++ DEBUG("%02x", out[i]); ++ ++ DEBUG("\n"); ++ kfree(out); ++ ++ err = nisttrng_uninstantiate(&priv->nisttrng); ++ if (err < 0 && err != CRYPTO_NOT_INSTANTIATED) ++ return -1; ++ ++ /* SDK doc example - DRBG Validation */ ++ err = nisttrng_uninstantiate(&priv->nisttrng); ++ if (err && err != CRYPTO_NOT_INSTANTIATED) ++ return -1; ++ ++ if (nisttrng_set_nonce_mode(&priv->nisttrng, 1) < 0) ++ return -1; ++ ++ out = kmalloc(64, GFP_KERNEL); ++ str_to_384_bit(buf_seed1, tmp); ++ if (nisttrng_get_entropy_input(&priv->nisttrng, tmp, 0) < 0) ++ return -1; ++ ++ str_to_384_bit(buf_ps1, tmp); ++ if (nisttrng_instantiate(&priv->nisttrng, 256, 1, tmp) < 0) ++ return -1; ++ ++ str_to_384_bit(buf_seed2, tmp); ++ if (nisttrng_get_entropy_input(&priv->nisttrng, tmp, 0) < 0) ++ return -1; ++ ++ str_to_384_bit(buf_addin1, tmp); ++ if (nisttrng_generate(&priv->nisttrng, out, 64, 256, 1, tmp) < 0) ++ return -1; ++ ++ str_to_384_bit(buf_seed3, tmp); ++ if (nisttrng_get_entropy_input(&priv->nisttrng, tmp, 0) < 0) ++ return -1; ++ ++ str_to_384_bit(buf_addin2, tmp); ++ if (nisttrng_generate(&priv->nisttrng, out, 64, 256, 1, tmp) < 0) ++ return -1; ++ ++ memcpy(priv->rand_out, out, 64); ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RO(ckr); ++static DEVICE_ATTR_RO(features); ++static DEVICE_ATTR_RW(secure); ++static DEVICE_ATTR_RW(nonce); ++static DEVICE_ATTR_RW(sec_strength); ++ ++static DEVICE_ATTR_RW(mode_reg); ++static DEVICE_ATTR_RW(smode_reg); ++static DEVICE_ATTR_RW(alarm_reg); ++static DEVICE_ATTR_RO(rand_reg); ++static DEVICE_ATTR_RO(rand_out); ++static DEVICE_ATTR_RO(rand_out_vtrng); ++static DEVICE_ATTR_RW(seed_reg); ++static DEVICE_ATTR_RW(npa_data_reg); ++static DEVICE_ATTR_RW(ctrl_reg); ++static DEVICE_ATTR_RW(istat_reg); ++static DEVICE_ATTR_RO(stat_reg); ++static DEVICE_ATTR_RW(rnc_reg); ++static DEVICE_ATTR_RW(rbc_reg); ++ ++static DEVICE_ATTR_RW(ia_wdata_reg); ++static DEVICE_ATTR_RO(ia_rdata_reg); ++static DEVICE_ATTR_RW(ia_addr_reg); ++static DEVICE_ATTR_RW(ia_cmd_reg); ++static DEVICE_ATTR_RO(hw_state); ++ ++static DEVICE_ATTR_RO(collect_ent); ++static DEVICE_ATTR_RO(collect_ent_nsout); ++static DEVICE_ATTR_WO(nonce_seed_with_df); ++static DEVICE_ATTR_WO(nonce_seed_direct); ++static DEVICE_ATTR_WO(instantiate); ++static DEVICE_ATTR_WO(uninstantiate); ++static DEVICE_ATTR_WO(reseed); ++static DEVICE_ATTR_WO(generate); ++static DEVICE_ATTR_WO(generate_pub_vtrng); ++static DEVICE_ATTR_WO(kat); ++ ++static DEVICE_ATTR_RW(max_bits_per_req); ++static DEVICE_ATTR_RW(max_req_per_seed); ++ ++static DEVICE_ATTR_WO(test_attr); ++ ++static const struct attribute_group nisttrng_attr_group = { ++ .attrs = ++ (struct attribute *[]){ ++ &dev_attr_ckr.attr, ++ //&dev_attr_stepping.attr, ++ &dev_attr_features.attr, &dev_attr_secure.attr, ++ &dev_attr_nonce.attr, &dev_attr_sec_strength.attr, ++ ++ &dev_attr_mode_reg.attr, &dev_attr_smode_reg.attr, ++ &dev_attr_alarm_reg.attr, &dev_attr_rand_reg.attr, ++ &dev_attr_rand_out.attr, &dev_attr_rand_out_vtrng.attr, ++ &dev_attr_seed_reg.attr, &dev_attr_npa_data_reg.attr, ++ &dev_attr_ctrl_reg.attr, &dev_attr_istat_reg.attr, ++ &dev_attr_stat_reg.attr, &dev_attr_rnc_reg.attr, ++ &dev_attr_rbc_reg.attr, ++ ++ &dev_attr_ia_wdata_reg.attr, ++ &dev_attr_ia_rdata_reg.attr, &dev_attr_ia_addr_reg.attr, ++ &dev_attr_ia_cmd_reg.attr, &dev_attr_hw_state.attr, ++ ++ &dev_attr_collect_ent.attr, ++ &dev_attr_collect_ent_nsout.attr, ++ &dev_attr_nonce_seed_with_df.attr, ++ &dev_attr_nonce_seed_direct.attr, ++ &dev_attr_instantiate.attr, ++ &dev_attr_uninstantiate.attr, &dev_attr_reseed.attr, ++ &dev_attr_generate.attr, ++ &dev_attr_generate_pub_vtrng.attr, &dev_attr_kat.attr, ++ ++ &dev_attr_max_bits_per_req.attr, ++ &dev_attr_max_req_per_seed.attr, ++ ++ &dev_attr_test_attr.attr, NULL }, ++}; ++ ++static int nisttrng_self_test(struct nist_trng_state *nist_trng) ++{ ++ u32 seed[16], out[4], x, y; ++ ++ static const u32 exp128[10][4] = { ++ { 0x5db79bb2, 0xc3a0df1e, 0x099482b6, ++ 0xc319981e }, // The 1st generated output ++ { 0xb344d301, 0xdbd97ca0, 0x6e66e668, ++ 0x0bcd4625 }, // The 2nd generate output ++ { 0xec553f18, 0xa0e5c3cb, 0x752c03c2, ++ 0x5e7b04f7 }, // The 3rd generate output ++ { 0xcfe23e6e, 0x5302edc2, 0xdbf7b05b, ++ 0x2c817c0f }, // The 4th generate output ++ { 0xbd5a8726, 0x028c43d0, 0xb77ac4e3, ++ 0x0844ba2c }, // The 5th generate output ++ { 0xa63b4c0e, 0x8d11d0ba, 0x08b5a10f, ++ 0xab731aff }, // The 6th generate output ++ { 0xb7b56a2f, 0x1d84d1f0, 0xe48d1a0a, ++ 0x43a010a6 }, // The 7th generate output ++ { 0xcf66439d, 0xc937451d, 0x75c34d20, ++ 0x21a21398 }, // The 8th generate output ++ { 0xcb6f0a57, 0x5ff34705, 0x08838e49, ++ 0x21137614 }, // The 9th generate output ++ { 0x61c48b24, 0x25c18d29, 0xc6005e4e, ++ 0xae3b0389 }, // The 10th generate output ++ }; ++ ++ static const u32 exp256[10][4] = { ++ { 0x1f1a1441, 0xa0865ece, 0x9ff8d5b9, ++ 0x3f78ace6 }, // The 1st generated output ++ { 0xf8190a86, 0x6d6ded2a, 0xc4d0e9bf, ++ 0x24dab55c }, // The 2nd generate output ++ { 0xd3948b74, 0x3dfea516, 0x9c3b86a2, ++ 0xeb184b41 }, // The 3rd generate output ++ { 0x2eb82ab6, 0x2aceefda, 0xc0cf6a5f, ++ 0xa45cb333 }, // The 4th generate output ++ { 0xa49b1c7b, 0x5b51bac7, 0x7586770b, ++ 0x8cb2c392 }, // The 5th generate output ++ { 0x3f3ba09d, 0xa2c9ad29, 0x9687fb8f, ++ 0xa5ae3fd5 }, // The 6th generate output ++ { 0x11dd1076, 0xe37e86cb, 0xced0220a, ++ 0x00448c4f }, // The 7th generate output ++ { 0x955a5e52, 0x84ee38b1, 0xb3271e5f, ++ 0x097751e3 }, // The 8th generate output ++ { 0x5cd73ba8, 0xd8a36a1e, 0xa8a2d7c3, ++ 0xa96de048 }, // The 9th generate output ++ { 0xfb374c63, 0x827b85fa, 0x244e0c7a, ++ 0xa09afd39 }, // The 10th generate output ++ }; ++ ++ int ret, enable, rate, urun; ++ u32 tmp; ++ ++ for (x = 0; x < 16; x++) ++ seed[x] = 0x12345679 * (x + 1); ++ ++ DEBUG("Doing a self-test with security strength of 128\n"); ++ ret = nisttrng_uninstantiate(nist_trng); ++ if (ret && ret != CRYPTO_NOT_INSTANTIATED) ++ goto ERR; ++ ++ //if ((ret = nisttrng_set_secure_mode(nist_trng, 0))) { goto ERR; } ++ ret = nisttrng_set_nonce_mode(nist_trng, 1); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_set_sec_strength(nist_trng, 128); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_get_entropy_input(nist_trng, seed, 0); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_instantiate(nist_trng, 128, 0, NULL); ++ if (ret) ++ goto ERR; ++ ++ if (nist_trng->config.build_cfg0.edu_present) { ++ ret = nisttrng_wait_fifo_full(nist_trng); ++ if (ret) ++ goto ERR; ++ } ++ ++ ret = nisttrng_generate(nist_trng, out, 16, 128, 0, NULL); ++ if (ret) ++ goto ERR; ++ ++ if (nist_trng->config.features.extra_ps_present) { ++ DEBUG("skip KAT with extra_ps_present\n"); ++ } else { ++ DEBUG("nist_trng: AES-128 Self-test output: "); ++ for (x = 0; x < 4; x++) ++ DEBUG("0x%08lx ", (unsigned long)out[x]); ++ ++ if (nist_trng->config.build_cfg0.edu_present) { ++ if (nist_trng->config.edu_build_cfg0 ++ .esm_channel) { //if esm_channel is available the first random number goes to esm ++ for (x = 0; x < 4; x++) { ++ if (out[x] != exp128[1][x]) ++ ret = 1; ++ } ++ } ++ } else { ++ for (x = 0; x < 4; x++) { ++ if (out[x] != exp128[0][x]) ++ ret = 1; ++ } ++ } ++ ++ if (ret) { ++ SYNHW_PRINT("... FAILED comparison\n"); ++ ret = -1; ++ goto ERR; ++ } else { ++ DEBUG("... PASSED\n"); ++ } ++ } ++ ++ // if edu is available check all the pvtrng's ++ if (nist_trng->config.build_cfg0.edu_present) { ++ for (x = 0; ++ x < nist_trng->config.edu_build_cfg0.public_vtrng_channels; ++ x++) { ++ DEBUG("vtrng %d\n", x); ++ ret = nisttrng_generate_public_vtrng(nist_trng, out, 16, x); ++ if (ret) ++ goto ERR; ++ ++ for (y = 0; y < 4; y++) { ++ DEBUG("0x%08lx ", (unsigned long)out[y]); ++ if (out[y] != exp128[x + 2][y]) ++ ret = 1; ++ } ++ if (ret) { ++ SYNHW_PRINT("... FAILED comparison\n"); ++ ret = -1; ++ goto ERR; ++ } else { ++ DEBUG("... PASSED\n"); ++ } ++ } ++ } ++ // if edu is available empty the fifo before creating the new instance with strength of 256 ++ if (nist_trng->config.build_cfg0.edu_present) { ++ nisttrng_rnc(nist_trng, ++ NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_FINISH_TO_IDLE); ++ tmp = NIST_TRNG_REG_ISTAT_DONE; ++ //always clear the busy bit after disabling RNC ++ pdu_io_write32(nist_trng->base + NIST_TRNG_REG_ISTAT, tmp); ++ tmp = pdu_io_read32(nist_trng->base + NIST_TRNG_REG_ISTAT); ++ do { ++ ret = nisttrng_generate_public_vtrng(nist_trng, out, 16, 0); ++ if (ret) ++ goto ERR; ++ ++ tmp = pdu_io_read32(nist_trng->base + ++ NIST_TRNG_EDU_STAT); ++ ++ } while (!NIST_TRNG_EDU_STAT_FIFO_EMPTY(tmp)); ++ } ++ ++ if (nist_trng->config.features.drbg_arch == AES256) { ++ // test AES-256 mode ++ DEBUG("Doing a self-test with security strength of 256\n"); ++ ret = nisttrng_uninstantiate(nist_trng); ++ if (ret && ret != CRYPTO_NOT_INSTANTIATED) ++ goto ERR; ++ ++ ret = nisttrng_set_nonce_mode(nist_trng, 1); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_set_sec_strength(nist_trng, 256); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_get_entropy_input(nist_trng, seed, 0); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_instantiate(nist_trng, 256, 0, NULL); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_generate(nist_trng, out, 16, 256, 0, NULL); ++ if (ret) ++ goto ERR; ++ ++ if (nist_trng->config.features.extra_ps_present) { ++ DEBUG("skip KAT with extra_ps_present\n"); ++ } else { ++ DEBUG("nist_trng: AES-256 Self-test output: "); ++ for (x = 0; x < 4; x++) ++ DEBUG("0x%08lx ", (unsigned long)out[x]); ++ ++ for (x = 0; x < 4; x++) { ++ if (out[x] != exp256[0][x]) ++ ret = 1; ++ } ++ if (ret) { ++ SYNHW_PRINT("... FAILED comparison\n"); ++ ret = -1; ++ goto ERR; ++ } else { ++ DEBUG("... PASSED\n"); ++ } ++ } ++ } ++ ++ // if edu is available check all the pvtrng's ++ if (nist_trng->config.build_cfg0.edu_present) { ++ for (x = 0; ++ x < nist_trng->config.edu_build_cfg0.public_vtrng_channels; ++ x++) { ++ DEBUG("vtrng 256 %d\n", x); ++ ret = nisttrng_generate_public_vtrng(nist_trng, out, 16, x); ++ if (ret) ++ goto ERR; ++ ++ for (y = 0; y < 4; y++) { ++ DEBUG("0x%08lx ", (unsigned long)out[y]); ++ if (out[y] != exp256[x + 1][y]) ++ ret = 1; ++ } ++ if (ret) { ++ SYNHW_PRINT("... FAILED comparison\n"); ++ ret = -1; ++ goto ERR; ++ } else { ++ DEBUG("... PASSED\n"); ++ } ++ } ++ ++ //Test RBC channels ++ // enable RBC channels with rate of 2 and urun 1 ++ enable = 1; ++ rate = 2; ++ urun = 1; ++ for (x = 0; x < nist_trng->config.edu_build_cfg0.rbc_channels; ++ x++) { ++ ret = nisttrng_rbc(nist_trng, enable, x, rate, urun); ++ if (ret) ++ goto ERR; ++ ++ tmp = pdu_io_read32(nist_trng->base + ++ NIST_TRNG_EDU_RBC_CTRL); ++ ++ switch (x) { ++ case 0: ++ if (rate != NISTTRNG_EDU_RBC_CTRL_GET_CH_RATE(tmp, _NIST_TRNG_EDU_RBC_CTRL_CH0_RATE) || ++ urun != NISTTRNG_EDU_RBC_CTRL_GET_CH_URUN_BLANK(tmp, _NIST_TRNG_EDU_RBC_CTRL_CH0_URUN_BLANK)) { ++ goto ERR; ++ } ++ break; ++ case 1: ++ if (rate != NISTTRNG_EDU_RBC_CTRL_GET_CH_RATE(tmp, _NIST_TRNG_EDU_RBC_CTRL_CH1_RATE) || ++ urun != NISTTRNG_EDU_RBC_CTRL_GET_CH_URUN_BLANK(tmp, _NIST_TRNG_EDU_RBC_CTRL_CH1_URUN_BLANK)) { ++ goto ERR; ++ } ++ break; ++ case 2: ++ if (rate != NISTTRNG_EDU_RBC_CTRL_GET_CH_RATE(tmp, _NIST_TRNG_EDU_RBC_CTRL_CH2_RATE) || ++ urun != NISTTRNG_EDU_RBC_CTRL_GET_CH_URUN_BLANK(tmp, _NIST_TRNG_EDU_RBC_CTRL_CH2_URUN_BLANK)) { ++ goto ERR; ++ } ++ break; ++ default: ++ DEBUG("Incorrect rbc_num = %d\n", x); ++ goto ERR; ++ } ++ } ++ DEBUG("RBC test passed\n"); ++ } ++ ++ //IF RNCis not disable, disable it ++ if (pdu_io_read32(nist_trng->base + NIST_TRNG_EDU_RNC_CTRL) != ++ NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_FINISH_TO_IDLE) { ++ nisttrng_rnc(nist_trng, ++ NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_FINISH_TO_IDLE); ++ tmp = NIST_TRNG_REG_ISTAT_DONE; ++ //always clear the busy bit after disabling RNC ++ pdu_io_write32(nist_trng->base + NIST_TRNG_REG_ISTAT, tmp); ++ } ++ ++ /* back to the noise mode */ ++ ret = nisttrng_set_nonce_mode(nist_trng, 0); ++ if (ret) ++ goto ERR; ++ ++ ret = nisttrng_zeroize(nist_trng); ++ if (ret) ++ goto ERR; ++ERR: ++ return ret; ++} ++ ++static int nisttrng_driver_probe(struct platform_device *pdev) ++{ ++ struct synopsys_nisttrng_driver *data; ++ struct hwrng *hwrng_driver_info = 0; ++ struct resource *cfg, *irq; ++ u32 *base_addr; ++ int ret; ++ ++ // version ++ SYNHW_PRINT("DWC_TRNG_DriverSDK_%s\n", TRNG_VERSION); ++ ++ cfg = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); ++ ++ if (!cfg || !irq) { ++ SYNHW_PRINT("no memory or IRQ resource\n"); ++ return -ENOMEM; ++ } ++ ++ DEBUG("=================================================================\n"); ++ DEBUG("nisttrng_probe: Device at %08lx(%08lx) of size %lu bytes\n", ++ (unsigned long)cfg->start, (unsigned long)cfg->end, ++ (unsigned long)resource_size(cfg)); ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(struct synopsys_nisttrng_driver), ++ GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, data); ++ ++ base_addr = pdu_linux_map_regs(&pdev->dev, cfg); ++ if (IS_ERR(base_addr)) { ++ dev_err(&pdev->dev, "unable to remap io mem\n"); ++ return PTR_ERR(base_addr); ++ } ++ ++ ret = nisttrng_init(&data->nisttrng, (u32 *)base_addr); ++ if (ret) { ++ SYNHW_PRINT("NIST_TRNG init failed (%d)\n", ret); ++ devm_kfree(&pdev->dev, data); ++ return ret; ++ } ++ ++ /* if max_reads is not 0, change the max_req_per_seed according to max_reads */ ++ if (max_reads) { ++ ret = nisttrng_set_reminder_max_req_per_seed(&data->nisttrng, max_reads); ++ if (ret) { ++ SYNHW_PRINT("NIST_TRNG maximum request-per-seed setup failed (%d)\n", ++ ret); ++ devm_kfree(&pdev->dev, data); ++ return ret; ++ } ++ } ++ ++ // issue quick self test ++ ret = nisttrng_self_test(&data->nisttrng); ++ if (ret) { ++ devm_kfree(&pdev->dev, data); ++ return -ENOMEM; ++ } ++ ++ // ready the device for use ++ ret = nisttrng_instantiate(&data->nisttrng, ++ data->nisttrng.config.features.drbg_arch ? 256 : 128, 1, NULL); ++ if (ret) { ++ SYNHW_PRINT("NIST_TRNG instantiate failed (%d)\n", ret); ++ devm_kfree(&pdev->dev, data); ++ return -ENOMEM; ++ } ++ ++ // at this point the device should be ready for a call to gen_random ++ hwrng_driver_info = ++ devm_kzalloc(&pdev->dev, sizeof(struct hwrng), GFP_KERNEL); ++ if (!hwrng_driver_info) { ++ devm_kfree(&pdev->dev, data); ++ return -ENOMEM; ++ } ++ ++ hwrng_driver_info->name = devm_kzalloc(&pdev->dev, ++ sizeof(SYNOPSYS_HWRNG_DRIVER_NAME) + 1, GFP_KERNEL); ++ if (!hwrng_driver_info->name) { ++ devm_kfree(&pdev->dev, data); ++ devm_kfree(&pdev->dev, hwrng_driver_info); ++ return -ENOMEM; ++ } ++ ++ memset((void *)hwrng_driver_info->name, 0, ++ sizeof(SYNOPSYS_HWRNG_DRIVER_NAME) + 1); ++ strscpy((char *)hwrng_driver_info->name, SYNOPSYS_HWRNG_DRIVER_NAME, ++ sizeof(SYNOPSYS_HWRNG_DRIVER_NAME)); ++ ++ hwrng_driver_info->read = &nisttrng_hwrng_driver_read; ++ hwrng_driver_info->data_present = 0; ++ hwrng_driver_info->priv = (unsigned long)pdev; ++ hwrng_driver_info->quality = 1024; ++ ++ data->hwrng_drv = hwrng_driver_info; ++ ret = hwrng_register(hwrng_driver_info); ++ ++ if (ret) { ++ SYNHW_PRINT("unable to load HWRNG driver (error %d)\n", ret); ++ devm_kfree(&pdev->dev, (void *)hwrng_driver_info->name); ++ devm_kfree(&pdev->dev, hwrng_driver_info); ++ devm_kfree(&pdev->dev, data); ++ return ret; ++ } ++ ++ ret = sysfs_create_group(&pdev->dev.kobj, &nisttrng_attr_group); ++ if (ret < 0) { ++ SYNHW_PRINT("unable to initialize sysfs group (error %d)\n", ++ ret); ++ hwrng_unregister(hwrng_driver_info); ++ devm_kfree(&pdev->dev, (void *)hwrng_driver_info->name); ++ devm_kfree(&pdev->dev, hwrng_driver_info); ++ devm_kfree(&pdev->dev, data); ++ return ret; ++ } ++ SYNHW_PRINT("SYN NIST_TRNG registering HW_RANDOM\n"); ++ return 0; ++} ++ ++static void nisttrng_driver_remove(struct platform_device *pdev) ++{ ++ struct synopsys_nisttrng_driver *data = platform_get_drvdata(pdev); ++ struct hwrng *hwrng_driver_info = (struct hwrng *)data->hwrng_drv; ++ ++ SYNHW_PRINT("SYN NIST_TRNG unregistering from HW_RANDOM\n"); ++ hwrng_unregister(hwrng_driver_info); ++ sysfs_remove_group(&pdev->dev.kobj, &nisttrng_attr_group); ++ devm_kfree(&pdev->dev, (void *)hwrng_driver_info->name); ++ devm_kfree(&pdev->dev, hwrng_driver_info); ++ devm_kfree(&pdev->dev, data); ++} ++ ++static struct platform_driver s_nisttrng_platform_driver_info = { ++ .probe = nisttrng_driver_probe, ++ .remove = nisttrng_driver_remove, ++ .driver = { ++ .name = "nist_trng", ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init nisttrng_platform_driver_start(void) ++{ ++ return platform_driver_register(&s_nisttrng_platform_driver_info); ++} ++ ++static void __exit nisttrng_platform_driver_end(void) ++{ ++ platform_driver_unregister(&s_nisttrng_platform_driver_info); ++} ++ ++module_init(nisttrng_platform_driver_start); ++module_exit(nisttrng_platform_driver_end); ++ ++module_param(max_reads, ulong, 0); ++MODULE_PARM_DESC(max_reads, "Max # of reads between reseeds (default is 128)"); ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Synopsys, Inc."); +diff --git a/drivers/char/hw_random/dwc/src/trng/trng/nist_trng.c b/drivers/char/hw_random/dwc/src/trng/trng/nist_trng.c +--- a/drivers/char/hw_random/dwc/src/trng/trng/nist_trng.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/trng/nist_trng.c 2026-09-09 15:51:42.910265392 +0000 +@@ -0,0 +1,956 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2012-2016 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "nisttrng_hw.h" ++#include "nisttrng.h" ++ ++/* Initialize the NIST_TRNG state structure */ ++int nisttrng_init(struct nist_trng_state *state, u32 *base) ++{ ++ int err; ++ u32 tmp; ++ ++ DEBUG(">> %s: initialize the NIST_TRNG\n", __func__); ++ ++ memset(state, 0, sizeof(*state)); ++ ++ state->base = base; ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* hardware features*/ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_FEATURES); ++ ++ state->config.features.drbg_arch = NIST_TRNG_REG_FEATURES_AES_256(tmp); ++ state->config.features.extra_ps_present = ++ NIST_TRNG_REG_FEATURES_EXTRA_PS_PRESENT(tmp); ++ state->config.features.secure_rst_state = ++ NIST_TRNG_REG_FEATURES_SECURE_RST_STATE(tmp); ++ state->config.features.diag_level_basic_trng = ++ NIST_TRNG_REG_FEATURES_DIAG_LEVEL_BASIC_TRNG(tmp); ++ state->config.features.diag_level_stat_hlt = ++ NIST_TRNG_REG_FEATURES_DIAG_LEVEL_ST_HLT(tmp); ++ state->config.features.diag_level_ns = ++ NIST_TRNG_REG_FEATURES_DIAG_LEVEL_NS(tmp); ++ ++ /* corekit */ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_COREKIT_REL); ++ state->config.corekit_rel.ext_enum = NIST_TRNG_REG_EXT_ENUM(tmp); ++ state->config.corekit_rel.ext_ver = NIST_TRNG_REG_EXT_VER(tmp); ++ state->config.corekit_rel.rel_num = NIST_TRNG_REG_REL_NUM(tmp); ++ ++ /* clear registers */ ++ pdu_io_write32(state->base + NIST_TRNG_REG_ALARM, 0xFFFFFFFF); ++ pdu_io_write32(state->base + NIST_TRNG_REG_ISTAT, 0xFFFFFFFF); ++ ++ /* setup the NIST_TRNG in secure mode, self seeding mode, with prediction resistance, maximum possible security strength */ ++ /* SMODE */ ++ tmp = 0; ++ tmp = NIST_TRNG_REG_SMODE_SET_SECURE_EN(tmp, 1); ++ tmp = NIST_TRNG_REG_SMODE_SET_NONCE(tmp, 0); ++ tmp = NIST_TRNG_REG_SMODE_SET_MAX_REJECTS(tmp, ++ NIST_TRNG_DFLT_MAX_REJECTS); ++ pdu_io_write32(state->base + NIST_TRNG_REG_SMODE, tmp); ++ state->status.secure_mode = 1; ++ state->status.nonce_mode = 0; ++ /* MODE */ ++ tmp = 0; ++ if (state->config.features.drbg_arch == AES256) { ++ tmp = NIST_TRNG_REG_MODE_SET_SEC_ALG(tmp, 1); ++ state->status.sec_strength = SEC_STRNT_AES256; ++ ++ } else if (state->config.features.drbg_arch == AES128) { ++ tmp = NIST_TRNG_REG_MODE_SET_SEC_ALG(tmp, 0); ++ state->status.sec_strength = SEC_STRNT_AES128; ++ ++ } else { ++ SYNHW_PRINT("Invalid DRBG architecture"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ tmp = NIST_TRNG_REG_MODE_SET_PRED_RESIST(tmp, 1); ++ pdu_io_write32(state->base + NIST_TRNG_REG_MODE, 0); ++ state->status.pred_resist = 1; ++ /* rest of the status */ ++ state->status.alarm_code = 0; ++ state->status.pad_ps_addin = 0; ++ ++ /* reminders - set the counters to the standard's maximum values. An API is be provided to change those on demand.*/ ++ nisttrng_set_reminder_max_bits_per_req(state, ++ NIST_DFLT_MAX_BITS_PER_REQ); ++ nisttrng_set_reminder_max_req_per_seed(state, ++ NIST_DFLT_MAX_REQ_PER_SEED); ++ ++ /* display features */ ++ SYNHW_PRINT("NIST_TRNG: Hardware rel_num=0x%x, ext_ver=0x%x, ext_enum=0x%x\n", ++ state->config.corekit_rel.rel_num, ++ state->config.corekit_rel.ext_ver, ++ state->config.corekit_rel.ext_enum); ++ switch (state->config.features.drbg_arch) { ++ case AES128: ++ DEBUG("NIST_TRNG: DRBG Architecture=128-bit AES, Extra Personalization Existence=%u\n", ++ state->config.features.extra_ps_present); ++ break; ++ case AES256: ++ DEBUG("NIST_TRNG: DRBG Architecture=256-bit AES, Extra Personalization Existence=%u\n", ++ state->config.features.extra_ps_present); ++ break; ++ default: ++ SYNHW_PRINT("Invalid DRBG architecture"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ DEBUG("initialization is done, going for a zeroize\n"); ++ ++ // BUILD_CFG0 ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_BUILD_CFG0); ++ state->config.build_cfg0.core_type = NIST_TRNG_REG_CFG0_CORE_TYPE(tmp); ++ state->config.build_cfg0.bg8 = NIST_TRNG_REG_CFG0_BG8(tmp); ++ state->config.build_cfg0.cdc_synch_depth = ++ NIST_TRNG_REG_CFG0_CDC_SYNCH_DEPTH(tmp); ++ state->config.build_cfg0.background_noise = ++ NIST_TRNG_REG_CFG0_BACGROUND_NOISE(tmp); ++ state->config.build_cfg0.edu_present = ++ NIST_TRNG_REG_CFG0_EDU_PRESENT(tmp); ++ state->config.build_cfg0.aes_datapath = ++ NIST_TRNG_REG_CFG0_AES_DATAPATH(tmp); ++ state->config.build_cfg0.aes_max_key_size = ++ NIST_TRNG_REG_CFG0_AES_MAX_KEY_SIZE(tmp); ++ state->config.build_cfg0.personilzation_str = ++ NIST_TRNG_REG_CFG0_PERSONILIZATION_STR(tmp); ++ DEBUG("NIST_TRNG: BUILD_CFG0 core_type=%u, bg8=%u, cdc_synch_depth=%u, background_noise=%u\n", ++ state->config.build_cfg0.core_type, state->config.build_cfg0.bg8, ++ state->config.build_cfg0.cdc_synch_depth, ++ state->config.build_cfg0.background_noise); ++ DEBUG("edu_present=%u, aes_datapath=%u, aes_max_key_size=%u, personilzation_str=%u\n", ++ state->config.build_cfg0.edu_present, ++ state->config.build_cfg0.aes_datapath, ++ state->config.build_cfg0.aes_max_key_size, ++ state->config.build_cfg0.personilzation_str); ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_BUILD_CFG1); ++ DEBUG("NIST_TRNG: NIST_TRNG_REG_BUILD_CFG1=0x%x\n", tmp); ++ state->config.build_cfg1.num_raw_noise_blks = ++ NIST_TRNG_REG_CFG1_NUM_RAW_NOISE_BLKS(tmp); ++ state->config.build_cfg1.sticky_startup = ++ NIST_TRNG_REG_CFG1_STICKY_STARTUP(tmp); ++ state->config.build_cfg1.auto_correlation_test = ++ NIST_TRNG_REG_CFG1_AUTO_CORRELATION_TEST(tmp); ++ state->config.build_cfg1.mono_bit_test = ++ NIST_TRNG_REG_CFG1_MONO_BIT_TEST(tmp); ++ state->config.build_cfg1.run_test = NIST_TRNG_REG_CFG1_RUN_TEST(tmp); ++ state->config.build_cfg1.poker_test = ++ NIST_TRNG_REG_CFG1_POKER_TEST(tmp); ++ state->config.build_cfg1.raw_ht_adap_test = ++ NIST_TRNG_REG_CFG1_RAW_HT_ADAP_TEST(tmp); ++ state->config.build_cfg1.raw_ht_rep_test = ++ NIST_TRNG_REG_CFG1_RAW_HT_REP_TEST(tmp); ++ state->config.build_cfg1.ent_src_rep_smpl_size = ++ NIST_TRNG_REG_CFG1_ENT_SRC_REP_SMPL_SIZE(tmp); ++ state->config.build_cfg1.ent_src_rep_test = ++ NIST_TRNG_REG_CFG1_ENT_SRC_REP_TEST(tmp); ++ state->config.build_cfg1.ent_src_rep_min_entropy = ++ NIST_TRNG_REG_CFG1_ENT_SRC_REP_MIN_ENTROPY(tmp); ++ DEBUG("NIST_TRNG: BUILD_CFG1 num_raw_noise_blks=%u, sticky_startup=%u, auto_correlation_test=%u\n", ++ state->config.build_cfg1.num_raw_noise_blks, ++ state->config.build_cfg1.sticky_startup, ++ state->config.build_cfg1.auto_correlation_test); ++ DEBUG("mono_bit_test=%u, run_test=%u, poker_test=%u, raw_ht_adap_test=%u\n", ++ state->config.build_cfg1.mono_bit_test, ++ state->config.build_cfg1.run_test, ++ state->config.build_cfg1.poker_test, ++ state->config.build_cfg1.raw_ht_adap_test); ++ DEBUG("raw_ht_rep_test=%u, ent_src_rep_smpl_size=%u, ent_src_rep_test=%u, ent_src_rep_min_entropy=%u\n", ++ state->config.build_cfg1.raw_ht_rep_test, ++ state->config.build_cfg1.ent_src_rep_smpl_size, ++ state->config.build_cfg1.ent_src_rep_test, ++ state->config.build_cfg1.ent_src_rep_min_entropy); ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_BUILD_CFG0); ++ state->config.edu_build_cfg0.rbc2_rate_width = ++ NIST_TRNG_REG_EDU_CFG0_RBC2_RATE_WIDTH(tmp); ++ state->config.edu_build_cfg0.rbc1_rate_width = ++ NIST_TRNG_REG_EDU_CFG0_RBC1_RATE_WIDTH(tmp); ++ state->config.edu_build_cfg0.rbc0_rate_width = ++ NIST_TRNG_REG_EDU_CFG0_RBC0_RATE_WIDTH(tmp); ++ state->config.edu_build_cfg0.public_vtrng_channels = ++ NIST_TRNG_REG_EDU_CFG0_PUBLIC_VTRNG_CHANNELS(tmp); ++ state->config.edu_build_cfg0.esm_channel = ++ NIST_TRNG_REG_EDU_CFG0_ESM_CHANNEL(tmp); ++ state->config.edu_build_cfg0.rbc_channels = ++ NIST_TRNG_REG_EDU_CFG0_RBC_CHANNELS(tmp); ++ state->config.edu_build_cfg0.fifo_depth = ++ NIST_TRNG_REG_EDU_CFG0_FIFO_DEPTH(tmp); ++ DEBUG("NIST_TRNG: EDU_BUILD_CFG0 rbc2_rate_width=%u, rbc1_rate_width=%u, rbc0_rate_width=%u\n", ++ state->config.edu_build_cfg0.rbc2_rate_width, ++ state->config.edu_build_cfg0.rbc1_rate_width, ++ state->config.edu_build_cfg0.rbc0_rate_width); ++ DEBUG("public_vtrng_channels=%u, esm_channel=%u, rbc_channels=%u, fifo_depth=%u\n", ++ state->config.edu_build_cfg0.public_vtrng_channels, ++ state->config.edu_build_cfg0.esm_channel, ++ state->config.edu_build_cfg0.rbc_channels, ++ state->config.edu_build_cfg0.fifo_depth); ++ ++ state->status.edu_vstat.seed_enum = ++ NIST_TRNG_REG_EDU_VSTAT_SEED_ENUM(tmp); ++ state->status.edu_vstat.rnc_enabled = ++ NIST_TRNG_REG_EDU_VSTAT_RNC_ENABLED(tmp); ++ ++ err = nisttrng_zeroize(state); ++ if (err) ++ goto ERR; ++ ++ err = CRYPTO_OK; ++ state->status.current_state = NIST_TRNG_STATE_INITIALIZE; ++ERR: ++ DEBUG("--- %s Return, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_init */ ++EXPORT_SYMBOL(nisttrng_init); ++ ++/* Instantiate the DRBG state */ ++int nisttrng_instantiate(struct nist_trng_state *state, int req_sec_strength, ++ int pred_resist, void *personal_str) ++{ ++ int err; ++ u32 tmp; ++ u32 zero_ps[12] = { 0 }; ++ int i = 0; ++ ++ DEBUG(">> %s: security strength = %u, pred_resist = %u, personilization string existence = %u\n", ++ __func__, req_sec_strength, pred_resist, (personal_str) ? 1 : 0); ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* If DRBG is already instantiated or if current state does not allow an instantiate, return error */ ++ if (DRBG_INSTANTIATED(state->status.current_state)) { ++ DEBUG("Initial check: DRBG state is already instantiated\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ if (state->status.current_state != NIST_TRNG_STATE_INITIALIZE && ++ state->status.current_state != NIST_TRNG_STATE_UNINSTANTIATE) { ++ DEBUG("Cannot instantiate in the current state (%u)\n", ++ state->status.current_state); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* if hardware is not configured to accept extra personalization string, but personal_str is not NULL, return error */ ++ if (!state->config.features.extra_ps_present && personal_str) { ++ DEBUG("HW config does not allow extra PS\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* Validate and set the security strength */ ++ err = nisttrng_set_sec_strength(state, req_sec_strength); ++ if (err) ++ goto ERR; ++ ++ /* get entropy - noise seeding. If the mode is nonce, get_entropy must be called by the user prior to the instantiate function */ ++ DEBUG("Seeding mode is: %s\n", ++ state->status.nonce_mode ? "Nonce" : "Noise"); ++ if (!state->status.nonce_mode) { /* noise seeding */ ++ err = nisttrng_get_entropy_input(state, NULL, 0); ++ if (err) ++ goto ERR; ++ } ++ ++ /* load the personilization string if hardware is configured to accept it */ ++ if (state->config.features.extra_ps_present) { ++ /* if HW is configured to accept personilizatoin string, it will use whatever is in the NPA_DATAx. So, if the string is NULL, just load 0. */ ++ if (!personal_str) ++ personal_str = &zero_ps[0]; ++ ++ err = nisttrng_load_ps_addin(state, personal_str); ++ if (err) ++ goto ERR; ++ } ++ ++ /* initiate the Create_State command and wait on done */ ++ DEBUG("Create the DRBG state\n"); ++ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_CREATE_STATE); ++ err = nisttrng_wait_on_done(state); ++ if (err) ++ goto ERR; ++ ++ /* check STAT register to make sure DRBG is instantiated */ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_STAT); ++ if (!NIST_TRNG_REG_STAT_GET_DRBG_STATE(tmp)) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* reset reminder and alarms counters */ ++ nisttrng_reset_counters(state); ++ ++ //if EDU is available enable RNC and disable prediction resistance , disable all RBC,s ++ //state->config.build_cfg0.edu_present = 0; ++ if (state->config.build_cfg0.edu_present) { ++ //disable prediction resistance ++ err = nisttrng_set_pred_resist(state, 0); ++ if (err) ++ goto ERR; ++ ++ //enable RNC ++ nisttrng_rnc(state, NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_ENABLE); ++ // disable all RBC,s ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_RBC_CTRL); ++ for (i = 0; i < state->config.edu_build_cfg0.rbc_channels; ++ i++) { ++ err = nisttrng_rbc(state, 0, i, 0, ++ CHX_URUN_BLANK_AFTER_RESET); ++ if (err) ++ goto ERR; ++ } ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_RBC_CTRL); ++ ++ } else { ++ /* set the prediction resistance */ ++ err = nisttrng_set_pred_resist(state, pred_resist); ++ if (err) ++ goto ERR; ++ } ++ ++ err = CRYPTO_OK; ++ state->status.current_state = NIST_TRNG_STATE_INSTANTIATE; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_instantiate */ ++EXPORT_SYMBOL(nisttrng_instantiate); ++ ++/* Uninstantiate the DRBG state and zeroize */ ++int nisttrng_uninstantiate(struct nist_trng_state *state) ++{ ++ int err; ++ int err_tmp; ++ u32 tmp; ++ ++ DEBUG(">> %s: uninstantiate the DRBG and zeroize\n", __func__); ++ //printf(" nisttrng_uninstantiate: uninstantiate the DRBG and zeroize\n"); ++ err = CRYPTO_OK; ++ err_tmp = CRYPTO_OK; ++ ++ //disable RNC ++ if (state->config.build_cfg0.edu_present) { ++ if (state->status.edu_vstat.rnc_enabled) { ++ DEBUG("%s: disable RNC\n", __func__); ++ nisttrng_rnc(state, NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_FINISH_TO_IDLE); ++ //always clear the busy bit after disabling RNC ++ pdu_io_write32(state->base + NIST_TRNG_REG_ISTAT, tmp); ++ } ++ } ++ ++ /* if DRBG is instantiated, return CRYPTO_NOT_INSTANTIATED, but still do the zeroize */ ++ if (!DRBG_INSTANTIATED(state->status.current_state)) ++ err_tmp = CRYPTO_NOT_INSTANTIATED; ++ ++ /* zeroize */ ++ err = nisttrng_zeroize(state); ++ if (err) ++ goto ERR; ++ ++ if (err == CRYPTO_OK && err_tmp == CRYPTO_NOT_INSTANTIATED) ++ err = CRYPTO_NOT_INSTANTIATED; ++ ++ state->status.current_state = NIST_TRNG_STATE_UNINSTANTIATE; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_uninstantiate */ ++EXPORT_SYMBOL(nisttrng_uninstantiate); ++ ++/* enable/disable specific rbc ++ * rbc_num = rbc channel num ++ * urun_blnk = underrun blanking duration for rbc channel ++ * rate = sets rate of serial entropy output for rbc channel ++ */ ++int nisttrng_rbc(struct nist_trng_state *state, int enable, int rbc_num, int rate, ++ int urun_blnk) ++{ ++ int err = 0; ++ u32 tmp_rbc = 0; ++ ++ tmp_rbc = pdu_io_read32(state->base + NIST_TRNG_EDU_RBC_CTRL); ++ ++ if (enable) { ++ if (rate > 15) { ++ DEBUG("Incorrect rate = %d\n", rate); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ if (urun_blnk > 3) { ++ DEBUG("Incorrect urun_blnk = %d\n", urun_blnk); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ } else { //disable ++ rate = NISTTRNG_EDU_RBC_CTRL_GET_CH_RATE_AFTER_RESET; ++ urun_blnk = NISTTRNG_EDU_RBC_CTRL_SET_CH_URUN_BLANK_AFTER_RESET; ++ } ++ ++ switch (rbc_num) { ++ case 0: ++ tmp_rbc = NISTTRNG_EDU_RBC_CTRL_SET_CH_RATE(rate, tmp_rbc, _NIST_TRNG_EDU_RBC_CTRL_CH0_RATE); ++ tmp_rbc = NISTTRNG_EDU_RBC_CTRL_SET_CH_URUN_BLANK(urun_blnk, tmp_rbc, ++ _NIST_TRNG_EDU_RBC_CTRL_CH0_URUN_BLANK); ++ ++ break; ++ case 1: ++ tmp_rbc = NISTTRNG_EDU_RBC_CTRL_SET_CH_RATE(rate, tmp_rbc, _NIST_TRNG_EDU_RBC_CTRL_CH1_RATE); ++ tmp_rbc = NISTTRNG_EDU_RBC_CTRL_SET_CH_URUN_BLANK(urun_blnk, tmp_rbc, ++ _NIST_TRNG_EDU_RBC_CTRL_CH1_URUN_BLANK); ++ ++ break; ++ case 2: ++ tmp_rbc = NISTTRNG_EDU_RBC_CTRL_SET_CH_RATE(rate, tmp_rbc, _NIST_TRNG_EDU_RBC_CTRL_CH2_RATE); ++ tmp_rbc = NISTTRNG_EDU_RBC_CTRL_SET_CH_URUN_BLANK(urun_blnk, tmp_rbc, ++ _NIST_TRNG_EDU_RBC_CTRL_CH2_URUN_BLANK); ++ break; ++ default: ++ DEBUG("Incorrect rbc_num = %d\n", rbc_num); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ pdu_io_write32(state->base + NIST_TRNG_EDU_RBC_CTRL, tmp_rbc); ++ ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} ++ ++/* Reseed */ ++int nisttrng_reseed(struct nist_trng_state *state, int pred_resist, void *addin_str) ++{ ++ int rnc_flag = 0; ++ int err; ++ ++ DEBUG(">> %s: pred_resist = %u, additional strign existence = %u\n", ++ __func__, pred_resist, (addin_str) ? 1 : 0); ++ ++ if (state->config.build_cfg0.edu_present) { ++ if (state->status.edu_vstat.rnc_enabled) { ++ // disable_rnc ++ err = nisttrng_rnc(state, NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_DISABLE_TO_HOLD); ++ if (err) ++ goto ERR; ++ ++ rnc_flag = 1; ++ } ++ } ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* if the DRBG is not instantiated return error */ ++ if (!DRBG_INSTANTIATED(state->status.current_state)) { ++ DEBUG("DRBG is not instantiated\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* if pred_resist is set but, pred_resist that the DRBG is instantiated with is not 1, return error */ ++ err = nisttrng_set_pred_resist(state, pred_resist); ++ if (err) ++ goto ERR; ++ ++ /* get entropy - noise seeding. If the mode is nonce, get_entropy must be called by the user prior to the instantiate function */ ++ if (!state->status.nonce_mode) { /* noise seeding */ ++ err = nisttrng_get_entropy_input(state, NULL, 0); ++ if (err) ++ goto ERR; ++ } ++ ++ /* if addin_str is not NULL, it means that the additionl input is available and has to be loaded */ ++ if (addin_str) { ++ /* set the ADDIN_PRESENT field of the MODE register to 1 */ ++ err = nisttrng_set_addin_present(state, 1); ++ if (err) ++ goto ERR; ++ ++ /* load the additional input */ ++ err = nisttrng_load_ps_addin(state, addin_str); ++ if (err) ++ goto ERR; ++ ++ } else { ++ /* set the ADDIN_PRESENT field of the MODE register to 0 */ ++ err = nisttrng_set_addin_present(state, 0); ++ if (err) ++ goto ERR; ++ } ++ ++ /* initiate the reseed and wait on done */ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_RENEW_STATE); ++ err = nisttrng_wait_on_done(state); ++ if (err) ++ goto ERR; ++ ++ /* reset reminder and alarms counters */ ++ nisttrng_reset_counters(state); ++ ++ if (rnc_flag) { ++ // rnc_enable ++ err = nisttrng_rnc(state, NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_ENABLE); ++ if (err) ++ goto ERR; ++ } ++ ++ err = CRYPTO_OK; ++ state->status.current_state = NIST_TRNG_STATE_RESEED; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_reseed */ ++EXPORT_SYMBOL(nisttrng_reseed); ++ ++static int nisttrng_vtrng_wait_on_busy(struct nist_trng_state *state, int priv, int vtrng) ++{ ++ u32 tmp, t; ++ ++ t = NIST_TRNG_RETRY_MAX; ++ ++ if (priv) { //private vtrng ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_VSTAT); ++ } while (NIST_TRNG_REG_EDU_VSTAT_BUSY(tmp) && --t); ++ ++ } else { //public vtrng ++ do { ++ tmp = pdu_io_read32(state->base + ++ NIST_TRNG_EDU_VTRNG_VSTAT0 + ++ 8 * vtrng); ++ } while (NIST_TRNG_REG_EDU_VSTAT_BUSY(tmp) && --t); ++ } ++ ++ if (t) ++ return CRYPTO_OK; ++ ++ SYNHW_PRINT("wait_on_: failed timeout: %08lx\n", ++ (unsigned long)tmp); ++ ++ return CRYPTO_TIMEOUT; ++} /* nisttrng_vtrng_wait_on_busy */ ++ ++int nisttrng_generate_public_vtrng(struct nist_trng_state *state, void *random_bits, ++ unsigned long req_num_bytes, int vtrng) ++{ ++ int err = 0; ++ u32 tmp; ++ unsigned int remained_bytes; ++ unsigned long req_num_blks; ++ int i, j; ++ ++ DEBUG(">> %s : requested number of bytes = %lu, vtrng num = %u\n", ++ __func__, req_num_bytes, vtrng); ++ ++ /* make sure random_bits is not NULL */ ++ if (!random_bits) { ++ DEBUG("random_bits pointer cannot be NULL\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ if (vtrng > state->config.edu_build_cfg0.public_vtrng_channels) { ++ DEBUG("vtrng channel invalid (%u)\n", vtrng); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ if (state->status.edu_vstat.rnc_enabled == 0) { ++ DEBUG("rnc_disabled\n"); ++ } ++ ++ if (state->status.edu_vstat.seed_enum == 0) { ++ DEBUG("not seed_enum\n"); ++ } ++ ++ /* loop on generate to get the requested number of bits. Each generate gives NIST_TRNG_RAND_BLK_SIZE_BITS bits. */ ++ req_num_blks = ((req_num_bytes * 8) % NIST_TRNG_RAND_BLK_SIZE_BITS) ? ++ (((req_num_bytes * 8) / NIST_TRNG_RAND_BLK_SIZE_BITS) + 1) : ++ ((req_num_bytes * 8) / NIST_TRNG_RAND_BLK_SIZE_BITS); ++ ++ for (i = 0; i < req_num_blks; i++) { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_VTRNG_VCTRL0 + ++ (vtrng * 8)); ++ tmp = NIST_TRNG_EDU_VTRNG_VCTRL_CMD_SET(tmp, NIST_TRNG_EDU_VTRNG_VCTRL_CMD_GET_RANDOM); ++ pdu_io_write32(state->base + NIST_TRNG_EDU_VTRNG_VCTRL0 + (vtrng * 8), ++ tmp); ++ ++ // check busy ++ err = nisttrng_vtrng_wait_on_busy(state, 0, vtrng); ++ if (err) ++ goto ERR; ++ ++ // check for error ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_VTRNG_VISTAT0 + ++ (vtrng * 8)); ++ if (NIST_TRNG_REG_EDU_VSTAT_ANY_RW1(tmp)) { ++ DEBUG("EDU_VSTAT_ANY_RW1 set 0x%x\n", tmp); ++ } ++ ++ // check that all valid ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_VTRNG_VSTAT0 + ++ 8 * vtrng); ++ if ((NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD0(tmp) == 0) || ++ (NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD1(tmp) == 0) || ++ (NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD2(tmp) == 0) || ++ (NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD3(tmp) == 0)) { ++ DEBUG("EDU_VSTAT_SLICE_VLD fail 0x%x\n", tmp); ++ } ++ ++ /* read the generated random number block and store */ ++ for (j = 0; j < (NIST_TRNG_RAND_BLK_SIZE_BITS / 32); j++) { ++ tmp = pdu_io_read32(state->base + ++ NIST_TRNG_EDU_VTRNG_VRAND0_0 + ++ (vtrng * 8) + j); ++ /* copy to random_bits byte-by-byte, until req_num_bytes are copied */ ++ remained_bytes = req_num_bytes - ++ (i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4); ++ if (remained_bytes > 4) { ++ memcpy(random_bits + i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4, &tmp, 4); ++ ++ /* decrement the bits counter and return error if generated more than the maximum*/ ++ state->counters.bits_per_req_left = ++ state->counters.bits_per_req_left - ++ 4 * 8; ++ if (state->counters.bits_per_req_left < 0) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ } else { ++ memcpy(random_bits + i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4, &tmp, remained_bytes); ++ ++ /* decrement the bits counter and return error if generated more than the maximum*/ ++ state->counters.bits_per_req_left = ++ state->counters.bits_per_req_left - ++ remained_bytes * 8; ++ if (state->counters.bits_per_req_left < 0) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ break; ++ } ++ } ++ } ++ ++ err = CRYPTO_OK; ++ state->status.current_state = NIST_TRNG_STATE_GENERATE; ++ERR: ++ if (err) ++ random_bits = NULL; ++ ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} ++ ++static int nisttrng_generate_private_vtrng(struct nist_trng_state *state, void *random_bits, ++ unsigned long req_num_bytes) ++{ ++ int err; ++ u32 tmp; ++ unsigned int remained_bytes; ++ unsigned long req_num_blks; ++ int i, j; ++ ++ DEBUG(">> %s : requested number of bytes = %lu ", ++ __func__, req_num_bytes); ++ ++ /* requested number of bits has to be less that the programmed maximum */ ++ if ((req_num_bytes * 8) > state->counters.max_bits_per_req) { ++ SYNHW_PRINT("requested number of bits (%lu) is larger than the set maximum (%lu)\n", ++ (req_num_bytes * 8), state->counters.max_bits_per_req); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* make sure random_bits is not NULL */ ++ if (!random_bits) { ++ SYNHW_PRINT("random_bits pointer cannot be NULL\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ if (state->status.edu_vstat.rnc_enabled == 0) { ++ DEBUG("rnc_disabled\n"); ++ } ++ ++ if (state->status.edu_vstat.seed_enum == 0) { ++ DEBUG("not seed_enum\n"); ++ } ++ ++ /* loop on generate to get the requested number of bits. Each generate gives NIST_TRNG_RAND_BLK_SIZE_BITS bits. */ ++ req_num_blks = ((req_num_bytes * 8) % NIST_TRNG_RAND_BLK_SIZE_BITS) ? ++ (((req_num_bytes * 8) / NIST_TRNG_RAND_BLK_SIZE_BITS) + 1) : ++ ((req_num_bytes * 8) / NIST_TRNG_RAND_BLK_SIZE_BITS); ++ ++ for (i = 0; i < req_num_blks; i++) { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_VCTRL); ++ tmp = NIST_TRNG_EDU_VTRNG_VCTRL_CMD_SET(tmp, NIST_TRNG_EDU_VTRNG_VCTRL_CMD_GET_RANDOM); ++ pdu_io_write32(state->base + NIST_TRNG_EDU_VCTRL, tmp); ++ ++ // check busy ++ err = nisttrng_vtrng_wait_on_busy(state, 1, 0); ++ if (err) ++ goto ERR; ++ ++ // check for error ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_VISTAT); ++ if (NIST_TRNG_REG_EDU_VSTAT_ANY_RW1(tmp)) { ++ DEBUG("EDU_VSTAT_ANY_RW1 set 0x%x\n", tmp); ++ } ++ ++ //check that all valid ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_VSTAT); ++ if ((NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD0(tmp) == 0) || ++ (NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD1(tmp) == 0) || ++ (NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD2(tmp) == 0) || ++ (NIST_TRNG_REG_EDU_VSTAT_SLICE_VLD3(tmp) == 0)) { ++ DEBUG("EDU_VSTAT_SLICE_VLD fail 0x%x\n", tmp); ++ } ++ ++ /* read the generated random number block and store */ ++ for (j = 0; j < (NIST_TRNG_RAND_BLK_SIZE_BITS / 32); j++) { ++ tmp = pdu_io_read32(state->base + ++ NIST_TRNG_EDU_VRAND_0 + j); ++ /* copy to random_bits byte-by-byte, until req_num_bytes are copied */ ++ remained_bytes = req_num_bytes - ++ (i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4); ++ if (remained_bytes > 4) { ++ memcpy(random_bits + i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4, &tmp, 4); ++ ++ /* decrement the bits counter and return error if generated more than the maximum*/ ++ state->counters.bits_per_req_left = ++ state->counters.bits_per_req_left - ++ 4 * 8; ++ if (state->counters.bits_per_req_left < 0) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ } else { ++ memcpy(random_bits + i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4, &tmp, remained_bytes); ++ ++ /* decrement the bits counter and return error if generated more than the maximum*/ ++ state->counters.bits_per_req_left = ++ state->counters.bits_per_req_left - ++ remained_bytes * 8; ++ if (state->counters.bits_per_req_left < 0) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ break; ++ } ++ } ++ } ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} ++ ++/* Generate */ ++int nisttrng_generate(struct nist_trng_state *state, void *random_bits, ++ unsigned long req_num_bytes, int req_sec_strength, ++ int pred_resist, void *addin_str) ++{ ++ int err; ++ int reseed_required; ++ ++ DEBUG(">> %s: requested number of bytes = %lu, security strength = %u, pred_resist = %u, additional string existence = %u\n", ++ __func__, req_num_bytes, req_sec_strength, pred_resist, ++ (addin_str) ? 1 : 0); ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* if the DRBG is not instantiated return error */ ++ if (!DRBG_INSTANTIATED(state->status.current_state)) { ++ SYNHW_PRINT("DRBG is not instantiated\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* requested number of bits has to be less that the programmed maximum */ ++ if ((req_num_bytes * 8) > state->counters.max_bits_per_req) { ++ SYNHW_PRINT("requested number of bits (%lu) is larger than the set maximum (%lu)\n", ++ (req_num_bytes * 8), state->counters.max_bits_per_req); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* security strength has to be lower than what the DRBG is instantiated with. set_sec_strength function checks this. */ ++ err = nisttrng_set_sec_strength(state, req_sec_strength); ++ if (err) ++ goto ERR; ++ ++ /* set the prediction resistance - if pred_resist is set but, pred_resist that the DRBG is instantiated with is not 1, return error */ ++ err = nisttrng_set_pred_resist(state, pred_resist); ++ if (err) ++ goto ERR; ++ ++ /* make sure random_bits is not NULL */ ++ if (!random_bits) { ++ DEBUG("random_bits pointer cannot be NULL\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* set the reseed required flag to 0. The loop is to check at the end whether a reseed is required at the end and jump back to reseed and generate if needed. This is the NIST mandated procedure */ ++ reseed_required = 0; ++ ++ if (!addin_str) { ++ /* set the ADDIN_PRESENT field of the MODE register to 1 */ ++ err = nisttrng_set_addin_present(state, 0); ++ if (err) ++ goto ERR; ++ } ++ ++ do { ++ void *generate_addin_str = addin_str; ++ ++ if (pred_resist | reseed_required) { ++ err = nisttrng_reseed(state, pred_resist, addin_str); ++ if (err) ++ goto ERR; ++ ++ /* SP800-90a says that if reseed is executed, any additional input string is only used in the reseed phase and replaced by NULL in the generate phase */ ++ generate_addin_str = NULL; ++ err = nisttrng_set_addin_present(state, 0); ++ if (err) ++ goto ERR; ++ ++ /* ADDIN_PRESENT field in MODE has to be set back to 0 to avoid illegal cmd sequence */ ++ reseed_required = 0; ++ } ++ ++ /* generate process */ ++ if (nisttrng_check_seed_lifetime(state) == CRYPTO_RESEED_REQUIRED) { ++ reseed_required = 1; ++ ++ } else { ++ reseed_required = 0; ++ ++ /* Refresh_Addin command if additional input is not NULL*/ ++ if (generate_addin_str) { ++ err = nisttrng_refresh_addin(state, generate_addin_str); ++ if (err) ++ goto ERR; ++ } ++ ++ /* Generate all random bits */ ++ /* if EDU present then get random number from private vtrng */ ++ ++ //state->config.build_cfg0.edu_present = 0; ++ if (state->config.build_cfg0.edu_present) { ++ err = nisttrng_generate_private_vtrng(state, random_bits, ++ req_num_bytes); ++ if (err) ++ goto ERR; ++ ++ } else { ++ err = nisttrng_gen_random(state, random_bits, ++ req_num_bytes); ++ if (err) ++ goto ERR; ++ ++ /* Advance the state - if it returns CRYPTO_RESEED_REQUIRED, have to jump back and do a reseed and generate */ ++ err = nisttrng_advance_state(state); ++ if (err) ++ goto ERR; ++ } ++ } ++ } while (reseed_required); ++ ++ err = CRYPTO_OK; ++ state->status.current_state = NIST_TRNG_STATE_GENERATE; ++ERR: ++ if (err) ++ random_bits = NULL; ++ ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_generate */ ++EXPORT_SYMBOL(nisttrng_generate); +diff --git a/drivers/char/hw_random/dwc/src/trng/trng/nist_trng_private.c b/drivers/char/hw_random/dwc/src/trng/trng/nist_trng_private.c +--- a/drivers/char/hw_random/dwc/src/trng/trng/nist_trng_private.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/char/hw_random/dwc/src/trng/trng/nist_trng_private.c 2026-09-09 15:51:42.910265392 +0000 +@@ -0,0 +1,1022 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * This Synopsys software and associated documentation (hereinafter the ++ * "Software") is an unsupported proprietary work of Synopsys, Inc. unless ++ * otherwise expressly agreed to in writing between Synopsys and you. The ++ * Software IS NOT an item of Licensed Software or a Licensed Product under ++ * any End User Software License Agreement or Agreement for Licensed Products ++ * with Synopsys or any supplement thereto. Synopsys is a registered trademark ++ * of Synopsys, Inc. Other names included in the SOFTWARE may be the ++ * trademarks of their respective owners. ++ * ++ * The contents of this file are dual-licensed; you may select either version ++ * 2 of the GNU General Public License ("GPL") or the BSD-3-Clause license ++ * ("BSD-3-Clause"). The GPL is included in the COPYING file accompanying the ++ * SOFTWARE. The BSD License is copied below. ++ * ++ * BSD-3-Clause License: ++ * Copyright (c) 2012-2016 Synopsys, Inc. and/or its affiliates. ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions, and the following disclaimer, without ++ * modification. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. The names of the above-listed copyright holders may not be used to ++ * endorse or promote products derived from this software without specific ++ * prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "nisttrng_hw.h" ++#include "nisttrng.h" ++ ++/* Wait functions */ ++static int nisttrng_wait_on_(struct nist_trng_state *state, u32 mask) ++{ ++ u32 tmp; ++ int t; ++ ++ t = NIST_TRNG_RETRY_MAX; ++ ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_ISTAT); ++ } while (!(tmp & (mask | NIST_TRNG_REG_ISTAT_ALARMS)) && --t); ++ ++ if (tmp & NIST_TRNG_REG_ISTAT_ALARMS) ++ return nisttrng_get_alarms(state); ++ ++ if (t) { ++ pdu_io_write32(state->base + NIST_TRNG_REG_ISTAT, mask); ++ return CRYPTO_OK; ++ ++ } else { ++ SYNHW_PRINT("wait_on_: failed timeout: %08lx\n", ++ (unsigned long)tmp); ++ return CRYPTO_TIMEOUT; ++ } ++} /* nisttrng_wait_on_ */ ++ ++int nisttrng_wait_on_done(struct nist_trng_state *state) ++{ ++ return nisttrng_wait_on_(state, NIST_TRNG_REG_ISTAT_DONE); ++} /* nisttrng_wait_on_done */ ++EXPORT_SYMBOL(nisttrng_wait_on_done); ++ ++int nisttrng_wait_on_noise_rdy(struct nist_trng_state *state) ++{ ++ return nisttrng_wait_on_(state, NIST_TRNG_REG_ISTAT_NOISE_RDY); ++} /* nisttrng_wait_on_noise_rdy */ ++ ++static int nisttrng_wait_on_zeroize(struct nist_trng_state *state) ++{ ++ return nisttrng_wait_on_(state, NIST_TRNG_REG_ISTAT_ZEROIZE); ++} /* nisttrng_wait_on_zeroize */ ++ ++static int nisttrng_wait_on_kat_completed(struct nist_trng_state *state) ++{ ++ return nisttrng_wait_on_(state, NIST_TRNG_REG_ISTAT_KAT_COMPLETE); ++} /* nisttrng_wait_on_kat_completed */ ++ ++int nisttrng_wait_on_busy(struct nist_trng_state *state) ++{ ++ u32 tmp, t; ++ ++ t = NIST_TRNG_RETRY_MAX; ++ ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_STAT); ++ } while ((tmp & NIST_TRNG_REG_STAT_BUSY) && --t); ++ ++ if (t) ++ return CRYPTO_OK; ++ ++ SYNHW_PRINT("wait_on_busy: failed timeout: %08lx\n", ++ (unsigned long)tmp); ++ return CRYPTO_TIMEOUT; ++} /* nisttrng_wait_on_busy */ ++EXPORT_SYMBOL(nisttrng_wait_on_busy); ++ ++/* Read and return alarm. Zeroize if there is an alarm*/ ++int nisttrng_get_alarms(struct nist_trng_state *state) ++{ ++ u32 tmp; ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_ISTAT); ++ if (tmp & NIST_TRNG_REG_ISTAT_ALARMS) { ++ // alarm happened ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_ALARM); ++ DEBUG("Received alarm: %lx\n", (unsigned long)tmp); ++ // clear istat ++ pdu_io_write32(state->base + NIST_TRNG_REG_ISTAT, ++ NIST_TRNG_REG_ISTAT_ALARMS); ++ pdu_io_write32(state->base + NIST_TRNG_REG_ALARM, 0x1F); ++ state->status.alarm_code = tmp & 0x1F; ++ ++ /* zeroize if there was an alarm */ ++ if (state->status.alarm_code != ++ NIST_TRNG_REG_ALARM_FAILED_TEST_ID_OK) { ++ nisttrng_zeroize(state); ++ } ++ } else { ++ state->status.alarm_code = 0; ++ } ++ ++ if (state->status.alarm_code) ++ return CRYPTO_FATAL; ++ else ++ return CRYPTO_OK; ++} /* nisttrng_get_alarms */ ++EXPORT_SYMBOL(nisttrng_get_alarms); ++ ++/* Reset reminder and alarm counters */ ++int nisttrng_reset_counters(struct nist_trng_state *state) ++{ ++ state->counters.bits_per_req_left = state->counters.max_bits_per_req; ++ state->counters.req_per_seed_left = state->counters.max_req_per_seed; ++ ++ return 0; ++} /* nisttrng_reset_counters */ ++EXPORT_SYMBOL(nisttrng_reset_counters); ++ ++/* When a zeroize happens some of the struct objects should reset */ ++int nisttrng_reset_state(struct nist_trng_state *state) ++{ ++ nisttrng_reset_counters(state); ++ state->status.pad_ps_addin = 0; ++ state->status.current_state = NIST_TRNG_STATE_UNINSTANTIATE; ++ ++ return 0; ++} /* nisttrng_reset_state */ ++ ++/* ---------- Set field APIs ---------- */ ++ ++/* ++ * Sets the security strength of the DRBG instance. ++ * > req_sec_strength has to be an integer. The API chooses one of SEC_STRNT_AES128 or SEC_STRNT_AES256 as follows: ++ * 0 < req_sec_strength <= 128 --> security strength = SEC_STRNT_AES128 ++ * 128 < req_sec_strength <= 256 --> security strength = SEC_STRNT_AES256 ++ * else --> Invalid security strength ++ * > If the DRBG is instantiated, a new security strength change request with greater security strength will return error. ++ */ ++int nisttrng_set_sec_strength(struct nist_trng_state *state, int req_sec_strength) ++{ ++ int err; ++ u32 tmp; ++ enum nisttrng_sec_strength chosen_sec_strength; ++ ++ DEBUG(">> %s: security strength = %i\n", __func__, ++ req_sec_strength); ++ ++ /* choose the security strength */ ++ /* set the security strength to the lowest security strength greater or equal to the req_sec_strenght from the set {128, 256} */ ++ if (REQ_SEC_STRENGTH_IS_VALID(req_sec_strength)) { ++ if (req_sec_strength > 0 && req_sec_strength <= 128) { ++ chosen_sec_strength = SEC_STRNT_AES128; ++ ++ } else if (((req_sec_strength > 128) && ++ (req_sec_strength <= 256)) && ++ (state->config.features.drbg_arch == AES256)) { ++ chosen_sec_strength = SEC_STRNT_AES256; ++ ++ } else { /* should not get here, because we have already checked the validity */ ++ DEBUG("Invalid security strength\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ } else { ++ DEBUG("Invalid security strength\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ DEBUG("chosen security strength = %u\n", chosen_sec_strength); ++ ++ /* set the security strenght - at this point security strength is validated and converted */ ++ if (DRBG_INSTANTIATED(state->status.current_state) && ++ chosen_sec_strength != state->status.sec_strength) { ++ /* security strength can only change when the DRBG is not instantiated. */ ++ /* if the new security strength is less that what the DRBG is instantiated with, accept it, but don't change in HW. If it's more, return error */ ++ if (chosen_sec_strength < state->status.sec_strength) { ++ DEBUG("Lowering the security strength. DRBG is already instantiated.\n"); ++ state->status.pad_ps_addin = 4; ++ state->status.sec_strength = chosen_sec_strength; ++ ++ } else { ++ state->status.pad_ps_addin = 0; ++ DEBUG("Cannot select a higher security strenght once the DRBG is instantiated\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ } else { ++ DEBUG("Updating the security strength.\n"); ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_MODE); ++ tmp = NIST_TRNG_REG_MODE_SET_SEC_ALG(tmp, chosen_sec_strength); ++ pdu_io_write32(state->base + NIST_TRNG_REG_MODE, tmp); ++ ++ state->status.pad_ps_addin = 0; ++ state->status.sec_strength = chosen_sec_strength; ++ } ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_set_sec_strength */ ++EXPORT_SYMBOL(nisttrng_set_sec_strength); ++ ++/* ++ * Sets the ADDIN_PRESENT field of the MODE register according to the addin_present input. ++ */ ++int nisttrng_set_addin_present(struct nist_trng_state *state, int addin_present) ++{ ++ u32 tmp; ++ ++ DEBUG(">> %s, adding_present = %u\n", __func__, ++ addin_present); ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_MODE); ++ tmp = NIST_TRNG_REG_MODE_SET_ADDIN_PRESENT(tmp, addin_present); ++ pdu_io_write32(state->base + NIST_TRNG_REG_MODE, tmp); ++ ++ DEBUG("--- Return %s, err = %i\n", __func__, 0); ++ return 0; ++} /* nisttrng_set_addin_present */ ++EXPORT_SYMBOL(nisttrng_set_addin_present); ++ ++/* ++ * Sets the PRED_RESIST field of the MODE register according to the pred_resist input. ++ * > If the DRBG is instantiated with prediction resistance of 0, and a change to the prediction resistance of 1 is requested, ++ * the API will return an error. ++ */ ++int nisttrng_set_pred_resist(struct nist_trng_state *state, int pred_resist) ++{ ++ int err; ++ u32 tmp; ++ ++ DEBUG(">> %s: pred_resist = %u\n", __func__, pred_resist); ++ ++ /* if DRBG is instantiated, prediction resistance can only change from 1 to 0 and not vice versa. This is a NIST requirement. */ ++ if (DRBG_INSTANTIATED(state->status.current_state) && pred_resist && ++ !state->status.pred_resist) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_MODE); ++ tmp = NIST_TRNG_REG_MODE_SET_PRED_RESIST(tmp, pred_resist); ++ pdu_io_write32(state->base + NIST_TRNG_REG_MODE, tmp); ++ ++ state->status.pred_resist = pred_resist; ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_set_pred_resist */ ++EXPORT_SYMBOL(nisttrng_set_pred_resist); ++ ++/* ++ * Puts the NIST_TRNG in either the SECURE or PROMISCUOUS mode. ++ * > A value of 1 for secure_mode puts the core in the SECURE mode and a value of 0 puts it in the PROMISCUOUS mode. ++ * > Any change to the secure mode of the NIST_TRNG will result in a complete zeroize, and will set the seeding mode to self-seeding. ++ * A zeroize will not destroy the programmed mode and ALARM register value. ++ * It keeps the programmed mode to avoid re-programming. ++ * It also, maintains the ALARM register value, so that the user can read the value to understand the reason of the occurred alarm. ++ */ ++int nisttrng_set_secure_mode(struct nist_trng_state *state, int secure_mode) ++{ ++ int err; ++ u32 tmp; ++ int t; ++ ++ DEBUG(">> %s: secure_mode = %u\n", __func__, secure_mode); ++ ++ t = NIST_TRNG_RETRY_MAX; ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_SMODE); ++ tmp = NIST_TRNG_REG_SMODE_SET_SECURE_EN(tmp, secure_mode); ++ pdu_io_write32(state->base + NIST_TRNG_REG_SMODE, tmp); ++ ++ /* wait until STAT register indicates that the mode is applied */ ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_STAT); ++ } while ((NIST_TRNG_REG_STAT_GET_SECURE(tmp) != secure_mode) && --t); ++ ++ if (!t) { ++ err = CRYPTO_TIMEOUT; ++ goto ERR; ++ } ++ ++ /* if secure mode changes, a zeroize will happen in HW. */ ++ if (state->status.secure_mode != secure_mode) { ++ DEBUG("secure mode changed. zeroize happened. reset sw state\n"); ++ /* nonce mode goes back to default. */ ++ state->status.nonce_mode = 0; ++ /* reset the SW state */ ++ nisttrng_reset_state(state); ++ } ++ ++ state->status.secure_mode = secure_mode; ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_set_secure_mode */ ++EXPORT_SYMBOL(nisttrng_set_secure_mode); ++ ++/* ++ * To change the seeding mode of the NIST_TRNG. ++ * > A value of 1 for nonce_mode will put the NIST_TRNG in the nonce seeding mode, which means that the seed will be provided by the user, ++ * unlike the noise or self-seeding mode (normal mode of operation) in which the seed is generated by the internal entropy source. ++ * > Any transition to or from the nonce mode will zeroize the NIST_TRNG. ++ */ ++int nisttrng_set_nonce_mode(struct nist_trng_state *state, int nonce_mode) ++{ ++ int err; ++ u32 tmp; ++ int t; ++ ++ DEBUG(">> %s: nonce_mode = %u\n", __func__, nonce_mode); ++ ++ t = NIST_TRNG_RETRY_MAX; ++ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_SMODE); ++ tmp = NIST_TRNG_REG_SMODE_SET_NONCE(tmp, nonce_mode); ++ pdu_io_write32(state->base + NIST_TRNG_REG_SMODE, tmp); ++ ++ /* wait until STAT register indicates that the mode is applied */ ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_STAT); ++ } while ((NIST_TRNG_REG_STAT_GET_NONCE(tmp) != nonce_mode) && --t); ++ ++ if (!t) { ++ err = CRYPTO_TIMEOUT; ++ goto ERR; ++ } ++ ++ /* if nonce mode changes, a zeroize will happen in HW. */ ++ if (state->status.nonce_mode != nonce_mode) { ++ DEBUG("nonce mode changed. zeroize happened. reset sw state\n"); ++ /* reset the SW state */ ++ nisttrng_reset_state(state); ++ } ++ ++ state->status.nonce_mode = nonce_mode; ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_set_nonce_mode */ ++EXPORT_SYMBOL(nisttrng_set_nonce_mode); ++ ++/* ---------- Load data APIs ---------- */ ++/* ++ * Loads the additional input or personalization string into the NPA_DATAx registers. ++ * > Loads the proper number of bits (256 or 384) according to the security strength stored in the state handle. ++ */ ++int nisttrng_load_ps_addin(struct nist_trng_state *state, void *input_str) ++{ ++ int err; ++ int i, j; ++ int str_size; ++ ++ DEBUG(">> %s starts...\n", __func__); ++ ++ /* return error if the pointer is NULL */ ++ if (!input_str) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* calculate the length based on the security strength */ ++ if (state->status.sec_strength == SEC_STRNT_AES128) ++ str_size = 8; /* 256/32 */ ++ else if (state->status.sec_strength == SEC_STRNT_AES256) ++ str_size = 12; /* 384/32 */ ++ ++ for (i = 0; i < str_size; i++) { ++ pdu_io_write32(state->base + NIST_TRNG_REG_NPA_DATA0 + i, ++ ((u32 *)input_str)[i]); ++ } ++ ++ j = str_size + state->status.pad_ps_addin; ++ /* if security strength is lowered after the DRBG is instantiated, pad PS and ADDIN with 0 at the MSB side */ ++ DEBUG("pad NPA_DATA with %u zeros at the MSB side\n", ++ state->status.pad_ps_addin); ++ for (i = str_size; i < j; i++) ++ pdu_io_write32(state->base + NIST_TRNG_REG_NPA_DATA0 + i, 0); ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_load_ps_addin */ ++EXPORT_SYMBOL(nisttrng_load_ps_addin); ++ ++/* ---------- Command APIs ---------- */ ++/* ++ * Provides entropy and is used in both nonce and noise (self) seeding modes of operation: ++ * > If the NIST_TRNG is in the nonce mode, entropy must be provided by the user; otherwise (in the self-seeding mode) entropy will be generated by the internal entropy source of the NIST_TRNG. ++ * > In the noise mode, calling the API will initiate a seeding command. Depending on the programmed security strength, a 256 or 384-bit seed will be generated. ++ * > Inputs 2 and 3 are only used when the core is in the nonce mode. ++ * > In the nonce mode, the NIST_TRNG can be seeded either through 2 or 3 blocks of 512-bit nonce values which are passed to the internal derivation function to increase the entropy, ++ * or it can be seeded by a 256 or 384-bit nonce written directly into the SEEDx registers. ++ * Passing a value of 1 to nonce_operation selects the former scenario and a value of 0 selects the latter. ++ * > The input_nonce pointer must point to a memory location with a sufficient number of initialized bits. ++ * > Table below shows the required number of bits depending on the nonce_operation and the security strength values. ++ * nonce_operation | Security Strength | Bit length requirement ++ * ------------------------------------------------------------------------------------------ ++ * 1 (using the Derivation Function) | SEC_STRNT_AES128 | 2x512 = 1024 ++ * 1 (using the Derivation Function) | SEC_STRNT_AES256 | 3x512 = 1536 ++ * 0 (loading the seed into SEEDx) | SEC_STRNT_AES128 | 256 ++ * 0 (loading the seed into SEEDx) | SEC_STRNT_AES256 | 384 ++ * > Generated entropy is secret information held securely within the HW and remains inaccessible to the user, unless the HW core is in the PROMISCUOUS mode. ++ */ ++int nisttrng_get_entropy_input(struct nist_trng_state *state, void *input_nonce, ++ int nonce_operation) ++{ ++ int err; ++ int nonce_ld_cntr = 0; ++ int i, j; ++ ++ DEBUG(">> %s: seeding mode = %s, nonce_operation = %u\n", __func__, ++ (state->status.nonce_mode ? "Nonce" : "Noise"), nonce_operation); ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* --- Seeding --- */ ++ if (state->status.nonce_mode) { /* --- nonce mode --- */ ++ if (!input_nonce) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ nonce_ld_cntr = 0; ++ ++ if (state->status.sec_strength == SEC_STRNT_AES128) ++ nonce_ld_cntr = 2; ++ else if (state->status.sec_strength == SEC_STRNT_AES256) ++ nonce_ld_cntr = 3; ++ ++ if (nonce_operation) { /* load the noise inside NPA_DATAx register and issue gen_nonce command */ ++ for (i = 0; i < nonce_ld_cntr; i++) { ++ /* load the nonoce */ ++ for (j = 0; j < 16; j++) { ++ pdu_io_write32(state->base + ++ NIST_TRNG_REG_NPA_DATA0 + j, ++ ((u32 *)input_nonce)[16 * i + j]); ++ } ++ ++ /* issue the command and wait on done */ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_GEN_NONCE); ++ ++ if (nisttrng_wait_on_done(state)) { ++ err = CRYPTO_FATAL; ++ goto ERR; ++ }; ++ } ++ ++ } else { ++ /* load the nonoce */ ++ for (i = 0; i < 4 * nonce_ld_cntr; i++) { ++ pdu_io_write32(state->base + NIST_TRNG_REG_SEED0 + i, ++ ((u32 *)input_nonce)[i]); ++ } ++ } ++ } else { /* --- noise mode --- */ ++ /* issue the command and wait on done */ ++ DEBUG("issue the Gen_Noise command\n"); ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_GEN_NOISE); ++ ++ if (nisttrng_wait_on_done(state)) { ++ err = CRYPTO_FATAL; ++ goto ERR; ++ }; ++ } ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_get_entropy_input */ ++EXPORT_SYMBOL(nisttrng_get_entropy_input); ++ ++/* ++ * Generate Function: ++ * > The Generate function in NIST_TRNG HW is broken down into 3 steps: Refresh_Addin, Gen_Random and Advance_State. ++ * nisttrng_generate incorporates all these steps and some extra checks into one simple API. ++ * > There is one API for each step, below || ++ * \/ ++ */ ++/* ++ * Generate Part 1 - Refresh_Addin: Additional input string is used to add to the HW state entropy. ++ * > This API calls nisttrng_set_addin_present to set the ADDIN_PRESENT field of the MODE register to 1. ++ * > Then it loads the additional input provided by addin_str pointer into the NPA_DATAx by calling the nisttrng_load_ps_addin. ++ * > Then, it issues a Refresh_Addin command to the HW. ++ * > If the addin_str pointer is NULL, the API will return error. ++ */ ++int nisttrng_refresh_addin(struct nist_trng_state *state, void *addin_str) ++{ ++ int err; ++ ++ DEBUG(">> %s starts...\n", __func__); ++ ++ /* if the DRBG is not intantiated return error */ ++ if (!DRBG_INSTANTIATED(state->status.current_state)) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* This API should not be called with a NULL additional input string */ ++ if (!addin_str) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* set the ADDIN_PRESENT field of the MODE register to 1 */ ++ err = nisttrng_set_addin_present(state, 1); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_load_ps_addin(state, addin_str); ++ if (err) ++ goto ERR; ++ ++ /* execute the command and wait on done*/ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_REFRESH_ADDIN); ++ ++ err = nisttrng_wait_on_done(state); ++ if (err) ++ goto ERR; ++ ++ err = 0; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_refresh_addin */ ++EXPORT_SYMBOL(nisttrng_refresh_addin); ++ ++/* ++ * Generate Part 2 - Gen_Random: generates the requested number of bits. ++ * > This API issues the Gen_Random command to the HW as many times as indicated by req_num_bytes to generate the requested number of bits. ++ * > If the requested number of bits (i.e. 128×req_num_blks) is more than the maximum value specified by max_bits_per_req, the API will return error. ++ * > Random bits will be returned in random_bits. ++ */ ++int nisttrng_gen_random(struct nist_trng_state *state, void *random_bits, ++ unsigned long req_num_bytes) ++{ ++ int err; ++ int i, j; ++ u32 tmp; ++ unsigned int remained_bytes; ++ unsigned long req_num_blks; ++ ++ DEBUG(">> %s: req_num_bytes = %lu\n", __func__, req_num_bytes); ++ ++ /* if the DRBG is not intantiated return error */ ++ if (!DRBG_INSTANTIATED(state->status.current_state)) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* requested number of bits has to be less that the programmed maximum */ ++ if ((req_num_bytes * 8) > state->counters.max_bits_per_req) { ++ DEBUG("requested number of bits (%lu) is larger than the set maximum (%lu)\n", ++ (req_num_bytes * 8), state->counters.max_bits_per_req); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* make sure random_bits is not NULL */ ++ if (!random_bits) { ++ DEBUG("random_bits pointer cannot be NULL\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* loop on generate to get the requested number of bits. Each generate gives NIST_TRNG_RAND_BLK_SIZE_BITS bits. */ ++ req_num_blks = ++ ((req_num_bytes * 8) % NIST_TRNG_RAND_BLK_SIZE_BITS) ? ++ (((req_num_bytes * 8) / NIST_TRNG_RAND_BLK_SIZE_BITS) + ++ 1) : ++ ((req_num_bytes * 8) / NIST_TRNG_RAND_BLK_SIZE_BITS); ++ ++ for (i = 0; i < req_num_blks; i++) { ++ /* issue gen_random and wait on done */ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_GEN_RANDOM); ++ ++ err = nisttrng_wait_on_done(state); ++ if (err) ++ goto ERR; ++ ++ /* read the generated random number block and store */ ++ for (j = 0; j < (NIST_TRNG_RAND_BLK_SIZE_BITS / 32); j++) { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_RAND0 + j); ++ /* copy to random_bits byte-by-byte, until req_num_bytes are copied */ ++ remained_bytes = req_num_bytes - ++ (i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4); ++ if (remained_bytes > 4) { ++ memcpy(random_bits + ++ i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4, &tmp, 4); ++ ++ /* decrement the bits counter and return error if generated more than the maximum*/ ++ state->counters.bits_per_req_left = ++ state->counters.bits_per_req_left - ++ 4 * 8; ++ ++ if (state->counters.bits_per_req_left < 0) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ } else { ++ memcpy(random_bits + i * (NIST_TRNG_RAND_BLK_SIZE_BITS / 8) + ++ j * 4, &tmp, remained_bytes); ++ ++ /* decrement the bits counter and return error if generated more than the maximum*/ ++ state->counters.bits_per_req_left = ++ state->counters.bits_per_req_left - ++ remained_bytes * 8; ++ ++ if (state->counters.bits_per_req_left < 0) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ break; ++ } ++ } ++ } ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_gen_random */ ++EXPORT_SYMBOL(nisttrng_gen_random); ++ ++/* ++ * Generate Part 3 - Advance the state: advances the state of the DRBG. ++ * > This API issues the Advance_State command to the HW. ++ * > Then it updates the counter for the number of generate requests per seed. ++ * > The counter must be checked every time before starting the Generate process and a reseed must be issued if the limit is reached. This check is incorporated inside nisttrng_generate API. ++ * > Note that we don't have to provide additional input again for this API, because if it had been provided in refresh_addin stage, HW will lock the NPA_DATAx, so it will be still available ++ */ ++int nisttrng_advance_state(struct nist_trng_state *state) ++{ ++ int err; ++ ++ DEBUG(">> %s starts...\n", __func__); ++ ++ /* if the DRBG is not intantiated return error */ ++ if (!DRBG_INSTANTIATED(state->status.current_state)) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* make sure there is no alarm and the core is not busy */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = nisttrng_wait_on_busy(state); ++ if (err) ++ goto ERR; ++ ++ /* issue advance_state and wait on done */ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_ADVANCE_STATE); ++ err = nisttrng_wait_on_done(state); ++ if (err) ++ goto ERR; ++ ++ /* generate is finished, reset the bits_per_req_left counter */ ++ state->counters.bits_per_req_left = state->counters.max_bits_per_req; ++ ++ --state->counters.req_per_seed_left; ++ if (state->counters.req_per_seed_left < 0) { ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } /* just a check */ ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_advance_state */ ++ ++int nisttrng_check_seed_lifetime(struct nist_trng_state *state) ++{ ++ int err; ++ ++ if (state->counters.req_per_seed_left <= 0) { ++ DEBUG("maximum number of requests per seed is reached\n"); ++ err = CRYPTO_RESEED_REQUIRED; ++ goto ERR; ++ } ++ ++ err = CRYPTO_OK; ++ERR: ++ return err; ++} ++EXPORT_SYMBOL(nisttrng_advance_state); ++ ++/* ++ * Perform Known Answer Test ++ * > The NIST_TRNG can perform a KAT on the DRBG and the derivation function inside the entropy source. There are also two different vectors available to do the KAT. ++ * > The kat_sel input selects whether the KAT should be performed on the DRBG or the derivation function. ++ * > The kat_vec input chooses the KAT vector. ++ * > Selections are done by writing the values to the MODE register. ++ * > If the KAT fails, the API returns error. ++ */ ++int nisttrng_kat(struct nist_trng_state *state, int kat_sel, int kat_vec) ++{ ++ int err; ++ u32 tmp; ++ ++ DEBUG(">> %s: kat_sel = %u, kat_vec = %u\n", __func__, ++ kat_sel, kat_vec); ++ ++ /* set KAT_SEL and KAT_VEC */ ++ tmp = pdu_io_read32(state->base + NIST_TRNG_REG_MODE); ++ tmp = NIST_TRNG_REG_MODE_SET_KAT_SEL(tmp, kat_sel); ++ tmp = NIST_TRNG_REG_MODE_SET_KAT_VEC(tmp, kat_vec); ++ pdu_io_write32(state->base + NIST_TRNG_REG_MODE, tmp); ++ ++ /* issue the command and wait on kat_completed */ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_KAT); ++ ++ err = nisttrng_wait_on_kat_completed(state); ++ if (err) ++ goto ERR; ++ ++ /* check for alarms */ ++ err = nisttrng_get_alarms(state); ++ if (err) ++ goto ERR; ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_kat */ ++EXPORT_SYMBOL(nisttrng_kat); ++ ++/* ++ * Performs a full KAT with all four combinations of the kat_sel and kat_vec ++ * > If any of the KAT fails, the API returns error. ++ */ ++int nisttrng_full_kat(struct nist_trng_state *state) ++{ ++ int err; ++ ++ DEBUG(">> %s starts...\n", __func__); ++ ++ /* SEL = 0, Vec = 0 */ ++ err = nisttrng_kat(state, 0, 0); ++ if (err) ++ goto ERR; ++ ++ /* SEL = 0, Vec = 1 */ ++ err = nisttrng_kat(state, 0, 1); ++ if (err) ++ goto ERR; ++ ++ /* SEL = 1, Vec = 0 */ ++ err = nisttrng_kat(state, 1, 0); ++ if (err) ++ goto ERR; ++ ++ /* SEL = 1, Vec = 1 */ ++ err = nisttrng_kat(state, 1, 1); ++ if (err) ++ goto ERR; ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_full_kat */ ++EXPORT_SYMBOL(nisttrng_full_kat); ++ ++/* ++ * max_bits_per_req reminder initialized by nisttrng_init can change using this API. ++ * > If this API is called when the DRBG is instantiated, an error will be returned. ++ * > If the requested maximum is more than the standard's limit (determinded by NIST_TRNG_DFLT_MAX_BITS_PER_REQ), the API will return an error. ++ */ ++int nisttrng_set_reminder_max_bits_per_req(struct nist_trng_state *state, ++ unsigned long max_bits_per_req) ++{ ++ int err; ++ ++ DEBUG(">> %s: %lu\n", __func__, max_bits_per_req); ++ ++ /* if the DRBG is instantiated, cannot change the value */ ++ if (DRBG_INSTANTIATED(state->status.current_state)) { ++ DEBUG("cannot change the reminder value when DRBG is already instantiated\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* requested value cannot be more than NIST's limit */ ++ if (max_bits_per_req > NIST_DFLT_MAX_BITS_PER_REQ) { ++ DEBUG("requested max_bits_per_req is more than standard's limit\n"); ++ err = CRYPTO_INVALID_ARGUMENT; ++ goto ERR; ++ } ++ ++ state->counters.max_bits_per_req = max_bits_per_req; ++ state->counters.bits_per_req_left = max_bits_per_req; ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} ++EXPORT_SYMBOL(nisttrng_set_reminder_max_bits_per_req); ++ ++/* ++ * max_req_per_seed reminder initialized by nisttrng_init can change using this API. ++ * > If this API is called when the DRBG is instantiated, an error will be returned. ++ * > If the requested maximum is more than the standard's limit (determinded by NIST_TRNG_DFLT_MAX_REQ_PER_SEED), the API will return an error. ++ */ ++int nisttrng_set_reminder_max_req_per_seed(struct nist_trng_state *state, ++ unsigned long long max_req_per_seed) ++{ ++ int err; ++ ++ DEBUG(">> %s: %llu\n", __func__, max_req_per_seed); ++ ++ /* if the DRBG is instantiated, cannot change the value */ ++ if (DRBG_INSTANTIATED(state->status.current_state)) { ++ DEBUG("cannot change the reminder value when DRBG is already instantiated\n"); ++ err = CRYPTO_FAILED; ++ goto ERR; ++ } ++ ++ /* requested value cannot be more than NIST's limit */ ++ if (max_req_per_seed > NIST_DFLT_MAX_REQ_PER_SEED) { ++ DEBUG("requested max_req_per_seed is more than standard's limit\n"); ++ err = CRYPTO_INVALID_ARGUMENT; ++ goto ERR; ++ } ++ ++ state->counters.max_req_per_seed = max_req_per_seed; ++ state->counters.req_per_seed_left = max_req_per_seed; ++ ++ err = CRYPTO_OK; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} ++EXPORT_SYMBOL(nisttrng_set_reminder_max_req_per_seed); ++ ++/* ++ * Zeroize command ++ * > A zeroize will not destroy the programmed mode and ALARM register value. ++ * It keeps the programmed mode to avoid re-programming. ++ * It also, maintains the ALARM register value, so that the user can read the value to understand the reason of the occurred alarm. ++ */ ++int nisttrng_zeroize(struct nist_trng_state *state) ++{ ++ int err; ++ ++ DEBUG(">> %s: zeroize the core\n", __func__); ++ ++ /* issue zeroize command */ ++ pdu_io_write32(state->base + NIST_TRNG_REG_CTRL, ++ NIST_TRNG_REG_CTRL_CMD_ZEROIZE); ++ ++ /* wait on zeroize done */ ++ err = nisttrng_wait_on_zeroize(state); ++ if (err) ++ goto ERR; ++ ++ /* reset the SW state */ ++ nisttrng_reset_state(state); ++ ++ err = CRYPTO_OK; ++ state->status.current_state = NIST_TRNG_STATE_UNINSTANTIATE; ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} /* nisttrng_zeroize */ ++EXPORT_SYMBOL(nisttrng_zeroize); ++ ++int nisttrng_rnc(struct nist_trng_state *state, int rnc_ctrl_cmd) ++{ ++ int err = 0; ++ u32 tmp; ++ ++ DEBUG(">> %s cmd %d\n", __func__, rnc_ctrl_cmd); ++ ++ if (rnc_ctrl_cmd > NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_FINISH_TO_IDLE) { ++ DEBUG(">> Invalid cmd %d\n", rnc_ctrl_cmd); ++ err = -1; ++ goto ERR; ++ } ++ ++ if (!state->config.build_cfg0.edu_present) { ++ DEBUG(">> edu not present\n"); ++ err = -1; ++ goto ERR; ++ } ++ ++ pdu_io_write32(state->base + NIST_TRNG_EDU_RNC_CTRL, rnc_ctrl_cmd); ++ if (rnc_ctrl_cmd == NIST_TRNG_EDU_RNC_CTRL_CMD_RNC_ENABLE) { ++ // wait till rnc is enabled ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_STAT); ++ } while (!NIST_TRNG_EDU_STAT_RNC_ENABLED(tmp)); ++ ++ state->status.edu_vstat.rnc_enabled = 1; ++ ++ } else { ++ // wait till rnc is idle (disabled) ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_STAT); ++ } while (NIST_TRNG_EDU_STAT_RNC_ENABLED(tmp)); ++ ++ state->status.edu_vstat.rnc_enabled = 0; ++ } ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} ++EXPORT_SYMBOL(nisttrng_rnc); ++ ++int nisttrng_wait_fifo_full(struct nist_trng_state *state) ++{ ++ int err = 0; ++ u32 tmp, t; ++ ++ t = NIST_TRNG_RETRY_MAX; ++ ++ DEBUG(">> %s starts...\n", __func__); ++ ++ do { ++ tmp = pdu_io_read32(state->base + NIST_TRNG_EDU_STAT); ++ } while ((!NIST_TRNG_EDU_STAT_FIFO_FULL(tmp)) && --t); ++ ++ if (t) { ++ err = CRYPTO_OK; ++ } else { ++ DEBUG("wait_on_fifo_full: failed timeout: %08lx\n", ++ (unsigned long)tmp); ++ err = CRYPTO_TIMEOUT; ++ goto ERR; ++ } ++ ++ERR: ++ DEBUG("--- Return %s, err = %i\n", __func__, err); ++ return err; ++} +diff --git a/drivers/i3c/mctp/Kconfig b/drivers/i3c/mctp/Kconfig +--- a/drivers/i3c/mctp/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i3c/mctp/Kconfig 2026-09-09 15:51:42.670269018 +0000 +@@ -0,0 +1,23 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++config I3C_MCTP ++ tristate "I3C Controller MCTP driver" ++ depends on I3C ++help ++ Say yes here to enable the I3C MCTP driver for I3C HW that is ++ configured as an I3C Controller Device on the I3C Bus. ++ ++config I3C_MCTP_HDR_DDR ++ bool "transfer with HDR-DDR mode" ++ depends on I3C_MCTP ++ default n ++ help ++ Say yes here to use the HDR-DDR mode as default to transfer data if ++ the device support it. ++ ++config I3C_TARGET_MCTP ++ tristate "I3C Target MCTP driver" ++ depends on I3C ++ select CRC8 ++help ++ Say yes here to enable the I3C MCTP driver for I3C HW that is ++ configured as an I3C Target Device on the I3C Bus. +diff --git a/drivers/i3c/mctp/Makefile b/drivers/i3c/mctp/Makefile +--- a/drivers/i3c/mctp/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i3c/mctp/Makefile 2026-09-09 15:51:42.670269018 +0000 +@@ -0,0 +1,3 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++obj-$(CONFIG_I3C_MCTP) += i3c-mctp.o ++obj-$(CONFIG_I3C_TARGET_MCTP) += i3c-target-mctp.o +diff --git a/drivers/i3c/mctp/i3c-mctp.c b/drivers/i3c/mctp/i3c-mctp.c +--- a/drivers/i3c/mctp/i3c-mctp.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i3c/mctp/i3c-mctp.c 2026-09-09 15:51:42.670269018 +0000 +@@ -0,0 +1,697 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright (C) 2022 Intel Corporation.*/ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#define I3C_MCTP_MINORS 32 ++#define CCC_DEVICE_STATUS_PENDING_INTR(x) (((x) & GENMASK(3, 0)) >> 0) ++#define POLLING_TIMEOUT_MS 50 ++#define MCTP_INTERRUPT_NUMBER 1 ++#define RX_RING_COUNT 16 ++#define I3C_MCTP_MIN_TRANSFER_SIZE 69 ++#define I3C_MCTP_IBI_PAYLOAD_SIZE 2 ++ ++struct i3c_mctp { ++ struct i3c_device *i3c; ++ struct cdev cdev; ++ struct device *dev; ++ struct delayed_work polling_work; ++ struct platform_device *i3c_peci; ++ int id; ++ /* ++ * Restrict an access to the /dev descriptor to one ++ * user at a time. ++ */ ++ spinlock_t device_file_lock; ++ int device_open; ++ /* Currently only one userspace client is supported */ ++ struct i3c_mctp_client *default_client; ++ struct i3c_mctp_client *peci_client; ++ u16 max_read_len; ++ u16 max_write_len; ++}; ++ ++struct i3c_mctp_client { ++ struct i3c_mctp *priv; ++ struct ptr_ring rx_queue; ++ wait_queue_head_t wait_queue; ++}; ++ ++static struct class *i3c_mctp_class; ++static dev_t i3c_mctp_devt; ++static DEFINE_IDA(i3c_mctp_ida); ++ ++static struct kmem_cache *packet_cache; ++ ++/** ++ * i3c_mctp_packet_alloc() - allocates i3c_mctp_packet ++ * ++ * @flags: the type of memory to allocate ++ * ++ * Allocates i3c_mctp_packet via slab allocation ++ * Return: pointer to the packet, NULL if some error occurred ++ */ ++void *i3c_mctp_packet_alloc(gfp_t flags) ++{ ++ return kmem_cache_alloc(packet_cache, flags); ++} ++EXPORT_SYMBOL_GPL(i3c_mctp_packet_alloc); ++ ++/** ++ * i3c_mctp_packet_free() - frees i3c_mctp_packet ++ * ++ * @packet: pointer to the packet which should be freed ++ * ++ * Frees i3c_mctp_packet previously allocated via slab allocation ++ */ ++void i3c_mctp_packet_free(void *packet) ++{ ++ kmem_cache_free(packet_cache, packet); ++} ++EXPORT_SYMBOL_GPL(i3c_mctp_packet_free); ++ ++static void i3c_mctp_client_free(struct i3c_mctp_client *client) ++{ ++ ptr_ring_cleanup(&client->rx_queue, &i3c_mctp_packet_free); ++ ++ kfree(client); ++} ++ ++static struct i3c_mctp_client *i3c_mctp_client_alloc(struct i3c_mctp *priv) ++{ ++ struct i3c_mctp_client *client; ++ int ret; ++ ++ client = kzalloc(sizeof(*client), GFP_KERNEL); ++ if (!client) ++ goto out; ++ ++ client->priv = priv; ++ ret = ptr_ring_init(&client->rx_queue, RX_RING_COUNT, GFP_KERNEL); ++ if (ret) ++ return ERR_PTR(ret); ++ init_waitqueue_head(&client->wait_queue); ++out: ++ return client; ++} ++ ++static struct i3c_mctp_client *i3c_mctp_find_client(struct i3c_mctp *priv, ++ struct i3c_mctp_packet *packet) ++{ ++ u8 *msg_hdr = (u8 *)packet->data.payload; ++ u8 mctp_type = msg_hdr[MCTP_MSG_HDR_MSG_TYPE_OFFSET]; ++ u16 vendor = (msg_hdr[MCTP_MSG_HDR_VENDOR_OFFSET] << 8 ++ | msg_hdr[MCTP_MSG_HDR_VENDOR_OFFSET + 1]); ++ u8 intel_msg_op_code = msg_hdr[MCTP_MSG_HDR_OPCODE_OFFSET]; ++ ++ if (priv->peci_client && mctp_type == MCTP_MSG_TYPE_VDM_PCI && ++ vendor == MCTP_VDM_PCI_INTEL_VENDOR_ID && intel_msg_op_code == MCTP_VDM_PCI_INTEL_PECI) ++ return priv->peci_client; ++ ++ return priv->default_client; ++} ++ ++static struct i3c_mctp_packet *i3c_mctp_read_packet(struct i3c_device *i3c) ++{ ++ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3c)); ++ struct i3c_mctp_packet *rx_packet; ++ struct i3c_priv_xfer xfers = { ++ .rnw = true, ++ }; ++ int ret; ++ ++ rx_packet = i3c_mctp_packet_alloc(GFP_KERNEL); ++ if (!rx_packet) ++ return ERR_PTR(-ENOMEM); ++ ++ rx_packet->size = I3C_MCTP_PACKET_SIZE; ++ xfers.len = rx_packet->size; ++ xfers.data.in = &rx_packet->data; ++ ++ /* Check against packet size + PEC byte to make sure that we always try to read max */ ++ if (priv->max_read_len != xfers.len + 1) { ++ dev_dbg(i3cdev_to_dev(i3c), "Length mismatch. MRL = %d, xfers.len = %d", ++ priv->max_read_len, xfers.len); ++ i3c_mctp_packet_free(rx_packet); ++ return ERR_PTR(-EINVAL); ++ } ++ if (i3c->desc->info.hdr_cap & BIT(I3C_HDR_DDR) && ++ IS_ENABLED(CONFIG_I3C_MCTP_HDR_DDR)) { ++ struct i3c_hdr_cmd cmds; ++ ++ cmds.mode = I3C_HDR_DDR; ++ cmds.code = 0x80; ++ cmds.ndatawords = DIV_ROUND_UP(rx_packet->size, 2); ++ cmds.data.in = &rx_packet->data; ++ ret = i3c_device_send_hdr_cmds(i3c, &cmds, 1); ++ if (!ret) ++ rx_packet->size = cmds.ndatawords; ++ } else { ++ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); ++ if (!ret) ++ rx_packet->size = xfers.len; ++ } ++ if (ret) { ++ i3c_mctp_packet_free(rx_packet); ++ return ERR_PTR(ret); ++ } ++ ++ return rx_packet; ++} ++ ++static void i3c_mctp_dispatch_packet(struct i3c_mctp *priv, struct i3c_mctp_packet *packet) ++{ ++ struct i3c_mctp_client *client = i3c_mctp_find_client(priv, packet); ++ int ret; ++ ++ ret = ptr_ring_produce(&client->rx_queue, packet); ++ if (ret) ++ i3c_mctp_packet_free(packet); ++ else ++ wake_up_all(&client->wait_queue); ++} ++ ++static void i3c_mctp_polling_work(struct work_struct *work) ++{ ++ struct i3c_mctp *priv = container_of(to_delayed_work(work), struct i3c_mctp, polling_work); ++ struct i3c_device *i3cdev = priv->i3c; ++ struct i3c_mctp_packet *rx_packet; ++ struct i3c_device_info info; ++ int ret; ++ ++ i3c_device_get_info(i3cdev, &info); ++ ret = i3c_device_getstatus_ccc(i3cdev, &info); ++ if (ret) ++ return; ++ ++ if (CCC_DEVICE_STATUS_PENDING_INTR(info.status) != MCTP_INTERRUPT_NUMBER) ++ return; ++ ++ rx_packet = i3c_mctp_read_packet(i3cdev); ++ if (IS_ERR(rx_packet)) ++ goto out; ++ ++ i3c_mctp_dispatch_packet(priv, rx_packet); ++out: ++ schedule_delayed_work(&priv->polling_work, msecs_to_jiffies(POLLING_TIMEOUT_MS)); ++} ++ ++static ssize_t i3c_mctp_write(struct file *file, const char __user *buf, size_t count, ++ loff_t *f_pos) ++{ ++ struct i3c_mctp *priv = file->private_data; ++ struct i3c_device *i3c = priv->i3c; ++ struct i3c_priv_xfer xfers = { ++ .rnw = false, ++ .len = count, ++ }; ++ u8 *data; ++ int ret; ++ ++ /* ++ * Check against packet size + PEC byte ++ * to not send more data than it was set in the probe ++ */ ++ if (priv->max_write_len < xfers.len + 1) { ++ dev_dbg(i3cdev_to_dev(i3c), "Length mismatch. MWL = %d, xfers.len = %d", ++ priv->max_write_len, xfers.len); ++ return -EINVAL; ++ } ++ ++ data = memdup_user(buf, count); ++ if (IS_ERR(data)) ++ return PTR_ERR(data); ++ ++ if (i3c->desc->info.hdr_cap & BIT(I3C_HDR_DDR) && ++ IS_ENABLED(CONFIG_I3C_MCTP_HDR_DDR)) { ++ struct i3c_hdr_cmd cmds; ++ ++ cmds.mode = I3C_HDR_DDR; ++ cmds.code = 0; ++ cmds.ndatawords = DIV_ROUND_UP(count, 2); ++ cmds.data.out = data; ++ ret = i3c_device_send_hdr_cmds(i3c, &cmds, 1); ++ } else { ++ xfers.data.out = data; ++ ++ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); ++ } ++ kfree(data); ++ return ret ?: count; ++} ++ ++static ssize_t i3c_mctp_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) ++{ ++ struct i3c_mctp *priv = file->private_data; ++ struct i3c_mctp_client *client = priv->default_client; ++ struct i3c_mctp_packet *rx_packet; ++ ++ if (count > sizeof(rx_packet->data)) ++ count = sizeof(rx_packet->data); ++ ++ rx_packet = ptr_ring_consume(&client->rx_queue); ++ if (!rx_packet) ++ return -EAGAIN; ++ ++ if (count > rx_packet->size) ++ count = rx_packet->size; ++ ++ if (copy_to_user(buf, &rx_packet->data, count)) ++ return -EFAULT; ++ ++ i3c_mctp_packet_free(rx_packet); ++ ++ return count; ++} ++ ++static int i3c_mctp_open(struct inode *inode, struct file *file) ++{ ++ struct i3c_mctp *priv = container_of(inode->i_cdev, struct i3c_mctp, cdev); ++ ++ spin_lock(&priv->device_file_lock); ++ if (priv->device_open) { ++ spin_unlock(&priv->device_file_lock); ++ return -EBUSY; ++ } ++ priv->device_open++; ++ /* Discard all of the packet in the rx_queue */ ++ while (ptr_ring_consume(&priv->default_client->rx_queue)) ++ ; ++ spin_unlock(&priv->device_file_lock); ++ ++ file->private_data = priv; ++ ++ return 0; ++} ++ ++static int i3c_mctp_release(struct inode *inode, struct file *file) ++{ ++ struct i3c_mctp *priv = file->private_data; ++ ++ spin_lock(&priv->device_file_lock); ++ priv->device_open--; ++ spin_unlock(&priv->device_file_lock); ++ ++ file->private_data = NULL; ++ ++ return 0; ++} ++ ++static __poll_t i3c_mctp_poll(struct file *file, struct poll_table_struct *pt) ++{ ++ struct i3c_mctp *priv = file->private_data; ++ __poll_t ret = 0; ++ ++ poll_wait(file, &priv->default_client->wait_queue, pt); ++ ++ if (__ptr_ring_peek(&priv->default_client->rx_queue)) ++ ret |= EPOLLIN; ++ ++ return ret; ++} ++ ++static const struct file_operations i3c_mctp_fops = { ++ .owner = THIS_MODULE, ++ .read = i3c_mctp_read, ++ .write = i3c_mctp_write, ++ .poll = i3c_mctp_poll, ++ .open = i3c_mctp_open, ++ .release = i3c_mctp_release, ++}; ++ ++/** ++ * i3c_mctp_add_peci_client() - registers PECI client ++ * @i3c: I3C device to get the PECI client for ++ * ++ * Return: pointer to PECI client, -ENOMEM - in case of client alloc fault ++ */ ++struct i3c_mctp_client *i3c_mctp_add_peci_client(struct i3c_device *i3c) ++{ ++ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3c)); ++ struct i3c_mctp_client *client; ++ ++ client = i3c_mctp_client_alloc(priv); ++ if (IS_ERR(client)) ++ return ERR_PTR(-ENOMEM); ++ ++ priv->peci_client = client; ++ ++ return priv->peci_client; ++} ++EXPORT_SYMBOL_GPL(i3c_mctp_add_peci_client); ++ ++/** ++ * i3c_mctp_remove_peci_client() - un-registers PECI client ++ * @client: i3c_mctp_client to be freed ++ */ ++void i3c_mctp_remove_peci_client(struct i3c_mctp_client *client) ++{ ++ struct i3c_mctp *priv = client->priv; ++ ++ i3c_mctp_client_free(priv->peci_client); ++ ++ priv->peci_client = NULL; ++} ++EXPORT_SYMBOL_GPL(i3c_mctp_remove_peci_client); ++ ++static struct i3c_mctp *i3c_mctp_alloc(struct i3c_device *i3c) ++{ ++ struct i3c_mctp *priv; ++ int id; ++ ++ priv = devm_kzalloc(i3cdev_to_dev(i3c), sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return ERR_PTR(-ENOMEM); ++ ++ id = ida_alloc(&i3c_mctp_ida, GFP_KERNEL); ++ if (id < 0) { ++ pr_err("i3c_mctp: no minor number available!\n"); ++ return ERR_PTR(id); ++ } ++ ++ priv->id = id; ++ priv->i3c = i3c; ++ ++ spin_lock_init(&priv->device_file_lock); ++ ++ return priv; ++} ++ ++static void i3c_mctp_ibi_handler(struct i3c_device *dev, const struct i3c_ibi_payload *payload) ++{ ++ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(dev)); ++ struct i3c_mctp_packet *rx_packet; ++ ++ rx_packet = i3c_mctp_read_packet(dev); ++ if (IS_ERR(rx_packet)) ++ return; ++ ++ i3c_mctp_dispatch_packet(priv, rx_packet); ++} ++ ++static int i3c_mctp_init(struct i3c_driver *drv) ++{ ++ int ret; ++ ++ packet_cache = kmem_cache_create_usercopy("mctp-i3c-packet", ++ sizeof(struct i3c_mctp_packet), 0, 0, 0, ++ sizeof(struct i3c_mctp_packet), NULL); ++ if (IS_ERR(packet_cache)) { ++ ret = PTR_ERR(packet_cache); ++ goto out; ++ } ++ ++ /* Dynamically request unused major number */ ++ ret = alloc_chrdev_region(&i3c_mctp_devt, 0, I3C_MCTP_MINORS, "i3c-mctp"); ++ if (ret) ++ goto out; ++ ++ /* Create a class to populate sysfs entries*/ ++ i3c_mctp_class = class_create("i3c-mctp"); ++ if (IS_ERR(i3c_mctp_class)) { ++ ret = PTR_ERR(i3c_mctp_class); ++ goto out_unreg_chrdev; ++ } ++ ++ i3c_driver_register(drv); ++ ++ return 0; ++ ++out_unreg_chrdev: ++ unregister_chrdev_region(i3c_mctp_devt, I3C_MCTP_MINORS); ++out: ++ pr_err("i3c_mctp: driver initialisation failed\n"); ++ return ret; ++} ++ ++static void i3c_mctp_free(struct i3c_driver *drv) ++{ ++ i3c_driver_unregister(drv); ++ class_destroy(i3c_mctp_class); ++ unregister_chrdev_region(i3c_mctp_devt, I3C_MCTP_MINORS); ++ kmem_cache_destroy(packet_cache); ++} ++ ++static int i3c_mctp_enable_ibi(struct i3c_device *i3cdev) ++{ ++ struct i3c_ibi_setup ibireq = { ++ .handler = i3c_mctp_ibi_handler, ++ .max_payload_len = 2, ++ .num_slots = 10, ++ }; ++ int ret; ++ ++ ret = i3c_device_request_ibi(i3cdev, &ibireq); ++ if (ret) ++ return ret; ++ ret = i3c_device_enable_ibi(i3cdev); ++ if (ret) ++ i3c_device_free_ibi(i3cdev); ++ ++ return ret; ++} ++ ++static void i3c_mctp_disable_ibi(struct i3c_device *i3cdev) ++{ ++ i3c_device_disable_ibi(i3cdev); ++ i3c_device_free_ibi(i3cdev); ++} ++ ++/** ++ * i3c_mctp_get_eid() - receive MCTP EID assigned to the device ++ * ++ * @client: client for the device to get the EID for ++ * @domain_id: requested domain ID ++ * @eid: pointer to store EID value ++ * ++ * Receive MCTP endpoint ID dynamically assigned by the MCTP Bus Owner ++ * Return: 0 in case of success, a negative error code otherwise. ++ */ ++int i3c_mctp_get_eid(struct i3c_mctp_client *client, u8 domain_id, u8 *eid) ++{ ++ /* TODO: Implement EID assignment basing on domain ID */ ++ *eid = 1; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(i3c_mctp_get_eid); ++ ++/** ++ * i3c_mctp_send_packet() - send mctp packet ++ * ++ * @tx_packet: the allocated packet that needs to be send via I3C ++ * @i3c: i3c device to send the packet to ++ * ++ * Return: 0 in case of success, a negative error code otherwise. ++ */ ++int i3c_mctp_send_packet(struct i3c_device *i3c, struct i3c_mctp_packet *tx_packet) ++{ ++ if (i3c->desc->info.hdr_cap & BIT(I3C_HDR_DDR) && ++ IS_ENABLED(CONFIG_I3C_MCTP_HDR_DDR)) { ++ struct i3c_hdr_cmd cmds; ++ ++ cmds.mode = I3C_HDR_DDR; ++ cmds.code = 0; ++ cmds.ndatawords = DIV_ROUND_UP(tx_packet->size, 2); ++ cmds.data.out = &tx_packet->data; ++ return i3c_device_send_hdr_cmds(i3c, &cmds, 1); ++ } ++ struct i3c_priv_xfer xfers; ++ ++ xfers.rnw = false; ++ xfers.len = tx_packet->size; ++ xfers.data.out = &tx_packet->data; ++ return i3c_device_do_priv_xfers(i3c, &xfers, 1); ++} ++EXPORT_SYMBOL_GPL(i3c_mctp_send_packet); ++ ++/** ++ * i3c_mctp_receive_packet() - receive mctp packet ++ * ++ * @client: i3c_mctp_client to receive the packet from ++ * @timeout: timeout, in jiffies ++ * ++ * The function will sleep for up to @timeout if no packet is ready to read. ++ * ++ * Returns struct i3c_mctp_packet from or ERR_PTR in case of error or the ++ * timeout elapsed. ++ */ ++struct i3c_mctp_packet *i3c_mctp_receive_packet(struct i3c_mctp_client *client, ++ unsigned long timeout) ++{ ++ struct i3c_mctp_packet *rx_packet; ++ int ret; ++ ++ ret = wait_event_interruptible_timeout(client->wait_queue, ++ __ptr_ring_peek(&client->rx_queue), timeout); ++ if (ret < 0) ++ return ERR_PTR(ret); ++ else if (ret == 0) ++ return ERR_PTR(-ETIME); ++ ++ rx_packet = ptr_ring_consume(&client->rx_queue); ++ if (!rx_packet) ++ return ERR_PTR(-EAGAIN); ++ ++ return rx_packet; ++} ++EXPORT_SYMBOL_GPL(i3c_mctp_receive_packet); ++ ++static void i3c_mctp_i3c_event_cb(struct i3c_device *dev, enum i3c_event event) ++{ ++ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(dev)); ++ ++ switch (event) { ++ case i3c_event_prepare_for_rescan: ++ /* ++ * Disable IBI and polling mode blindly. ++ */ ++ i3c_mctp_disable_ibi(dev); ++ cancel_delayed_work(&priv->polling_work); ++ break; ++ case i3c_event_rescan_done: ++ if (i3c_mctp_enable_ibi(dev)) { ++ INIT_DELAYED_WORK(&priv->polling_work, ++ i3c_mctp_polling_work); ++ schedule_delayed_work(&priv->polling_work, ++ msecs_to_jiffies(POLLING_TIMEOUT_MS)); ++ } ++ break; ++ default: ++ break; ++ } ++} ++ ++static int i3c_mctp_probe(struct i3c_device *i3cdev) ++{ ++ int ibi_payload_size = I3C_MCTP_IBI_PAYLOAD_SIZE; ++ struct device *dev = i3cdev_to_dev(i3cdev); ++ struct i3c_device_info info; ++ struct i3c_mctp *priv; ++ int ret; ++ ++ priv = i3c_mctp_alloc(i3cdev); ++ if (IS_ERR(priv)) ++ return PTR_ERR(priv); ++ ++ cdev_init(&priv->cdev, &i3c_mctp_fops); ++ ++ priv->cdev.owner = THIS_MODULE; ++ ret = cdev_add(&priv->cdev, MKDEV(MAJOR(i3c_mctp_devt), priv->id), 1); ++ if (ret) ++ goto error_cdev; ++ ++ /* register this i3c device with the driver core */ ++ priv->dev = device_create(i3c_mctp_class, dev, ++ MKDEV(MAJOR(i3c_mctp_devt), priv->id), ++ NULL, "i3c-mctp-%d", priv->id); ++ if (IS_ERR(priv->dev)) { ++ ret = PTR_ERR(priv->dev); ++ goto error; ++ } ++ ++ ret = i3c_device_control_pec(i3cdev, true); ++ if (ret) ++ dev_warn(priv->dev, "Hardware not support pec"); ++ ++ priv->default_client = i3c_mctp_client_alloc(priv); ++ if (IS_ERR(priv->default_client)) ++ goto error; ++ ++ dev_set_drvdata(i3cdev_to_dev(i3cdev), priv); ++ ++ priv->i3c_peci = platform_device_register_data(i3cdev_to_dev(i3cdev), "peci-i3c", priv->id, ++ NULL, 0); ++ if (IS_ERR(priv->i3c_peci)) ++ dev_warn(priv->dev, "failed to register peci-i3c device\n"); ++ ++ i3c_device_register_event_cb(i3cdev, i3c_mctp_i3c_event_cb); ++ if (i3c_mctp_enable_ibi(i3cdev)) { ++ INIT_DELAYED_WORK(&priv->polling_work, i3c_mctp_polling_work); ++ schedule_delayed_work(&priv->polling_work, msecs_to_jiffies(POLLING_TIMEOUT_MS)); ++ ibi_payload_size = 0; ++ } ++ ++ i3c_device_get_info(i3cdev, &info); ++ ++ ret = i3c_device_getmrl_ccc(i3cdev, &info); ++ if (ret || info.max_read_len != I3C_MCTP_MIN_TRANSFER_SIZE) ++ ret = i3c_device_setmrl_ccc(i3cdev, &info, I3C_MCTP_MIN_TRANSFER_SIZE, ++ ibi_payload_size); ++ if (ret && info.max_read_len != I3C_MCTP_MIN_TRANSFER_SIZE) { ++ dev_err(dev, "Failed to set MRL!, ret = %d\n", ret); ++ goto error_peci; ++ } ++ priv->max_read_len = info.max_read_len; ++ ++ ret = i3c_device_getmwl_ccc(i3cdev, &info); ++ if (ret || info.max_write_len != I3C_MCTP_MIN_TRANSFER_SIZE) ++ ret = i3c_device_setmwl_ccc(i3cdev, &info, I3C_MCTP_MIN_TRANSFER_SIZE); ++ if (ret && info.max_write_len != I3C_MCTP_MIN_TRANSFER_SIZE) { ++ dev_err(dev, "Failed to set MWL!, ret = %d\n", ret); ++ goto error_peci; ++ } ++ priv->max_write_len = info.max_write_len; ++ ++ return 0; ++ ++error_peci: ++ platform_device_unregister(priv->i3c_peci); ++ i3c_device_disable_ibi(i3cdev); ++ i3c_device_free_ibi(i3cdev); ++error: ++ cdev_del(&priv->cdev); ++error_cdev: ++ put_device(dev); ++ return ret; ++} ++ ++static void i3c_mctp_remove(struct i3c_device *i3cdev) ++{ ++ struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3cdev)); ++ ++ i3c_mctp_disable_ibi(i3cdev); ++ i3c_mctp_client_free(priv->default_client); ++ priv->default_client = NULL; ++ platform_device_unregister(priv->i3c_peci); ++ ++ device_destroy(i3c_mctp_class, MKDEV(MAJOR(i3c_mctp_devt), priv->id)); ++ cdev_del(&priv->cdev); ++ ida_free(&i3c_mctp_ida, priv->id); ++} ++ ++static const struct i3c_device_id i3c_mctp_ids[] = { ++ I3C_CLASS(0xCC, 0x0), ++ I3C_DEVICE(0x3f6, 0x8000, (void *)0), ++ I3C_DEVICE(0x3f6, 0x8001, (void *)0), ++ I3C_DEVICE(0x3f6, 0xA001, (void *)0), ++ I3C_DEVICE(0x3f6, 0xA003, (void *)0), ++ I3C_DEVICE(0x3f6, 0x0503, (void *)0), ++ { }, ++}; ++ ++static struct i3c_driver i3c_mctp_drv = { ++ .driver.name = "i3c-mctp", ++ .id_table = i3c_mctp_ids, ++ .probe = i3c_mctp_probe, ++ .remove = i3c_mctp_remove, ++}; ++ ++module_driver(i3c_mctp_drv, i3c_mctp_init, i3c_mctp_free); ++MODULE_AUTHOR("Oleksandr Shulzhenko "); ++MODULE_DESCRIPTION("I3C MCTP driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/i3c/mctp/i3c-target-mctp.c b/drivers/i3c/mctp/i3c-target-mctp.c +--- a/drivers/i3c/mctp/i3c-target-mctp.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i3c/mctp/i3c-target-mctp.c 2026-09-09 15:51:42.670269018 +0000 +@@ -0,0 +1,489 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright (C) 2022 Intel Corporation.*/ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define I3C_CRC8_POLYNOMIAL 0x07 ++DECLARE_CRC8_TABLE(i3c_crc8_table); ++ ++#define I3C_TARGET_MCTP_MINORS 32 ++#define RX_RING_COUNT 16 ++ ++/* ++ * IBI Mandatory Data Byte ++ * https://www.mipi.org/mipi_i3c_mandatory_data_byte_values_public ++ * ++ * MCTP: ++ * bit[7:5] = 3'b101 ++ * bit[4:0] = 5'h0E ++ */ ++#define I3C_MCTP_MDB 0xae ++ ++static struct class *i3c_target_mctp_class; ++static dev_t i3c_target_mctp_devt; ++static DEFINE_IDA(i3c_target_mctp_ida); ++ ++struct mctp_client; ++ ++struct i3c_target_mctp { ++ struct i3c_device *i3cdev; ++ struct cdev cdev; ++ int id; ++ struct mctp_client *client; ++ spinlock_t client_lock; /* to protect client access */ ++ bool mdb_append_pec; ++}; ++ ++struct mctp_client { ++ struct kref ref; ++ struct i3c_target_mctp *priv; ++ struct ptr_ring rx_queue; ++ wait_queue_head_t wait_queue; ++}; ++ ++struct mctp_packet { ++ u8 *data; ++ u16 count; ++}; ++ ++static void *i3c_target_mctp_packet_alloc(u16 count) ++{ ++ struct mctp_packet *packet; ++ u8 *data; ++ ++ packet = kzalloc(sizeof(*packet), GFP_ATOMIC); ++ if (!packet) ++ return NULL; ++ ++ data = kzalloc(count, GFP_ATOMIC); ++ if (!data) { ++ kfree(packet); ++ return NULL; ++ } ++ ++ packet->data = data; ++ packet->count = count; ++ ++ return packet; ++} ++ ++static void i3c_target_mctp_packet_free(void *data) ++{ ++ struct mctp_packet *packet = data; ++ ++ kfree(packet->data); ++ kfree(packet); ++} ++ ++static struct mctp_client *i3c_target_mctp_client_alloc(struct i3c_target_mctp *priv) ++{ ++ struct mctp_client *client; ++ ++ client = kzalloc(sizeof(*client), GFP_KERNEL); ++ if (!client) ++ goto out; ++ ++ kref_init(&client->ref); ++ client->priv = priv; ++ ptr_ring_init(&client->rx_queue, RX_RING_COUNT, GFP_KERNEL); ++out: ++ return client; ++} ++ ++static void i3c_target_mctp_client_free(struct kref *ref) ++{ ++ struct mctp_client *client = container_of(ref, typeof(*client), ref); ++ ++ ptr_ring_cleanup(&client->rx_queue, &i3c_target_mctp_packet_free); ++ ++ kfree(client); ++} ++ ++static void i3c_target_mctp_client_get(struct mctp_client *client) ++{ ++ kref_get(&client->ref); ++} ++ ++static void i3c_target_mctp_client_put(struct mctp_client *client) ++{ ++ kref_put(&client->ref, &i3c_target_mctp_client_free); ++} ++ ++static void ++i3c_target_mctp_rx_packet_enqueue(struct i3c_device *i3cdev, const u8 *data, size_t count) ++{ ++ struct i3c_target_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3cdev)); ++ struct mctp_client *client; ++ struct mctp_packet *packet; ++ int ret; ++ ++ spin_lock(&priv->client_lock); ++ client = priv->client; ++ if (client) ++ i3c_target_mctp_client_get(client); ++ spin_unlock(&priv->client_lock); ++ ++ if (!client) ++ return; ++ ++ packet = i3c_target_mctp_packet_alloc(count); ++ if (!packet) ++ goto err; ++ ++ memcpy(packet->data, data, count); ++ ++ ret = ptr_ring_produce(&client->rx_queue, packet); ++ if (ret) ++ i3c_target_mctp_packet_free(packet); ++ else ++ wake_up_all(&client->wait_queue); ++err: ++ i3c_target_mctp_client_put(client); ++} ++ ++static struct mctp_client *i3c_target_mctp_create_client(struct i3c_target_mctp *priv) ++{ ++ struct mctp_client *client; ++ int ret; ++ ++ /* Currently, we support just one client. */ ++ spin_lock_irq(&priv->client_lock); ++ ret = priv->client ? -EBUSY : 0; ++ spin_unlock_irq(&priv->client_lock); ++ ++ if (ret) ++ return ERR_PTR(ret); ++ ++ client = i3c_target_mctp_client_alloc(priv); ++ if (!client) ++ return ERR_PTR(-ENOMEM); ++ ++ init_waitqueue_head(&client->wait_queue); ++ ++ spin_lock_irq(&priv->client_lock); ++ priv->client = client; ++ spin_unlock_irq(&priv->client_lock); ++ ++ return client; ++} ++ ++static void i3c_target_mctp_delete_client(struct mctp_client *client) ++{ ++ struct i3c_target_mctp *priv = client->priv; ++ ++ spin_lock_irq(&priv->client_lock); ++ priv->client = NULL; ++ spin_unlock_irq(&priv->client_lock); ++ ++ i3c_target_mctp_client_put(client); ++} ++ ++static int i3c_target_mctp_open(struct inode *inode, struct file *file) ++{ ++ struct i3c_target_mctp *priv = container_of(inode->i_cdev, struct i3c_target_mctp, cdev); ++ struct mctp_client *client; ++ ++ client = i3c_target_mctp_create_client(priv); ++ if (IS_ERR(client)) ++ return PTR_ERR(client); ++ ++ file->private_data = client; ++ ++ return 0; ++} ++ ++static int i3c_target_mctp_release(struct inode *inode, struct file *file) ++{ ++ struct mctp_client *client = file->private_data; ++ ++ i3c_target_mctp_delete_client(client); ++ ++ return 0; ++} ++ ++static ssize_t i3c_target_mctp_read(struct file *file, char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_client *client = file->private_data; ++ struct mctp_packet *rx_packet; ++ ++ rx_packet = ptr_ring_consume_irq(&client->rx_queue); ++ if (!rx_packet) ++ return -EAGAIN; ++ ++ if (count < rx_packet->count) { ++ count = -EINVAL; ++ goto err_free; ++ } ++ if (count > rx_packet->count) ++ count = rx_packet->count; ++ ++ if (copy_to_user(buf, rx_packet->data, count)) ++ count = -EFAULT; ++err_free: ++ i3c_target_mctp_packet_free(rx_packet); ++ ++ return count; ++} ++ ++static u8 *pec_append(u8 addr_rnw, u8 *buf, u8 len) ++{ ++ u8 pec_v; ++ ++ pec_v = crc8(i3c_crc8_table, &addr_rnw, 1, 0); ++ pec_v = crc8(i3c_crc8_table, buf, len, pec_v); ++ buf[len] = pec_v; ++ ++ return buf; ++} ++ ++static ssize_t i3c_target_mctp_write(struct file *file, const char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_client *client = file->private_data; ++ struct i3c_target_mctp *priv = client->priv; ++ struct i3c_priv_xfer xfers[2] = {}; ++ struct i3c_device_info info; ++ u8 *tx_data; ++ u8 *ibi_data; ++ int ret; ++ bool ibi_enabled = i3c_device_is_ibi_enabled(priv->i3cdev); ++ ++ if (!ibi_enabled) { ++ dev_warn(i3cdev_to_dev(priv->i3cdev), "IBI not enabled\n"); ++ return count; ++ } ++ if (priv->mdb_append_pec) ++ ibi_data = kzalloc(2, GFP_KERNEL); ++ else ++ ibi_data = kzalloc(1, GFP_KERNEL); ++ if (!ibi_data) ++ return -ENOMEM; ++ ibi_data[0] = I3C_MCTP_MDB; ++ ++ tx_data = kzalloc(count, GFP_KERNEL); ++ if (!tx_data) { ++ ret = -ENOMEM; ++ goto free_ibi; ++ } ++ ++ if (copy_from_user(tx_data, buf, count)) { ++ ret = -EFAULT; ++ goto out_packet; ++ } ++ ++ i3c_device_get_info(priv->i3cdev, &info); ++ if (priv->mdb_append_pec) { ++ pec_append(info.dyn_addr << 1 | 0x1, ibi_data, 1); ++ xfers[0].len = 2; ++ } else { ++ xfers[0].len = 1; ++ } ++ xfers[0].data.out = ibi_data; ++ ++ xfers[1].data.out = tx_data; ++ xfers[1].len = count; ++ ++ ret = i3c_device_pending_read_notify(priv->i3cdev, &xfers[1], ++ &xfers[0]); ++ if (ret) ++ goto out_packet; ++ ret = count; ++ ++out_packet: ++ kfree(tx_data); ++free_ibi: ++ kfree(ibi_data); ++ return ret; ++} ++ ++static __poll_t i3c_target_mctp_poll(struct file *file, struct poll_table_struct *pt) ++{ ++ struct mctp_client *client = file->private_data; ++ __poll_t ret = 0; ++ ++ poll_wait(file, &client->wait_queue, pt); ++ ++ if (__ptr_ring_peek(&client->rx_queue)) ++ ret |= EPOLLIN; ++ ++ /* ++ * TODO: Add support for "write" readiness. ++ * DW-I3C has a hardware queue that has finite number of entries. ++ * If we try to issue more writes that space in this queue allows for, ++ * we're in trouble. This should be handled by error from write() and ++ * poll() blocking for write events. ++ */ ++ return ret; ++} ++ ++static const struct file_operations i3c_target_mctp_fops = { ++ .owner = THIS_MODULE, ++ .open = i3c_target_mctp_open, ++ .release = i3c_target_mctp_release, ++ .read = i3c_target_mctp_read, ++ .write = i3c_target_mctp_write, ++ .poll = i3c_target_mctp_poll, ++}; ++ ++static struct i3c_target_read_setup i3c_target_mctp_rx_packet_setup = { ++ .handler = i3c_target_mctp_rx_packet_enqueue, ++}; ++ ++static ssize_t mdb_append_pec_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct i3c_device *i3cdev = dev_get_drvdata(dev); ++ struct i3c_target_mctp *priv = i3cdev_get_drvdata(i3cdev); ++ ssize_t ret; ++ ++ ret = sysfs_emit(buf, "%d\n", priv->mdb_append_pec); ++ ++ return ret; ++} ++ ++static ssize_t mdb_append_pec_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct i3c_device *i3cdev = dev_get_drvdata(dev); ++ struct i3c_target_mctp *priv = i3cdev_get_drvdata(i3cdev); ++ bool res; ++ int ret; ++ ++ ret = kstrtobool(buf, &res); ++ if (ret) ++ return ret; ++ ++ priv->mdb_append_pec = res; ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(mdb_append_pec); ++ ++static int i3c_target_mctp_probe(struct i3c_device *i3cdev) ++{ ++ struct device *parent = i3cdev_to_dev(i3cdev); ++ struct i3c_target_mctp *priv; ++ struct device *dev; ++ int ret; ++ ++ priv = devm_kzalloc(parent, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ ret = ida_alloc(&i3c_target_mctp_ida, GFP_KERNEL); ++ if (ret < 0) ++ return ret; ++ priv->id = ret; ++ ++ priv->i3cdev = i3cdev; ++ spin_lock_init(&priv->client_lock); ++ ++ cdev_init(&priv->cdev, &i3c_target_mctp_fops); ++ priv->cdev.owner = THIS_MODULE; ++ ++ ret = cdev_add(&priv->cdev, ++ MKDEV(MAJOR(i3c_target_mctp_devt), priv->id), 1); ++ if (ret) ++ goto err_ida; ++ ++ dev = device_create(i3c_target_mctp_class, parent, ++ MKDEV(MAJOR(i3c_target_mctp_devt), priv->id), i3cdev, ++ "i3c-mctp-target-%d", priv->id); ++ if (IS_ERR(dev)) { ++ ret = PTR_ERR(dev); ++ goto err_cdev; ++ } ++ ++ /* ++ * By default, the PEC is appended to the MDB as a hardware workaround for the AST2600 I3C ++ * controller as primary controller. ++ */ ++ priv->mdb_append_pec = 1; ++ ++ ret = device_create_file(dev, &dev_attr_mdb_append_pec); ++ if (unlikely(ret)) { ++ dev_err(dev, "Failed creating device attrs\n"); ++ ret = -EINVAL; ++ goto err_device; ++ } ++ ++ i3cdev_set_drvdata(i3cdev, priv); ++ ++ i3c_target_read_register(i3cdev, &i3c_target_mctp_rx_packet_setup); ++ ++ crc8_populate_msb(i3c_crc8_table, I3C_CRC8_POLYNOMIAL); ++ ++ return 0; ++ ++err_device: ++ device_destroy(i3c_target_mctp_class, ++ MKDEV(MAJOR(i3c_target_mctp_devt), priv->id)); ++err_cdev: ++ cdev_del(&priv->cdev); ++err_ida: ++ ida_free(&i3c_target_mctp_ida, priv->id); ++ ++ return ret; ++} ++ ++static void i3c_target_mctp_remove(struct i3c_device *i3cdev) ++{ ++ struct i3c_target_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3cdev)); ++ ++ device_destroy(i3c_target_mctp_class, ++ MKDEV(MAJOR(i3c_target_mctp_devt), priv->id)); ++ cdev_del(&priv->cdev); ++ ida_free(&i3c_target_mctp_ida, priv->id); ++} ++ ++static const struct i3c_device_id i3c_target_mctp_ids[] = { ++ I3C_CLASS(0xcc, 0x0), ++ { }, ++}; ++ ++static struct i3c_driver i3c_target_mctp_drv = { ++ .driver.name = "i3c-target-mctp", ++ .id_table = i3c_target_mctp_ids, ++ .probe = i3c_target_mctp_probe, ++ .remove = i3c_target_mctp_remove, ++ .target = true, ++}; ++ ++static int i3c_target_mctp_init(struct i3c_driver *drv) ++{ ++ int ret; ++ ++ ret = alloc_chrdev_region(&i3c_target_mctp_devt, 0, ++ I3C_TARGET_MCTP_MINORS, "i3c-target-mctp"); ++ if (ret) ++ return ret; ++ ++ i3c_target_mctp_class = class_create("i3c-target-mctp"); ++ if (IS_ERR(i3c_target_mctp_class)) { ++ unregister_chrdev_region(i3c_target_mctp_devt, I3C_TARGET_MCTP_MINORS); ++ return PTR_ERR(i3c_target_mctp_class); ++ } ++ ++ return i3c_driver_register(drv); ++} ++ ++static void i3c_target_mctp_fini(struct i3c_driver *drv) ++{ ++ i3c_driver_unregister(drv); ++ class_destroy(i3c_target_mctp_class); ++ unregister_chrdev_region(i3c_target_mctp_devt, I3C_TARGET_MCTP_MINORS); ++} ++ ++module_driver(i3c_target_mctp_drv, i3c_target_mctp_init, i3c_target_mctp_fini); ++MODULE_AUTHOR("Iwona Winiarska "); ++MODULE_DESCRIPTION("I3C Target MCTP driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig +--- a/drivers/jtag/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/jtag/Kconfig 2026-09-09 15:51:40.136307308 +0000 +@@ -0,0 +1,31 @@ ++menuconfig JTAG ++ tristate "JTAG support" ++ help ++ This provides basic core functionality support for JTAG class devices. ++ Hardware that is equipped with a JTAG microcontroller can be ++ supported by using this driver's interfaces. ++ This driver exposes a set of IOCTLs to the user space for ++ the following commands: ++ SDR: Performs an IEEE 1149.1 Data Register scan ++ SIR: Performs an IEEE 1149.1 Instruction Register scan. ++ RUNTEST: Forces the IEEE 1149.1 bus to a run state for a specified ++ number of clocks or a specified time period. ++ ++ If you want this support, you should say Y here. ++ ++ To compile this driver as a module, choose M here: the module will ++ be called jtag. ++ ++menuconfig JTAG_ASPEED ++ tristate "Aspeed SoC JTAG controller support" ++ depends on JTAG && HAS_IOMEM ++ depends on ARCH_ASPEED || COMPILE_TEST ++ help ++ This provides a support for Aspeed JTAG device, equipped on ++ Aspeed SoC 24xx and 25xx families. Drivers allows programming ++ of hardware devices, connected to SoC through the JTAG interface. ++ ++ If you want this support, you should say Y here. ++ ++ To compile this driver as a module, choose M here: the module will ++ be called jtag-aspeed. +diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile +--- a/drivers/jtag/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/jtag/Makefile 2026-09-09 15:51:40.136307308 +0000 +@@ -0,0 +1,2 @@ ++obj-$(CONFIG_JTAG) += jtag.o ++obj-$(CONFIG_JTAG_ASPEED) += jtag-aspeed.o +diff --git a/drivers/jtag/jtag.c b/drivers/jtag/jtag.c +--- a/drivers/jtag/jtag.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/jtag/jtag.c 2026-09-09 15:51:40.137307293 +0000 +@@ -0,0 +1,387 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// Copyright (c) 2018 Mellanox Technologies. All rights reserved. ++// Copyright (c) 2018 Oleksandr Shamray ++// Copyright (c) 2019 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static char *end_status_str[] = { "tlr", "idle", "selDR", "capDR", "sDR", ++ "ex1DR", "pDR", "ex2DR", "updDR", "selIR", ++ "capIR", "sIR", "ex1IR", "pIR", "ex2IR", ++ "updIR", "current" }; ++ ++struct jtag { ++ struct miscdevice miscdev; ++ const struct jtag_ops *ops; ++ int id; ++ unsigned long *priv; ++}; ++ ++static DEFINE_IDA(jtag_ida); ++ ++void *jtag_priv(struct jtag *jtag) ++{ ++ return jtag->priv; ++} ++EXPORT_SYMBOL_GPL(jtag_priv); ++ ++static long jtag_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct jtag *jtag = file->private_data; ++ struct jtag_tap_state tapstate; ++ struct jtag_xfer xfer; ++ struct bitbang_packet bitbang; ++ struct tck_bitbang *bitbang_data; ++ struct jtag_mode mode; ++ u8 *xfer_data; ++ u32 data_size; ++ u32 value; ++ u32 active; ++ int err; ++ ++ if (!arg) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case JTAG_GIOCFREQ: ++ if (!jtag->ops->freq_get) ++ return -EOPNOTSUPP; ++ ++ err = jtag->ops->freq_get(jtag, &value); ++ if (err) ++ break; ++ dev_dbg(jtag->miscdev.parent, "JTAG_GIOCFREQ: freq get = %d", ++ value); ++ ++ if (put_user(value, (__u32 __user *)arg)) ++ err = -EFAULT; ++ break; ++ ++ case JTAG_SIOCFREQ: ++ if (!jtag->ops->freq_set) ++ return -EOPNOTSUPP; ++ ++ if (get_user(value, (__u32 __user *)arg)) ++ return -EFAULT; ++ if (value == 0) ++ return -EINVAL; ++ ++ err = jtag->ops->freq_set(jtag, value); ++ dev_dbg(jtag->miscdev.parent, "JTAG_SIOCFREQ: freq set = %d", ++ value); ++ break; ++ ++ case JTAG_SIOCSTATE: ++ if (copy_from_user(&tapstate, (const void __user *)arg, ++ sizeof(struct jtag_tap_state))) ++ return -EFAULT; ++ ++ if (tapstate.from > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ if (tapstate.endstate > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ if (tapstate.reset > JTAG_FORCE_RESET) ++ return -EINVAL; ++ ++ dev_dbg(jtag->miscdev.parent, ++ "JTAG_SIOCSTATE: status set from %s to %s reset %d tck %d", ++ end_status_str[tapstate.from], ++ end_status_str[tapstate.endstate], tapstate.reset, ++ tapstate.tck); ++ ++ err = jtag->ops->status_set(jtag, &tapstate); ++ break; ++ ++ case JTAG_IOCXFER: ++ { ++ u8 ubit_mask = GENMASK(7, 0); ++ u8 remaining_bits = 0x0; ++ union pad_config padding; ++ ++ if (copy_from_user(&xfer, (const void __user *)arg, ++ sizeof(struct jtag_xfer))) ++ return -EFAULT; ++ ++ if (xfer.length >= JTAG_MAX_XFER_DATA_LEN) ++ return -EINVAL; ++ ++ if (xfer.type > JTAG_SDR_XFER) ++ return -EINVAL; ++ ++ if (xfer.direction > JTAG_READ_WRITE_XFER) ++ return -EINVAL; ++ ++ if (xfer.from > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ if (xfer.endstate > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ data_size = DIV_ROUND_UP(xfer.length, BITS_PER_BYTE); ++ xfer_data = memdup_user(u64_to_user_ptr(xfer.tdio), data_size); ++ ++ /* Save unused remaining bits in this transfer */ ++ if ((xfer.length % BITS_PER_BYTE)) { ++ ubit_mask = GENMASK((xfer.length % BITS_PER_BYTE) - 1, ++ 0); ++ remaining_bits = xfer_data[data_size - 1] & ~ubit_mask; ++ } ++ ++ if (IS_ERR(xfer_data)) ++ return -EFAULT; ++ padding.int_value = xfer.padding; ++ dev_dbg(jtag->miscdev.parent, ++ "JTAG_IOCXFER: type: %s direction: %d, END : %s, padding: (value: %d) pre_pad: %d post_pad: %d, len: %d\n", ++ xfer.type ? "DR" : "IR", xfer.direction, ++ end_status_str[xfer.endstate], padding.pad_data, ++ padding.pre_pad_number, padding.post_pad_number, ++ xfer.length); ++ ++ print_hex_dump_debug("I:", DUMP_PREFIX_NONE, 16, 1, xfer_data, ++ data_size, false); ++ ++ err = jtag->ops->xfer(jtag, &xfer, xfer_data); ++ if (err) { ++ kfree(xfer_data); ++ return err; ++ } ++ ++ print_hex_dump_debug("O:", DUMP_PREFIX_NONE, 16, 1, xfer_data, ++ data_size, false); ++ ++ /* Restore unused remaining bits in this transfer */ ++ xfer_data[data_size - 1] = (xfer_data[data_size - 1] ++ & ubit_mask) | remaining_bits; ++ ++ err = copy_to_user(u64_to_user_ptr(xfer.tdio), ++ (void *)xfer_data, data_size); ++ kfree(xfer_data); ++ if (err) ++ return -EFAULT; ++ ++ if (copy_to_user((void __user *)arg, (void *)&xfer, ++ sizeof(struct jtag_xfer))) ++ return -EFAULT; ++ break; ++ } ++ ++ case JTAG_GIOCSTATUS: ++ err = jtag->ops->status_get(jtag, &value); ++ if (err) ++ break; ++ dev_dbg(jtag->miscdev.parent, "JTAG_GIOCSTATUS: status get %s", ++ end_status_str[value]); ++ ++ err = put_user(value, (__u32 __user *)arg); ++ break; ++ case JTAG_IOCBITBANG: ++ if (copy_from_user(&bitbang, (const void __user *)arg, ++ sizeof(struct bitbang_packet))) ++ return -EFAULT; ++ ++ if (bitbang.length >= JTAG_MAX_XFER_DATA_LEN) ++ return -EINVAL; ++ ++ data_size = bitbang.length * sizeof(struct tck_bitbang); ++ bitbang_data = memdup_user((void __user *)bitbang.data, ++ data_size); ++ if (IS_ERR(bitbang_data)) ++ return -EFAULT; ++ ++ err = jtag->ops->bitbang(jtag, &bitbang, bitbang_data); ++ if (err) { ++ kfree(bitbang_data); ++ return err; ++ } ++ err = copy_to_user((void __user *)bitbang.data, ++ (void *)bitbang_data, data_size); ++ kfree(bitbang_data); ++ if (err) ++ return -EFAULT; ++ break; ++ case JTAG_SIOCMODE: ++ if (!jtag->ops->mode_set) ++ return -EOPNOTSUPP; ++ ++ if (copy_from_user(&mode, (const void __user *)arg, ++ sizeof(struct jtag_mode))) ++ return -EFAULT; ++ ++ dev_dbg(jtag->miscdev.parent, ++ "JTAG_SIOCMODE: mode set feature %d mode %d", ++ mode.feature, mode.mode); ++ err = jtag->ops->mode_set(jtag, &mode); ++ break; ++ case JTAG_SIOCTRST: ++ if (!jtag->ops->trst_set) ++ return -EOPNOTSUPP; ++ ++ if (get_user(active, (__u32 __user *)arg)) ++ return -EFAULT; ++ ++ dev_dbg(jtag->miscdev.parent, ++ "JTAG_SIOCTRST: active %d", active); ++ ++ err = jtag->ops->trst_set(jtag, active); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ return err; ++} ++ ++static int jtag_open(struct inode *inode, struct file *file) ++{ ++ struct jtag *jtag = container_of(file->private_data, ++ struct jtag, ++ miscdev); ++ ++ file->private_data = jtag; ++ if (jtag->ops->enable(jtag)) ++ return -EBUSY; ++ return nonseekable_open(inode, file); ++} ++ ++static int jtag_release(struct inode *inode, struct file *file) ++{ ++ struct jtag *jtag = file->private_data; ++ ++ if (jtag->ops->disable(jtag)) ++ return -EBUSY; ++ return 0; ++} ++ ++static const struct file_operations jtag_fops = { ++ .owner = THIS_MODULE, ++ .open = jtag_open, ++ .llseek = noop_llseek, ++ .unlocked_ioctl = jtag_ioctl, ++ .release = jtag_release, ++}; ++ ++struct jtag *jtag_alloc(struct device *host, size_t priv_size, ++ const struct jtag_ops *ops) ++{ ++ struct jtag *jtag; ++ ++ if (!host) ++ return NULL; ++ ++ if (!ops) ++ return NULL; ++ ++ if (!ops->status_set || !ops->status_get || !ops->xfer) ++ return NULL; ++ ++ jtag = kzalloc(sizeof(*jtag), GFP_KERNEL); ++ if (!jtag) ++ return NULL; ++ jtag->priv = kzalloc(priv_size, GFP_KERNEL); ++ if (!jtag->priv) ++ return NULL; ++ ++ jtag->ops = ops; ++ jtag->miscdev.parent = host; ++ ++ return jtag; ++} ++EXPORT_SYMBOL_GPL(jtag_alloc); ++ ++void jtag_free(struct jtag *jtag) ++{ ++ kfree(jtag); ++} ++EXPORT_SYMBOL_GPL(jtag_free); ++ ++static int jtag_register(struct jtag *jtag) ++{ ++ struct device *dev = jtag->miscdev.parent; ++ int err; ++ int id; ++ ++ if (!dev) ++ return -ENODEV; ++ ++ id = ida_simple_get(&jtag_ida, 0, 0, GFP_KERNEL); ++ if (id < 0) ++ return id; ++ ++ jtag->id = id; ++ ++ jtag->miscdev.fops = &jtag_fops; ++ jtag->miscdev.minor = MISC_DYNAMIC_MINOR; ++ jtag->miscdev.name = kasprintf(GFP_KERNEL, "jtag%d", id); ++ if (!jtag->miscdev.name) { ++ err = -ENOMEM; ++ goto err_jtag_alloc; ++ } ++ ++ err = misc_register(&jtag->miscdev); ++ if (err) { ++ dev_err(jtag->miscdev.parent, "Unable to register device\n"); ++ goto err_jtag_name; ++ } ++ return 0; ++ ++err_jtag_name: ++ kfree(jtag->miscdev.name); ++err_jtag_alloc: ++ ida_simple_remove(&jtag_ida, id); ++ return err; ++} ++ ++static void jtag_unregister(struct jtag *jtag) ++{ ++ misc_deregister(&jtag->miscdev); ++ kfree(jtag->miscdev.name); ++ ida_simple_remove(&jtag_ida, jtag->id); ++} ++ ++static void devm_jtag_unregister(struct device *dev, void *res) ++{ ++ jtag_unregister(*(struct jtag **)res); ++} ++ ++int devm_jtag_register(struct device *dev, struct jtag *jtag) ++{ ++ struct jtag **ptr; ++ int ret; ++ ++ ptr = devres_alloc(devm_jtag_unregister, sizeof(struct jtag *), ++ GFP_KERNEL); ++ if (!ptr) ++ return -ENOMEM; ++ ++ ret = jtag_register(jtag); ++ if (!ret) { ++ *ptr = jtag; ++ devres_add(dev, ptr); ++ } else { ++ devres_free(ptr); ++ } ++ return ret; ++} ++EXPORT_SYMBOL_GPL(devm_jtag_register); ++ ++static void __exit jtag_exit(void) ++{ ++ ida_destroy(&jtag_ida); ++} ++ ++module_exit(jtag_exit); ++ ++MODULE_AUTHOR("Oleksandr Shamray "); ++MODULE_DESCRIPTION("Generic jtag support"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/net/mctp/mctp-pcie-vdm.c b/drivers/net/mctp/mctp-pcie-vdm.c +--- a/drivers/net/mctp/mctp-pcie-vdm.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/net/mctp/mctp-pcie-vdm.c 2026-09-09 15:51:45.736222689 +0000 +@@ -0,0 +1,373 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * mctp-pcie-vdm.c - MCTP-over-PCIe-VDM (DMTF DSP0238) transport binding driver. ++ * ++ * DSP0238 is available at: ++ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0238_1.2.0.pdf ++ * ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// 64bytes mctp payload + 4bytes mctp header ++#define MCTP_PCIE_VDM_MIN_MTU (64 + 4) ++#define MCTP_PCIE_VDM_MAX_MTU 512 ++/* 16byte */ ++#define MCTP_PCIE_VDM_HDR_SIZE 16 ++#define MCTP_PAYLOAD_IC_TYPE_SIZE 1 ++#define MCTP_RECEIVE_PKT_TIMEOUT_MS 5 ++ ++#define MCTP_PCIE_VDM_NET_DEV_TX_QUEUE_LEN 1100 ++#define MCTP_PCIE_VDM_DEV_TX_QUEUE_SIZE 64 ++ ++#define MCTP_PCIE_VDM_FMT_4DW 0x3 ++#define MCTP_PCIE_VDM_TYPE_MSG 0x10 ++#define MCTP_PCIE_VDM_CODE 0x0 ++/* PCIe VDM message code */ ++#define MCTP_PCIE_VDM_MSG_CODE 0x7F ++#define MCTP_PCIE_VDM_VENDOR_ID 0x1AB4 ++/* MCTP message type */ ++#define MCTP_MSG_TYPE_MASK GENMASK(6, 0) ++#define MCTP_PCIE_VDM_MSG_TYPE 0x7E ++ ++#define MCTP_PCIE_SWAP_NET_ENDIAN(arr, len) \ ++ do { \ ++ u32 *p = (u32 *)(arr); \ ++ for (int i = 0; i < (len); i++) { \ ++ p[i] = htonl(p[i]); \ ++ } \ ++ } while (0) ++ ++#define MCTP_PCIE_SWAP_LITTLE_ENDIAN(arr, len) \ ++ do { \ ++ u32 *p = (u32 *)(arr); \ ++ for (int i = 0; i < (len); i++) { \ ++ p[i] = ntohl(p[i]); \ ++ p[i] = cpu_to_le32(p[i]); \ ++ } \ ++ } while (0) ++ ++enum mctp_pcie_vdm_route_type { ++ MCTP_PCIE_VDM_ROUTE_TO_RC = 0, ++ MCTP_PCIE_VDM_ROUTE_BY_ID = 2, ++ MCTP_PCIE_VDM_BROADCAST_FROM_RC = 3, ++}; ++ ++struct mctp_pcie_vdm_hdr { ++ u32 length : 10, rsvd0 : 2, attr : 2, ep : 1, td : 1, rsvd1 : 4, tc : 3, ++ rsvd2 : 1, route_type : 5, fmt : 2, rsvd3 : 1; ++ u8 msg_code; ++ u8 tag_vdm_code : 4, tag_pad_len : 2, tag_rsvd : 2; ++ u16 pci_req_id; ++ u16 pci_vendor_id; ++ u16 pci_target_id; ++}; ++ ++struct mctp_pcie_vdm_dev { ++ struct device *dev; ++ const struct mctp_pcie_vdm_ops *callback_ops; ++}; ++ ++static const struct mctp_pcie_vdm_hdr mctp_pcie_vdm_hdr_template = { ++ .fmt = MCTP_PCIE_VDM_FMT_4DW, ++ .route_type = MCTP_PCIE_VDM_TYPE_MSG | MCTP_PCIE_VDM_ROUTE_BY_ID, ++ .tag_vdm_code = MCTP_PCIE_VDM_CODE, ++ .msg_code = MCTP_PCIE_VDM_MSG_CODE, ++ .pci_vendor_id = MCTP_PCIE_VDM_VENDOR_ID, ++ .attr = 0, ++}; ++ ++static void mctp_pcie_vdm_display_skb_buff_data(struct sk_buff *skb) ++{ ++ int i = 0; ++ ++ while ((i + 4) < skb->len) { ++ pr_debug("%02x %02x %02x %02x\n", skb->data[i], ++ skb->data[i + 1], skb->data[i + 2], skb->data[i + 3]); ++ i += 4; ++ } ++ ++ char buf[16] = { 0 }; ++ char *p = buf; ++ ++ while (i < skb->len) { ++ p += snprintf(p, sizeof(buf) - (p - buf), "%02x ", ++ skb->data[i]); ++ i++; ++ } ++ pr_debug("%s\n", buf); ++} ++ ++static int mctp_pcie_vdm_xmit(struct net_device *ndev, struct sk_buff *skb) ++{ ++ struct net_device_stats *stats; ++ struct mctp_pcie_vdm_hdr *hdr; ++ struct mctp_pcie_vdm_dev *vdm_dev; ++ u8 *hdr_byte; ++ u16 payload_len_dw; ++ u16 payload_len_byte; ++ int rc; ++ ++ stats = &ndev->stats; ++ vdm_dev = netdev_priv(ndev); ++ hdr = (struct mctp_pcie_vdm_hdr *)skb->data; ++ hdr_byte = skb->data; ++ payload_len_dw = (ALIGN(skb->len, sizeof(u32)) - MCTP_PCIE_VDM_HDR_SIZE) / sizeof(u32); ++ payload_len_byte = skb->len - MCTP_PCIE_VDM_HDR_SIZE; ++ ++ hdr->length = payload_len_dw; ++ hdr->tag_pad_len = ++ ALIGN(payload_len_byte, sizeof(u32)) - payload_len_byte; ++ pr_debug("%s: skb len %d pad len %d\n", __func__, skb->len, ++ hdr->tag_pad_len); ++ MCTP_PCIE_SWAP_NET_ENDIAN((u32 *)hdr, ++ sizeof(struct mctp_pcie_vdm_hdr) / sizeof(u32)); ++ ++ mctp_pcie_vdm_display_skb_buff_data(skb); ++ rc = vdm_dev->callback_ops->send_packet(vdm_dev->dev, skb->data, payload_len_dw * sizeof(u32)); ++ ++ if (rc) { ++ pr_err("%s: failed to send packet, rc %d\n", __func__, rc); ++ stats->tx_errors++; ++ if (rc != -ENOSPC && rc != -EBUSY) ++ stats->tx_dropped++; ++ } else { ++ stats->tx_packets++; ++ stats->tx_bytes += (skb->len - sizeof(struct mctp_pcie_vdm_hdr)); ++ } ++ return rc; ++} ++ ++static netdev_tx_t mctp_pcie_vdm_start_xmit(struct sk_buff *skb, ++ struct net_device *ndev) ++{ ++ int rc; ++ netdev_tx_t ret; ++ ++ pr_debug("%s: skb len %u\n", __func__, skb->len); ++ ++ if (skb) { ++ rc = mctp_pcie_vdm_xmit(ndev, skb); ++ if (rc) { ++ pr_err("%s: failed to send packet, rc %d\n", __func__, rc); ++ if (rc == -ENOSPC || rc == -EBUSY) { ++ ret = NETDEV_TX_BUSY; ++ return ret; ++ } ++ } ++ ret = NETDEV_TX_OK; ++ kfree_skb(skb); ++ } ++ return ret; ++} ++ ++static void mctp_pcie_vdm_uninit(struct net_device *ndev) ++{ ++ struct mctp_pcie_vdm_dev *vdm_dev; ++ ++ vdm_dev = netdev_priv(ndev); ++ pr_info("%s: uninitializing vdm_dev %s\n", __func__, ++ ndev->name); ++ vdm_dev->callback_ops->uninit(vdm_dev->dev); ++} ++ ++static int mctp_pcie_vdm_hdr_create(struct sk_buff *skb, ++ struct net_device *ndev, ++ unsigned short type, const void *daddr, ++ const void *saddr, unsigned int len) ++{ ++ u8 dest_addr[3] = {0}; ++ struct mctp_pcie_vdm_hdr *hdr = ++ (struct mctp_pcie_vdm_hdr *)skb_push(skb, sizeof(*hdr)); ++ ++ pr_debug("%s type %d len %d\n", __func__, type, len); ++ memcpy(hdr, &mctp_pcie_vdm_hdr_template, sizeof(*hdr)); ++ if (daddr) { ++ memcpy(dest_addr, (u8 *)daddr, sizeof(dest_addr)); ++ hdr->route_type |= dest_addr[0] & GENMASK(2, 0); ++ hdr->pci_target_id = dest_addr[1] << 8 | dest_addr[2]; ++ pr_debug("%s dst route %d addr %d\n", __func__, hdr->route_type, hdr->pci_target_id); ++ } ++ ++ if (saddr) { ++ pr_debug("%s src addr %d\n", __func__, *(u16 *)saddr); ++ hdr->pci_req_id = *(u16 *)saddr; ++ } ++ ++ return 0; ++} ++ ++static const struct net_device_ops mctp_pcie_vdm_net_ops = { ++ .ndo_start_xmit = mctp_pcie_vdm_start_xmit, ++ .ndo_uninit = mctp_pcie_vdm_uninit, ++}; ++ ++static const struct header_ops mctp_pcie_vdm_net_hdr_ops = { ++ .create = mctp_pcie_vdm_hdr_create, ++}; ++ ++static void mctp_pcie_vdm_net_setup(struct net_device *ndev) ++{ ++ ndev->type = ARPHRD_MCTP; ++ ++ ndev->mtu = MCTP_PCIE_VDM_MIN_MTU; ++ ndev->min_mtu = MCTP_PCIE_VDM_MIN_MTU; ++ ndev->max_mtu = MCTP_PCIE_VDM_MAX_MTU; ++ ndev->tx_queue_len = MCTP_PCIE_VDM_NET_DEV_TX_QUEUE_LEN; ++ ndev->addr_len = 3; //PCIe bdf is 2bytes + 1byte route type ++ ndev->hard_header_len = sizeof(struct mctp_pcie_vdm_hdr); ++ ++ ndev->netdev_ops = &mctp_pcie_vdm_net_ops; ++ ndev->header_ops = &mctp_pcie_vdm_net_hdr_ops; ++} ++ ++static int mctp_pcie_vdm_add_net_dev(struct net_device **dev, const char *ifname) ++{ ++ struct net_device *ndev = alloc_netdev(sizeof(struct mctp_pcie_vdm_dev), ++ ifname ? ifname : "mctppci%d", ++ NET_NAME_UNKNOWN, ++ mctp_pcie_vdm_net_setup); ++ ++ if (!ndev) { ++ pr_err("%s: failed to allocate net device\n", __func__); ++ return -ENOMEM; ++ } ++ dev_net_set(ndev, current->nsproxy->net_ns); ++ ++ *dev = ndev; ++ int rc; ++ ++ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_PCIE_VDM); ++ if (rc) { ++ pr_err("%s: failed to register net device\n", __func__); ++ free_netdev(ndev); ++ return rc; ++ } ++ return rc; ++} ++ ++void mctp_pcie_vdm_receive_packet(struct net_device *ndev) ++{ ++ struct mctp_pcie_vdm_dev *vdm_dev; ++ u8 *packet; ++ ++ vdm_dev = netdev_priv(ndev); ++ packet = vdm_dev->callback_ops->recv_packet(vdm_dev->dev); ++ ++ while (!IS_ERR(packet)) { ++ MCTP_PCIE_SWAP_LITTLE_ENDIAN((u32 *)packet, ++ sizeof(struct mctp_pcie_vdm_hdr) / sizeof(u32)); ++ struct mctp_pcie_vdm_hdr *vdm_hdr = (struct mctp_pcie_vdm_hdr *)packet; ++ struct mctp_skb_cb *cb; ++ struct net_device_stats *stats; ++ struct sk_buff *skb; ++ u16 len; ++ int net_status; ++ ++ stats = &ndev->stats; ++ len = vdm_hdr->length * sizeof(u32) - ++ vdm_hdr->tag_pad_len; ++ len += (MCTP_PCIE_VDM_HDR_SIZE - sizeof(struct mctp_pcie_vdm_hdr)); ++ skb = netdev_alloc_skb(ndev, len); ++ pr_debug("%s: received packet size: %d\n", __func__, ++ len); ++ ++ if (!skb) { ++ stats->rx_errors++; ++ pr_err("%s: failed to alloc skb\n", __func__); ++ continue; ++ } ++ ++ skb->protocol = htons(ETH_P_MCTP); ++ /* put data into tail sk buff */ ++ skb_put_data(skb, &packet[sizeof(struct mctp_pcie_vdm_hdr)], len); ++ mctp_pcie_vdm_display_skb_buff_data(skb); ++ ++ cb = __mctp_cb(skb); ++ cb->halen = 3; // route type | bdf address ++ cb->haddr[0] = vdm_hdr->route_type & GENMASK(2, 0); ++ cb->haddr[1] = vdm_hdr->pci_req_id >> 8; ++ cb->haddr[2] = vdm_hdr->pci_req_id & 0xFF; ++ net_status = netif_rx(skb); ++ if (net_status == NET_RX_SUCCESS) { ++ stats->rx_packets++; ++ stats->rx_bytes += len; ++ } else { ++ stats->rx_dropped++; ++ } ++ ++ vdm_dev->callback_ops->free_packet(packet); ++ packet = vdm_dev->callback_ops->recv_packet(vdm_dev->dev); ++ } ++} ++ ++struct net_device *mctp_pcie_vdm_add_dev(struct device *dev, ++ const struct mctp_pcie_vdm_ops *ops, ++ const char *ifname) ++{ ++ struct net_device *ndev; ++ struct mctp_pcie_vdm_dev *vdm_dev; ++ int rc; ++ ++ rc = mctp_pcie_vdm_add_net_dev(&ndev, ifname); ++ if (rc) { ++ pr_err("%s: failed to add net device\n", __func__); ++ return ERR_PTR(rc); ++ } ++ ++ vdm_dev = netdev_priv(ndev); ++ vdm_dev->dev = dev; ++ vdm_dev->callback_ops = ops; ++ ++ return ndev; ++} ++EXPORT_SYMBOL_GPL(mctp_pcie_vdm_add_dev); ++ ++void mctp_pcie_vdm_remove_dev(struct net_device *vdm_dev) ++{ ++ pr_debug("%s: removing vdm_dev %s\n", __func__, vdm_dev->name); ++ ++ if (vdm_dev) { ++ mctp_unregister_netdev(vdm_dev); ++ free_netdev(vdm_dev); ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_pcie_vdm_remove_dev); ++ ++void mctp_pcie_vdm_set_carrier(struct net_device *ndev, bool up) ++{ ++ if (!ndev) { ++ pr_err("%s: ndev is NULL\n", __func__); ++ return; ++ } ++ ++ if (up) ++ netif_carrier_on(ndev); ++ else ++ netif_carrier_off(ndev); ++} ++EXPORT_SYMBOL_GPL(mctp_pcie_vdm_set_carrier); +diff --git a/include/linux/mctp-pcie-vdm.h b/include/linux/mctp-pcie-vdm.h +--- a/include/linux/mctp-pcie-vdm.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/linux/mctp-pcie-vdm.h 2026-09-09 15:51:45.727222825 +0000 +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * mctp-pcie-vdm.h - MCTP-over-PCIe-VDM (DMTF DSP0238) transport binding Interface ++ * for PCIe VDM devices to register and implement. ++ * ++ */ ++ ++#ifndef __LINUX_MCTP_PCIE_VDM_H ++#define __LINUX_MCTP_PCIE_VDM_H ++ ++#include ++#include ++#include ++ ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) ++ ++/** ++ * @send_packet: referenced to send packets with PCIe VDM header packed. ++ * @recv_packet: referenced multiple times until no RX packet to be handled. ++ * received pointer shall start from the PCIe VDM header. ++ * @free_packet: referenced when the packet is processed and okay to be freed. ++ * @uninit: uninitialize the device. ++ */ ++struct mctp_pcie_vdm_ops { ++ int (*send_packet)(struct device *dev, u8 *data, size_t len); ++ u8 *(*recv_packet)(struct device *dev); ++ void (*free_packet)(void *packet); ++ void (*uninit)(struct device *dev); ++}; ++ ++struct net_device *mctp_pcie_vdm_add_dev(struct device *dev, ++ const struct mctp_pcie_vdm_ops *ops, ++ const char *ifname); ++void mctp_pcie_vdm_receive_packet(struct net_device *ndev); ++void mctp_pcie_vdm_remove_dev(struct net_device *ndev); ++void mctp_pcie_vdm_set_carrier(struct net_device *ndev, bool up); ++ ++#endif /* CONFIG_MCTP_TRANSPORT_PCIE_VDM */ ++#endif /* __LINUX_MCTP_PCIE_VDM_H */ +diff --git a/include/trace/events/xdma.h b/include/trace/events/xdma.h +--- a/include/trace/events/xdma.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/trace/events/xdma.h 2026-09-09 15:51:45.744222568 +0000 +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++ ++#undef TRACE_SYSTEM ++#define TRACE_SYSTEM xdma ++ ++#if !defined(_TRACE_XDMA_H) || defined(TRACE_HEADER_MULTI_READ) ++#define _TRACE_XDMA_H ++ ++#include ++ ++TRACE_EVENT(xdma_start, ++ TP_PROTO(const struct aspeed_xdma *ctx, const struct aspeed_xdma_cmd *cmd), ++ TP_ARGS(ctx, cmd), ++ TP_STRUCT__entry(__field(bool, dir_upstream) ++ __field(unsigned int, index) ++ __field(__u64, host) ++ __field(__u64, pitch) ++ __field(__u64, cmd) ++ ), ++ TP_fast_assign(__entry->dir_upstream = ctx->upstream; ++ __entry->index = ctx->cmd_idx; ++ __entry->host = cmd->host_addr; ++ __entry->pitch = cmd->pitch; ++ __entry->cmd = cmd->cmd; ++ ), ++ TP_printk("%s cmd:%u [%08llx %016llx %016llx]", ++ __entry->dir_upstream ? "upstream" : "downstream", ++ __entry->index, ++ __entry->host, ++ __entry->pitch, ++ __entry->cmd ++ ) ++); ++ ++TRACE_EVENT(xdma_irq, ++ TP_PROTO(u32 sts), ++ TP_ARGS(sts), ++ TP_STRUCT__entry(__field(__u32, status) ++ ), ++ TP_fast_assign(__entry->status = sts; ++ ), ++ TP_printk("sts:%08x", ++ __entry->status ++ ) ++); ++ ++TRACE_EVENT(xdma_reset, ++ TP_PROTO(const struct aspeed_xdma *ctx), ++ TP_ARGS(ctx), ++ TP_STRUCT__entry(__field(bool, dir_upstream) ++ __field(bool, in_progress) ++ ), ++ TP_fast_assign(__entry->dir_upstream = ctx->upstream; ++ __entry->in_progress = ++ ctx->current_client ? ctx->current_client->in_progress : false; ++ ), ++ TP_printk("%sin progress%s", ++ __entry->in_progress ? "" : "not ", ++ __entry->in_progress ? (__entry->dir_upstream ? ++ " upstream" : " downstream") : "" ++ ) ++); ++ ++TRACE_EVENT(xdma_perst, ++ TP_PROTO(const struct aspeed_xdma *ctx), ++ TP_ARGS(ctx), ++ TP_STRUCT__entry(__field(bool, in_reset) ++ ), ++ TP_fast_assign(__entry->in_reset = ctx->in_reset; ++ ), ++ TP_printk("%s", ++ __entry->in_reset ? "in reset" : "" ++ ) ++); ++ ++TRACE_EVENT(xdma_unmap, ++ TP_PROTO(const struct aspeed_xdma_client *client), ++ TP_ARGS(client), ++ TP_STRUCT__entry(__field(__u32, phys) ++ __field(__u32, size) ++ ), ++ TP_fast_assign(__entry->phys = client->phys; ++ __entry->size = client->size; ++ ), ++ TP_printk("p:%08x s:%08x", ++ __entry->phys, ++ __entry->size ++ ) ++); ++ ++TRACE_EVENT(xdma_mmap_error, ++ TP_PROTO(const struct aspeed_xdma_client *client, unsigned long vm_start), ++ TP_ARGS(client, vm_start), ++ TP_STRUCT__entry(__field(__u32, phys) ++ __field(__u32, size) ++ __field(unsigned long, vm_start) ++ ), ++ TP_fast_assign(__entry->phys = client->phys; ++ __entry->size = client->size; ++ __entry->vm_start = vm_start; ++ ), ++ TP_printk("p:%08x s:%08x v:%08lx", ++ __entry->phys, ++ __entry->size, ++ __entry->vm_start ++ ) ++); ++ ++TRACE_EVENT(xdma_mmap, ++ TP_PROTO(const struct aspeed_xdma_client *client), ++ TP_ARGS(client), ++ TP_STRUCT__entry(__field(__u32, phys) ++ __field(__u32, size) ++ ), ++ TP_fast_assign(__entry->phys = client->phys; ++ __entry->size = client->size; ++ ), ++ TP_printk("p:%08x s:%08x", ++ __entry->phys, ++ __entry->size ++ ) ++); ++ ++#endif /* _TRACE_XDMA_H */ ++ ++#include diff --git a/patches-sonic/0002-drop_monitor-Extend-WJH-buffer-linux-channel.patch b/patches-sonic/0002-drop_monitor-Extend-WJH-buffer-linux-channel.patch new file mode 100644 index 000000000..81d3c85f3 --- /dev/null +++ b/patches-sonic/0002-drop_monitor-Extend-WJH-buffer-linux-channel.patch @@ -0,0 +1,238 @@ +From 1eee33e3c2c47fe2ca9dbad0712c0686ecdbf60a Mon Sep 17 00:00:00 2001 +From: Zhengfeng Dou +Date: Thu, 29 Jun 2023 12:15:19 +0800 +Subject: [PATCH] drop_monitor: Extend WJH buffer linux channel + +SDK WJH-Libs is using following information for shared buffer drops: +* egress_port +* tc +* egress_port_valid +* original_occupancy +* original_latency +* drop_reason.id + +The above metadata is missing when using linux channel (drop monitor). +This patch is to add the extended information also for linux channel. +--- + include/net/devlink.h | 18 ++++++++++++++++++ + include/uapi/linux/net_dropmon.h | 5 +++++ + net/core/drop_monitor.c | 112 +++++++++++++++++++++++++++ + 3 files changed, 135 insertions(+) + +diff --git a/include/net/devlink.h b/include/net/devlink.h +index db5eff6..6024ce6 100644 +--- a/include/net/devlink.h ++++ b/include/net/devlink.h +@@ -21,6 +21,10 @@ + #include + #include + ++#ifndef SX_EXTEND_WJH_BUFFER_LINUX_CHANNEL ++#define SX_EXTEND_WJH_BUFFER_LINUX_CHANNEL ++#endif ++ + struct devlink; + struct devlink_linecard; + +@@ -753,6 +757,11 @@ struct devlink_health_reporter_ops { + * @dev_tracker: refcount tracker for @input_dev. + * @fa_cookie: Flow action user cookie. + * @trap_type: Trap type. ++ * @output_port_dev: Output port netdevice. ++ * @output_lag_dev: Output lag netdevice. ++ * @out_tc: Output port tclass. ++ * @out_tc_occ: Output port tclass buffer occupancy. ++ * @latency: End-to-end latency. + */ + struct devlink_trap_metadata { + const char *trap_name; +@@ -763,6 +772,15 @@ struct devlink_trap_metadata { + + const struct flow_action_cookie *fa_cookie; + enum devlink_trap_type trap_type; ++ struct net_device *output_port_dev; ++ struct net_device *output_lag_dev; ++ u16 out_tc; ++ u64 out_tc_occ; ++ u64 latency; ++ u8 out_tc_valid:1, ++ out_tc_occ_valid:1, ++ latency_valid:1, ++ unused:5; + }; + + /** +diff --git a/include/uapi/linux/net_dropmon.h b/include/uapi/linux/net_dropmon.h +index 84f622a..d6f6399 100644 +--- a/include/uapi/linux/net_dropmon.h ++++ b/include/uapi/linux/net_dropmon.h +@@ -94,6 +94,11 @@ enum net_dm_attr { + NET_DM_ATTR_HW_DROPS, /* flag */ + NET_DM_ATTR_FLOW_ACTION_COOKIE, /* binary */ + NET_DM_ATTR_REASON, /* string */ ++ NET_DM_ATTR_OUT_PORT, /* nested */ ++ NET_DM_ATTR_OUT_LAG, /* nested */ ++ NET_DM_ATTR_OUT_TC, /* u16 */ ++ NET_DM_ATTR_OUT_TC_OCC, /* u64 */ ++ NET_DM_ATTR_LATENCY, /* u64 */ + + __NET_DM_ATTR_MAX, + NET_DM_ATTR_MAX = __NET_DM_ATTR_MAX - 1 +diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c +index 2e0ae33..b5d8ea3 100644 +--- a/net/core/drop_monitor.c ++++ b/net/core/drop_monitor.c +@@ -609,6 +609,36 @@ nla_put_failure: + return -EMSGSIZE; + } + ++static int net_dm_packet_report_out_port_put(struct sk_buff *msg, int ifindex, ++ const char *name, bool is_lag) ++{ ++ struct nlattr *attr; ++ int attrtype = NET_DM_ATTR_OUT_PORT; ++ ++ if (is_lag) { ++ attrtype = NET_DM_ATTR_OUT_LAG; ++ } ++ ++ attr = nla_nest_start(msg, attrtype); ++ if (!attr) ++ return -EMSGSIZE; ++ ++ if (ifindex && ++ nla_put_u32(msg, NET_DM_ATTR_PORT_NETDEV_IFINDEX, ifindex)) ++ goto nla_put_failure; ++ ++ if (name && nla_put_string(msg, NET_DM_ATTR_PORT_NETDEV_NAME, name)) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, attr); ++ ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, attr); ++ return -EMSGSIZE; ++} ++ + static int net_dm_packet_report_fill(struct sk_buff *msg, struct sk_buff *skb, + size_t payload_len) + { +@@ -749,6 +779,16 @@ net_dm_flow_action_cookie_size(const struct devlink_trap_metadata *hw_metadata) + nla_total_size(hw_metadata->fa_cookie->cookie_len) : 0; + } + ++static size_t net_dm_out_port_size(void) ++{ ++ /* NET_DM_ATTR_OUT_PORT nest */ ++ return nla_total_size(0) + ++ /* NET_DM_ATTR_PORT_NETDEV_IFINDEX */ ++ nla_total_size(sizeof(u32)) + ++ /* NET_DM_ATTR_PORT_NETDEV_NAME */ ++ nla_total_size(IFNAMSIZ + 1); ++} ++ + static size_t + net_dm_hw_packet_report_size(size_t payload_len, + const struct devlink_trap_metadata *hw_metadata) +@@ -774,6 +814,16 @@ net_dm_hw_packet_report_size(size_t payload_len, + nla_total_size(sizeof(u32)) + + /* NET_DM_ATTR_PROTO */ + nla_total_size(sizeof(u16)) + ++ /* NET_DM_ATTR_OUT_PORT */ ++ net_dm_out_port_size() + ++ /* NET_DM_ATTR_OUT_LAG */ ++ net_dm_out_port_size() + ++ /* NET_DM_ATTR_OUT_TC */ ++ nla_total_size(sizeof(u16)) + ++ /* NET_DM_ATTR_OUT_TC_OCC */ ++ nla_total_size(sizeof(u64)) + ++ /* NET_DM_ATTR_LATENCY */ ++ nla_total_size(sizeof(u64)) + + /* NET_DM_ATTR_PAYLOAD */ + nla_total_size(payload_len); + } +@@ -819,6 +869,43 @@ static int net_dm_hw_packet_report_fill(struct sk_buff *msg, + hw_metadata->fa_cookie->cookie)) + goto nla_put_failure; + ++ if (hw_metadata->output_port_dev) { ++ struct net_device *dev = hw_metadata->output_port_dev; ++ int rc; ++ ++ rc = net_dm_packet_report_out_port_put(msg, dev->ifindex, ++ dev->name, false); ++ if (rc) ++ goto nla_put_failure; ++ } ++ ++ if (hw_metadata->output_lag_dev) { ++ struct net_device *dev = hw_metadata->output_lag_dev; ++ int rc; ++ ++ rc = net_dm_packet_report_out_port_put(msg, dev->ifindex, ++ dev->name, true); ++ if (rc) ++ goto nla_put_failure; ++ } ++ ++ if (hw_metadata->out_tc_valid) { ++ if (nla_put_u16(msg, NET_DM_ATTR_OUT_TC, hw_metadata->out_tc)) ++ goto nla_put_failure; ++ } ++ ++ if (hw_metadata->out_tc_occ_valid) { ++ if (nla_put_u64_64bit(msg, NET_DM_ATTR_OUT_TC_OCC, ++ hw_metadata->out_tc_occ, NET_DM_ATTR_PAD)) ++ goto nla_put_failure; ++ } ++ ++ if (hw_metadata->latency_valid) { ++ if (nla_put_u64_64bit(msg, NET_DM_ATTR_LATENCY, ++ hw_metadata->latency, NET_DM_ATTR_PAD)) ++ goto nla_put_failure; ++ } ++ + if (nla_put_u64_64bit(msg, NET_DM_ATTR_TIMESTAMP, + ktime_to_ns(skb->tstamp), NET_DM_ATTR_PAD)) + goto nla_put_failure; +@@ -885,6 +972,27 @@ net_dm_hw_metadata_copy(const struct devlink_trap_metadata *metadata) + netdev_hold(hw_metadata->input_dev, &hw_metadata->dev_tracker, + GFP_ATOMIC); + ++ hw_metadata->output_port_dev = metadata->output_port_dev; ++ if (hw_metadata->output_port_dev) ++ netdev_hold(hw_metadata->output_port_dev, &hw_metadata->dev_tracker, GFP_ATOMIC); ++ ++ hw_metadata->output_lag_dev = metadata->output_lag_dev; ++ if (hw_metadata->output_lag_dev) ++ netdev_hold(hw_metadata->output_lag_dev, &hw_metadata->dev_tracker, GFP_ATOMIC); ++ ++ hw_metadata->out_tc_valid = metadata->out_tc_valid; ++ if (hw_metadata->out_tc_valid) ++ hw_metadata->out_tc = metadata->out_tc; ++ ++ hw_metadata->out_tc_occ_valid = metadata->out_tc_occ_valid; ++ ++ if (hw_metadata->out_tc_occ_valid) ++ hw_metadata->out_tc_occ = metadata->out_tc_occ; ++ ++ hw_metadata->latency_valid = metadata->latency_valid; ++ if (hw_metadata->latency_valid) ++ hw_metadata->latency = metadata->latency; ++ + return hw_metadata; + + free_trap_name: +@@ -900,6 +1008,10 @@ static void + net_dm_hw_metadata_free(struct devlink_trap_metadata *hw_metadata) + { + netdev_put(hw_metadata->input_dev, &hw_metadata->dev_tracker); ++ if (hw_metadata->output_port_dev) ++ netdev_put(hw_metadata->output_port_dev, &hw_metadata->dev_tracker); ++ if (hw_metadata->output_lag_dev) ++ netdev_put(hw_metadata->output_lag_dev, &hw_metadata->dev_tracker); + kfree(hw_metadata->fa_cookie); + kfree(hw_metadata->trap_name); + kfree(hw_metadata->trap_group_name); diff --git a/patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch b/patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch new file mode 100644 index 000000000..59c5c4b7d --- /dev/null +++ b/patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch @@ -0,0 +1,1183 @@ +From ecaf5d322f21f2070a94310b54bc1069bde0c471 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 11:31:35 +0300 +Subject: [PATCH 6.12 2/5] mctp: aspeed: IRoT: Add initial support + +Add initial support for NVIDIA's Internal Root of Trust (IRoT) on the +Aspeed AST27xx BMC, exposed as an MCTP transport over the hardware +mailbox. + +A new CONFIG_NVIDIA_AST27XX_IROT option (depends on ARCH_ASPEED) builds +the nvidia-ast27xx-irot driver, which creates an MCTP network device for +a BMC device-tree node compatible with "nvidia,ast27xx,irot" (for +example mctpirot0). + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/Kconfig | 8 + + drivers/net/mctp/Makefile | 1 + + drivers/net/mctp/nvidia-ast27xx-irot.c | 1121 ++++++++++++++++++++++++ + 3 files changed, 1130 insertions(+) + create mode 100644 drivers/net/mctp/nvidia-ast27xx-irot.c + +diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig +index fea55902e..2fc8e675b 100644 +--- a/drivers/net/mctp/Kconfig ++++ b/drivers/net/mctp/Kconfig +@@ -47,6 +47,14 @@ config MCTP_TRANSPORT_I3C + A MCTP protocol network device is created for each I3C bus + having a "mctp-controller" devicetree property. + ++config NVIDIA_AST27XX_IROT ++ tristate "NVIDIA AST27XX IRoT support" ++ depends on ARCH_ASPEED ++ help ++ Enable support for NVIDIA AST27XX IRoT over mailbox MCTP. ++ Select this if the BMC device tree provides a "nvidia,ast27xx,irot" ++ node (for example mctpirot0). ++ + config MCTP_TRANSPORT_SPI + tristate "MCTP Over SPI Transprt" + help +diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile +index cfb127d78..996808975 100644 +--- a/drivers/net/mctp/Makefile ++++ b/drivers/net/mctp/Makefile +@@ -2,6 +2,7 @@ obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o + obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o + obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o ++obj-$(CONFIG_NVIDIA_AST27XX_IROT) += nvidia-ast27xx-irot.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o +diff --git a/drivers/net/mctp/nvidia-ast27xx-irot.c b/drivers/net/mctp/nvidia-ast27xx-irot.c +new file mode 100644 +index 000000000..383ad4e82 +--- /dev/null ++++ b/drivers/net/mctp/nvidia-ast27xx-irot.c +@@ -0,0 +1,1121 @@ ++/// @file ++/// @brief Kernel module for MCTP communication with NVIDIA's IRoT for ASPEED's ast27xx series of BMC. ++/// @details ++/// Enable with the following device tree to create a mctp device called mctpirot0. ++/// @code{.dts} ++/// / { ++/// mctpirot0 { ++/// compatible = "nvidia,ast27xx,irot"; ++/// mboxes = <&mbox0 0>; ++/// mbox-names = "irot"; ++/// status = "okay"; ++/// }; ++/// }; ++/// ++/// &mbox0 { ++/// status = "okay"; ++/// }; ++/// @endcode ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nvidia_irot_ast27xx_msg_ns.h" ++ ++#define NVIDIA_IROT_QUEUE_SIZE 20 ++ ++#define NVIDIA_IROT_MCTP_LOG_ENABLED 0 ++ ++#if NVIDIA_IROT_MCTP_LOG_ENABLED ++/// @brief The max number of bytes to log per packet. ++/// @note 10 is a decent value as gets most header and protocol specific status codes. ++#define NVIDIA_IROT_MCTP_LOG_BYTES 10 ++#endif ++ ++#define LOG_IMPL(lvl, fmt, ...) \ ++ printk(lvl "NVIDIA-AST27XX-IROT: " fmt "\n", ##__VA_ARGS__) ++ ++/// @brief Helper logging macros. ++/// @details Has the same API as printf() and a newline is automatically appended. ++/// @{ ++#define LOG_INF(...) LOG_IMPL(KERN_INFO, __VA_ARGS__) ++#define LOG_WRN(...) LOG_IMPL(KERN_WARNING, __VA_ARGS__) ++#define LOG_ERR(...) LOG_IMPL(KERN_ERR, __VA_ARGS__) ++/// @} ++ ++/// @brief State shared between the mailbox callback and worker threads. ++struct nvidia_irot_driver_shared_state { ++ /// @brief Shared memory. ++ /// @details Its sizes will be zero if not initialized. ++ /// @note Is written to only once. ++ struct nvidia_irot_message_data_buffers shmem; ++ ++ /// @brief The pending MCTP read, if one exists. ++ /// @details pending_read.header.command will be zero if no read is pending. ++ struct nvidia_irot_message pending_read; ++ ++ /// @brief The pending MCTP write, if one exists. ++ /// @details pending_write.header.command will be zero if no write is pending. ++ struct nvidia_irot_message pending_write; ++ ++ /// @brief A ping that we've recieved that needs responding to. ++ /// @details rx_ping.command will be zero if no ping needs responding to. ++ struct nvidia_irot_message rx_ping; ++ ++ /// @brief If one of our responses gets dropped we may get a duplicate command. ++ /// The mailbox callback deduplicates the command handling but we need to send back a response. ++ /// This variable holds the response. ++ /// @details duplicate_response.command will be zero if no response needs to be sent. ++ struct nvidia_irot_message duplicate_response; ++}; ++ ++/// @brief State that is private for worker threads. ++struct nvidia_irot_driver_worker_state { ++ /// @brief Uncacheable RAM address from memremap for reading MCTP packets. ++ /// @note Is written to only once. ++ const void *rx_addr; ++ ++ /// @brief Uncacheable RAM address from memremap for writing MCTP packets. ++ /// @note Is written to only once. ++ void *tx_addr; ++ ++ /// @brief The next counter to use for mailbox command output. ++ u32 next_tx_counter; ++}; ++ ++/// @brief State that is private to the mailbox callback. ++struct nvidia_irot_driver_mailbox_state { ++ /// @brief The counter of the most recent rx message. ++ u32 prev_rx_counter; ++ ++ /// @brief Set to false if there is no prev_rx_counter. ++ bool has_prev_rx_counter; ++}; ++ ++/// @brief Driver type. ++struct nvidia_irot_driver { ++ /// @brief Spin lock that protects shared_state. ++ /// @warning Must not suspend when holding this lock. ++ struct spinlock shared_spin; ++ ++ /// @brief Shared state. ++ /// @details shared_spin must be held when interacting with this state. ++ struct nvidia_irot_driver_shared_state shared_state; ++ ++ /// @brief Mutex that protects worker_state. ++ struct mutex worker_mutex; ++ ++ /// @brief State used only by worker functions. ++ /// @details worker_mutex must be held when interacting with this state. ++ struct nvidia_irot_driver_worker_state worker_state; ++ ++ /// @brief State that is private to the mailbox rx callback. ++ /// @details As it is private requires no locking. ++ struct nvidia_irot_driver_mailbox_state mailbox_state; ++ ++ /// @brief Signals to the driver probe function that shared memory is ++ /// ready to be set up. ++ struct semaphore probe_sem; ++ ++ /// @brief RX worker wait queue. ++ wait_queue_head_t rx_worker_wq; ++ ++ /// @brief Set to 1 if the rx_worker has work to do. ++ atomic_t rx_work_ready; ++ ++ /// @brief wait queue that is woken when tx_queue is pushed to. ++ wait_queue_head_t tx_queue_ready_wq; ++ ++ /// @brief wait queue that is woken when shared memory becomes available. ++ wait_queue_head_t tx_shmem_ready_wq; ++ ++ /// @brief Set to 1 when shared memory becomes ready. ++ atomic_t tx_shmem_ready; ++ ++ /// @brief Mailbox objects. ++ /// @{ ++ struct mbox_client mbox_client; ++ struct mbox_chan *mbox_chan; ++ /// @} ++ ++ /// @brief Worker threads. ++ /// @{ ++ struct task_struct *rx_worker; ++ struct task_struct *tx_worker; ++ /// @} ++ ++ /// @brief Network device used by the linux kernel for this driver. ++ struct net_device *netdev; ++ ++ /// @brief Packet queue from the network device to the tx worker thread. ++ struct sk_buff_head tx_queue; ++}; ++ ++static int copy_to_shmem(struct nvidia_irot_message_span dst_phy, ++ void *dst_virt, const struct sk_buff *src) ++{ ++ if (src == NULL) { ++ return -EINVAL; ++ } ++ if (dst_phy.size < src->len) { ++ return -EINVAL; ++ } ++ if (dst_virt == NULL) { ++ return -EFAULT; ++ } ++ const int ret = skb_copy_bits(src, 0, dst_virt, src->len); ++ dma_wmb(); ++ return ret; ++} ++ ++static int copy_from_shmem(struct sk_buff *dst, ++ struct nvidia_irot_message_span src_phy, ++ const void *src_virt) ++{ ++ if (src_virt == NULL) { ++ return -EFAULT; ++ } ++ dma_rmb(); ++ (void)skb_put_data(dst, src_virt, src_phy.size); ++ return 0; ++} ++ ++/// @brief Sends a message over mailbox. ++/// @param driver The driver managing the mailbox. ++/// @param msg The message to send. ++/// @retval 0 On success. ++/// @returns -errno on error. ++static int send_message(struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_message *msg) ++{ ++ if (driver == NULL || driver->mbox_chan == NULL || msg == NULL) { ++ return -EINVAL; ++ } ++ const int ret = mbox_send_message(driver->mbox_chan, (void *)msg); ++ if (ret < 0) { ++ LOG_ERR("mbox_send_message failed, ret: %d", ret); ++ return ret; ++ } ++ return 0; ++} ++ ++/// @brief Extracts a physical address from a nvidia_irot_message_span. ++static resource_size_t addr_from_span(struct nvidia_irot_message_span shmem) ++{ ++ return (((resource_size_t)shmem.address.high) << 32U) | ++ shmem.address.low; ++} ++ ++/// @brief Gets a copy of a driver's shared_state and handles all generic data processing. ++/// @pre driver->worker_mutex is held, driver->shared_spin is not held. ++/// @post The lock state is the same as on entry. driver->worker_mutex is never dropped by this function. ++/// @param driver The driver managing the IPC connection. ++/// @param [out] shared_state_copy A copy of the shared state extracted by this function. ++static void worker_locked_handle_shared_state( ++ struct nvidia_irot_driver *driver, ++ struct nvidia_irot_driver_shared_state *shared_state_copy) ++{ ++ // copy out shared_state with the lock held ++ spin_lock_irq(&driver->shared_spin); ++ memcpy(shared_state_copy, &driver->shared_state, ++ sizeof(*shared_state_copy)); ++ spin_unlock_irq(&driver->shared_spin); ++ ++ // process the shared state ++ ++ // init shared memory regions if needed ++ // NOTE: shmem is write once so can use our local copy. ++ // TODO: should we bounds check the addresses we get from IRoT? ++ if (driver->worker_state.rx_addr == NULL && ++ shared_state_copy->shmem.a35_read.size != 0) { ++ driver->worker_state.rx_addr = memremap( ++ addr_from_span(shared_state_copy->shmem.a35_read), ++ shared_state_copy->shmem.a35_read.size, MEMREMAP_WC); ++ } ++ if (driver->worker_state.tx_addr == NULL && ++ shared_state_copy->shmem.a35_write.size != 0) { ++ driver->worker_state.tx_addr = memremap( ++ addr_from_span(shared_state_copy->shmem.a35_write), ++ shared_state_copy->shmem.a35_write.size, MEMREMAP_WC); ++ } ++ ++ // handle pings if needed ++ if (shared_state_copy->rx_ping.command == nvidia_irot_cc_ping) { ++ struct nvidia_irot_message response = NVIDIA_IROT_MESSAGE_INIT; ++ response.command = nvidia_irot_cc_ping_rsp; ++ response.data.value = shared_state_copy->rx_ping.data.value; ++ if (0 == send_message(driver, &response)) { ++ // Sending the ping response succeeded. ++ // Clear the in flight ping only if the counter still matches. ++ // If the counters do not match a new ping request came in ++ // which can be handled by a future caller of this function. ++ shared_state_copy->rx_ping.command = 0; ++ spin_lock_irq(&driver->shared_spin); ++ if (driver->shared_state.rx_ping.data.value == ++ shared_state_copy->rx_ping.data.value) { ++ driver->shared_state.rx_ping.command = 0; ++ } ++ spin_unlock_irq(&driver->shared_spin); ++ } ++ } ++ ++ // handle duplicate command responses if needed ++ // NOTE: duplicate_response is only written to by the mailbox callback ++ // if it is cleared and is only cleared by worker_mutex owner. ++ // So we have exclusive write access so can use our local copy. ++ if (shared_state_copy->duplicate_response.command != 0) { ++ if (0 == send_message(driver, ++ &shared_state_copy->duplicate_response)) { ++ // success, clear the scheduled response. ++ shared_state_copy->duplicate_response.command = 0; ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.duplicate_response.command = 0; ++ spin_unlock_irq(&driver->shared_spin); ++ } ++ } ++} ++ ++/// @brief Gets the start offset of a allocation from within a region. ++/// @param region The region containing the allocation. ++/// @param allocation The allocation that must be fully contained within the region. ++/// @returns The offset on success. ++/// @returns -errno value on failure. ++static ssize_t get_start_offset(struct nvidia_irot_message_span region, ++ struct nvidia_irot_message_span allocation) ++{ ++ const resource_size_t region_start = addr_from_span(region); ++ const resource_size_t region_end = region_start + region.size; ++ if (region_end < region_start) { ++ // region wrap around ++ return -EFAULT; ++ } ++ const resource_size_t allocation_start = addr_from_span(allocation); ++ const resource_size_t allocation_end = ++ allocation_start + allocation.size; ++ if (allocation_end < allocation_start) { ++ // allocation wrap around ++ return -EFAULT; ++ } ++ if (allocation_start < region_start) { ++ // allocation starts too soon ++ return -EFAULT; ++ } ++ if (allocation_end > region_end) { ++ // allocation goes for too long ++ return -EFAULT; ++ } ++ return allocation_start - region_start; ++} ++ ++/// @brief Call at the end of device_read() to finialize global state and buffer management. ++/// @details If the read succeeded the pending read the IRoT is informed and the pending read is cleared. ++/// If the read failed another reader will be awoken. ++/// @pre driver->worker_mutex is held. ++/// @post No locks are held. ++/// @param driver The driver managing the read. ++/// @param result The result of the read. ++/// @param message The message that was read. ++/// @returns result unless an error was detected. ++static ssize_t finish_read_and_unlock(struct nvidia_irot_driver *driver, ++ ssize_t result, ++ const struct nvidia_irot_message *message) ++{ ++ if (result >= 0 && result != message->data.mctp.packet.size) { ++ // success return code that does not match message size, convert to fault. ++ result = -EFAULT; ++ } ++ ++ if (result >= 0) { ++ // success ++ ++ // clear the pending read ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.pending_read.command = 0; ++ spin_unlock_irq(&driver->shared_spin); ++ ++ // free the shared memory via mailbox command ++ struct nvidia_irot_message response = NVIDIA_IROT_MESSAGE_INIT; ++ response.command = nvidia_irot_cc_mctp_done; ++ response.data.value = message->data.mctp.counter; ++ // dropped ACKs are handled by IRoT retransmission and command deduplication ++ (void)send_message(driver, &response); ++ ++ mutex_unlock(&driver->worker_mutex); ++ return result; ++ } ++ ++ // failure, a future reader will handle this ++ mutex_unlock(&driver->worker_mutex); ++ return result; ++} ++ ++struct nvidia_irot_netdev_priv { ++ struct nvidia_irot_driver *driver; ++}; ++ ++netdev_tx_t nvidia_irot_start_xmit(struct sk_buff *skb, struct net_device *dev) ++{ ++ netdev_tx_t status = NETDEV_TX_BUSY; ++ ++ struct nvidia_irot_netdev_priv *const private = netdev_priv(dev); ++ struct nvidia_irot_driver *const driver = private->driver; ++ ++ unsigned long flags; ++ ++#if NVIDIA_IROT_MCTP_LOG_ENABLED ++ // Log the first up to NVIDIA_IROT_MCTP_LOG_BYTES bytes of skb as hex bytes separated by spaces ++ { ++ int n = skb->len < NVIDIA_IROT_MCTP_LOG_BYTES ? ++ skb->len : ++ NVIDIA_IROT_MCTP_LOG_BYTES; ++ u8 data[NVIDIA_IROT_MCTP_LOG_BYTES] = { 0 }; ++ skb_copy_bits(skb, 0, data, n); ++ char hexbuf[3 * NVIDIA_IROT_MCTP_LOG_BYTES + 1] = { 0 }; ++ int i; ++ for (i = 0; i < n; ++i) { ++ snprintf(hexbuf + i * 3, 4, "%02x ", data[i]); ++ } ++ if (n > 0) ++ hexbuf[3 * n - 1] = '\0'; // Remove trailing space ++ LOG_INF("L->I: %s", hexbuf); ++ } ++#endif ++ ++ // locking the queue for the entire push so we can atomically inspect ++ // the length to manage the network interface queue enable state. ++ spin_lock_irqsave(&driver->tx_queue.lock, flags); ++ if (skb_queue_len(&driver->tx_queue) >= NVIDIA_IROT_QUEUE_SIZE) { ++ // error: queue already full ++ status = NETDEV_TX_BUSY; ++ LOG_WRN("mctp tx queue overflow"); ++ netif_stop_queue(dev); ++ } else { ++ // push the packet ++ status = NETDEV_TX_OK; ++ __skb_queue_tail(&driver->tx_queue, skb); ++ if (skb_queue_len(&driver->tx_queue) == ++ NVIDIA_IROT_QUEUE_SIZE) { ++ // stop the queue to prevent excessive RAM usage ++ netif_stop_queue(dev); ++ } ++ } ++ spin_unlock_irqrestore(&driver->tx_queue.lock, flags); ++ ++ if (status == NETDEV_TX_OK) { ++ wake_up(&driver->tx_queue_ready_wq); ++ } ++ return status; ++} ++ ++int nvidia_irot_open(struct net_device *dev) ++{ ++ netif_start_queue(dev); ++ return 0; ++} ++ ++int nvidia_irot_stop(struct net_device *dev) ++{ ++ netif_stop_queue(dev); ++ return 0; ++} ++ ++static struct net_device_ops nvidia_irot_nops = { ++ .ndo_start_xmit = nvidia_irot_start_xmit, ++ .ndo_open = nvidia_irot_open, ++ .ndo_stop = nvidia_irot_stop, ++}; ++ ++void nvidia_irot_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ // NOTE: will be replaced outside of setup ++ dev->min_mtu = 68; ++ dev->max_mtu = 68; ++ dev->mtu = 68; ++ ++ dev->hard_header_len = 0; ++ dev->tx_queue_len = NVIDIA_IROT_QUEUE_SIZE; ++ dev->netdev_ops = &nvidia_irot_nops; ++ ++ dev->addr_len = 0; ++} ++ ++static int nvidia_irot_create_mctp_dev(struct nvidia_irot_driver *driver, ++ const char *name) ++{ ++ driver->netdev = alloc_netdev(sizeof(struct nvidia_irot_netdev_priv), ++ name, NET_NAME_PREDICTABLE, ++ nvidia_irot_netdev_setup); ++ if (!driver->netdev) { ++ return -ENOMEM; ++ } ++ ++ spin_lock_irq(&driver->shared_spin); ++ const u32 mtu = driver->shared_state.shmem.mtu_limit; ++ spin_unlock_irq(&driver->shared_spin); ++ if (mtu >= 68) { ++ driver->netdev->min_mtu = mtu; ++ driver->netdev->max_mtu = mtu; ++ driver->netdev->mtu = mtu; ++ } ++ ++ struct nvidia_irot_netdev_priv *priv = netdev_priv(driver->netdev); ++ priv->driver = driver; ++ ++ const int ret = mctp_register_netdev(driver->netdev, NULL, MCTP_PHYS_BINDING_VENDOR); ++ if (ret) { ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ return ret; ++ } ++ return 0; ++} ++ ++/// @brief Gets the driver managing a mailbox client. ++/// @details There are two methods to identify the mailbox client: ++/// container_of, and dev_get_drvdata(client->dev). ++/// This function does both and ensures they match. ++/// @retval NULL On error. ++static struct nvidia_irot_driver * ++nvidia_irot_driver_of_mbox(struct mbox_client *client) ++{ ++ if (client == NULL) { ++ LOG_ERR("null client input to %s", __func__); ++ return NULL; ++ } ++ ++ void *const driver_from_container_of = ++ container_of(client, struct nvidia_irot_driver, mbox_client); ++ ++ if (client->dev == NULL) { ++ LOG_ERR("null client->dev input to %s", __func__); ++ return NULL; ++ } ++ void *const driver_from_devdata = dev_get_drvdata(client->dev); ++ ++ if (driver_from_container_of != driver_from_devdata) { ++ LOG_ERR("container_of and dev_get_drvdata(client->dev) disagree in %s", ++ __func__); ++ return NULL; ++ } ++ return (struct nvidia_irot_driver *)driver_from_devdata; ++} ++ ++static void wake_rx_worker(struct nvidia_irot_driver *driver) ++{ ++ atomic_xchg(&driver->rx_work_ready, 1); ++ wake_up(&driver->rx_worker_wq); ++} ++ ++/// @brief Handles a duplicate command by scheduling a response ++/// without scheduling command handling. ++/// @details Performs no IO and does not suspend as is ++/// called from a callback that does not allow this. ++/// @pre driver->shared_spin is locked by the caller. ++/// @param driver The driver handling the mailbox. ++/// @param command The duplicate command. ++static void ++locked_handle_duplicate_rx_command(struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_message *command) ++{ ++ struct nvidia_irot_message *staged_response = ++ &driver->shared_state.duplicate_response; ++ ++ if (staged_response->command != 0) { ++ // already have a duplicate response schedule ++ return; ++ } ++ if (command->command != nvidia_irot_cc_mctp) { ++ // currently this is the only command that needs duplication logic. ++ return; ++ } ++ ++ // build the response ++ struct nvidia_irot_message response = NVIDIA_IROT_MESSAGE_INIT; ++ response.command = nvidia_irot_cc_mctp_done; ++ response.data.value = command->data.mctp.counter; ++ ++ *staged_response = response; ++ ++ // wake the worker to handle the response ++ wake_rx_worker(driver); ++} ++ ++/// @brief Handles a read message. ++/// @details Performs no IO and does not suspend as is ++/// called from a callback that does not allow this. ++/// @pre driver->shared_spin is locked by the caller. ++/// @details Called from a mailbox callback so must not block. ++/// @param driver The driver managing the mailbox. ++/// @param message The read message. ++static void locked_handle_rx_message(struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_message *message) ++{ ++ switch (message->command) { ++ case nvidia_irot_cc_ping: ++ // ping: stage and wake the worker ++ // NOTE: don't need duplicate command checking as ping command handling does nothing. ++ driver->shared_state.rx_ping = *message; ++ wake_rx_worker(driver); ++ return; ++ ++ case nvidia_irot_cc_mctp_buffer: ++ // got buffer addresses, check for duplicate ++ if (driver->shared_state.shmem.a35_read.size != 0) { ++ return; ++ } ++ if (driver->shared_state.shmem.a35_write.size != 0) { ++ return; ++ } ++ // is not a duplicate, inform the startup code ++ driver->shared_state.shmem = message->data.buffers; ++ up(&driver->probe_sem); ++ return; ++ ++ case nvidia_irot_cc_mctp: ++ // new mctp packet ++ if (driver->shared_state.pending_read.command != 0) { ++ // read already pending, drop ++ return; ++ } ++ if (driver->mailbox_state.has_prev_rx_counter && ++ driver->mailbox_state.prev_rx_counter == ++ message->data.mctp.counter) { ++ // duplicate command, send response with no processing ++ locked_handle_duplicate_rx_command(driver, message); ++ return; ++ } ++ // stage the packet and wake a reader ++ driver->shared_state.pending_read = *message; ++ driver->mailbox_state.has_prev_rx_counter = true; ++ driver->mailbox_state.prev_rx_counter = ++ message->data.mctp.counter; ++ wake_rx_worker(driver); ++ return; ++ ++ case nvidia_irot_cc_mctp_done: ++ // tx buffer emptied ++ if (driver->shared_state.pending_write.command == 0) { ++ // no write was pending, ignore ++ return; ++ } ++ if (driver->shared_state.pending_write.data.mctp.counter != ++ message->data.value) { ++ // message counter missmatch, ignore ++ return; ++ } ++ // unstage the pending write and wake writers ++ driver->shared_state.pending_write.command = 0; ++ atomic_xchg(&driver->tx_shmem_ready, 1); ++ wake_up(&driver->tx_shmem_ready_wq); ++ return; ++ ++ default: ++ // unexpected message type, ignore ++ return; ++ } ++} ++ ++/// @brief Callback for when we recive a notification from the IRoT that ++/// shared memory has been updated. ++/// @param client The mailbox client handling notification receptions. ++/// @param data The 32-byte message sent over IPC. ++static void nvidia_irot_on_notification(struct mbox_client *client, void *data) ++{ ++ struct nvidia_irot_driver *const driver = ++ nvidia_irot_driver_of_mbox(client); ++ if (!driver) { ++ return; ++ } ++ struct nvidia_irot_message message; ++ memcpy(&message, data, sizeof(message)); ++ unsigned long flags; ++ spin_lock_irqsave(&driver->shared_spin, flags); ++ locked_handle_rx_message(driver, &message); ++ spin_unlock_irqrestore(&driver->shared_spin, flags); ++} ++ ++static int nvidia_irot_load_mailbox(struct device *dev, ++ struct nvidia_irot_driver *driver) ++{ ++ struct mbox_chan *chan = NULL; ++ struct mbox_client *client = &driver->mbox_client; ++ ++ // bind mailbox to this device ++ client->dev = dev; ++ ++ // bind notification RX callback ++ client->rx_callback = nvidia_irot_on_notification; ++ ++ // on tx wait for the IRoT to clear the notification ++ client->tx_done = NULL; ++ client->tx_block = true; ++ client->tx_tout = 250; ++ ++ // Request mailbox channel ++ chan = mbox_request_channel_byname(client, "irot"); ++ if (IS_ERR(chan)) { ++ dev_err(dev, "Failed to request mailbox channel\n"); ++ return PTR_ERR(chan); ++ } ++ ++ // Store mailbox info in driver ++ driver->mbox_chan = chan; ++ ++ dev_info(dev, "Mailbox channel configured successfully\n"); ++ ++ return 0; ++} ++ ++/// @brief Handles reading a message from shared memory. ++/// @returns The length of the read message on success. ++/// @returns A negative status code on failure. ++static ssize_t worker_locked_handle_read( ++ struct nvidia_irot_driver *driver, ++ struct nvidia_irot_driver_shared_state *shared_state_copy) ++{ ++ ssize_t ret = -EINVAL; ++ ++ if (shared_state_copy->pending_read.command != nvidia_irot_cc_mctp || ++ shared_state_copy->pending_read.data.mctp.packet.size == 0) { ++ return -EINVAL; ++ } ++ ++ const size_t length = ++ shared_state_copy->pending_read.data.mctp.packet.size; ++ const ssize_t offset = get_start_offset( ++ shared_state_copy->shmem.a35_read, ++ shared_state_copy->pending_read.data.mctp.packet); ++ if (offset < 0) { ++ // TODO: Should we clear the pending transaction if it is invalid? ++ // This is kind of a fatal error currently. ++ LOG_ERR("bad read from shared memory"); ++ return -EINVAL; ++ } ++ ++ struct sk_buff *skb; ++ skb = netdev_alloc_skb(driver->netdev, length); ++ if (!skb) { ++ LOG_ERR("failed to allocate skb for read packet"); ++ return -ENOMEM; ++ } ++ skb->protocol = htons(ETH_P_MCTP); ++ ret = copy_from_shmem(skb, ++ shared_state_copy->pending_read.data.mctp.packet, ++ driver->worker_state.rx_addr + offset); ++ if (ret != 0) { ++ LOG_ERR("failed to copy read packet from shared memory"); ++ kfree_skb(skb); ++ return -EINVAL; ++ } ++ skb_reset_mac_header(skb); ++ skb_reset_network_header(skb); ++ ++ struct mctp_skb_cb *const cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++#if NVIDIA_IROT_MCTP_LOG_ENABLED ++ { ++ int n = skb->len < NVIDIA_IROT_MCTP_LOG_BYTES ? ++ skb->len : ++ NVIDIA_IROT_MCTP_LOG_BYTES; ++ u8 data[NVIDIA_IROT_MCTP_LOG_BYTES] = { 0 }; ++ skb_copy_bits(skb, 0, data, n); ++ char hexbuf[3 * NVIDIA_IROT_MCTP_LOG_BYTES + 1] = { 0 }; ++ int i; ++ for (i = 0; i < n; ++i) { ++ snprintf(hexbuf + i * 3, 4, "%02x ", data[i]); ++ } ++ if (n > 0) ++ hexbuf[3 * n - 1] = '\0'; // Remove trailing space ++ LOG_INF("I->L: %s", hexbuf); ++ } ++#endif ++ ++ ret = netif_receive_skb(skb); ++ if (ret != NET_RX_SUCCESS) { ++ LOG_ERR("netif_rx failed with %d", (int)ret); ++ // NOTE: skb seems to still be freed by a failed netif_receive_skb. ++ return -EINVAL; ++ } ++ return length; ++} ++ ++static int nvidia_irot_rx_worker(void *data) ++{ ++ struct nvidia_irot_driver *const driver = data; ++ ++ for (;;) { ++ wait_event_interruptible( ++ driver->rx_worker_wq, ++ kthread_should_stop() || ++ atomic_read(&driver->rx_work_ready)); ++ if (kthread_should_stop()) { ++ return 0; ++ } ++ atomic_xchg(&driver->rx_work_ready, 0); ++ struct nvidia_irot_driver_shared_state state; ++ mutex_lock(&driver->worker_mutex); ++ worker_locked_handle_shared_state(driver, &state); ++ const int ret = worker_locked_handle_read(driver, &state); ++ (void)finish_read_and_unlock(driver, ret, &state.pending_read); ++ } ++ ++ // unreachable ++ BUG(); ++} ++ ++static ssize_t worker_locked_handle_write( ++ struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_driver_shared_state *shared_state_copy, ++ struct sk_buff *skb) ++{ ++ int ret = -EINVAL; ++ const size_t count = skb->len; ++ if (count == 0) { ++ // empty packets make no sense ++ return -EINVAL; ++ } ++ ++ if (count > shared_state_copy->shmem.mtu_limit) { ++ LOG_ERR("attempted to write mctp packet of length %zu to irot with mtu limit of %u", ++ count, shared_state_copy->shmem.mtu_limit); ++ return -EINVAL; ++ } ++ ++ // write to shmem ++ ret = copy_to_shmem(shared_state_copy->shmem.a35_write, ++ driver->worker_state.tx_addr, skb); ++ if (ret < 0) { ++ LOG_ERR("copy_to_shmem failed"); ++ return ret; ++ } ++ ++ // build the IPC message ++ struct nvidia_irot_message message = NVIDIA_IROT_MESSAGE_INIT; ++ message.command = nvidia_irot_cc_mctp; ++ message.data.mctp.counter = driver->worker_state.next_tx_counter++; ++ message.data.mctp.packet.address = ++ shared_state_copy->shmem.a35_write.address; ++ message.data.mctp.packet.size = count; ++ ++ // stage the pending write ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.pending_write = message; ++ spin_unlock_irq(&driver->shared_spin); ++ ++ // perform the write ++ ret = send_message(driver, &message); ++ if (ret < 0) { ++ // send failed, clear the pending write ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.pending_write.command = 0; ++ spin_unlock_irq(&driver->shared_spin); ++ return ret; ++ } ++ ++ // successfully sent, mailbox callback will clear pending_write ++ return count; ++} ++ ++static int nvidia_irot_tx_worker(void *data) ++{ ++ struct nvidia_irot_driver *const driver = data; ++ struct sk_buff *skb = NULL; ++ ++ for (;;) { ++ // get a packet to send ++ while (!skb) { ++ wait_event_interruptible( ++ driver->tx_queue_ready_wq, ++ !skb_queue_empty(&driver->tx_queue) || ++ kthread_should_stop()); ++ if (kthread_should_stop()) { ++ return 0; ++ } ++ ++ skb = skb_dequeue(&driver->tx_queue); ++ netif_start_queue(driver->netdev); ++ if (skb) { ++ break; ++ } ++ } ++ // we now have a packet to write ++ ++ // wait for shared memory to be ready for a written packet ++ struct nvidia_irot_driver_shared_state shared_state_copy; ++ for (;;) { ++ wait_event_interruptible( ++ driver->tx_shmem_ready_wq, ++ kthread_should_stop() || ++ atomic_read(&driver->tx_shmem_ready)); ++ if (kthread_should_stop()) { ++ kfree_skb(skb); ++ return 0; ++ } ++ atomic_xchg(&driver->tx_shmem_ready, 0); ++ ++ mutex_lock(&driver->worker_mutex); ++ worker_locked_handle_shared_state(driver, ++ &shared_state_copy); ++ if (driver->worker_state.tx_addr != NULL && ++ shared_state_copy.pending_write.command == 0) { ++ // shmem is init and no write is pending, allow the write. ++ // NOTE: pending_write is only cleared by mailbox callback, ++ // so since we hold worker_mutex and know that pending_write is ++ // clear we have exclusive write access. ++ break; ++ } ++ mutex_unlock(&driver->worker_mutex); ++ ++ // loop to reload and recheck state after notification ++ } ++ // We now hold the worker_mutex and tx buffer ownership. ++ ++ // send the packet ++ const ssize_t status = worker_locked_handle_write( ++ driver, &shared_state_copy, skb); ++ mutex_unlock(&driver->worker_mutex); ++ if (status < 0) { ++ // TODO: could attempt to send again, but have never seen this error ++ LOG_ERR("failed to send MCTP packet, status: %d", ++ (int)status); ++ } ++ kfree_skb(skb); ++ skb = NULL; ++ } ++ ++ // unreachable ++ BUG(); ++} ++ ++/// @brief Inits shared memory by requesting the IRoT to send buffer ++/// information and waiting for its response. ++static int nvidia_irot_init_shmem(struct nvidia_irot_driver *driver) ++{ ++ // NOTE: worker_mutex does not need to be held by ++ // this function as no workers are started yet. ++ ++ struct nvidia_irot_driver_shared_state shared_state_copy; ++ shared_state_copy.shmem.mtu_limit = 0; ++ ++ int i; ++ for (i = 0; i < 3; ++i) { ++ // request memory buffers from the IRoT ++ struct nvidia_irot_message message = NVIDIA_IROT_MESSAGE_INIT; ++ message.command = nvidia_irot_cc_get_mctp_buffers; ++ const int ret = send_message(driver, &message); ++ if (ret < 0) { ++ LOG_ERR("failed to send startup IPC message, ret: %d", ++ ret); ++ return ret; ++ } ++ ++ // wait for the response ++ const u64 end_jiffies64 = get_jiffies_64() + (5 * HZ); ++ while (time_is_after_jiffies64(end_jiffies64)) { ++ (void)down_timeout(&driver->probe_sem, HZ); ++ worker_locked_handle_shared_state(driver, ++ &shared_state_copy); ++ if (shared_state_copy.shmem.mtu_limit != 0) { ++ // success, got shared memory info ++ break; ++ } ++ } ++ ++ if (shared_state_copy.shmem.mtu_limit == 0) { ++ LOG_WRN("timed out waiting for IRoT buffer information"); ++ } else { ++ break; ++ } ++ } ++ ++ if (shared_state_copy.shmem.mtu_limit == 0) { ++ LOG_ERR("Timeout waiting for IRoT to provide shared memory buffers"); ++ return -ETIMEDOUT; ++ } ++ return 0; ++} ++ ++/// @brief Performs cleanup for our custom driver bound to the given platform_device. ++/// @param pdev The device having its driver cleaned up. ++/// @note The driver can be in any stage of initialization including completely unbound. ++static void clean_driver(struct platform_device *pdev) ++{ ++ if (pdev == NULL) { ++ return; ++ } ++ ++ struct nvidia_irot_driver *const driver = ++ (struct nvidia_irot_driver *)platform_get_drvdata(pdev); ++ if (driver == NULL) { ++ return; ++ } ++ ++ // remove worker threads ++ if (driver->rx_worker) { ++ if (0 != kthread_stop(driver->rx_worker)) { ++ LOG_ERR("failed to stop rx worker thread"); ++ } ++ driver->rx_worker = NULL; ++ } ++ if (driver->tx_worker) { ++ if (0 != kthread_stop(driver->tx_worker)) { ++ LOG_ERR("failed to stop tx worker thread"); ++ } ++ driver->tx_worker = NULL; ++ } ++ ++ // remove network device ++ if (driver->netdev) { ++ // Stop the queue to prevent new packets ++ netif_tx_disable(driver->netdev); ++ ++ // Purge any remaining packets in the queue ++ skb_queue_purge(&driver->tx_queue); ++ ++ // Explicitly bring the device down if it's still running ++ rtnl_lock(); ++ if (netif_running(driver->netdev)) { ++ dev_close(driver->netdev); ++ } ++ rtnl_unlock(); ++ ++ // Unregister the netdev - this should handle final cleanup ++ mctp_unregister_netdev(driver->netdev); ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ } ++ ++ // free shared memory ++ // NOTE: workers are stopped so can use worker_state freely ++ if (driver->worker_state.rx_addr) { ++ memunmap((void *)driver->worker_state.rx_addr); ++ driver->worker_state.rx_addr = NULL; ++ } ++ if (driver->worker_state.tx_addr) { ++ memunmap(driver->worker_state.tx_addr); ++ driver->worker_state.tx_addr = NULL; ++ } ++ ++ // release mailbox ++ if (driver->mbox_chan) { ++ mbox_free_channel(driver->mbox_chan); ++ driver->mbox_chan = NULL; ++ } ++ ++ // the driver is always heap allocated and we have exclusive ownership of it, free it. ++ platform_set_drvdata(pdev, NULL); ++ kfree(driver); ++}; ++ ++static int nvidia_irot_probe(struct platform_device *pdev) ++{ ++ struct device *const dev = &pdev->dev; ++ dev_info(dev, "Probe starting for device: %s\n", dev_name(dev)); ++ ++ int ret = -EINVAL; ++ struct nvidia_irot_driver *const driver = ++ kzalloc(sizeof(struct nvidia_irot_driver), GFP_KERNEL); ++ if (!driver) { ++ dev_err(dev, "Failed to allocate memory for driver\n"); ++ return -ENOMEM; ++ } ++ ++ platform_set_drvdata(pdev, driver); ++ spin_lock_init(&driver->shared_spin); ++ mutex_init(&driver->worker_mutex); ++ sema_init(&driver->probe_sem, 0); ++ init_waitqueue_head(&driver->rx_worker_wq); ++ atomic_set(&driver->rx_work_ready, 0); ++ init_waitqueue_head(&driver->tx_queue_ready_wq); ++ init_waitqueue_head(&driver->tx_shmem_ready_wq); ++ skb_queue_head_init(&driver->tx_queue); ++ ++ ret = nvidia_irot_load_mailbox(dev, driver); ++ if (ret != 0) { ++ dev_err(dev, "Failed to load mailbox.\n"); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ ret = nvidia_irot_init_shmem(driver); ++ if (ret < 0) { ++ dev_err(dev, "failed to establish shared memory, ret: %d\n", ++ ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ // Signal that shared memory is ready for TX operations ++ atomic_set(&driver->tx_shmem_ready, 1); ++ wake_up(&driver->tx_shmem_ready_wq); ++ ++ ret = nvidia_irot_create_mctp_dev(driver, dev_name(dev)); ++ if (ret < 0) { ++ dev_err(dev, "failed to create mctp device, ret: %d\n", ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ driver->rx_worker = kthread_run(nvidia_irot_rx_worker, driver, ++ "%s-rx-worker", dev_name(dev)); ++ if (IS_ERR(driver->rx_worker)) { ++ dev_err(dev, "Failed to create rx worker thread.\n"); ++ driver->rx_worker = NULL; ++ clean_driver(pdev); ++ return -EINVAL; ++ } ++ driver->tx_worker = kthread_run(nvidia_irot_tx_worker, driver, ++ "%s-tx-worker", dev_name(dev)); ++ if (IS_ERR(driver->tx_worker)) { ++ dev_err(dev, "Failed to create tx worker thread.\n"); ++ driver->tx_worker = NULL; ++ clean_driver(pdev); ++ return -EINVAL; ++ } ++ ++ dev_info(dev, "Probe successful for device: %s\n", dev_name(dev)); ++ return 0; ++} ++ ++static void nvidia_irot_remove(struct platform_device *pdev) ++{ ++ dev_info(&pdev->dev, "Removing device: %s\n", dev_name(&pdev->dev)); ++ clean_driver(pdev); ++ dev_info(&pdev->dev, "Device removed: %s\n", dev_name(&pdev->dev)); ++} ++ ++static const struct of_device_id nvidia_irot_match[] = { ++ { .compatible = "nvidia,ast27xx,irot" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, nvidia_irot_match); ++ ++static struct platform_driver nvidia_irot_driver = { ++ .probe = nvidia_irot_probe, ++ .remove = nvidia_irot_remove, ++ .driver = ++ { ++ .name = "nvidia-ast27xx-irot", ++ .of_match_table = nvidia_irot_match, ++ }, ++}; ++ ++module_platform_driver(nvidia_irot_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("NVIDIA IRoT MCTP Driver for ASPEED AST27xx BMC"); +-- +2.34.1 + diff --git a/patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch b/patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch new file mode 100644 index 000000000..f49434b8c --- /dev/null +++ b/patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch @@ -0,0 +1,618 @@ +From: Mrinmoy Ghosh +Date: Sat, 16 Aug 2025 03:11:45 +0000 +Subject: [PATCH 2/3] net: bridge: vxlan: Protocol field in bridge fdb + +Add an optional "protocol" field for bridge FDB and VXLAN FDB entries to +distinguish between control plane and data plane learned MAC addresses. + +In EVPN Multihoming, MAC addresses can be learned via: +- Control plane (ZEBRA protocol): Static MACs distributed by FRR/BGP +- Data plane (HW/KERNEL protocol): Dynamic MACs learned from traffic + +Implementation: +- New field: protocol in net_bridge_fdb_entry and vxlan_fdb structures +- Protocol values: standard routing protocol values (RTPROT_*) +- NDA_PROTOCOL attribute encoded in netlink messages for FDB entries +- br_fdb_add: parse NDA_PROTOCOL from netlink add path +- fdb_add_entry: propagate protocol for non-extern_learn path +- vxlan_fdb_update_existing: guard against RTPROT_UNSPEC overwriting + previously-set protocol (prevents vxlan_snoop from clearing + control-plane-set protocol) +- br_switchdev_event: correct argument order for protocol parameter + +Signed-off-by: Mrinmoy Ghosh +Signed-off-by: Tamer Ahmed +--- + drivers/net/vxlan/vxlan_core.c | 108 +++++++++++++++++++++--------------- + drivers/net/vxlan/vxlan_private.h | 7 ++- + drivers/net/vxlan/vxlan_vnifilter.c | 11 ++-- + net/bridge/br.c | 2 +- + net/bridge/br_fdb.c | 70 +++++++++++++++++------ + net/bridge/br_private.h | 4 +- + 6 files changed, 128 insertions(+), 74 deletions(-) + +diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c +index ffb0b61..7e08ae3 100644 +--- a/drivers/net/vxlan/vxlan_core.c ++++ b/drivers/net/vxlan/vxlan_core.c +@@ -200,6 +200,8 @@ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan, + peernet2id(dev_net(vxlan->dev), vxlan->net))) + goto nla_put_failure; + ++ if (nla_put_u8(skb, NDA_PROTOCOL, fdb->protocol)) ++ goto nla_put_failure; + if (send_eth && nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->eth_addr)) + goto nla_put_failure; + if (nh) { +@@ -250,6 +252,7 @@ static inline size_t vxlan_nlmsg_size(void) + { + return NLMSG_ALIGN(sizeof(struct ndmsg)) + + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ ++ + nla_total_size(sizeof(__u8)) /* NDA_PROTOCOL */ + + nla_total_size(sizeof(struct in6_addr)) /* NDA_DST */ + + nla_total_size(sizeof(__be16)) /* NDA_PORT */ + + nla_total_size(sizeof(__be32)) /* NDA_VNI */ +@@ -890,7 +893,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, + const u8 *mac, union vxlan_addr *ip, + __u16 state, __be16 port, __be32 src_vni, + __be32 vni, __u32 ifindex, __u16 ndm_flags, +- u32 nhid, struct vxlan_fdb **fdb, ++ u32 nhid, u8 protocol, struct vxlan_fdb **fdb, + struct netlink_ext_ack *extack, u32 ext_flags) + { + struct vxlan_rdst *rd = NULL; +@@ -906,6 +909,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, + if (!f) + return -ENOMEM; + ++ f->protocol = protocol; + if (nhid) + rc = vxlan_fdb_nh_update(vxlan, f, nhid, extack); + else +@@ -985,7 +989,7 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + __be16 port, __be32 vni, + __u32 ifindex, __u16 ndm_flags, + struct vxlan_fdb *f, u32 nhid, +- bool swdev_notify, ++ u8 protocol, bool swdev_notify, + struct netlink_ext_ack *extack, u32 ext_flags) + { + __u16 fdb_flags = (ndm_flags & ~NTF_USE); +@@ -1025,6 +1029,12 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + } + } + ++ if (protocol != RTPROT_UNSPEC && f->protocol != protocol) { ++ f->protocol = protocol; ++ WRITE_ONCE(f->updated, jiffies); ++ notify = 1; ++ } ++ + if ((old_ext_flags ^ ext_flags) & NTF_EXT_MH_PEER_SYNC) { + notify = 1; + if (ext_flags & NTF_EXT_MH_PEER_SYNC) +@@ -1093,7 +1103,7 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, + __u16 state, __u16 flags, + __be16 port, __be32 src_vni, __be32 vni, + __u32 ifindex, __u16 ndm_flags, u32 nhid, +- bool swdev_notify, ++ u8 protocol, bool swdev_notify, + struct netlink_ext_ack *extack, u32 ext_flags) + { + __u16 fdb_flags = (ndm_flags & ~NTF_USE); +@@ -1107,8 +1117,8 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, + + netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip); + rc = vxlan_fdb_create(vxlan, mac, ip, state, port, src_vni, +- vni, ifindex, fdb_flags, nhid, &f, extack, +- ext_flags); ++ vni, ifindex, fdb_flags, nhid, protocol, &f, ++ extack, ext_flags); + if (rc < 0) + return rc; + +@@ -1131,8 +1141,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + __u16 state, __u16 flags, + __be16 port, __be32 src_vni, __be32 vni, + __u32 ifindex, __u16 ndm_flags, u32 nhid, +- bool swdev_notify, struct netlink_ext_ack *extack, +- u32 ext_flags) ++ u8 protocol, bool swdev_notify, ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + struct vxlan_fdb *f; + +@@ -1146,15 +1156,16 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + + return vxlan_fdb_update_existing(vxlan, ip, state, flags, port, + vni, ifindex, ndm_flags, f, +- nhid, swdev_notify, extack, ext_flags); ++ nhid, protocol, swdev_notify, ++ extack, ext_flags); + } else { + if (!(flags & NLM_F_CREATE)) + return -ENOENT; + + return vxlan_fdb_update_create(vxlan, mac, ip, state, flags, + port, src_vni, vni, ifindex, +- ndm_flags, nhid, swdev_notify, +- extack, ext_flags); ++ ndm_flags, nhid, protocol, ++ swdev_notify, extack, ext_flags); + } + } + +@@ -1168,7 +1179,7 @@ static void vxlan_fdb_dst_destroy(struct vxlan_dev *vxlan, struct vxlan_fdb *f, + + static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan, + union vxlan_addr *ip, __be16 *port, __be32 *src_vni, +- __be32 *vni, u32 *ifindex, u32 *nhid, ++ __be32 *vni, u32 *ifindex, u32 *nhid, u8 *protocol, + struct netlink_ext_ack *extack, u32 *ext_flags) + { + struct net *net = dev_net(vxlan->dev); +@@ -1252,11 +1263,15 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan, + else + *nhid = 0; + +- if (tb[NDA_FLAGS_EXT]) { ++ if (tb[NDA_FLAGS_EXT] && ext_flags) + *ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); +- } else { ++ else if (ext_flags) + *ext_flags = 0; +- } ++ ++ if (tb[NDA_PROTOCOL]) ++ *protocol = nla_get_u8(tb[NDA_PROTOCOL]); ++ else ++ *protocol = RTPROT_UNSPEC; + + return 0; + } +@@ -1275,6 +1290,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + u32 ifindex, nhid; + u32 hash_index; + u32 ext_flags; ++ u8 protocol; + int err; + + if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_REACHABLE))) { +@@ -1287,7 +1303,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + return -EINVAL; + + err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex, +- &nhid, extack, &ext_flags); ++ &nhid, &protocol, extack, &ext_flags); + if (err) + return err; + +@@ -1299,7 +1315,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + err = vxlan_fdb_update(vxlan, addr, &ip, ndm->ndm_state, flags, + port, src_vni, vni, ifindex, + ndm->ndm_flags | NTF_VXLAN_ADDED_BY_USER, +- nhid, true, extack, ext_flags); ++ nhid, protocol, true, extack, ext_flags); + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +@@ -1347,21 +1363,30 @@ static int vxlan_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], + struct vxlan_dev *vxlan = netdev_priv(dev); + union vxlan_addr ip; + __be32 src_vni, vni; ++ struct vxlan_fdb *f; + u32 ifindex, nhid; + u32 hash_index; +- u32 ext_flags; + __be16 port; ++ u8 protocol; + int err; + + err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex, +- &nhid, extack, &ext_flags); ++ &nhid, &protocol, extack, NULL); + if (err) + return err; + + hash_index = fdb_head_index(vxlan, addr, src_vni); + spin_lock_bh(&vxlan->hash_lock[hash_index]); ++ if (protocol != RTPROT_UNSPEC) { ++ f = vxlan_find_mac_tx(vxlan, addr, src_vni); ++ if (!f || f->protocol != protocol) { ++ err = -ENOENT; ++ goto out; ++ } ++ } + err = __vxlan_fdb_delete(vxlan, addr, ip, port, src_vni, vni, ifindex, + true); ++out: + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +@@ -1510,13 +1535,12 @@ static bool vxlan_snoop(struct net_device *dev, + + /* close off race between vxlan_flush and incoming packets */ + if (netif_running(dev)) +- vxlan_fdb_update(vxlan, src_mac, src_ip, +- NUD_REACHABLE, +- NLM_F_EXCL|NLM_F_CREATE, +- vxlan->cfg.dst_port, +- vni, +- vxlan->default_dst.remote_vni, +- ifindex, NTF_SELF, 0, true, NULL, 0); ++ vxlan_fdb_update(vxlan, src_mac, src_ip, NUD_REACHABLE, ++ NLM_F_EXCL | NLM_F_CREATE, ++ vxlan->cfg.dst_port, vni, ++ vxlan->default_dst.remote_vni, ifindex, ++ NTF_SELF, 0, RTPROT_UNSPEC, true, NULL, ++ 0); + spin_unlock(&vxlan->hash_lock[hash_index]); + } + +@@ -3964,14 +3988,12 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev, + + /* create an fdb entry for a valid default destination */ + if (!vxlan_addr_any(&dst->remote_ip)) { +- err = vxlan_fdb_create(vxlan, all_zeros_mac, +- &dst->remote_ip, ++ err = vxlan_fdb_create(vxlan, all_zeros_mac, &dst->remote_ip, + NUD_REACHABLE | NUD_PERMANENT, +- vxlan->cfg.dst_port, +- dst->remote_vni, +- dst->remote_vni, +- dst->remote_ifindex, +- NTF_SELF, 0, &f, extack, 0); ++ vxlan->cfg.dst_port, dst->remote_vni, ++ dst->remote_vni, dst->remote_ifindex, ++ NTF_SELF, 0, RTPROT_UNSPEC, &f, extack, ++ 0); + if (err) + return err; + } +@@ -4402,10 +4424,10 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], + &conf.remote_ip, + NUD_REACHABLE | NUD_PERMANENT, + NLM_F_APPEND | NLM_F_CREATE, +- vxlan->cfg.dst_port, +- conf.vni, conf.vni, +- conf.remote_ifindex, +- NTF_SELF, 0, true, extack, 0); ++ vxlan->cfg.dst_port, conf.vni, ++ conf.vni, conf.remote_ifindex, ++ NTF_SELF, 0, RTPROT_UNSPEC, true, ++ extack, 0); + if (err) { + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + netdev_adjacent_change_abort(dst->remote_dev, +@@ -4754,14 +4777,11 @@ vxlan_fdb_external_learn_add(struct net_device *dev, + + spin_lock_bh(&vxlan->hash_lock[hash_index]); + err = vxlan_fdb_update(vxlan, fdb_info->eth_addr, &fdb_info->remote_ip, +- NUD_REACHABLE, +- NLM_F_CREATE | NLM_F_REPLACE, +- fdb_info->remote_port, +- fdb_info->vni, +- fdb_info->remote_vni, +- fdb_info->remote_ifindex, +- NTF_USE | NTF_SELF | NTF_EXT_LEARNED, +- 0, false, extack, 0); ++ NUD_REACHABLE, NLM_F_CREATE | NLM_F_REPLACE, ++ fdb_info->remote_port, fdb_info->vni, ++ fdb_info->remote_vni, fdb_info->remote_ifindex, ++ NTF_USE | NTF_SELF | NTF_EXT_LEARNED, 0, ++ RTPROT_UNSPEC, false, extack, 0); + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +diff --git a/drivers/net/vxlan/vxlan_private.h b/drivers/net/vxlan/vxlan_private.h +index e0878e0..c3cc3fa 100644 +--- a/drivers/net/vxlan/vxlan_private.h ++++ b/drivers/net/vxlan/vxlan_private.h +@@ -36,6 +36,7 @@ struct vxlan_fdb { + __be32 vni; + u16 flags; /* see ndm_flags and below */ + u32 ext_flags; ++ u8 protocol; + struct list_head nh_list; + struct nexthop __rcu *nh; + struct vxlan_dev __rcu *vdev; +@@ -180,7 +181,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, + const u8 *mac, union vxlan_addr *ip, + __u16 state, __be16 port, __be32 src_vni, + __be32 vni, __u32 ifindex, __u16 ndm_flags, +- u32 nhid, struct vxlan_fdb **fdb, ++ u32 nhid, u8 protocol, struct vxlan_fdb **fdb, + struct netlink_ext_ack *extack, u32 ext_flags); + int __vxlan_fdb_delete(struct vxlan_dev *vxlan, + const unsigned char *addr, union vxlan_addr ip, +@@ -193,8 +194,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + __u16 state, __u16 flags, + __be16 port, __be32 src_vni, __be32 vni, + __u32 ifindex, __u16 ndm_flags, u32 nhid, +- bool swdev_notify, struct netlink_ext_ack *extack, +- u32 ext_flags); ++ u8 protocol, bool swdev_notify, ++ struct netlink_ext_ack *extack, u32 ext_flags); + void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, + __be32 default_vni, struct vxlan_rdst *rdst, bool did_rsc); + int vxlan_vni_in_use(struct net *src_net, struct vxlan_dev *vxlan, +diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c +index af38b5b..f8396be 100644 +--- a/drivers/net/vxlan/vxlan_vnifilter.c ++++ b/drivers/net/vxlan/vxlan_vnifilter.c +@@ -489,15 +489,12 @@ static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni, + hash_index = fdb_head_index(vxlan, all_zeros_mac, vni); + spin_lock_bh(&vxlan->hash_lock[hash_index]); + if (remote_ip && !vxlan_addr_any(remote_ip)) { +- err = vxlan_fdb_update(vxlan, all_zeros_mac, +- remote_ip, ++ err = vxlan_fdb_update(vxlan, all_zeros_mac, remote_ip, + NUD_REACHABLE | NUD_PERMANENT, + NLM_F_APPEND | NLM_F_CREATE, +- vxlan->cfg.dst_port, +- vni, +- vni, +- dst->remote_ifindex, +- NTF_SELF, 0, true, extack, 0); ++ vxlan->cfg.dst_port, vni, vni, ++ dst->remote_ifindex, NTF_SELF, 0, ++ RTPROT_UNSPEC, true, extack, 0); + if (err) { + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + return err; +diff --git a/net/bridge/br.c b/net/bridge/br.c +index 72b3b54..94ea448 100644 +--- a/net/bridge/br.c ++++ b/net/bridge/br.c +@@ -166,7 +166,7 @@ static int br_switchdev_event(struct notifier_block *unused, + case SWITCHDEV_FDB_ADD_TO_BRIDGE: + fdb_info = ptr; + err = br_fdb_external_learn_add(br, p, fdb_info->addr, +- fdb_info->vid, ++ fdb_info->vid, RTPROT_UNSPEC, + fdb_info->locked, false, 0); + if (err) { + err = notifier_from_errno(err); +diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c +index 202d791..71c1ada 100644 +--- a/net/bridge/br_fdb.c ++++ b/net/bridge/br_fdb.c +@@ -132,6 +132,8 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, + goto nla_put_failure; + if (nla_put_u32(skb, NDA_MASTER, br->dev->ifindex)) + goto nla_put_failure; ++ if (nla_put_u8(skb, NDA_PROTOCOL, fdb->protocol)) ++ goto nla_put_failure; + if (nla_put_u32(skb, NDA_FLAGS_EXT, ext_flags)) + goto nla_put_failure; + +@@ -176,6 +178,7 @@ static inline size_t fdb_nlmsg_size(void) + return NLMSG_ALIGN(sizeof(struct ndmsg)) + + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ + + nla_total_size(sizeof(u32)) /* NDA_MASTER */ ++ + nla_total_size(sizeof(u8)) /* NDA_PROTOCOL */ + + nla_total_size(sizeof(u32)) /* NDA_FLAGS_EXT */ + + nla_total_size(sizeof(u16)) /* NDA_VLAN */ + + nla_total_size(sizeof(struct nda_cacheinfo)) +@@ -1066,7 +1069,7 @@ static bool fdb_handle_notify(struct net_bridge_fdb_entry *fdb, u8 notify) + /* Update (create or replace) forwarding database entry */ + static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, + const u8 *addr, struct ndmsg *ndm, u16 flags, u16 vid, +- struct nlattr *nfea_tb[]) ++ u8 protocol, struct nlattr *nfea_tb[]) + { + bool is_sticky = !!(ndm->ndm_flags & NTF_STICKY); + bool refresh = !nfea_tb[NFEA_DONT_REFRESH]; +@@ -1122,6 +1125,11 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, + atomic_dec(&br->fdb_n_learned); + } + ++ if (fdb->protocol != protocol) { ++ fdb->protocol = protocol; ++ modified = true; ++ } ++ + if (fdb_to_nud(br, fdb) != state) { + if (state & NUD_PERMANENT) { + set_bit(BR_FDB_LOCAL, &fdb->flags); +@@ -1163,7 +1171,8 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, + + static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br, + struct net_bridge_port *p, const unsigned char *addr, +- u16 nlh_flags, u16 vid, struct nlattr *nfea_tb[], ++ u16 nlh_flags, u16 vid, u8 protocol, ++ struct nlattr *nfea_tb[], + struct netlink_ext_ack *extack, u32 ext_flags) + { + int err = 0; +@@ -1188,11 +1197,12 @@ static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br, + "FDB entry towards bridge must be permanent"); + return -EINVAL; + } +- err = br_fdb_external_learn_add(br, p, addr, vid, false, true, +- ext_flags); ++ err = br_fdb_external_learn_add(br, p, addr, vid, protocol, ++ false, true, ext_flags); + } else { + spin_lock_bh(&br->hash_lock); +- err = fdb_add_entry(br, p, addr, ndm, nlh_flags, vid, nfea_tb); ++ err = fdb_add_entry(br, p, addr, ndm, nlh_flags, vid, ++ protocol, nfea_tb); + spin_unlock_bh(&br->hash_lock); + } + +@@ -1215,6 +1225,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + struct net_bridge_port *p = NULL; + struct net_bridge_vlan *v; + struct net_bridge *br = NULL; ++ u8 protocol = RTPROT_UNSPEC; + u32 ext_flags = 0; + int err = 0; + +@@ -1247,6 +1258,9 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + if (tb[NDA_FLAGS_EXT]) + ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); + ++ if (tb[NDA_PROTOCOL]) ++ protocol = nla_get_u8(tb[NDA_PROTOCOL]); ++ + if (ext_flags & NTF_EXT_LOCKED) { + NL_SET_ERR_MSG_MOD(extack, "Cannot add FDB entry with \"locked\" flag set"); + return -EINVAL; +@@ -1273,11 +1287,11 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + } + + /* VID was specified, so use it. */ +- err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid, nfea_tb, +- extack, ext_flags); ++ err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid, protocol, ++ nfea_tb, extack, ext_flags); + } else { +- err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, nfea_tb, +- extack, ext_flags); ++ err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, protocol, ++ nfea_tb, extack, ext_flags); + if (err || !vg || !vg->num_vlans) + goto out; + +@@ -1289,7 +1303,8 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + if (!br_vlan_should_use(v)) + continue; + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, v->vid, +- nfea_tb, extack, ext_flags); ++ protocol, nfea_tb, extack, ++ ext_flags); + if (err) + goto out; + } +@@ -1301,7 +1316,7 @@ out: + + static int fdb_delete_by_addr_and_port(struct net_bridge *br, + const struct net_bridge_port *p, +- const u8 *addr, u16 vlan) ++ const u8 *addr, u16 vlan, u8 protocol) + { + struct net_bridge_fdb_entry *fdb; + +@@ -1309,6 +1324,14 @@ static int fdb_delete_by_addr_and_port(struct net_bridge *br, + if (!fdb || READ_ONCE(fdb->dst) != p) + return -ENOENT; + ++ /* If a protocol was provided, delete only matching entries so ++ * independent control-plane and data-plane FDB entries can ++ * coexist without a delete for one protocol removing the other. ++ */ ++ if (protocol != RTPROT_UNSPEC && ++ fdb->protocol != protocol) ++ return -ENOENT; ++ + fdb_delete(br, fdb, true); + + return 0; +@@ -1316,12 +1339,12 @@ static int fdb_delete_by_addr_and_port(struct net_bridge *br, + + static int __br_fdb_delete(struct net_bridge *br, + const struct net_bridge_port *p, +- const unsigned char *addr, u16 vid) ++ const unsigned char *addr, u16 vid, u8 protocol) + { + int err; + + spin_lock_bh(&br->hash_lock); +- err = fdb_delete_by_addr_and_port(br, p, addr, vid); ++ err = fdb_delete_by_addr_and_port(br, p, addr, vid, protocol); + spin_unlock_bh(&br->hash_lock); + + return err; +@@ -1335,10 +1358,14 @@ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], + { + struct net_bridge_vlan_group *vg; + struct net_bridge_port *p = NULL; ++ u8 protocol = RTPROT_UNSPEC; + struct net_bridge_vlan *v; + struct net_bridge *br; + int err; + ++ if (tb[NDA_PROTOCOL]) ++ protocol = nla_get_u8(tb[NDA_PROTOCOL]); ++ + if (netif_is_bridge_master(dev)) { + br = netdev_priv(dev); + vg = br_vlan_group(br); +@@ -1360,17 +1387,17 @@ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], + return -EINVAL; + } + +- err = __br_fdb_delete(br, p, addr, vid); ++ err = __br_fdb_delete(br, p, addr, vid, protocol); + } else { + err = -ENOENT; +- err &= __br_fdb_delete(br, p, addr, 0); ++ err &= __br_fdb_delete(br, p, addr, 0, protocol); + if (!vg || !vg->num_vlans) + return err; + + list_for_each_entry(v, &vg->vlan_list, vlist) { + if (!br_vlan_should_use(v)) + continue; +- err &= __br_fdb_delete(br, p, addr, v->vid); ++ err &= __br_fdb_delete(br, p, addr, v->vid, protocol); + } + } + +@@ -1430,8 +1457,8 @@ void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p) + } + + int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, +- const unsigned char *addr, u16 vid, bool locked, +- bool swdev_notify, u32 ext_flags) ++ const unsigned char *addr, u16 vid, u8 protocol, ++ bool locked, bool swdev_notify, u32 ext_flags) + { + struct net_bridge_fdb_entry *fdb; + bool modified = false; +@@ -1465,6 +1492,7 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + err = -ENOMEM; + goto err_unlock; + } ++ fdb->protocol = protocol; + fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); + } else { + if (locked && +@@ -1512,6 +1540,12 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags)) + atomic_dec(&br->fdb_n_learned); + ++ if (protocol != RTPROT_UNSPEC && ++ fdb->protocol != protocol) { ++ modified = true; ++ fdb->protocol = protocol; ++ } ++ + if (modified) + fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); + } +diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h +index bb431db..022b7b4 100644 +--- a/net/bridge/br_private.h ++++ b/net/bridge/br_private.h +@@ -292,6 +292,7 @@ struct net_bridge_fdb_entry { + struct net_bridge_fdb_key key; + struct hlist_node fdb_node; + unsigned long flags; ++ u8 protocol; + + /* write-heavy members should not affect lookups */ + unsigned long updated ____cacheline_aligned_in_smp; +@@ -870,7 +871,8 @@ int br_fdb_sync_static(struct net_bridge *br, struct net_bridge_port *p); + void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p); + int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + const unsigned char *addr, u16 vid, +- bool locked, bool swdev_notify, u32 ext_flags); ++ u8 protocol, bool locked, bool swdev_notify, ++ u32 ext_flags); + int br_fdb_external_learn_del(struct net_bridge *br, struct net_bridge_port *p, + const unsigned char *addr, u16 vid, + bool swdev_notify); +-- +2.34.1 diff --git a/patches-sonic/0002-platform-mellanox-mlxbf-pmc-Add-support-for-monitori.patch b/patches-sonic/0002-platform-mellanox-mlxbf-pmc-Add-support-for-monitori.patch new file mode 100644 index 000000000..88354f80e --- /dev/null +++ b/patches-sonic/0002-platform-mellanox-mlxbf-pmc-Add-support-for-monitori.patch @@ -0,0 +1,126 @@ +From f9a8958b29d408941ad18087f3f2c19f06c820fb Mon Sep 17 00:00:00 2001 +From: Shravan Kumar Ramani +Date: Thu, 9 Jan 2025 09:39:21 -0500 +Subject: [PATCH 02/78] platform/mellanox: mlxbf-pmc: Add support for + monitoring cycle count +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add support for programming any counter to monitor the cycle count. +This will allow the user to repurpose and dedicate any of the counters +in the block to counting cycles. + +Signed-off-by: Shravan Kumar Ramani +Reviewed-by: David Thompson +Reviewed-by: Vadim Pasternak +Link: https://lore.kernel.org/r/bd7747897cf1ce77e2e39e271cac21684830f7e7.1736413033.git.shravankr@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxbf-pmc.c | 61 +++++++++++++++++++++++++++++++++++ + 1 file changed, 61 insertions(+) + +diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c +index fbb8128..197a98e 100644 +--- a/drivers/platform/mellanox/mlxbf-pmc.c ++++ b/drivers/platform/mellanox/mlxbf-pmc.c +@@ -88,6 +88,7 @@ + #define MLXBF_PMC_CRSPACE_PERFMON_CTL(n) (n * MLXBF_PMC_CRSPACE_PERFMON_REG0_SZ) + #define MLXBF_PMC_CRSPACE_PERFMON_EN BIT(30) + #define MLXBF_PMC_CRSPACE_PERFMON_CLR BIT(28) ++#define MLXBF_PMC_CRSPACE_PERFMON_COUNT_CLOCK(n) (MLXBF_PMC_CRSPACE_PERFMON_CTL(n) + 0x4) + #define MLXBF_PMC_CRSPACE_PERFMON_VAL0(n) (MLXBF_PMC_CRSPACE_PERFMON_CTL(n) + 0xc) + + /** +@@ -114,6 +115,7 @@ struct mlxbf_pmc_attribute { + * @attr_event: Attributes for "event" sysfs files + * @attr_event_list: Attributes for "event_list" sysfs files + * @attr_enable: Attributes for "enable" sysfs files ++ * @attr_count_clock: Attributes for "count_clock" sysfs files + * @block_attr: All attributes needed for the block + * @block_attr_grp: Attribute group for the block + */ +@@ -126,6 +128,7 @@ struct mlxbf_pmc_block_info { + struct mlxbf_pmc_attribute *attr_event; + struct mlxbf_pmc_attribute attr_event_list; + struct mlxbf_pmc_attribute attr_enable; ++ struct mlxbf_pmc_attribute attr_count_clock; + struct attribute *block_attr[MLXBF_PMC_MAX_ATTRS]; + struct attribute_group block_attr_grp; + }; +@@ -1763,6 +1766,49 @@ static ssize_t mlxbf_pmc_enable_store(struct device *dev, + return count; + } + ++/* Show function for "count_clock" sysfs files - only for crspace */ ++static ssize_t mlxbf_pmc_count_clock_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct mlxbf_pmc_attribute *attr_count_clock = container_of( ++ attr, struct mlxbf_pmc_attribute, dev_attr); ++ unsigned int blk_num; ++ u32 reg; ++ ++ blk_num = attr_count_clock->nr; ++ ++ if (mlxbf_pmc_readl(pmc->block[blk_num].mmio_base + ++ MLXBF_PMC_CRSPACE_PERFMON_COUNT_CLOCK(pmc->block[blk_num].counters), ++ ®)) ++ return -EINVAL; ++ ++ return sysfs_emit(buf, "%u\n", reg); ++} ++ ++/* Store function for "count_clock" sysfs files - only for crspace */ ++static ssize_t mlxbf_pmc_count_clock_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct mlxbf_pmc_attribute *attr_count_clock = container_of( ++ attr, struct mlxbf_pmc_attribute, dev_attr); ++ unsigned int blk_num; ++ u32 reg; ++ int err; ++ ++ blk_num = attr_count_clock->nr; ++ ++ err = kstrtouint(buf, 0, ®); ++ if (err < 0) ++ return err; ++ ++ mlxbf_pmc_write(pmc->block[blk_num].mmio_base + ++ MLXBF_PMC_CRSPACE_PERFMON_COUNT_CLOCK(pmc->block[blk_num].counters), ++ MLXBF_PMC_WRITE_REG_32, reg); ++ ++ return count; ++} ++ + /* Populate attributes for blocks with counters to monitor performance */ + static int mlxbf_pmc_init_perftype_counter(struct device *dev, unsigned int blk_num) + { +@@ -1801,6 +1847,21 @@ static int mlxbf_pmc_init_perftype_counter(struct device *dev, unsigned int blk_ + attr = NULL; + } + ++ if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_CRSPACE) { ++ /* Program crspace counters to count clock cycles using "count_clock" sysfs */ ++ attr = &pmc->block[blk_num].attr_count_clock; ++ attr->dev_attr.attr.mode = 0644; ++ attr->dev_attr.show = mlxbf_pmc_count_clock_show; ++ attr->dev_attr.store = mlxbf_pmc_count_clock_store; ++ attr->nr = blk_num; ++ attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, ++ "count_clock"); ++ if (!attr->dev_attr.attr.name) ++ return -ENOMEM; ++ pmc->block[blk_num].block_attr[++i] = &attr->dev_attr.attr; ++ attr = NULL; ++ } ++ + pmc->block[blk_num].attr_counter = devm_kcalloc( + dev, pmc->block[blk_num].counters, + sizeof(struct mlxbf_pmc_attribute), GFP_KERNEL); +-- +2.8.4 + diff --git a/patches-sonic/0002-regmap-i2c-fix-sparse-warning-in-regmap_smbus_word_w.patch b/patches-sonic/0002-regmap-i2c-fix-sparse-warning-in-regmap_smbus_word_w.patch new file mode 100755 index 000000000..56c4e7009 --- /dev/null +++ b/patches-sonic/0002-regmap-i2c-fix-sparse-warning-in-regmap_smbus_word_w.patch @@ -0,0 +1,42 @@ +From 30e29269c68f696bd5c35d73f254f476646d35bc Mon Sep 17 00:00:00 2001 +From: Nishanth Sampath Kumar +Date: Tue, 19 May 2026 09:41:53 -0700 +Subject: [PATCH] regmap-i2c: fix sparse warning in + regmap_smbus_word_write_reg16 + +i2c_smbus_write_word_data() expects a plain u16, but cpu_to_le16() +returns __le16 (a sparse-restricted endian type), causing: + + drivers/base/regmap/regmap-i2c.c:340: sparse: incorrect type in + argument 3 (different base types) + expected unsigned short [usertype] value + got restricted __le16 [usertype] + +SMBus already defines byte ordering internally, so cpu_to_le16() is +wrong here. Replace it with a plain (u16) cast. + +Fixes: bad4bd28abf4 ("regmap-i2c: add SMBus byte/word reg16 bus for adapters lacking I2C_FUNC_I2C") +Reported-by: kernel test robot +Closes: https://lore.kernel.org/oe-kbuild-all/202605161621.mY5zFh4D-lkp@intel.com/ +Signed-off-by: Nishanth Sampath Kumar +[ upstream 19a5211cda12 ] +--- + drivers/base/regmap/regmap-i2c.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c +index 31e30dfced19..51a04961faf7 100644 +--- a/drivers/base/regmap/regmap-i2c.c ++++ b/drivers/base/regmap/regmap-i2c.c +@@ -337,7 +337,7 @@ static int regmap_smbus_word_write_reg16(void *context, const void *data, + val = ((u8 *)data)[2]; + + return i2c_smbus_write_word_data(i2c, addr_hi, +- cpu_to_le16(((u16)val << 8) | addr_lo)); ++ ((u16)val << 8) | addr_lo); + } + + static const struct regmap_bus regmap_smbus_byte_word_reg16 = { +-- +2.25.1 + diff --git a/patches-sonic/0002-x86-CPU-AMD-Ignore-invalid-reset-reason-value.patch b/patches-sonic/0002-x86-CPU-AMD-Ignore-invalid-reset-reason-value.patch new file mode 100644 index 000000000..911861ce2 --- /dev/null +++ b/patches-sonic/0002-x86-CPU-AMD-Ignore-invalid-reset-reason-value.patch @@ -0,0 +1,64 @@ +From aa429e1fbeaa168555aea99038f30a0e05b369e5 Mon Sep 17 00:00:00 2001 +From: Yazen Ghannam +Date: Mon, 21 Jul 2025 18:11:54 +0000 +Subject: [PATCH 2/2] x86/CPU/AMD: Ignore invalid reset reason value + +[ Upstream commit e9576e078220c50ace9e9087355423de23e25fa5 ] + +The reset reason value may be "all bits set", e.g. 0xFFFFFFFF. This is a +commonly used error response from hardware. This may occur due to a real +hardware issue or when running in a VM. + +The user will see all reset reasons reported in this case. + +Check for an error response value and return early to avoid decoding +invalid data. + +Also, adjust the data variable type to match the hardware register size. + +Fixes: ab8131028710 ("x86/CPU/AMD: Print the reason for the last reset") +Reported-by: Libing He +Signed-off-by: Yazen Ghannam +Signed-off-by: Borislav Petkov (AMD) +Reviewed-by: Mario Limonciello +Cc: stable@vger.kernel.org +Link: https://lore.kernel.org/20250721181155.3536023-1-yazen.ghannam@amd.com +--- + arch/x86/kernel/cpu/amd.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c +index c3194101a92a..b40f841479f7 100644 +--- a/arch/x86/kernel/cpu/amd.c ++++ b/arch/x86/kernel/cpu/amd.c +@@ -1243,8 +1243,8 @@ static const char * const s5_reset_reason_txt[] = { + + static __init int print_s5_reset_status_mmio(void) + { +- unsigned long value; + void __iomem *addr; ++ u32 value; + int i; + + if (!cpu_feature_enabled(X86_FEATURE_ZEN)) +@@ -1257,12 +1257,16 @@ static __init int print_s5_reset_status_mmio(void) + value = ioread32(addr); + iounmap(addr); + ++ /* Value with "all bits set" is an error response and should be ignored. */ ++ if (value == U32_MAX) ++ return 0; ++ + for (i = 0; i < ARRAY_SIZE(s5_reset_reason_txt); i++) { + if (!(value & BIT(i))) + continue; + + if (s5_reset_reason_txt[i]) { +- pr_info("x86/amd: Previous system reset reason [0x%08lx]: %s\n", ++ pr_info("x86/amd: Previous system reset reason [0x%08x]: %s\n", + value, s5_reset_reason_txt[i]); + } + } +-- +2.43.0 + diff --git a/patches-sonic/0003-PCI-AER-Trace-error-event-before-ratelimiting.patch b/patches-sonic/0003-PCI-AER-Trace-error-event-before-ratelimiting.patch new file mode 100644 index 000000000..eccb87deb --- /dev/null +++ b/patches-sonic/0003-PCI-AER-Trace-error-event-before-ratelimiting.patch @@ -0,0 +1,69 @@ +From b6cbde42bc2406d734511a7b28499f9fda4fd91c Mon Sep 17 00:00:00 2001 +From: Bjorn Helgaas +Date: Thu, 22 May 2025 18:21:17 -0500 +Subject: [PATCH 03/12] PCI/AER: Trace error event before ratelimiting +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +As with the AER statistics, we always want to emit trace events, even if +the actual dmesg logging is rate limited. + +Call trace_aer_event() immediately after pci_dev_aer_stats_incr() so both +happen before ratelimiting. + +Signed-off-by: Bjorn Helgaas +Tested-by: Krzysztof Wilczyński +Reviewed-by: Ilpo Järvinen +Reviewed-by: Kuppuswamy Sathyanarayanan +Link: https://patch.msgid.link/20250522232339.1525671-12-helgaas@kernel.org +(cherry picked from commit 6bb4befbd65fa7f99688fb707e376637e5acfe36) +--- + drivers/pci/pcie/aer.c | 10 ++++------ + 1 file changed, 4 insertions(+), 6 deletions(-) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 48a33151d145..6fc993d05647 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -703,6 +703,8 @@ void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) + const char *level; + + pci_dev_aer_stats_incr(dev, info); ++ trace_aer_event(pci_name(dev), (info->status & ~info->mask), ++ info->severity, info->tlp_header_valid, &info->tlp); + + if (!info->status) { + pci_err(dev, "PCIe Bus Error: severity=%s, type=Inaccessible, (Unregistered Agent ID)\n", +@@ -730,9 +732,6 @@ void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) + out: + if (info->id && info->error_dev_num > 1 && info->id == id) + pci_err(dev, " Error of this Agent is reported first\n"); +- +- trace_aer_event(pci_name(dev), (info->status & ~info->mask), +- info->severity, info->tlp_header_valid, &info->tlp); + } + + static void aer_print_port_info(struct pci_dev *dev, struct aer_err_info *info) +@@ -785,6 +784,8 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + info.mask = mask; + + pci_dev_aer_stats_incr(dev, &info); ++ trace_aer_event(pci_name(dev), (status & ~mask), ++ aer_severity, tlp_header_valid, &aer->header_log); + + layer = AER_GET_LAYER_ERROR(aer_severity, status); + agent = AER_GET_AGENT(aer_severity, status); +@@ -800,9 +801,6 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + + if (tlp_header_valid) + __print_tlp_header(dev, &aer->header_log); +- +- trace_aer_event(pci_name(dev), (status & ~mask), +- aer_severity, tlp_header_valid, &aer->header_log); + } + EXPORT_SYMBOL_NS_GPL(pci_print_aer, CXL); + +-- +2.47.0 + diff --git a/patches-sonic/0003-aspeed-ast2700-shared-kernel-changes-update-to-6.12.94-00.07.04.patch b/patches-sonic/0003-aspeed-ast2700-shared-kernel-changes-update-to-6.12.94-00.07.04.patch new file mode 100644 index 000000000..997312d1c --- /dev/null +++ b/patches-sonic/0003-aspeed-ast2700-shared-kernel-changes-update-to-6.12.94-00.07.04.patch @@ -0,0 +1,1102 @@ +From: Chandrasekaran Swaminathan +Date: Wed, 09 Sep 2026 15:51:49 +0000 +Subject: [PATCH] Aspeed AST2700 support: modifications to shared/common kernel files + +Modifications to existing vendor-independent (shared/common) kernel +files - the common-code review surface (Kconfig/Makefile merges, +ftgmac100, pwm/core, clk-provider, pwm.h, videodev2.h), from a +fresh Aspeed merge at v00.07.04 on the 6.12.94 SONiC kernel. + +Signed-off-by: Chandrasekaran Swaminathan +--- +diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms +--- a/arch/arm64/Kconfig.platforms 2026-06-19 11:42:39.000000000 +0000 ++++ b/arch/arm64/Kconfig.platforms 2026-09-09 15:51:44.951234551 +0000 +@@ -40,6 +40,13 @@ + This enables support for Apple's in-house ARM SoC family, starting + with the Apple M1. + ++config ARCH_ASPEED ++ bool "Aspeed SoC family" ++ select AST2700_IRQ ++ help ++ Say yes if you intend to run on an Aspeed ast2700 or similar ++ seventh generation Aspeed BMCs. ++ + menuconfig ARCH_BCM + bool "Broadcom SoC Support" + +diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile +--- a/arch/arm64/boot/dts/Makefile 2026-09-09 15:51:11.436740996 +0000 ++++ b/arch/arm64/boot/dts/Makefile 2026-09-09 15:51:44.986234022 +0000 +@@ -9,6 +9,7 @@ + subdir-y += apm + subdir-y += apple + subdir-y += arm ++subdir-y += aspeed + subdir-y += bitmain + subdir-y += broadcom + subdir-y += cavium +diff --git a/crypto/Makefile b/crypto/Makefile +--- a/crypto/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/crypto/Makefile 2026-09-09 15:51:31.778433603 +0000 +@@ -181,6 +181,7 @@ + obj-$(CONFIG_CRYPTO_ECC) += ecc.o + obj-$(CONFIG_CRYPTO_ESSIV) += essiv.o + obj-$(CONFIG_CRYPTO_CURVE25519) += curve25519-generic.o ++obj-$(CONFIG_CRYPTO_DEV_ASPEED) += aspeed_crypt.o + + ecdh_generic-y += ecdh.o + ecdh_generic-y += ecdh_helper.o +diff --git a/drivers/Makefile b/drivers/Makefile +--- a/drivers/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/Makefile 2026-09-09 15:51:35.330379930 +0000 +@@ -195,3 +195,4 @@ + obj-$(CONFIG_DPLL) += dpll/ + + obj-$(CONFIG_S390) += s390/ ++obj-$(CONFIG_JTAG_ASPEED) += jtag/ +diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig +--- a/drivers/bus/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/bus/Kconfig 2026-09-09 15:51:29.763464053 +0000 +@@ -261,6 +261,12 @@ + configuration. Allows to adjust the priorities of all master + peripherals. + ++config ASPEED_LTPI ++ bool "Aspeed LTPI bus controller driver" ++ depends on ARCH_ASPEED ++ help ++ LVDS Tunneling Protocol and Interface (LTPI) bus controller ++ + source "drivers/bus/fsl-mc/Kconfig" + source "drivers/bus/mhi/Kconfig" + +diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile +--- a/drivers/bus/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/bus/Makefile 2026-09-09 15:51:34.715389223 +0000 +@@ -39,6 +39,7 @@ + obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o + + obj-$(CONFIG_DA8XX_MSTPRI) += da8xx-mstpri.o ++obj-$(CONFIG_ASPEED_LTPI) += aspeed-ltpi.o + + # MHI + obj-y += mhi/ +diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig +--- a/drivers/char/hw_random/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/char/hw_random/Kconfig 2026-09-09 15:51:30.808448261 +0000 +@@ -588,6 +588,21 @@ + + If unsure, say Y. + ++config HW_RANDOM_ASPEED ++ tristate "Aspeed Random Number Generator support" ++ depends on ARCH_ASPEED ++ default HW_RANDOM ++ help ++ This driver provides kernel-side support for the Random Number ++ Generator hardware found on Aspeed ast2600/ast2700 devices. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called aspeed-rng. ++ ++ If unsure, say Y. ++ ++source "drivers/char/hw_random/dwc/Kconfig" ++ + endif # HW_RANDOM + + config UML_RANDOM +diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile +--- a/drivers/char/hw_random/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/char/hw_random/Makefile 2026-09-09 15:51:36.020369504 +0000 +@@ -50,3 +50,5 @@ + obj-$(CONFIG_HW_RANDOM_POLARFIRE_SOC) += mpfs-rng.o + obj-$(CONFIG_HW_RANDOM_ROCKCHIP) += rockchip-rng.o + obj-$(CONFIG_HW_RANDOM_JH7110) += jh7110-trng.o ++obj-$(CONFIG_HW_RANDOM_ASPEED) += aspeed-rng.o ++obj-$(CONFIG_HW_RANDOM_DWC) += dwc/ +diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig +--- a/drivers/clk/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/clk/Kconfig 2026-09-09 15:51:28.136488639 +0000 +@@ -277,6 +277,20 @@ + The G4 and G5 series, including the ast2400 and ast2500, are supported + by this driver. + ++config COMMON_CLK_AST2700 ++ bool "Clock driver for AST2700 SoC" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ help ++ This driver provides support for clock on AST2700 SoC. ++ The driver is responsible for managing the various clocks required ++ by the peripherals and cores within the AST2700. ++ ++config COMMON_CLK_AST1700 ++ bool "Clock driver for AST1700" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ help ++ This driver supports the AST1700 clocks on the Aspeed BMC platforms. ++ + config COMMON_CLK_S2MPS11 + tristate "Clock driver for S2MPS1X/S5M8767 MFD" + depends on MFD_SEC_CORE || COMPILE_TEST +diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile +--- a/drivers/clk/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/clk/Makefile 2026-09-09 15:51:32.830417706 +0000 +@@ -48,6 +48,8 @@ + obj-$(CONFIG_COMMON_CLK_GEMINI) += clk-gemini.o + obj-$(CONFIG_COMMON_CLK_ASPEED) += clk-aspeed.o + obj-$(CONFIG_MACH_ASPEED_G6) += clk-ast2600.o ++obj-$(CONFIG_COMMON_CLK_AST2700) += clk-ast2700.o ++obj-$(CONFIG_COMMON_CLK_AST1700) += clk-ast1700.o + obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o + obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o + obj-$(CONFIG_COMMON_CLK_K210) += clk-k210.o +diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig +--- a/drivers/edac/Kconfig 2026-09-09 15:51:11.454740724 +0000 ++++ b/drivers/edac/Kconfig 2026-09-09 15:51:28.786478817 +0000 +@@ -520,6 +520,15 @@ + First, ECC must be configured in the bootloader. Then, this driver + will expose error counters via the EDAC kernel framework. + ++config EDAC_AST2700 ++ tristate "Aspeed AST2700 BMC SoC" ++ depends on ARCH_ASPEED ++ help ++ Support for error detection and correction on the Aspeed AST2700 ++ ++ First, ECC must be configured in the bootloader. Then, this driver ++ will expose error counters via the EDAC kernel framework. ++ + config EDAC_BLUEFIELD + tristate "Mellanox BlueField Memory ECC" + depends on ARM64 && ((MELLANOX_PLATFORM && ACPI) || COMPILE_TEST) +diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile +--- a/drivers/edac/Makefile 2026-09-09 15:51:11.454740724 +0000 ++++ b/drivers/edac/Makefile 2026-09-09 15:51:33.542406948 +0000 +@@ -83,6 +83,7 @@ + obj-$(CONFIG_EDAC_TI) += ti_edac.o + obj-$(CONFIG_EDAC_QCOM) += qcom_edac.o + obj-$(CONFIG_EDAC_ASPEED) += aspeed_edac.o ++obj-$(CONFIG_EDAC_AST2700) += edac_ast2700.o + obj-$(CONFIG_EDAC_BLUEFIELD) += bluefield_edac.o + obj-$(CONFIG_EDAC_DMC520) += dmc520_edac.o + obj-$(CONFIG_EDAC_ELBA) += elba_edac.o +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +--- a/drivers/gpio/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpio/Kconfig 2026-09-09 15:51:30.550452160 +0000 +@@ -180,6 +180,14 @@ + help + Say Y here to support Aspeed AST2500 SGPIO functionality. + ++config GPIO_ASPEED_LTPI ++ bool "Aspeed LTPI GPIO support" ++ depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO ++ select GPIO_GENERIC ++ select GPIOLIB_IRQCHIP ++ help ++ Say Y here to support Aspeed AST2700 LTPI GPIO functionality. ++ + config GPIO_ATH79 + tristate "Atheros AR71XX/AR724X/AR913X GPIO support" + default y if ATH79 +diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile +--- a/drivers/gpio/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/gpio/Makefile 2026-09-09 15:51:35.743373689 +0000 +@@ -37,6 +37,7 @@ + obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o + obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o + obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o ++obj-$(CONFIG_GPIO_ASPEED_LTPI) += gpio-aspeed-ltpi.o + obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o + obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o + obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/hwmon/Kconfig 2026-09-09 15:51:28.650480872 +0000 +@@ -423,6 +423,16 @@ + This driver can also be built as a module. If so, the module + will be called aspeed_g6_pwm_tach. + ++config SENSORS_ASPEED_CHASSIS ++ tristate "ASPEED CHASSIS Driver" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ help ++ This driver provides support for Aspeed ast2600 chassis intruded ++ detect support. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called aspeed-chassis. ++ + config SENSORS_ATXP1 + tristate "Attansic ATXP1 VID controller" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +--- a/drivers/hwmon/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/hwmon/Makefile 2026-09-09 15:51:33.388409275 +0000 +@@ -55,6 +55,7 @@ + obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o + obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o + obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o ++obj-$(CONFIG_SENSORS_ASPEED_CHASSIS) += aspeed-chassis.o + obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o + obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o + obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o +diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +--- a/drivers/i2c/busses/Kconfig 2026-09-09 15:51:11.425741162 +0000 ++++ b/drivers/i2c/busses/Kconfig 2026-09-09 15:51:28.068489667 +0000 +@@ -421,6 +421,17 @@ + This driver can also be built as a module. If so, the module + will be called i2c-altera. + ++config I2C_AST2600 ++ tristate "Aspeed I2C v2 Controller" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ select I2C_SMBUS ++ help ++ If you say yes to this option, support will be included for the ++ Aspeed I2C controller with new register set. ++ ++ This driver can also be built as a module. If so, the module ++ will be called i2c-ast2600. ++ + config I2C_ASPEED + tristate "Aspeed I2C Controller" + depends on ARCH_ASPEED || COMPILE_TEST +diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +--- a/drivers/i2c/busses/Makefile 2026-09-09 15:51:11.426741147 +0000 ++++ b/drivers/i2c/busses/Makefile 2026-09-09 15:51:32.739419081 +0000 +@@ -39,6 +39,7 @@ + obj-$(CONFIG_I2C_ALTERA) += i2c-altera.o + obj-$(CONFIG_I2C_AMD_MP2) += i2c-amd-mp2-pci.o i2c-amd-mp2-plat.o + obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o ++obj-$(CONFIG_I2C_AST2600) += i2c-ast2600.o + obj-$(CONFIG_I2C_AT91) += i2c-at91.o + i2c-at91-objs := i2c-at91-core.o i2c-at91-master.o + ifeq ($(CONFIG_I2C_AT91_SLAVE_EXPERIMENTAL),y) +diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig +--- a/drivers/irqchip/Kconfig 2026-09-09 15:51:11.465740558 +0000 ++++ b/drivers/irqchip/Kconfig 2026-09-09 15:51:30.232456966 +0000 +@@ -735,6 +735,9 @@ + Support for the Apple Interrupt Controller found on Apple Silicon SoCs, + such as the M1. + ++config AST2700_IRQ ++ bool ++ + config MCHP_EIC + bool "Microchip External Interrupt Controller" + depends on ARCH_AT91 || COMPILE_TEST +diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile +--- a/drivers/irqchip/Makefile 2026-09-09 15:51:11.465740558 +0000 ++++ b/drivers/irqchip/Makefile 2026-09-09 15:51:35.390379023 +0000 +@@ -85,6 +85,8 @@ + obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o + obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o + obj-$(CONFIG_STM32MP_EXTI) += irq-stm32mp-exti.o ++obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o irq-aspeed-intc.o irq-aspeed-e2m-ic.o ++obj-$(CONFIG_AST2700_IRQ) += irq-ast2700-intc0.o irq-ast2700-intc1.o + obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o + obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o + obj-$(CONFIG_IRQ_UNIPHIER_AIDET) += irq-uniphier-aidet.o +diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig +--- a/drivers/mailbox/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/mailbox/Kconfig 2026-09-09 15:51:30.484453157 +0000 +@@ -295,4 +295,12 @@ + acts as an interrupt controller for receiving interrupts from clients. + Say Y here if you want to build this driver. + ++config AST2700_MBOX ++ tristate "ASPEED AST2700 IPC driver" ++ depends on ARCH_ASPEED ++ help ++ Say y here to enable support for the AST2700 IPC mailbox driver, ++ providing an interface for invoking the inter-process communication ++ signals from the application processor to other masters. ++ + endif +diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile +--- a/drivers/mailbox/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/mailbox/Makefile 2026-09-09 15:51:35.677374687 +0000 +@@ -64,3 +64,5 @@ + obj-$(CONFIG_QCOM_CPUCP_MBOX) += qcom-cpucp-mbox.o + + obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o ++ ++obj-$(CONFIG_AST2700_MBOX) += ast2700-mailbox.o +diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig +--- a/drivers/net/can/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/net/can/Kconfig 2026-09-09 15:51:29.478468360 +0000 +@@ -66,6 +66,12 @@ + + if CAN_NETLINK + ++config CAN_ASPEED ++ tristate "ASPEED CAN" ++ depends on ARM64 || COMPILE_TEST ++ help ++ ASPEED CAN driver. ++ + config CAN_CALC_BITTIMING + bool "CAN bit-timing calculation" + default y +diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile +--- a/drivers/net/can/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/net/can/Makefile 2026-09-09 15:51:34.404393922 +0000 +@@ -15,6 +15,7 @@ + obj-y += usb/ + obj-y += softing/ + ++obj-$(CONFIG_CAN_ASPEED) += aspeed_can.o + obj-$(CONFIG_CAN_AT91) += at91_can.o + obj-$(CONFIG_CAN_BXCAN) += bxcan.o + obj-$(CONFIG_CAN_CAN327) += can327.o +diff --git a/drivers/net/ethernet/faraday/Kconfig b/drivers/net/ethernet/faraday/Kconfig +--- a/drivers/net/ethernet/faraday/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/net/ethernet/faraday/Kconfig 2026-09-09 15:51:44.742237709 +0000 +@@ -6,7 +6,7 @@ + config NET_VENDOR_FARADAY + bool "Faraday devices" + default y +- depends on ARM || COMPILE_TEST ++ depends on ARM || ARM64 || COMPILE_TEST + help + If you have a network (Ethernet) card belonging to this class, say Y. + +@@ -28,11 +28,10 @@ + + config FTGMAC100 + tristate "Faraday FTGMAC100 Gigabit Ethernet support" +- depends on ARM || COMPILE_TEST +- depends on !64BIT || BROKEN ++ depends on ARM || ARM64 || COMPILE_TEST + select PHYLIB + select FIXED_PHY +- select MDIO_ASPEED if MACH_ASPEED_G6 ++ select MDIO_ASPEED if ARCH_ASPEED + select CRC32 + help + This driver supports the FTGMAC100 Gigabit Ethernet controller +diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c +--- a/drivers/net/ethernet/faraday/ftgmac100.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/net/ethernet/faraday/ftgmac100.c 2026-09-09 15:51:44.742237709 +0000 +@@ -9,6 +9,7 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include + #include + #include + #include +@@ -19,12 +20,16 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include + #include ++#include ++#include ++#include + #include + #include + +@@ -98,6 +103,7 @@ + struct work_struct reset_task; + struct mii_bus *mii_bus; + struct clk *clk; ++ struct reset_control *rst; + + /* AST2500/AST2600 RMII ref clock gate */ + struct clk *rclk; +@@ -119,6 +125,9 @@ + /* Misc */ + bool need_mac_restart; + bool is_aspeed; ++ ++ /* AST2700 SGMII */ ++ struct phy *sgmii; + }; + + static int ftgmac100_reset_mac(struct ftgmac100 *priv, u32 maccr) +@@ -148,6 +157,29 @@ + { + u32 maccr = 0; + ++ /* Aspeed SoC needs SCU reset */ ++ if (priv->is_aspeed) { ++ int err; ++ ++ err = phy_reset(priv->sgmii); ++ if (err) { ++ dev_err(priv->dev, "Failed to reset sgmii\n"); ++ return err; ++ } ++ ++ err = reset_control_assert(priv->rst); ++ if (err) { ++ dev_err(priv->dev, "Failed to reset mac (%d)\n", err); ++ return err; ++ } ++ usleep_range(10000, 20000); ++ err = reset_control_deassert(priv->rst); ++ if (err) { ++ dev_err(priv->dev, "Failed to deassert mac reset (%d)\n", err); ++ return err; ++ } ++ } ++ + switch (priv->cur_speed) { + case SPEED_10: + case 0: /* no link */ +@@ -265,10 +297,12 @@ + iowrite32(reg, priv->base + FTGMAC100_OFFSET_ISR); + + /* Setup RX ring buffer base */ +- iowrite32(priv->rxdes_dma, priv->base + FTGMAC100_OFFSET_RXR_BADR); ++ iowrite32(lower_32_bits(priv->rxdes_dma), priv->base + FTGMAC100_OFFSET_RXR_BADR); ++ iowrite32(upper_32_bits(priv->rxdes_dma), priv->base + FTGMAC100_OFFSET_RXR_BADDR_HIGH); + + /* Setup TX ring buffer base */ +- iowrite32(priv->txdes_dma, priv->base + FTGMAC100_OFFSET_NPTXR_BADR); ++ iowrite32(lower_32_bits(priv->txdes_dma), priv->base + FTGMAC100_OFFSET_NPTXR_BADR); ++ iowrite32(upper_32_bits(priv->txdes_dma), priv->base + FTGMAC100_OFFSET_TXR_BADDR_HIGH); + + /* Configure RX buffer size */ + iowrite32(FTGMAC100_RBSR_SIZE(RX_BUF_SIZE), +@@ -321,6 +355,7 @@ + static void ftgmac100_start_hw(struct ftgmac100 *priv) + { + u32 maccr = ioread32(priv->base + FTGMAC100_OFFSET_MACCR); ++ struct phy_device *phydev = priv->netdev->phydev; + + /* Keep the original GMAC and FAST bits */ + maccr &= (FTGMAC100_MACCR_FAST_MODE | FTGMAC100_MACCR_GIGA_MODE); +@@ -349,12 +384,18 @@ + if (priv->netdev->features & NETIF_F_HW_VLAN_CTAG_RX) + maccr |= FTGMAC100_MACCR_RM_VLAN; + ++ if (of_device_is_compatible(priv->dev->of_node, "aspeed,ast2700-mac") && ++ phydev && phydev->interface == PHY_INTERFACE_MODE_RMII) ++ maccr |= FTGMAC100_MACCR_RMII_ENABLE; ++ + /* Hit the HW */ + iowrite32(maccr, priv->base + FTGMAC100_OFFSET_MACCR); + } + + static void ftgmac100_stop_hw(struct ftgmac100 *priv) + { ++ phy_exit(priv->sgmii); ++ + iowrite32(0, priv->base + FTGMAC100_OFFSET_MACCR); + } + +@@ -425,7 +466,9 @@ + priv->rx_skbs[entry] = skb; + + /* Store DMA address into RX desc */ +- rxdes->rxdes3 = cpu_to_le32(map); ++ rxdes->rxdes2 = cpu_to_le32(FIELD_PREP(FTGMAC100_RXDES2_RXBUF_BADR_HI, ++ upper_32_bits(map))); ++ rxdes->rxdes3 = cpu_to_le32(lower_32_bits(map)); + + /* Ensure the above is ordered vs clearing the OWN bit */ + dma_wmb(); +@@ -551,7 +594,8 @@ + csum_vlan & 0xffff); + + /* Tear down DMA mapping, do necessary cache management */ +- map = le32_to_cpu(rxdes->rxdes3); ++ map = le32_to_cpu(rxdes->rxdes3) | ++ ((le32_to_cpu(rxdes->rxdes2) & FTGMAC100_RXDES2_RXBUF_BADR_HI) << 16); + + #if defined(CONFIG_ARM) && !defined(CONFIG_ARM_DMA_USE_IOMMU) + /* When we don't have an iommu, we can save cycles by not +@@ -628,9 +672,12 @@ + struct ftgmac100_txdes *txdes, + u32 ctl_stat) + { +- dma_addr_t map = le32_to_cpu(txdes->txdes3); ++ dma_addr_t map; + size_t len; + ++ map = le32_to_cpu(txdes->txdes3) | ++ ((le32_to_cpu(txdes->txdes2) & FTGMAC100_TXDES2_TXBUF_BADR_HI) << 16); ++ + if (ctl_stat & FTGMAC100_TXDES0_FTS) { + len = skb_headlen(skb); + dma_unmap_single(priv->dev, map, len, DMA_TO_DEVICE); +@@ -784,7 +831,9 @@ + f_ctl_stat |= FTGMAC100_TXDES0_FTS; + if (nfrags == 0) + f_ctl_stat |= FTGMAC100_TXDES0_LTS; +- txdes->txdes3 = cpu_to_le32(map); ++ txdes->txdes2 = cpu_to_le32(FIELD_PREP(FTGMAC100_TXDES2_TXBUF_BADR_HI, ++ upper_32_bits((ulong)map))); ++ txdes->txdes3 = cpu_to_le32(lower_32_bits(map)); + txdes->txdes1 = cpu_to_le32(csum_vlan); + + /* Next descriptor */ +@@ -812,7 +861,9 @@ + ctl_stat |= FTGMAC100_TXDES0_LTS; + txdes->txdes0 = cpu_to_le32(ctl_stat); + txdes->txdes1 = 0; +- txdes->txdes3 = cpu_to_le32(map); ++ txdes->txdes2 = cpu_to_le32(FIELD_PREP(FTGMAC100_TXDES2_TXBUF_BADR_HI, ++ upper_32_bits((ulong)map))); ++ txdes->txdes3 = cpu_to_le32(lower_32_bits(map)); + + /* Next one */ + pointer = ftgmac100_next_tx_pointer(priv, pointer); +@@ -887,7 +938,10 @@ + for (i = 0; i < priv->rx_q_entries; i++) { + struct ftgmac100_rxdes *rxdes = &priv->rxdes[i]; + struct sk_buff *skb = priv->rx_skbs[i]; +- dma_addr_t map = le32_to_cpu(rxdes->rxdes3); ++ dma_addr_t map; ++ ++ map = le32_to_cpu(rxdes->rxdes3) | ++ ((le32_to_cpu(rxdes->rxdes2) & FTGMAC100_RXDES2_RXBUF_BADR_HI) << 16); + + if (!skb) + continue; +@@ -944,19 +998,19 @@ + priv->tx_skbs = kcalloc(MAX_TX_QUEUE_ENTRIES, sizeof(void *), + GFP_KERNEL); + if (!priv->tx_skbs) +- goto err_free_rx_skbs; ++ return -ENOMEM; + + /* Allocate descriptors */ + priv->rxdes = dma_alloc_coherent(priv->dev, + MAX_RX_QUEUE_ENTRIES * sizeof(struct ftgmac100_rxdes), + &priv->rxdes_dma, GFP_KERNEL); + if (!priv->rxdes) +- goto err_free_tx_skbs; ++ return -ENOMEM; + priv->txdes = dma_alloc_coherent(priv->dev, + MAX_TX_QUEUE_ENTRIES * sizeof(struct ftgmac100_txdes), + &priv->txdes_dma, GFP_KERNEL); + if (!priv->txdes) +- goto err_free_rxdes; ++ return -ENOMEM; + + /* Allocate scratch packet buffer */ + priv->rx_scratch = dma_alloc_coherent(priv->dev, +@@ -964,29 +1018,9 @@ + &priv->rx_scratch_dma, + GFP_KERNEL); + if (!priv->rx_scratch) +- goto err_free_txdes; ++ return -ENOMEM; + + return 0; +- +-err_free_txdes: +- dma_free_coherent(priv->dev, +- MAX_TX_QUEUE_ENTRIES * +- sizeof(struct ftgmac100_txdes), +- priv->txdes, priv->txdes_dma); +- priv->txdes = NULL; +-err_free_rxdes: +- dma_free_coherent(priv->dev, +- MAX_RX_QUEUE_ENTRIES * +- sizeof(struct ftgmac100_rxdes), +- priv->rxdes, priv->rxdes_dma); +- priv->rxdes = NULL; +-err_free_tx_skbs: +- kfree(priv->tx_skbs); +- priv->tx_skbs = NULL; +-err_free_rx_skbs: +- kfree(priv->rx_skbs); +- priv->rx_skbs = NULL; +- return -ENOMEM; + } + + static void ftgmac100_init_rings(struct ftgmac100 *priv) +@@ -1006,7 +1040,9 @@ + for (i = 0; i < priv->rx_q_entries; i++) { + rxdes = &priv->rxdes[i]; + rxdes->rxdes0 = 0; +- rxdes->rxdes3 = cpu_to_le32(priv->rx_scratch_dma); ++ rxdes->rxdes2 = cpu_to_le32(FIELD_PREP(FTGMAC100_RXDES2_RXBUF_BADR_HI, ++ upper_32_bits(priv->rx_scratch_dma))); ++ rxdes->rxdes3 = cpu_to_le32(lower_32_bits(priv->rx_scratch_dma)); + } + /* Mark the end of the ring */ + rxdes->rxdes0 |= cpu_to_le32(priv->rxdes0_edorr_mask); +@@ -1318,6 +1354,44 @@ + return work_done; + } + ++static int ftgmac100_init_sgmii(struct ftgmac100 *priv) ++{ ++ struct device_node *fixed_link_node = NULL; ++ int err; ++ ++ err = phy_init(priv->sgmii); ++ if (err) { ++ dev_err(priv->dev, "Failed to initialize sgmii\n"); ++ return err; ++ } ++ ++ /* If using fixed link in dts, sgmii need to be forced */ ++ fixed_link_node = of_get_child_by_name(priv->dev->of_node, "fixed-link"); ++ if (fixed_link_node) { ++ int speed; ++ ++ if (of_property_read_u32(fixed_link_node, "speed", &speed)) { ++ dev_err(priv->dev, "Failed to read speed from fixed-link node\n"); ++ err = -EINVAL; ++ goto err_out; ++ } ++ ++ err = phy_set_speed(priv->sgmii, speed); ++ if (err) ++ dev_err(priv->dev, "Failed to force sgmii speed\n"); ++ } else { ++ err = phy_set_speed(priv->sgmii, 0); ++ if (err) ++ dev_err(priv->dev, "Failed to enable sgmii Nway\n"); ++ } ++ ++err_out: ++ if (fixed_link_node) ++ of_node_put(fixed_link_node); ++ ++ return err; ++} ++ + static int ftgmac100_init_all(struct ftgmac100 *priv, bool ignore_alloc_err) + { + int err = 0; +@@ -1342,6 +1416,9 @@ + /* Enable all interrupts */ + iowrite32(FTGMAC100_INT_ALL, priv->base + FTGMAC100_OFFSET_IER); + ++ /* Configure SGMII controller */ ++ ftgmac100_init_sgmii(priv); ++ + return err; + } + +@@ -1752,15 +1829,16 @@ + struct ftgmac100 *priv = netdev_priv(netdev); + struct phy_device *phydev = netdev->phydev; + +- if (!phydev) +- return; ++ devm_phy_put(priv->dev, priv->sgmii); + +- phy_disconnect(phydev); +- if (of_phy_is_fixed_link(priv->dev->of_node)) +- of_phy_deregister_fixed_link(priv->dev->of_node); ++ if (phydev) { ++ phy_disconnect(phydev); ++ if (of_phy_is_fixed_link(priv->dev->of_node)) ++ of_phy_deregister_fixed_link(priv->dev->of_node); + +- if (priv->use_ncsi) +- fixed_phy_unregister(phydev); ++ if (priv->use_ncsi) ++ fixed_phy_unregister(phydev); ++ } + } + + static void ftgmac100_destroy_mdio(struct net_device *netdev) +@@ -1903,7 +1981,8 @@ + np = pdev->dev.of_node; + if (np && (of_device_is_compatible(np, "aspeed,ast2400-mac") || + of_device_is_compatible(np, "aspeed,ast2500-mac") || +- of_device_is_compatible(np, "aspeed,ast2600-mac"))) { ++ of_device_is_compatible(np, "aspeed,ast2600-mac") || ++ of_device_is_compatible(np, "aspeed,ast2700-mac"))) { + priv->rxdes0_edorr_mask = BIT(30); + priv->txdes0_edotr_mask = BIT(30); + priv->is_aspeed = true; +@@ -1934,40 +2013,22 @@ + goto err_phy_connect; + } + err = phy_connect_direct(netdev, phydev, ftgmac100_adjust_link, +- PHY_INTERFACE_MODE_MII); ++ PHY_INTERFACE_MODE_RMII); + if (err) { + dev_err(&pdev->dev, "Connecting PHY failed\n"); + goto err_phy_connect; + } +- } else if (np && of_phy_is_fixed_link(np)) { +- struct phy_device *phy; +- +- err = of_phy_register_fixed_link(np); +- if (err) { +- dev_err(&pdev->dev, "Failed to register fixed PHY\n"); +- goto err_phy_connect; +- } +- +- phy = of_phy_get_and_connect(priv->netdev, np, +- &ftgmac100_adjust_link); +- if (!phy) { +- dev_err(&pdev->dev, "Failed to connect to fixed PHY\n"); +- of_phy_deregister_fixed_link(np); +- err = -EINVAL; +- goto err_phy_connect; +- } +- +- /* Display what we found */ +- phy_attached_info(phy); +- } else if (np && of_get_property(np, "phy-handle", NULL)) { ++ } else if (np && (of_phy_is_fixed_link(np) || ++ of_get_property(np, "phy-handle", NULL))) { + struct phy_device *phy; + + /* Support "mdio"/"phy" child nodes for ast2400/2500 with + * an embedded MDIO controller. Automatically scan the DTS for + * available PHYs and register them. + */ +- if (of_device_is_compatible(np, "aspeed,ast2400-mac") || +- of_device_is_compatible(np, "aspeed,ast2500-mac")) { ++ if (of_get_property(np, "phy-handle", NULL) && ++ (of_device_is_compatible(np, "aspeed,ast2400-mac") || ++ of_device_is_compatible(np, "aspeed,ast2500-mac"))) { + err = ftgmac100_setup_mdio(netdev); + if (err) + goto err_setup_mdio; +@@ -2016,6 +2077,24 @@ + if (of_device_is_compatible(np, "aspeed,ast2600-mac")) + iowrite32(FTGMAC100_TM_DEFAULT, + priv->base + FTGMAC100_OFFSET_TM); ++ ++ if (of_device_is_compatible(np, "aspeed,ast2700-mac")) { ++ if (netdev->phydev->interface == PHY_INTERFACE_MODE_SGMII) { ++ priv->sgmii = devm_phy_optional_get(&pdev->dev, "sgmii"); ++ if (IS_ERR(priv->sgmii)) { ++ dev_err(priv->dev, "Failed to get sgmii phy (%ld)\n", ++ PTR_ERR(priv->sgmii)); ++ err = PTR_ERR(priv->sgmii); ++ goto err_register_netdev; ++ } ++ } ++ } ++ } ++ ++ priv->rst = devm_reset_control_get_optional_exclusive(priv->dev, NULL); ++ if (IS_ERR(priv->rst)) { ++ err = PTR_ERR(priv->rst); ++ goto err_register_netdev; + } + + /* Default ring sizes */ +@@ -2042,6 +2121,12 @@ + netdev->hw_features &= ~(NETIF_F_HW_CSUM | NETIF_F_RXCSUM); + netdev->features |= netdev->hw_features; + ++ err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); ++ if (err) { ++ dev_err(&pdev->dev, "64-bit DMA enable failed\n"); ++ goto err_register_netdev; ++ } ++ + /* register network device */ + err = register_netdev(netdev); + if (err) { +diff --git a/drivers/net/ethernet/faraday/ftgmac100.h b/drivers/net/ethernet/faraday/ftgmac100.h +--- a/drivers/net/ethernet/faraday/ftgmac100.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/net/ethernet/faraday/ftgmac100.h 2026-09-09 15:51:44.742237709 +0000 +@@ -57,6 +57,13 @@ + #define FTGMAC100_OFFSET_RX_RUNT 0xc0 + #define FTGMAC100_OFFSET_RX_CRCER_FTL 0xc4 + #define FTGMAC100_OFFSET_RX_COL_LOST 0xc8 ++/* reserved 0xcc - 0x174 */ ++#define FTGMAC100_OFFSET_TXR_BADDR_LOW 0x178 /* ast2700 */ ++#define FTGMAC100_OFFSET_TXR_BADDR_HIGH 0x17c /* ast2700 */ ++#define FTGMAC100_OFFSET_HPTXR_BADDR_LOW 0x180 /* ast2700 */ ++#define FTGMAC100_OFFSET_HPTXR_BADDR_HIGH 0x184 /* ast2700 */ ++#define FTGMAC100_OFFSET_RXR_BADDR_LOW 0x188 /* ast2700 */ ++#define FTGMAC100_OFFSET_RXR_BADDR_HIGH 0x18C /* ast2700 */ + + /* + * Interrupt status register & interrupt enable register +@@ -166,6 +173,7 @@ + #define FTGMAC100_MACCR_RX_MULTIPKT (1 << 16) + #define FTGMAC100_MACCR_RX_BROADPKT (1 << 17) + #define FTGMAC100_MACCR_DISCARD_CRCERR (1 << 18) ++#define FTGMAC100_MACCR_RMII_ENABLE BIT(20) /* defined in ast2700 */ + #define FTGMAC100_MACCR_FAST_MODE (1 << 19) + #define FTGMAC100_MACCR_SW_RST (1 << 31) + +@@ -225,6 +233,7 @@ + #define FTGMAC100_TXDES1_TX2FIC (1 << 30) + #define FTGMAC100_TXDES1_TXIC (1 << 31) + ++#define FTGMAC100_TXDES2_TXBUF_BADR_HI GENMASK(18, 16) + /* + * Receive descriptor, aligned to 16 bytes + */ +@@ -271,4 +280,6 @@ + #define FTGMAC100_RXDES1_UDP_CHKSUM_ERR (1 << 26) + #define FTGMAC100_RXDES1_IP_CHKSUM_ERR (1 << 27) + ++#define FTGMAC100_RXDES2_RXBUF_BADR_HI GENMASK(18, 16) ++ + #endif /* __FTGMAC100_H */ +diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig +--- a/drivers/pci/controller/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pci/controller/Kconfig 2026-09-09 15:51:30.876447234 +0000 +@@ -47,6 +47,31 @@ + + If unsure, say Y if you have an Apple Silicon system. + ++config PHY_ASPEED_COMBINE_PERST ++ bool "ASPEED PCIe RC combine PERST with internal reset" ++ depends on ARCH_ASPEED ++ help ++ Configure PERST to connect with the internal PHY reset. When ++ this option is disabled, the driver selects the separate internal ++ PHY reset path by default. Enable it only on boards that require ++ PERST and the internal PHY reset to toggle at the same time. ++ ++config PCIE_ASPEED ++ bool "ASPEED PCIe controller" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ depends on OF ++ depends on PCI_MSI ++ select IRQ_MSI_LIB ++ help ++ Enable this option to support the PCIe controller found on ASPEED ++ SoCs. ++ ++ This driver provides initialization and management for PCIe ++ Root Complex functionality, including INTx and MSI support. ++ ++ Select Y if your platform uses an ASPEED SoC and requires PCIe ++ connectivity. ++ + config PCI_VERSATILE + bool "ARM Versatile PB PCI controller" + depends on ARCH_VERSATILE || COMPILE_TEST +diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile +--- a/drivers/pci/controller/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pci/controller/Makefile 2026-09-09 15:51:36.101368280 +0000 +@@ -39,6 +39,7 @@ + obj-$(CONFIG_PCIE_HISI_ERR) += pcie-hisi-error.o + obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o + obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o ++obj-$(CONFIG_PCIE_ASPEED) += pcie-aspeed.o + + # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW + obj-y += dwc/ +diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig +--- a/drivers/phy/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/phy/Kconfig 2026-09-09 15:51:30.408454306 +0000 +@@ -84,6 +84,7 @@ + + source "drivers/phy/allwinner/Kconfig" + source "drivers/phy/amlogic/Kconfig" ++source "drivers/phy/aspeed/Kconfig" + source "drivers/phy/broadcom/Kconfig" + source "drivers/phy/cadence/Kconfig" + source "drivers/phy/freescale/Kconfig" +diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile +--- a/drivers/phy/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/phy/Makefile 2026-09-09 15:51:35.609375714 +0000 +@@ -13,6 +13,7 @@ + obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o + obj-y += allwinner/ \ + amlogic/ \ ++ aspeed/ \ + broadcom/ \ + cadence/ \ + freescale/ \ +diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c +--- a/drivers/pwm/core.c 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/pwm/core.c 2026-09-09 15:51:45.777222069 +0000 +@@ -426,9 +426,8 @@ + * chip. A negative error code is returned if the index is not valid for the + * specified PWM chip or if the PWM device cannot be requested. + */ +-static struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, +- unsigned int index, +- const char *label) ++struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, ++ unsigned int index, const char *label) + { + struct pwm_device *pwm; + int err; +@@ -446,6 +445,7 @@ + + return pwm; + } ++EXPORT_SYMBOL_GPL(pwm_request_from_chip); + + struct pwm_device * + of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *args) +diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig +--- a/drivers/reset/Kconfig 2026-09-09 15:51:11.443740890 +0000 ++++ b/drivers/reset/Kconfig 2026-09-09 15:51:30.998445390 +0000 +@@ -22,6 +22,13 @@ + This option enables support for the external reset functions for + peripheral PHYs on the Altera Arria10 System Resource Chip. + ++config RESET_ASPEED ++ tristate "ASPEED Reset Driver" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ select AUXILIARY_BUS ++ help ++ This enables the reset controller driver for AST2700. ++ + config RESET_ATH79 + bool "AR71xx Reset Driver" if COMPILE_TEST + default ATH79 +diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile +--- a/drivers/reset/Makefile 2026-09-09 15:51:11.443740890 +0000 ++++ b/drivers/reset/Makefile 2026-09-09 15:51:36.220366481 +0000 +@@ -5,6 +5,7 @@ + obj-y += sti/ + obj-y += tegra/ + obj-$(CONFIG_RESET_A10SR) += reset-a10sr.o ++obj-$(CONFIG_RESET_ASPEED) += reset-aspeed.o + obj-$(CONFIG_RESET_ATH79) += reset-ath79.o + obj-$(CONFIG_RESET_AXS10X) += reset-axs10x.o + obj-$(CONFIG_RESET_BCM6345) += reset-bcm6345.o +diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig +--- a/drivers/spi/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/spi/Kconfig 2026-09-09 15:51:30.595451480 +0000 +@@ -129,6 +129,16 @@ + controller (SPI) for the host firmware. The implementation + only supports SPI NOR. + ++config SPI_ASPEED_TXRX ++ tristate "Aspeed SPI controllers" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ depends on OF ++ help ++ This enables support for SPI controller driver in the Aspeed ++ AST2600, AST2500 and AST2400 SoCs when attached ++ to SPI device chips with half-duplex mode. On AST2700, ++ full-duplex mode can be enabled. ++ + config SPI_ATMEL + tristate "Atmel SPI Controller" + depends on ARCH_AT91 || COMPILE_TEST +diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile +--- a/drivers/spi/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/spi/Makefile 2026-09-09 15:51:35.786373039 +0000 +@@ -22,6 +22,7 @@ + obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o + obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o + obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.o ++obj-$(CONFIG_SPI_ASPEED_TXRX) += spi-aspeed-txrx.o + obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o + obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o + obj-$(CONFIG_SPI_AT91_USART) += spi-at91-usart.o +diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig +--- a/drivers/tty/serial/8250/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/tty/serial/8250/Kconfig 2026-09-09 15:51:29.653465715 +0000 +@@ -256,6 +256,15 @@ + To compile this driver as a module, choose M here: the module + will be called 8250_accent. + ++config SERIAL_8250_ASPEED ++ tristate "Aspeed UART" ++ depends on SERIAL_8250 && ARCH_ASPEED ++ select ASPEED_UDMA ++ help ++ If you have a system using an Aspeed AST26xx SoCs and wish to ++ make use of its 16550A-compatible UART devices with DMA support, ++ say Y to this option. If unsure, say N. ++ + config SERIAL_8250_ASPEED_VUART + tristate "Aspeed Virtual UART" + depends on SERIAL_8250 +diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile +--- a/drivers/tty/serial/8250/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/tty/serial/8250/Makefile 2026-09-09 15:51:34.583391218 +0000 +@@ -21,6 +21,7 @@ + obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o + obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o + obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o ++obj-$(CONFIG_SERIAL_8250_ASPEED) += 8250_aspeed.o + obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o + obj-$(CONFIG_SERIAL_8250_BCM7271) += 8250_bcm7271.o + obj-$(CONFIG_SERIAL_8250_BOCA) += 8250_boca.o +diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig +--- a/drivers/ufs/host/Kconfig 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/ufs/host/Kconfig 2026-09-09 15:51:30.276456301 +0000 +@@ -143,3 +143,12 @@ + + Select this if you have UFS controller on Unisoc chipset. + If unsure, say N. ++ ++config SCSI_UFS_ASPEED ++ tristate "Aspeed specific hooks to UFS controller platform driver" ++ depends on SCSI_UFSHCD_PLATFORM ++ help ++ This selects the Aspeed specific additions to UFSHCD platform driver. ++ ++ Selects this if you have Aspeed platform with UFS controller. ++ If unsure, say N. +diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile +--- a/drivers/ufs/host/Makefile 2026-06-19 11:42:39.000000000 +0000 ++++ b/drivers/ufs/host/Makefile 2026-09-09 15:51:35.436378328 +0000 +@@ -12,3 +12,4 @@ + obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o + obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o + obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o ++obj-$(CONFIG_SCSI_UFS_ASPEED) += ufs-aspeed.o +diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h +--- a/include/linux/clk-provider.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/linux/clk-provider.h 2026-09-09 15:51:45.755222402 +0000 +@@ -623,6 +623,24 @@ + NULL, (flags), (reg), (bit_idx), \ + (clk_gate_flags), (lock)) + /** ++ * devm_clk_hw_register_gate_parent_hw - register a gate clock with the clock ++ * framework ++ * @dev: device that is registering this clock ++ * @name: name of this clock ++ * @parent_hw: pointer to parent clk ++ * @flags: framework-specific flags for this clock ++ * @reg: register address to control gating of this clock ++ * @bit_idx: which bit in the register controls gating of this clock ++ * @clk_gate_flags: gate-specific flags for this clock ++ * @lock: shared register lock for this clock ++ */ ++#define devm_clk_hw_register_gate_parent_hw(dev, name, parent_hw, flags, \ ++ reg, bit_idx, clk_gate_flags, \ ++ lock) \ ++ __devm_clk_hw_register_gate((dev), NULL, (name), NULL, (parent_hw), \ ++ NULL, (flags), (reg), (bit_idx), \ ++ (clk_gate_flags), (lock)) ++/** + * devm_clk_hw_register_gate_parent_data - register a gate clock with the + * clock framework + * @dev: device that is registering this clock +diff --git a/include/linux/pwm.h b/include/linux/pwm.h +--- a/include/linux/pwm.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/linux/pwm.h 2026-09-09 15:51:45.787221918 +0000 +@@ -405,6 +405,9 @@ + int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner); + #define devm_pwmchip_add(dev, chip) __devm_pwmchip_add(dev, chip, THIS_MODULE) + ++struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, ++ unsigned int index, const char *label); ++ + struct pwm_device *of_pwm_xlate_with_flags(struct pwm_chip *chip, + const struct of_phandle_args *args); + struct pwm_device *of_pwm_single_xlate(struct pwm_chip *chip, +diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h +--- a/include/uapi/linux/videodev2.h 2026-06-19 11:42:39.000000000 +0000 ++++ b/include/uapi/linux/videodev2.h 2026-09-09 15:51:45.765222251 +0000 +@@ -880,6 +880,7 @@ + /* Flags */ + #define V4L2_PIX_FMT_FLAG_PREMUL_ALPHA 0x00000001 + #define V4L2_PIX_FMT_FLAG_SET_CSC 0x00000002 ++#define V4L2_PIX_FMT_FLAG_PARTIAL_JPG 0x00000004 + + /* + * F O R M A T E N U M E R A T I O N diff --git a/patches-sonic/0003-drop_monitor-Allow-running-multiple-instances-of-dropwatch.patch b/patches-sonic/0003-drop_monitor-Allow-running-multiple-instances-of-dropwatch.patch new file mode 100644 index 000000000..69055ac4a --- /dev/null +++ b/patches-sonic/0003-drop_monitor-Allow-running-multiple-instances-of-dropwatch.patch @@ -0,0 +1,146 @@ +From b1fe183ad73ab30c6ed6aef7b961e3b440dfa174 Mon Sep 17 00:00:00 2001 +From: Ido Schimmel +Date: Wed, 11 Feb 2026 18:55:30 +0200 +Subject: [PATCH] drop_monitor: Allow running multiple instances of dropwatch + +Despite the fact that the drop_monitor module sends multicast +notifications, it is not possible to run multiple instances of dropwatch +since tracing of software / hardware drops can only be enabled once. + +Solve this by maintaining a reference count of the number of times that +software / hardware drop tracing was enabled. Initialize the tracing +when the reference count is incremented from 0 and tear it down when +decremented back to 0. + +Note that like before, it is not possible to change configuration while +tracing is enabled. + +Signed-off-by: Ido Schimmel +--- + net/core/drop_monitor.c | 46 ++++++++++++++++++++--------------------- + 1 file changed, 23 insertions(+), 23 deletions(-) + +diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c +index 60d31c2feed3..29f2b8c6b8b9 100644 +--- a/net/core/drop_monitor.c ++++ b/net/core/drop_monitor.c +@@ -47,8 +47,8 @@ + * and the work handle that will send up + * netlink alerts + */ +-static int trace_state = TRACE_OFF; +-static bool monitor_hw; ++static refcount_t refcount_sw = REFCOUNT_INIT(0); ++static refcount_t refcount_hw = REFCOUNT_INIT(0); + + /* net_dm_mutex + * +@@ -1051,10 +1051,8 @@ static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) + const struct net_dm_alert_ops *ops; + int cpu, rc; + +- if (monitor_hw) { +- NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already enabled"); +- return -EAGAIN; +- } ++ if (refcount_inc_not_zero(&refcount_hw)) ++ return 0; + + ops = net_dm_alert_ops_arr[net_dm_alert_mode]; + +@@ -1079,7 +1077,7 @@ static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) + goto err_module_put; + } + +- monitor_hw = true; ++ refcount_set(&refcount_hw, 1); + + return 0; + +@@ -1107,14 +1105,14 @@ static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack) + const struct net_dm_alert_ops *ops; + int cpu; + +- if (!monitor_hw) { +- NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already disabled"); ++ /* Don't disable if already disabled. */ ++ if (!refcount_read(&refcount_hw)) + return; +- } + +- ops = net_dm_alert_ops_arr[net_dm_alert_mode]; ++ if (!refcount_dec_and_test(&refcount_hw)) ++ return; + +- monitor_hw = false; ++ ops = net_dm_alert_ops_arr[net_dm_alert_mode]; + + net_dm_hw_probe_unregister(ops); + +@@ -1141,6 +1139,9 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) + const struct net_dm_alert_ops *ops; + int cpu, rc; + ++ if (refcount_inc_not_zero(&refcount_sw)) ++ return 0; ++ + ops = net_dm_alert_ops_arr[net_dm_alert_mode]; + + if (!try_module_get(THIS_MODULE)) { +@@ -1174,6 +1175,8 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) + goto err_unregister_trace; + } + ++ refcount_set(&refcount_sw, 1); ++ + return 0; + + err_unregister_trace: +@@ -1197,6 +1200,13 @@ static void net_dm_trace_off_set(void) + const struct net_dm_alert_ops *ops; + int cpu; + ++ /* Don't disable if already disabled. */ ++ if (!refcount_read(&refcount_sw)) ++ return; ++ ++ if (!refcount_dec_and_test(&refcount_sw)) ++ return; ++ + ops = net_dm_alert_ops_arr[net_dm_alert_mode]; + + unregister_trace_napi_poll(ops->napi_poll_probe, NULL); +@@ -1224,11 +1234,6 @@ static int set_all_monitor_traces(int state, struct netlink_ext_ack *extack) + { + int rc = 0; + +- if (state == trace_state) { +- NL_SET_ERR_MSG_MOD(extack, "Trace state already set to requested state"); +- return -EAGAIN; +- } +- + switch (state) { + case TRACE_ON: + rc = net_dm_trace_on_set(extack); +@@ -1241,17 +1246,12 @@ static int set_all_monitor_traces(int state, struct netlink_ext_ack *extack) + break; + } + +- if (!rc) +- trace_state = state; +- else +- rc = -EINPROGRESS; +- + return rc; + } + + static bool net_dm_is_monitoring(void) + { +- return trace_state == TRACE_ON || monitor_hw; ++ return refcount_read(&refcount_sw) || refcount_read(&refcount_hw); + } + + static int net_dm_alert_mode_get_from_info(struct genl_info *info, +-- +GitLab + diff --git a/patches-sonic/0003-i2c-designware-Support-stuck-SDA-line-recovery.patch b/patches-sonic/0003-i2c-designware-Support-stuck-SDA-line-recovery.patch new file mode 100644 index 000000000..dc71eec8a --- /dev/null +++ b/patches-sonic/0003-i2c-designware-Support-stuck-SDA-line-recovery.patch @@ -0,0 +1,218 @@ +From 697b2b39c8e473c8a41b0a5d0947d36480b35354 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 08:36:35 +0000 +Subject: [PATCH 03/27] i2c-designware: Support stuck SDA line recovery + +This patch supports the Designware I2C stuck bus recovery feature. +The procedure for stuck SDA recovery involves a polling loop in +interrupt +mode. This should last just long enough for transmission of 9 bits, +after which the hardware should indicate that the recovery attempt is +complete. There have been examples where this fails, so there is also +a hard maximum on time in the recovery loop. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + drivers/i2c/busses/i2c-designware-common.c | 2 + + drivers/i2c/busses/i2c-designware-core.h | 9 +++ + drivers/i2c/busses/i2c-designware-master.c | 88 +++++++++++++++++++++++++++++- + 3 files changed, 96 insertions(+), 3 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c +index b328278..e6f1b29 100644 +--- a/drivers/i2c/busses/i2c-designware-common.c ++++ b/drivers/i2c/busses/i2c-designware-common.c +@@ -63,6 +63,8 @@ + "slave lost the bus while transmitting data to a remote master", + [ABRT_SLAVE_RD_INTX] = + "incorrect slave-transmitter mode configuration", ++ [ABRT_SDA_STUCK_AT_LOW] = ++ "sda line stuck low", + }; + + static int dw_reg_read(void *context, unsigned int reg, unsigned int *val) +diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h +index 2d32896..59d1eba 100644 +--- a/drivers/i2c/busses/i2c-designware-core.h ++++ b/drivers/i2c/busses/i2c-designware-core.h +@@ -78,6 +78,8 @@ + #define DW_IC_TX_ABRT_SOURCE 0x80 + #define DW_IC_ENABLE_STATUS 0x9c + #define DW_IC_CLR_RESTART_DET 0xa8 ++#define DW_IC_SCL_STUCK_AT_LOW 0xac ++#define DW_IC_SDA_STUCK_AT_LOW 0xb0 + #define DW_IC_SMBUS_INTR_MASK 0xcc + #define DW_IC_COMP_PARAM_1 0xf4 + #define DW_IC_COMP_VERSION 0xf8 +@@ -98,6 +100,7 @@ + #define DW_IC_INTR_GEN_CALL BIT(11) + #define DW_IC_INTR_RESTART_DET BIT(12) + #define DW_IC_INTR_MST_ON_HOLD BIT(13) ++#define DW_IC_INTR_SCL_STUCK_AT_LOW BIT(14) + + #define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \ + DW_IC_INTR_TX_ABRT | \ +@@ -111,12 +114,15 @@ + #define DW_IC_ENABLE_ENABLE BIT(0) + #define DW_IC_ENABLE_ABORT BIT(1) + ++#define DW_IC_SDA_STUCK_RECOVERY_ENABLE BIT(3) ++ + #define DW_IC_STATUS_ACTIVITY BIT(0) + #define DW_IC_STATUS_TFE BIT(2) + #define DW_IC_STATUS_RFNE BIT(3) + #define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) + #define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) + #define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7) ++#define DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED BIT(11) + + #define DW_IC_SDA_HOLD_RX_SHIFT 16 + #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16) +@@ -162,6 +168,7 @@ + #define ABRT_SLAVE_FLUSH_TXFIFO 13 + #define ABRT_SLAVE_ARBLOST 14 + #define ABRT_SLAVE_RD_INTX 15 ++#define ABRT_SDA_STUCK_AT_LOW 17 + + #define DW_IC_TX_ABRT_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK) + #define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK) +@@ -174,6 +181,7 @@ + #define DW_IC_TX_ABRT_10B_RD_NORSTRT BIT(ABRT_10B_RD_NORSTRT) + #define DW_IC_TX_ABRT_MASTER_DIS BIT(ABRT_MASTER_DIS) + #define DW_IC_TX_ARB_LOST BIT(ARB_LOST) ++#define DW_IC_TX_ABRT_SDA_STUCK_AT_LOW BIT(ABRT_SDA_STUCK_AT_LOW) + #define DW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX) + #define DW_IC_RX_ABRT_SLAVE_ARBLOST BIT(ABRT_SLAVE_ARBLOST) + #define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO) +@@ -283,6 +291,7 @@ struct dw_i2c_dev { + int rx_outstanding; + struct i2c_timings timings; + u32 sda_hold_time; ++ u32 sda_timeout_ms; + u16 ss_hcnt; + u16 ss_lcnt; + u16 fs_hcnt; +diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c +index 52dc666..18423d0 100644 +--- a/drivers/i2c/busses/i2c-designware-master.c ++++ b/drivers/i2c/busses/i2c-designware-master.c +@@ -41,6 +41,45 @@ static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev) + regmap_write(dev->map, DW_IC_CON, dev->master_cfg); + } + ++static int i2c_dw_issue_bus_clear(struct dw_i2c_dev *dev) ++{ ++ u32 ic_status, ic_enable; ++ unsigned long timeout; ++ ++ dev_info(dev->dev, "sda stuck; trying to recover\n"); ++ regmap_write(dev->map, DW_IC_INTR_MASK, 0); ++ regmap_read(dev->map, DW_IC_ENABLE, &ic_enable); ++ regmap_write(dev->map, DW_IC_ENABLE, ++ ic_enable | DW_IC_SDA_STUCK_RECOVERY_ENABLE); ++ ++ /* ++ * Poll, waiting for recovery to be done. This may take up to 9 SCL ++ * clocks and a STOP bit, though presumably the device will signal ++ * completion in less time if it recovers sooner. There are apparently ++ * cases where the recovery doesn't finish, so we have a timeout ++ * in the loop. ++ */ ++ timeout = jiffies + msecs_to_jiffies(50); ++ while (!time_after(jiffies, timeout)) { ++ regmap_read(dev->map, DW_IC_ENABLE, &ic_enable); ++ if (!(ic_enable & DW_IC_SDA_STUCK_RECOVERY_ENABLE)) ++ break; ++ usleep_range(1000, 2000); ++ } ++ regmap_read(dev->map, DW_IC_ENABLE, &ic_enable); ++ if (ic_enable & DW_IC_SDA_STUCK_RECOVERY_ENABLE) { ++ dev_err(dev->dev, "sda stuck recovery timed out\n"); ++ return -EIO; ++ } ++ regmap_read(dev->map, DW_IC_STATUS, &ic_status); ++ if ((ic_status & DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED) != 0) { ++ dev_err(dev->dev, "sda stuck recovery failed\n"); ++ return -EIO; ++ } ++ dev_info(dev->dev, "sda stuck recovery successful\n"); ++ return -EAGAIN; /* -EAGAIN to auto-retry */ ++} ++ + static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) + { + unsigned int comp_param1; +@@ -846,6 +885,14 @@ static int i2c_dw_wait_transfer(struct dw_i2c_dev *dev) + if (i2c_dw_is_controller_active(dev)) + dev_err(dev->dev, "controller active\n"); + ++ /* Look for a stuck bus */ ++ if (dev->cmd_err == DW_IC_ERR_TX_ABRT && ++ (dev->abort_source & DW_IC_TX_ABRT_SDA_STUCK_AT_LOW)) { ++ ret = i2c_dw_issue_bus_clear(dev); ++ i2c_dw_init_master(dev); ++ goto done; ++ } ++ + /* + * We must disable the adapter before returning and signaling the end + * of the current transfer. Otherwise the hardware might continue +@@ -940,6 +987,39 @@ static void i2c_dw_unprepare_recovery(struct i2c_adapter *adap) + i2c_dw_init_master(dev); + } + ++static int i2c_dw_probe_bus_clear_feature(struct dw_i2c_dev *dev) ++{ ++ u32 con, timeout; ++ int ret; ++ ++ /* only use controller bus_clear if the sda timeout is specified */ ++ if (!dev->sda_timeout_ms) ++ return 0; ++ ++ ret = i2c_dw_acquire_lock(dev); ++ if (ret) ++ return ret; ++ ++ /* ++ * Probe the availability of the BUS_CLEAR_FEATURE by setting the ++ * bit in IC_CON. If the bit reads back set, then the feature is ++ * available, otherwise it is not. ++ */ ++ regmap_write(dev->map, DW_IC_CON, DW_IC_DATA_CMD_FIRST_DATA_BYTE); ++ regmap_read(dev->map, DW_IC_CON, &con); ++ if (!(con & DW_IC_DATA_CMD_FIRST_DATA_BYTE)) ++ goto out; ++ dev_info(dev->dev, "running with controller bus clear recovery mode!"); ++ timeout = i2c_dw_clk_rate(dev) * dev->sda_timeout_ms; /* clk in kHz */ ++ regmap_write(dev->map, DW_IC_SDA_STUCK_AT_LOW, timeout); ++ dev->master_cfg |= DW_IC_DATA_CMD_FIRST_DATA_BYTE; ++out: ++ regmap_write(dev->map, DW_IC_CON, dev->master_cfg); ++ i2c_dw_release_lock(dev); ++ ++ return 0; ++} ++ + static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev) + { + struct i2c_bus_recovery_info *rinfo = &dev->rinfo; +@@ -947,9 +1027,11 @@ static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev) + struct gpio_desc *gpio; + + gpio = devm_gpiod_get_optional(dev->dev, "scl", GPIOD_OUT_HIGH); +- if (IS_ERR_OR_NULL(gpio)) +- return PTR_ERR_OR_ZERO(gpio); +- ++ if (IS_ERR_OR_NULL(gpio)) { ++ if (IS_ERR(gpio)) ++ return PTR_ERR(gpio); ++ return i2c_dw_probe_bus_clear_feature(dev); ++ } + rinfo->scl_gpiod = gpio; + + gpio = devm_gpiod_get_optional(dev->dev, "sda", GPIOD_IN); +-- +1.8.3.1 + diff --git a/patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch b/patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch new file mode 100644 index 000000000..d59cc248f --- /dev/null +++ b/patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch @@ -0,0 +1,45 @@ +From 053a09ab106bbeeefbd733dcf0892e4f40587e1c Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 18:29:00 +0300 +Subject: [PATCH 6.12 3/5] mctp: driver: net: Add global APIs for MCTP + +Export the MCTP transport tracepoints (mctp_transport_tx, +mctp_transport_rx and mctp_transport_error) via +EXPORT_TRACEPOINT_SYMBOL_GPL() so that MCTP transport drivers built as +loadable modules can emit them, and add the missing +MODULE_LICENSE("GPL") to the MCTP I2C error-injection module. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/mctp-i2c-error-inject.c | 2 ++ + net/mctp/af_mctp.c | 4 ++++ + 2 files changed, 6 insertions(+) + +diff --git a/drivers/net/mctp/mctp-i2c-error-inject.c b/drivers/net/mctp/mctp-i2c-error-inject.c +index c10040f87..d06943275 100644 +--- a/drivers/net/mctp/mctp-i2c-error-inject.c ++++ b/drivers/net/mctp/mctp-i2c-error-inject.c +@@ -846,3 +846,5 @@ void mctp_i2c_error_inject_module_exit(void) + mctp_i2c_error_inject_root = NULL; + } + EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_module_exit); ++ ++MODULE_LICENSE("GPL"); +diff --git a/net/mctp/af_mctp.c b/net/mctp/af_mctp.c +index d99c35b1c..f0435f634 100644 +--- a/net/mctp/af_mctp.c ++++ b/net/mctp/af_mctp.c +@@ -20,6 +20,10 @@ + #define CREATE_TRACE_POINTS + #include + ++EXPORT_TRACEPOINT_SYMBOL_GPL(mctp_transport_tx); ++EXPORT_TRACEPOINT_SYMBOL_GPL(mctp_transport_rx); ++EXPORT_TRACEPOINT_SYMBOL_GPL(mctp_transport_error); ++ + #include "mctp-socket-error-inject.h" + + /* socket implementation */ +-- +2.34.1 + diff --git a/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch b/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch new file mode 100644 index 000000000..7b67d2fa9 --- /dev/null +++ b/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch @@ -0,0 +1,384 @@ +From fe8cb206bf32a211cdf0675a1a7d3bf484ab660e Mon Sep 17 00:00:00 2001 +From: "Ido Schimmel" +Date: Fri, 13 Feb 2026 19:51:25 -0800 +Subject: [PATCH] neighbor: Add NTF_EXT_VALIDATED flag for externally validated + entries + +tl;dr +===== + +Add a new neighbor flag ("extern_valid") that can be used to indicate to +the kernel that a neighbor entry was learned and determined to be valid +externally. The kernel will not try to remove or invalidate such an +entry, leaving these decisions to the user space control plane. This is +needed for EVPN multi-homing where a neighbor entry for a multi-homed +host needs to be synced across all the VTEPs among which the host is +multi-homed. + +Background +========== + +In a typical EVPN multi-homing setup each host is multi-homed using a +set of links called ES (Ethernet Segment, i.e., LAG) to multiple leaf +switches (VTEPs). VTEPs that are connected to the same ES are called ES +peers. + +When a neighbor entry is learned on a VTEP, it is distributed to both ES +peers and remote VTEPs using EVPN MAC/IP advertisement routes. ES peers +use the neighbor entry when routing traffic towards the multi-homed host +and remote VTEPs use it for ARP/NS suppression. + +Motivation +========== + +If the ES link between a host and the VTEP on which the neighbor entry +was locally learned goes down, the EVPN MAC/IP advertisement route will +be withdrawn and the neighbor entries will be removed from both ES peers +and remote VTEPs. Routing towards the multi-homed host and ARP/NS +suppression can fail until another ES peer locally learns the neighbor +entry and distributes it via an EVPN MAC/IP advertisement route. + +"draft-rbickhart-evpn-ip-mac-proxy-adv-03" [1] suggests avoiding these +intermittent failures by having the ES peers install the neighbor +entries as before, but also injecting EVPN MAC/IP advertisement routes +with a proxy indication. When the previously mentioned ES link goes down +and the original EVPN MAC/IP advertisement route is withdrawn, the ES +peers will not withdraw their neighbor entries, but instead start aging +timers for the proxy indication. + +If an ES peer locally learns the neighbor entry (i.e., it becomes +"reachable"), it will restart its aging timer for the entry and emit an +EVPN MAC/IP advertisement route without a proxy indication. An ES peer +will stop its aging timer for the proxy indication if it observes the +removal of the proxy indication from at least one of the ES peers +advertising the entry. + +In the event that the aging timer for the proxy indication expired, an +ES peer will withdraw its EVPN MAC/IP advertisement route. If the timer +expired on all ES peers and they all withdrew their proxy +advertisements, the neighbor entry will be completely removed from the +EVPN fabric. + +Implementation +============== + +In the above scheme, when the control plane (e.g., FRR) advertises a +neighbor entry with a proxy indication, it expects the corresponding +entry in the data plane (i.e., the kernel) to remain valid and not be +removed due to garbage collection or loss of carrier. The control plane +also expects the kernel to notify it if the entry was learned locally +(i.e., became "reachable") so that it will remove the proxy indication +from the EVPN MAC/IP advertisement route. That is why these entries +cannot be programmed with dummy states such as "permanent" or "noarp". + +Instead, add a new neighbor flag ("extern_valid") which indicates that +the entry was learned and determined to be valid externally and should +not be removed or invalidated by the kernel. The kernel can probe the +entry and notify user space when it becomes "reachable" (it is initially +installed as "stale"). However, if the kernel does not receive a +confirmation, have it return the entry to the "stale" state instead of +the "failed" state. + +In other words, an entry marked with the "extern_valid" flag behaves +like any other dynamically learned entry other than the fact that the +kernel cannot remove or invalidate it. + +One can argue that the "extern_valid" flag should not prevent garbage +collection and that instead a neighbor entry should be programmed with +both the "extern_valid" and "extern_learn" flags. There are two reasons +for not doing that: + +1. Unclear why a control plane would like to program an entry that the + kernel cannot invalidate but can completely remove. + +2. The "extern_learn" flag is used by FRR for neighbor entries learned + on remote VTEPs (for ARP/NS suppression) whereas here we are + concerned with local entries. This distinction is currently irrelevant + for the kernel, but might be relevant in the future. + +Given that the flag only makes sense when the neighbor has a valid +state, reject attempts to add a neighbor with an invalid state and with +this flag set. For example: + + # ip neigh add 192.0.2.1 nud none dev br0.10 extern_valid + Error: Cannot create externally validated neighbor with an invalid state. + # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid + # ip neigh replace 192.0.2.1 nud failed dev br0.10 extern_valid + Error: Cannot mark neighbor as externally validated with an invalid state. + +The above means that a neighbor cannot be created with the +"extern_valid" flag and flags such as "use" or "managed" as they result +in a neighbor being created with an invalid state ("none") and +immediately getting probed: + + # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + Error: Cannot create externally validated neighbor with an invalid state. + +However, these flags can be used together with "extern_valid" after the +neighbor was created with a valid state: + + # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid + # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + +One consequence of preventing the kernel from invalidating a neighbor +entry is that by default it will only try to determine reachability +using unicast probes. This can be changed using the "mcast_resolicit" +sysctl: + + # sysctl net.ipv4.neigh.br0/10.mcast_resolicit + 0 + # tcpdump -nn -e -i br0.10 -Q out arp & + # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + # sysctl -wq net.ipv4.neigh.br0/10.mcast_resolicit=3 + # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + +iproute2 patches can be found here [2]. + +[1] https://datatracker.ietf.org/doc/html/draft-rbickhart-evpn-ip-mac-proxy-adv-03 +[2] https://github.com/idosch/iproute2/tree/submit/extern_valid_v1 + +Signed-off-by: Ido Schimmel +Acked-by: Daniel Borkmann +Link: https://patch.msgid.link/20250626073111.244534-2-idosch@nvidia.com +Signed-off-by: Jakub Kicinski +--- + include/net/neighbour.h | 5 ++- + include/uapi/linux/neighbour.h | 5 +++ + net/core/neighbour.c | 84 ++++++++++++++++++++++++++++++++++++------ + 3 files changed, 82 insertions(+), 12 deletions(-) + +diff --git a/include/net/neighbour.h b/include/net/neighbour.h +index 5804014..2082bd3 100644 +--- a/include/net/neighbour.h ++++ b/include/net/neighbour.h +@@ -267,14 +267,17 @@ static inline void *neighbour_priv(const struct neighbour *n) + #define NEIGH_UPDATE_F_ISROUTER BIT(6) + #define NEIGH_UPDATE_F_ADMIN BIT(7) + #define NEIGH_UPDATE_F_EXT_MH_PEER_SYNC BIT(8) ++#define NEIGH_UPDATE_F_EXT_VALIDATED BIT(9) + + /* In-kernel representation for NDA_FLAGS_EXT flags: */ + #define NTF_OLD_MASK 0xff + #define NTF_EXT_SHIFT 8 +-#define NTF_EXT_MASK (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC) ++#define NTF_EXT_MASK \ ++ (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC | NTF_EXT_EXT_VALIDATED) + + #define NTF_MANAGED (NTF_EXT_MANAGED << NTF_EXT_SHIFT) + #define NTF_MH_PEER_SYNC (NTF_EXT_MH_PEER_SYNC << NTF_EXT_SHIFT) ++#define NTF_EXT_VALIDATED (NTF_EXT_EXT_VALIDATED << NTF_EXT_SHIFT) + + extern const struct nla_policy nda_policy[]; + +diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h +index 12412eb..afaece4 100644 +--- a/include/uapi/linux/neighbour.h ++++ b/include/uapi/linux/neighbour.h +@@ -54,6 +54,7 @@ enum { + /* Extended flags under NDA_FLAGS_EXT: */ + #define NTF_EXT_MANAGED (1 << 0) + #define NTF_EXT_LOCKED (1 << 1) ++#define NTF_EXT_EXT_VALIDATED (1 << 2) + #define NTF_EXT_MH_PEER_SYNC (1 << 3) + + /* +@@ -93,6 +94,10 @@ enum { + * bridge in response to a host trying to communicate via a locked bridge port + * with MAB enabled. Their purpose is to notify user space that a host requires + * authentication. ++ * ++ * NTF_EXT_EXT_VALIDATED flagged neighbor entries were externally validated by ++ * a user space control plane. The kernel will not remove or invalidate them, ++ * but it can probe them and notify user space when they become reachable. + */ + + struct nda_cacheinfo { +diff --git a/net/core/neighbour.c b/net/core/neighbour.c +index 019aec2..888a46f 100644 +--- a/net/core/neighbour.c ++++ b/net/core/neighbour.c +@@ -135,11 +135,12 @@ static void neigh_update_gc_list(struct neighbour *n) + if (n->dead) + goto out; + +- /* remove from the gc list if new state is permanent or if neighbor +- * is externally learned; otherwise entry should be on the gc list ++ /* remove from the gc list if new state is permanent or if neighbor is ++ * externally learned / validated; otherwise entry should be on the gc ++ * list + */ + exempt_from_gc = n->nud_state & NUD_PERMANENT || +- n->flags & NTF_EXT_LEARNED; ++ n->flags & (NTF_EXT_LEARNED | NTF_EXT_VALIDATED); + on_gc_list = !list_empty(&n->gc_list); + + if (exempt_from_gc && on_gc_list) { +@@ -189,6 +190,8 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + ndm_flags |= (flags & NEIGH_UPDATE_F_EXT_MH_PEER_SYNC) ? + NTF_MH_PEER_SYNC : + 0; ++ ndm_flags |= ++ (flags & NEIGH_UPDATE_F_EXT_VALIDATED) ? NTF_EXT_VALIDATED : 0; + + if ((old_flags ^ ndm_flags) & NTF_EXT_LEARNED) { + if (ndm_flags & NTF_EXT_LEARNED) +@@ -214,6 +217,14 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + neigh->flags &= ~NTF_MH_PEER_SYNC; + *notify = 1; + } ++ if ((old_flags ^ ndm_flags) & NTF_EXT_VALIDATED) { ++ if (ndm_flags & NTF_EXT_VALIDATED) ++ neigh->flags |= NTF_EXT_VALIDATED; ++ else ++ neigh->flags &= ~NTF_EXT_VALIDATED; ++ *notify = 1; ++ *gc_update = true; ++ } + } + + static bool neigh_del(struct neighbour *n, struct neighbour __rcu **np, +@@ -407,7 +418,8 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev, + np = &n->next; + continue; + } +- if (skip_perm && n->nud_state & NUD_PERMANENT) { ++ if (skip_perm && (n->nud_state & NUD_PERMANENT || ++ n->flags & NTF_EXT_VALIDATED)) { + np = &n->next; + continue; + } +@@ -997,7 +1009,8 @@ static void neigh_periodic_work(struct work_struct *work) + + state = n->nud_state; + if ((state & (NUD_PERMANENT | NUD_IN_TIMER)) || +- (n->flags & NTF_EXT_LEARNED)) { ++ (n->flags & ++ (NTF_EXT_LEARNED | NTF_EXT_VALIDATED))) { + write_unlock(&n->lock); + goto next_elt; + } +@@ -1154,9 +1167,15 @@ static void neigh_timer_handler(struct timer_list *t) + + if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) && + atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) { +- WRITE_ONCE(neigh->nud_state, NUD_FAILED); ++ if (neigh->nud_state == NUD_PROBE && ++ neigh->flags & NTF_EXT_VALIDATED) { ++ WRITE_ONCE(neigh->nud_state, NUD_STALE); ++ neigh->updated = jiffies; ++ } else { ++ WRITE_ONCE(neigh->nud_state, NUD_FAILED); ++ neigh_invalidate(neigh); ++ } + notify = 1; +- neigh_invalidate(neigh); + goto out; + } + +@@ -1304,6 +1323,8 @@ static void neigh_update_hhs(struct neighbour *neigh) + NTF_ROUTER flag. + NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as + a router. ++ NEIGH_UPDATE_F_EXT_VALIDATED means that the entry will not be removed ++ or invalidated. + + Caller MUST hold reference count on the entry. + */ +@@ -2043,7 +2064,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + if (ndm_flags & NTF_PROXY) { + struct pneigh_entry *pn; + +- if (ndm_flags & NTF_MANAGED) { ++ if (ndm_flags & (NTF_MANAGED | NTF_EXT_VALIDATED)) { + NL_SET_ERR_MSG(extack, "Invalid NTF_* flag combination"); + goto out; + } +@@ -2072,8 +2093,9 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + neigh = neigh_lookup(tbl, dst, dev); + if (neigh == NULL) { + bool ndm_permanent = ndm->ndm_state & NUD_PERMANENT; +- bool exempt_from_gc = ndm_permanent || +- ndm_flags & NTF_EXT_LEARNED; ++ bool exempt_from_gc = ++ ndm_permanent || ++ ndm_flags & (NTF_EXT_LEARNED | NTF_EXT_VALIDATED); + + if (!(nlh->nlmsg_flags & NLM_F_CREATE)) { + err = -ENOENT; +@@ -2084,10 +2106,28 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + err = -EINVAL; + goto out; + } ++ if (ndm_flags & NTF_EXT_VALIDATED) { ++ u8 state = ndm->ndm_state; ++ ++ /* NTF_USE and NTF_MANAGED will result in the neighbor ++ * being created with an invalid state (NUD_NONE). ++ */ ++ if (ndm_flags & (NTF_USE | NTF_MANAGED)) ++ state = NUD_NONE; ++ ++ if (!(state & NUD_VALID)) { ++ NL_SET_ERR_MSG( ++ extack, ++ "Cannot create externally validated neighbor with an invalid state"); ++ err = -EINVAL; ++ goto out; ++ } ++ } + + neigh = ___neigh_create(tbl, dst, dev, + ndm_flags & +- (NTF_EXT_LEARNED | NTF_MANAGED), ++ (NTF_EXT_LEARNED | NTF_MANAGED | ++ NTF_EXT_VALIDATED), + exempt_from_gc, true); + if (IS_ERR(neigh)) { + err = PTR_ERR(neigh); +@@ -2099,6 +2139,26 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + neigh_release(neigh); + goto out; + } ++ if (ndm_flags & NTF_EXT_VALIDATED) { ++ u8 state = ndm->ndm_state; ++ ++ /* NTF_USE and NTF_MANAGED do not update the existing ++ * state other than clearing it if it was ++ * NUD_PERMANENT. ++ */ ++ if (ndm_flags & (NTF_USE | NTF_MANAGED)) ++ state = READ_ONCE(neigh->nud_state) & ++ ~NUD_PERMANENT; ++ ++ if (!(state & NUD_VALID)) { ++ NL_SET_ERR_MSG( ++ extack, ++ "Cannot mark neighbor as externally validated with an invalid state"); ++ err = -EINVAL; ++ neigh_release(neigh); ++ goto out; ++ } ++ } + + if (!(nlh->nlmsg_flags & NLM_F_REPLACE)) + flags &= ~(NEIGH_UPDATE_F_OVERRIDE | +@@ -2117,6 +2177,8 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + flags |= NEIGH_UPDATE_F_USE; + if (ndm_flags & NTF_MH_PEER_SYNC) + flags |= NEIGH_UPDATE_F_EXT_MH_PEER_SYNC; ++ if (ndm_flags & NTF_EXT_VALIDATED) ++ flags |= NEIGH_UPDATE_F_EXT_VALIDATED; + + err = __neigh_update(neigh, lladdr, ndm->ndm_state, flags, + NETLINK_CB(skb).portid, extack); +-- +2.34.1 diff --git a/patches-sonic/0003-platform-mellanox-mlxbf-pmc-Add-support-for-clock_me.patch b/patches-sonic/0003-platform-mellanox-mlxbf-pmc-Add-support-for-clock_me.patch new file mode 100644 index 000000000..fa5b393c7 --- /dev/null +++ b/patches-sonic/0003-platform-mellanox-mlxbf-pmc-Add-support-for-clock_me.patch @@ -0,0 +1,108 @@ +From 8dfc0be5792d269fd8203e02e7c224a355bf792c Mon Sep 17 00:00:00 2001 +From: Shravan Kumar Ramani +Date: Thu, 9 Jan 2025 09:39:22 -0500 +Subject: [PATCH 03/78] platform/mellanox: mlxbf-pmc: Add support for + clock_measure performance block +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The HW clock_measure counter info is passed to the driver from ACPI. +Create a new sub-directory for clock_measure events and provide +read access to the user. Writes are blocked since the fields are RO. + +Signed-off-by: Shravan Kumar Ramani +Reviewed-by: David Thompson +Reviewed-by: Vadim Pasternak +Link: https://lore.kernel.org/r/6ea0699497479dfde0a52fcb28aef55aee1bbc0b.1736413033.git.shravankr@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxbf-pmc.c | 46 ++++++++++++++++++++++++++++++++--- + 1 file changed, 42 insertions(+), 4 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c +index 197a98e..771a24c 100644 +--- a/drivers/platform/mellanox/mlxbf-pmc.c ++++ b/drivers/platform/mellanox/mlxbf-pmc.c +@@ -862,6 +862,37 @@ static const struct mlxbf_pmc_events mlxbf_pmc_llt_miss_events[] = { + {75, "HISTOGRAM_HISTOGRAM_BIN9"}, + }; + ++static const struct mlxbf_pmc_events mlxbf_pmc_clock_events[] = { ++ { 0x0, "FMON_CLK_LAST_COUNT_PLL_D1_INST0" }, ++ { 0x4, "REFERENCE_WINDOW_WIDTH_PLL_D1_INST0" }, ++ { 0x8, "FMON_CLK_LAST_COUNT_PLL_D1_INST1" }, ++ { 0xc, "REFERENCE_WINDOW_WIDTH_PLL_D1_INST1" }, ++ { 0x10, "FMON_CLK_LAST_COUNT_PLL_G1" }, ++ { 0x14, "REFERENCE_WINDOW_WIDTH_PLL_G1" }, ++ { 0x18, "FMON_CLK_LAST_COUNT_PLL_W1" }, ++ { 0x1c, "REFERENCE_WINDOW_WIDTH_PLL_W1" }, ++ { 0x20, "FMON_CLK_LAST_COUNT_PLL_T1" }, ++ { 0x24, "REFERENCE_WINDOW_WIDTH_PLL_T1" }, ++ { 0x28, "FMON_CLK_LAST_COUNT_PLL_A0" }, ++ { 0x2c, "REFERENCE_WINDOW_WIDTH_PLL_A0" }, ++ { 0x30, "FMON_CLK_LAST_COUNT_PLL_C0" }, ++ { 0x34, "REFERENCE_WINDOW_WIDTH_PLL_C0" }, ++ { 0x38, "FMON_CLK_LAST_COUNT_PLL_N1" }, ++ { 0x3c, "REFERENCE_WINDOW_WIDTH_PLL_N1" }, ++ { 0x40, "FMON_CLK_LAST_COUNT_PLL_I1" }, ++ { 0x44, "REFERENCE_WINDOW_WIDTH_PLL_I1" }, ++ { 0x48, "FMON_CLK_LAST_COUNT_PLL_R1" }, ++ { 0x4c, "REFERENCE_WINDOW_WIDTH_PLL_R1" }, ++ { 0x50, "FMON_CLK_LAST_COUNT_PLL_P1" }, ++ { 0x54, "REFERENCE_WINDOW_WIDTH_PLL_P1" }, ++ { 0x58, "FMON_CLK_LAST_COUNT_REF_100_INST0" }, ++ { 0x5c, "REFERENCE_WINDOW_WIDTH_REF_100_INST0" }, ++ { 0x60, "FMON_CLK_LAST_COUNT_REF_100_INST1" }, ++ { 0x64, "REFERENCE_WINDOW_WIDTH_REF_100_INST1" }, ++ { 0x68, "FMON_CLK_LAST_COUNT_REF_156" }, ++ { 0x6c, "REFERENCE_WINDOW_WIDTH_REF_156" }, ++}; ++ + static struct mlxbf_pmc_context *pmc; + + /* UUID used to probe ATF service. */ +@@ -1035,6 +1066,9 @@ static const struct mlxbf_pmc_events *mlxbf_pmc_event_list(const char *blk, size + } else if (strstr(blk, "llt")) { + events = mlxbf_pmc_llt_events; + size = ARRAY_SIZE(mlxbf_pmc_llt_events); ++ } else if (strstr(blk, "clock_measure")) { ++ events = mlxbf_pmc_clock_events; ++ size = ARRAY_SIZE(mlxbf_pmc_clock_events); + } else { + events = NULL; + size = 0; +@@ -1469,14 +1503,15 @@ static int mlxbf_pmc_read_event(unsigned int blk_num, u32 cnt_num, bool is_l3, u + /* Method to read a register */ + static int mlxbf_pmc_read_reg(unsigned int blk_num, u32 offset, u64 *result) + { +- u32 ecc_out; ++ u32 reg; + +- if (strstr(pmc->block_name[blk_num], "ecc")) { ++ if ((strstr(pmc->block_name[blk_num], "ecc")) || ++ (strstr(pmc->block_name[blk_num], "clock_measure"))) { + if (mlxbf_pmc_readl(pmc->block[blk_num].mmio_base + offset, +- &ecc_out)) ++ ®)) + return -EFAULT; + +- *result = ecc_out; ++ *result = reg; + return 0; + } + +@@ -1490,6 +1525,9 @@ static int mlxbf_pmc_read_reg(unsigned int blk_num, u32 offset, u64 *result) + /* Method to write to a register */ + static int mlxbf_pmc_write_reg(unsigned int blk_num, u32 offset, u64 data) + { ++ if (strstr(pmc->block_name[blk_num], "clock_measure")) ++ return -EINVAL; ++ + if (strstr(pmc->block_name[blk_num], "ecc")) { + return mlxbf_pmc_write(pmc->block[blk_num].mmio_base + offset, + MLXBF_PMC_WRITE_REG_32, data); +-- +2.8.4 + diff --git a/patches-sonic/0003-xilinx-spi-make-interrupt-optional.patch b/patches-sonic/0003-xilinx-spi-make-interrupt-optional.patch new file mode 100644 index 000000000..2f3c266ec --- /dev/null +++ b/patches-sonic/0003-xilinx-spi-make-interrupt-optional.patch @@ -0,0 +1,34 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v3 1/3] spi: dt-bindings: xilinx: make interrupts optional +From: Abdurrahman Hussain +Date: Mon, 19 Jan 2026 07:06:22 +0000 +Message-Id: <20260119-spi-xilinx-v3-1-4566c33bac0d@nexthop.ai> +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Both the hardware and driver already support polling mode. By removing +the mandatory IRQ requirement during probe, the driver can now fall +back to polling when an interrupt is unavailable, ensuring +compatibility with a wider range of systems. + +Signed-off-by: Abdurrahman Hussain +--- + Documentation/devicetree/bindings/spi/spi-xilinx.yaml | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/Documentation/devicetree/bindings/spi/spi-xilinx.yaml b/Documentation/devicetree/bindings/spi/spi-xilinx.yaml +index 4beb3af0416d..24e62530d432 100644 +--- a/Documentation/devicetree/bindings/spi/spi-xilinx.yaml ++++ b/Documentation/devicetree/bindings/spi/spi-xilinx.yaml +@@ -38,7 +38,6 @@ properties: + required: + - compatible + - reg +- - interrupts + + unevaluatedProperties: false + + +-- +2.52.0 diff --git a/patches-sonic/0004-PCI-AER-Simplify-add_error_device.patch b/patches-sonic/0004-PCI-AER-Simplify-add_error_device.patch new file mode 100644 index 000000000..b1eeb2357 --- /dev/null +++ b/patches-sonic/0004-PCI-AER-Simplify-add_error_device.patch @@ -0,0 +1,49 @@ +From 824b8af8cc8d81ef95d020dcfcd4338249f1f7de Mon Sep 17 00:00:00 2001 +From: Bjorn Helgaas +Date: Thu, 22 May 2025 18:21:23 -0500 +Subject: [PATCH 04/12] PCI/AER: Simplify add_error_device() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Return -ENOSPC error early so the usual path through add_error_device() is +the straightline code. + +Signed-off-by: Bjorn Helgaas +Reviewed-by: Ilpo Järvinen +Reviewed-by: Kuppuswamy Sathyanarayanan +Link: https://patch.msgid.link/20250522232339.1525671-18-helgaas@kernel.org +(cherry picked from commit d72bae423004aa7b4d94c34a7fd0b48b64305a08) +--- + drivers/pci/pcie/aer.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 6fc993d05647..a10a005ca6e9 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -811,12 +811,15 @@ EXPORT_SYMBOL_NS_GPL(pci_print_aer, CXL); + */ + static int add_error_device(struct aer_err_info *e_info, struct pci_dev *dev) + { +- if (e_info->error_dev_num < AER_MAX_MULTI_ERR_DEVICES) { +- e_info->dev[e_info->error_dev_num] = pci_dev_get(dev); +- e_info->error_dev_num++; +- return 0; +- } +- return -ENOSPC; ++ int i = e_info->error_dev_num; ++ ++ if (i >= AER_MAX_MULTI_ERR_DEVICES) ++ return -ENOSPC; ++ ++ e_info->dev[i] = pci_dev_get(dev); ++ e_info->error_dev_num++; ++ ++ return 0; + } + + /** +-- +2.47.0 + diff --git a/patches-sonic/0004-drop_monitor-Send-hardware-drop-notifications-to-the.patch b/patches-sonic/0004-drop_monitor-Send-hardware-drop-notifications-to-the.patch new file mode 100644 index 000000000..660dee3ae --- /dev/null +++ b/patches-sonic/0004-drop_monitor-Send-hardware-drop-notifications-to-the.patch @@ -0,0 +1,55 @@ +From 41e1e040ed42d47757819896fa275ce00301e162 Mon Sep 17 00:00:00 2001 +From: Ido Schimmel +Date: Wed, 11 Feb 2026 15:41:52 +0200 +Subject: [PATCH] drop_monitor: Send hardware drop notifications to the + appropriate netns + +Unlike software drops, it is always clear in which network namespace a +hardware drop occurred. + +Allow drop_monitor to work in different network namespaces and send +hardware drop notifications to the appropriate one. Other notifications +are still sent to the initial network namespace. + +Signed-off-by: Ido Schimmel +--- + net/core/drop_monitor.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c +index 29f2b8c6b8b9..e56d3e166df0 100644 +--- a/net/core/drop_monitor.c ++++ b/net/core/drop_monitor.c +@@ -911,6 +911,7 @@ static void net_dm_hw_packet_report(struct sk_buff *skb) + struct devlink_trap_metadata *hw_metadata; + struct sk_buff *msg; + size_t payload_len; ++ struct net *net; + int rc; + + if (skb->data > skb_mac_header(skb)) +@@ -934,7 +935,12 @@ static void net_dm_hw_packet_report(struct sk_buff *skb) + goto out; + } + +- genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL); ++ /* The network namespace cannot disappear since we are holding a ++ * reference on the net device. ++ */ ++ net = dev_net(hw_metadata->input_dev); ++ genlmsg_multicast_netns(&net_drop_monitor_family, net, msg, 0, 0, ++ GFP_KERNEL); + + out: + net_dm_hw_metadata_free(NET_DM_SKB_CB(skb)->hw_metadata); +@@ -1659,6 +1665,7 @@ static struct genl_family net_drop_monitor_family __ro_after_init = { + .name = "NET_DM", + .version = 2, + .maxattr = NET_DM_ATTR_MAX, ++ .netnsok = true, + .policy = net_dm_nl_policy, + .pre_doit = net_dm_nl_pre_doit, + .post_doit = net_dm_nl_post_doit, +-- +GitLab + diff --git a/patch/0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch b/patches-sonic/0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch similarity index 89% rename from patch/0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch rename to patches-sonic/0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch index 0791cff45..d0be2090f 100644 --- a/patch/0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch +++ b/patches-sonic/0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch @@ -1,6 +1,7 @@ From c3eab8b7b730548ade06b71fad6b1d12fd53f28a Mon Sep 17 00:00:00 2001 + From: Jagan Teki -Date: Tue, 12 Feb 2019 17:08:08 +0530 + Subject: [PATCH 4/6] dt-bindings: hwmon: Add missing documentation for lm75 Add missing dt-binding documentation for lm75 hwmon sensor. @@ -9,13 +10,13 @@ Signed-off-by: Jagan Teki Acked-by: Rob Herring Signed-off-by: Guenter Roeck --- - .../devicetree/bindings/hwmon/lm75.txt | 37 +++++++++++++++++++ + Documentation/devicetree/bindings/hwmon/lm75.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/lm75.txt diff --git a/Documentation/devicetree/bindings/hwmon/lm75.txt b/Documentation/devicetree/bindings/hwmon/lm75.txt new file mode 100644 -index 000000000000..12d8cf7cf592 +index 000000000..12d8cf7cf --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/lm75.txt @@ -0,0 +1,37 @@ @@ -56,6 +57,3 @@ index 000000000000..12d8cf7cf592 + compatible = "st,stlm75"; + reg = <0x48>; +}; --- -2.17.1 - diff --git a/patches-sonic/0004-i2c-Add-Lattice-RD1173-I2C-controller-driver.patch b/patches-sonic/0004-i2c-Add-Lattice-RD1173-I2C-controller-driver.patch new file mode 100644 index 000000000..32aa1a8eb --- /dev/null +++ b/patches-sonic/0004-i2c-Add-Lattice-RD1173-I2C-controller-driver.patch @@ -0,0 +1,904 @@ +From 480cacf951622a7e37359c85b0101b3115c194a1 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 08:45:12 +0000 +Subject: [PATCH 04/27] i2c: Add Lattice RD1173 I2C controller driver + +The Lattice RD1173 is a pair of I2C controllers accesed via a SPI +interface. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + drivers/i2c/busses/Kconfig | 10 + + drivers/i2c/busses/Makefile | 1 + + drivers/i2c/busses/i2c-rd1173.c | 845 ++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 856 insertions(+) + create mode 100644 drivers/i2c/busses/i2c-rd1173.c + +diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +index 0e679cc..b3cffaa 100644 +--- a/drivers/i2c/busses/Kconfig ++++ b/drivers/i2c/busses/Kconfig +@@ -1042,6 +1042,16 @@ config I2C_QUP + This driver can also be built as a module. If so, the module + will be called i2c-qup. + ++config I2C_RD1173 ++ tristate "External SPI to Lattice I2C adapter device" ++ depends on SPI && REGMAP ++ help ++ If you say yes to this option, support will be included for the ++ SPI to Lattice I2C-bus adapter device. ++ ++ This driver can also be built as a module. If so, the module ++ will be called i2c-rd1173. ++ + config I2C_RIIC + tristate "Renesas RIIC adapter" + depends on ARCH_RENESAS || COMPILE_TEST +diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +index ecc07c5..7314ce4 100644 +--- a/drivers/i2c/busses/Makefile ++++ b/drivers/i2c/busses/Makefile +@@ -101,6 +101,7 @@ obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o + obj-$(CONFIG_I2C_QCOM_CCI) += i2c-qcom-cci.o + obj-$(CONFIG_I2C_QCOM_GENI) += i2c-qcom-geni.o + obj-$(CONFIG_I2C_QUP) += i2c-qup.o ++obj-$(CONFIG_I2C_RD1173) += i2c-rd1173.o + obj-$(CONFIG_I2C_RIIC) += i2c-riic.o + obj-$(CONFIG_I2C_RK3X) += i2c-rk3x.o + obj-$(CONFIG_I2C_RZV2M) += i2c-rzv2m.o +diff --git a/drivers/i2c/busses/i2c-rd1173.c b/drivers/i2c/busses/i2c-rd1173.c +new file mode 100644 +index 0000000..fc01c49 +--- /dev/null ++++ b/drivers/i2c/busses/i2c-rd1173.c +@@ -0,0 +1,845 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Lattice RD1173 SPI to I2C bus interface driver ++ * ++ * Copyright (C) 2020-2022 Pensando Systems, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* SoC is assigned I2C bus 0 */ ++#define PORT1_I2C_BUS_NUM 1 ++#define PORT2_I2C_BUS_NUM 2 ++ ++/* SPI command bits 7:4 */ ++#define RD1173_CMD_REG_WR 0x00 /* write internal reg */ ++#define RD1173_CMD_REG_RD 0x10 /* read internal reg */ ++#define RD1173_CMD_INT_CHECK 0x20 /* interrupt check */ ++#define RD1173_CMD_I2C_WR_N 0x30 /* write N bytes */ ++#define RD1173_CMD_I2C_RD_N 0x40 /* read N bytes */ ++#define RD1173_CMD_RD_RX_FIFO 0x50 /* read rx fifo */ ++ ++/* Register address bits 3:0 */ ++#define RD1173_FIFO_STATUS_REG 0x01 ++#define RD1173_I2C0_CONFIG_REG 0x04 ++#define RD1173_I2C0_MODE_REG 0x05 ++#define RD1173_I2C0_CMD_STAT_REG 0x06 ++#define RD1173_I2C1_CONFIG_REG 0x0a ++#define RD1173_I2C1_MODE_REG 0x0b ++#define RD1173_I2C1_CMD_STAT_REG 0x0c ++ ++/* Register offset from master regbase */ ++#define RD1173_CONFIG_REG 0x00 ++#define RD1173_MODE_REG 0x01 ++#define RD1173_CMD_STAT_REG 0x02 ++ ++/* FIFO register definitions */ ++#define RD1173_FIFO_RX0_FULL 0x80 ++#define RD1173_FIFO_RX0_EMPTY 0x40 ++#define RD1173_FIFO_TX0_FULL 0x20 ++#define RD1173_FIFO_TX0_EMPTY 0x10 ++#define RD1173_FIFO_RX1_FULL 0x08 ++#define RD1173_FIFO_RX1_EMPTY 0x04 ++#define RD1173_FIFO_TX1_FULL 0x02 ++#define RD1173_FIFO_TX1_EMPTY 0x01 ++ ++/* Configuration register definitions */ ++#define RD1173_CONFIG_RESET 0x80 ++#define RD1173_CONFIG_RXFIFO_CLR 0x40 ++#define RD1173_CONFIG_TXFIFO_CLR 0x20 ++#define RD1173_CONFIG_ABORT 0x10 ++#define RD1173_CONFIG_RXREAD_CLR 0x08 ++#define RD1173_CONFIG_TXREAD_CLR 0x04 ++#define RD1173_CONFIG_INT_CLR 0x02 ++#define RD1173_CONFIG_START 0x01 ++ ++/* Mode register definitions */ ++#define RD1173_MODE_BPS1 0x80 ++#define RD1173_MODE_BPS0 0x40 ++#define RD1173_MODE_TX_IE 0x20 ++#define RD1173_MODE_ACK_POL 0x10 ++#define RD1173_MODE_RX_IE 0x08 ++ ++/* Command status register definitions */ ++#define RD1173_STAT_I2C_BUSY 0x80 ++#define RD1173_STAT_NO_ANS 0x40 ++#define RD1173_STAT_NO_ACK 0x20 ++#define RD1173_STAT_TX_ERR 0x10 ++#define RD1173_STAT_RX_ERR 0x08 ++#define RD1173_STAT_ABORT_ACK 0x04 ++#define RD1173_STAT_TS 0x02 ++ ++struct i2c_stats { ++ u32 i2c0_tx_complete; ++ u32 i2c0_rx_complete; ++ u32 i2c0_busy; ++ u32 i2c0_no_answer; ++ u32 i2c0_no_ack; ++ u32 i2c0_tx_error; ++ u32 i2c0_rx_error; ++ u32 i2c0_abort_ack; ++ ++ u32 i2c1_tx_complete; ++ u32 i2c1_rx_complete; ++ u32 i2c1_busy; ++ u32 i2c1_no_answer; ++ u32 i2c1_no_ack; ++ u32 i2c1_tx_error; ++ u32 i2c1_rx_error; ++ u32 i2c1_abort_ack; ++}; ++ ++struct rd1173_i2c_adapter { ++ struct rd1173dev *rd1173dev; ++ struct i2c_adapter i2c_adap; ++ int i2c_master; ++ int state; ++ u32 offset; ++}; ++ ++struct rd1173dev { ++ struct spi_device *spi; ++ const struct chipdesc *chip; ++ struct regmap *regmap; ++ struct rd1173_i2c_adapter i2c_adap[2]; ++ struct i2c_stats stats; ++ struct mutex xfer_active; ++ struct completion completion; ++ int active_port; ++}; ++ ++enum chiptype { ++ SPI2I2C_PENCPLD, ++}; ++ ++struct chipdesc { ++ u8 type; ++ u32 buffer_size; ++ const struct regmap_config *regmap_cfg; ++}; ++ ++#define i2c_show_simple(field, name, format_string, cast) \ ++static ssize_t \ ++show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ ++{ \ ++ struct spi_device *spi = to_spi_device(dev); \ ++ struct rd1173dev *rd1173dev = spi_get_drvdata(spi); \ ++ struct i2c_stats *i2c_stats = &rd1173dev->stats; \ ++ \ ++ return snprintf(buf, 20, format_string, cast i2c_stats->field); \ ++} ++ ++#define i2c_attr_show(field, name, format_string, type) \ ++ i2c_show_simple(field, name, format_string, (type)) \ ++static DEVICE_ATTR(name, 0444, show_##name, NULL) ++ ++i2c_attr_show(i2c0_tx_complete, i2c0_tx_complete, "%d\n", u32); ++i2c_attr_show(i2c0_rx_complete, i2c0_rx_complete, "%d\n", u32); ++i2c_attr_show(i2c0_busy, i2c0_busy, "%d\n", u32); ++i2c_attr_show(i2c0_no_answer, i2c0_no_answer, "%d\n", u32); ++i2c_attr_show(i2c0_no_ack, i2c0_no_ack, "%d\n", u32); ++i2c_attr_show(i2c0_tx_error, i2c0_tx_error, "%d\n", u32); ++i2c_attr_show(i2c0_rx_error, i2c0_rx_error, "%d\n", u32); ++i2c_attr_show(i2c0_abort_ack, i2c0_abort_ack, "%d\n", u32); ++ ++i2c_attr_show(i2c1_tx_complete, i2c1_tx_complete, "%d\n", u32); ++i2c_attr_show(i2c1_rx_complete, i2c1_rx_complete, "%d\n", u32); ++i2c_attr_show(i2c1_busy, i2c1_busy, "%d\n", u32); ++i2c_attr_show(i2c1_no_answer, i2c1_no_answer, "%d\n", u32); ++i2c_attr_show(i2c1_no_ack, i2c1_no_ack, "%d\n", u32); ++i2c_attr_show(i2c1_tx_error, i2c1_tx_error, "%d\n", u32); ++i2c_attr_show(i2c1_rx_error, i2c1_rx_error, "%d\n", u32); ++i2c_attr_show(i2c1_abort_ack, i2c1_abort_ack, "%d\n", u32); ++ ++static struct attribute *i2c_attrs[] = { ++ &dev_attr_i2c0_tx_complete.attr, ++ &dev_attr_i2c0_rx_complete.attr, ++ &dev_attr_i2c0_busy.attr, ++ &dev_attr_i2c0_no_answer.attr, ++ &dev_attr_i2c0_no_ack.attr, ++ &dev_attr_i2c0_tx_error.attr, ++ &dev_attr_i2c0_rx_error.attr, ++ &dev_attr_i2c0_abort_ack.attr, ++ ++ &dev_attr_i2c1_tx_complete.attr, ++ &dev_attr_i2c1_rx_complete.attr, ++ &dev_attr_i2c1_busy.attr, ++ &dev_attr_i2c1_no_answer.attr, ++ &dev_attr_i2c1_no_ack.attr, ++ &dev_attr_i2c1_tx_error.attr, ++ &dev_attr_i2c1_rx_error.attr, ++ &dev_attr_i2c1_abort_ack.attr, ++ NULL, ++}; ++ ++static const struct attribute_group i2c_attr_group = { ++ .attrs = i2c_attrs, ++}; ++ ++#define rd1173_show_simple(field, name, format_string, cast) \ ++static ssize_t \ ++show_##name(struct device *dev, struct device_attribute *attr, \ ++ char *buf) \ ++{ \ ++ struct spi_device *spi = to_spi_device(dev); \ ++ struct rd1173dev *rd1173dev = spi_get_drvdata(spi); \ ++ unsigned int val; \ ++ \ ++ regmap_read(rd1173dev->regmap, field, &val); \ ++ return snprintf(buf, 20, format_string, cast val); \ ++} ++ ++#define rd1173_store_simple(field, name, base) \ ++static ssize_t \ ++store_##name(struct device *dev, struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++{ \ ++ struct spi_device *spi = to_spi_device(dev); \ ++ struct rd1173dev *rd1173dev = spi_get_drvdata(spi); \ ++ unsigned long val; \ ++ int ret; \ ++ \ ++ ret = kstrtoul(buf, base, &val); \ ++ if (ret) return ret; \ ++ regmap_write(rd1173dev->regmap, field, (unsigned int)val); \ ++ return count; \ ++} ++ ++#define rd1173_attr_show(field, name, format_string, type) \ ++ rd1173_show_simple(field, name, format_string, (type)) \ ++static DEVICE_ATTR(name, 0444, show_##name, NULL) ++ ++#define rd1173_attr_rw(field, name, format_string, type) \ ++ rd1173_show_simple(field, name, format_string, (type)) \ ++ rd1173_store_simple(field, name, 0) \ ++static DEVICE_ATTR(name, 0644, show_##name, store_##name) ++ ++rd1173_attr_rw(RD1173_I2C0_CONFIG_REG, i2c0_cfg, "0x%02x\n", u32); ++rd1173_attr_rw(RD1173_I2C1_CONFIG_REG, i2c1_cfg, "0x%02x\n", u32); ++rd1173_attr_show(RD1173_I2C0_MODE_REG, i2c0_mode, "0x%02x\n", u32); ++rd1173_attr_show(RD1173_I2C1_MODE_REG, i2c1_mode, "0x%02x\n", u32); ++rd1173_attr_show(RD1173_I2C0_CMD_STAT_REG, i2c0_cmdstat, "0x%02x\n", u32); ++rd1173_attr_show(RD1173_I2C1_CMD_STAT_REG, i2c1_cmdstat, "0x%02x\n", u32); ++rd1173_attr_show(RD1173_FIFO_STATUS_REG, i2c_fifo_status, "0x%02x\n", u32); ++ ++static struct attribute *rd1173_attrs[] = { ++ &dev_attr_i2c0_cfg.attr, ++ &dev_attr_i2c1_cfg.attr, ++ &dev_attr_i2c0_mode.attr, ++ &dev_attr_i2c1_mode.attr, ++ &dev_attr_i2c0_cmdstat.attr, ++ &dev_attr_i2c1_cmdstat.attr, ++ &dev_attr_i2c_fifo_status.attr, ++ NULL, ++}; ++ ++static const struct attribute_group rd1173_attr_group = { ++ .attrs = rd1173_attrs, ++}; ++ ++static bool rd1173_writeable_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case RD1173_FIFO_STATUS_REG: ++ case RD1173_I2C0_CMD_STAT_REG: ++ case RD1173_I2C1_CMD_STAT_REG: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++static const struct regmap_config rd1173_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = RD1173_I2C1_CMD_STAT_REG, ++ .writeable_reg = rd1173_writeable_reg, ++}; ++ ++static const struct chipdesc chip_rd1173 = { ++ .type = SPI2I2C_PENCPLD, ++ .buffer_size = 8, ++ .regmap_cfg = &rd1173_regmap_config, ++}; ++ ++static irqreturn_t rd1173_irq_handler(int this_irq, void *data) ++{ ++ struct rd1173dev *rd1173dev = data; ++ struct rd1173_i2c_adapter *i2c0 = &rd1173dev->i2c_adap[0]; ++ struct rd1173_i2c_adapter *i2c1 = &rd1173dev->i2c_adap[1]; ++ int rc; ++ ++ if (rd1173dev->active_port == 1) { ++ rc = regmap_read(rd1173dev->regmap, ++ i2c0->offset + RD1173_CMD_STAT_REG, ++ &i2c0->state); ++ if (rc) ++ return IRQ_NONE; ++ ++ if (i2c0->state & RD1173_STAT_TS) { ++ complete(&i2c0->rd1173dev->completion); ++ return IRQ_HANDLED; ++ } ++ } else if (rd1173dev->active_port == 2) { ++ rc = regmap_read(rd1173dev->regmap, ++ i2c1->offset + RD1173_CMD_STAT_REG, ++ &i2c1->state); ++ if (rc) ++ return IRQ_NONE; ++ ++ if (i2c1->state & RD1173_STAT_TS) { ++ complete(&i2c1->rd1173dev->completion); ++ return IRQ_HANDLED; ++ } ++ } ++ return IRQ_NONE; ++} ++ ++/** ++ * reg_read - RD1173 internal register read ++ * @context: Pointer to rd1173 device ++ * @reg: Internal register address ++ * @val: Read data ++ * ++ * Return: zero on success, else a negative error code. ++ */ ++static int reg_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ struct device *dev = context; ++ struct spi_device *spi = to_spi_device(dev); ++ u8 txbuffer[1] = { RD1173_CMD_REG_RD | (reg & 0xf) }; ++ u8 rxbuffer[1]; ++ int rc; ++ ++ rc = spi_write_then_read(spi, txbuffer, sizeof(txbuffer), ++ rxbuffer, sizeof(rxbuffer)); ++ if (rc) { ++ dev_dbg(&spi->dev, "reg read error %d\n", rc); ++ return rc; ++ } ++ *val = rxbuffer[0]; ++ ++ return 0; ++} ++ ++/** ++ * reg_write - RD1173 internal register write ++ * @context: Pointer to rd1173 device ++ * @reg: Internal register address ++ * @val: Write data ++ * ++ * Return: zero on success, else a negative error code. ++ */ ++static int reg_write(void *context, unsigned int reg, unsigned int val) ++{ ++ struct device *dev = context; ++ struct spi_device *spi = to_spi_device(dev); ++ u8 txbuffer[2] = { RD1173_CMD_REG_WR | (reg & 0xf), val & 0xff }; ++ ++ return spi_write(spi, txbuffer, sizeof(txbuffer)); ++} ++ ++static struct regmap_bus regmap_rd1173_bus = { ++ .reg_write = reg_write, ++ .reg_read = reg_read, ++ .reg_format_endian_default = REGMAP_ENDIAN_BIG, ++ .val_format_endian_default = REGMAP_ENDIAN_BIG, ++}; ++ ++/** ++ * rd1173_reset - Reset i2c master ++ * @i2c: Pointer to rd1173 i2c master device ++ * ++ * Return: zero on success, else a negative error code. ++ */ ++static int rd1173_reset(struct rd1173_i2c_adapter *i2c) ++{ ++ struct device *dev = &i2c->rd1173dev->spi->dev; ++ int reset = RD1173_CONFIG_RESET | RD1173_CONFIG_INT_CLR | ++ RD1173_CONFIG_ABORT; ++ int enable = RD1173_MODE_TX_IE | RD1173_MODE_RX_IE; ++ int rc; ++ ++ dev_dbg(dev, "rd1173 i2c%d reset, cmdstat 0x%x\n", ++ i2c->i2c_adap.nr - 1, i2c->state); ++ ++ /* Assert reset */ ++ rc = regmap_update_bits(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_CONFIG_REG, ++ reset, reset); ++ if (rc) ++ dev_dbg(dev, "assert reset error %d\n", rc); ++ ++ /* Remove reset */ ++ rc = regmap_update_bits(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_CONFIG_REG, ++ reset, 0); ++ if (rc) ++ dev_dbg(dev, "deassert reset error %d\n", rc); ++ ++ /* Enable TX/RX interrupts, standard I2C speed */ ++ rc = regmap_update_bits(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_MODE_REG, ++ enable, enable); ++ if (rc) ++ dev_dbg(dev, "enable int error %d\n", rc); ++ ++ return rc; ++} ++ ++/** ++ * rd1173_fifo_clear - Clear TX and RX FIFOs ++ * @i2c: Pointer to rd1173 i2c master device ++ * ++ * Return: zero on success, else a negative error code. ++ */ ++static int rd1173_fifo_clear(struct rd1173_i2c_adapter *i2c) ++{ ++ struct device *dev = &i2c->rd1173dev->spi->dev; ++ int clr = RD1173_CONFIG_RXFIFO_CLR | RD1173_CONFIG_TXFIFO_CLR | ++ RD1173_CONFIG_RXREAD_CLR | RD1173_CONFIG_TXREAD_CLR; ++ int rc; ++ ++ /* Assert fifo clear */ ++ rc = regmap_update_bits(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_CONFIG_REG, ++ clr, clr); ++ if (rc) ++ dev_dbg(dev, "fifo clear error %d\n", rc); ++ ++ /* Deassert fifo clear */ ++ rc = regmap_update_bits(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_CONFIG_REG, ++ clr, 0); ++ if (rc) ++ dev_dbg(dev, "fifo clear error %d\n", rc); ++ ++ return rc; ++} ++ ++/** ++ * rd1173_read - Initiate a read from rd1173 i2c master ++ * @i2c: Pointer to rd1173 i2c master device ++ * @msg: Pointer to i2c message structure ++ * @cnt: Number of bytes to read (max 8) ++ * ++ * Return: Return: zero on success, else a negative error code. ++ */ ++static int rd1173_read(struct rd1173_i2c_adapter *i2c, struct i2c_msg *msg, u8 cnt) ++{ ++ u8 hdr[] = { RD1173_CMD_I2C_RD_N | (i2c->i2c_master << 1), ++ cnt, msg->addr }; ++ u8 start[] = { RD1173_CMD_REG_WR | (i2c->offset + RD1173_CONFIG_REG), ++ RD1173_CONFIG_START }; ++ int rc; ++ ++ rc = spi_write(i2c->rd1173dev->spi, hdr, sizeof(hdr)); ++ if (rc) ++ return rc; ++ ++ return spi_write(i2c->rd1173dev->spi, start, sizeof(start)); ++} ++ ++/** ++ * rd1173_write - Initiate a write to rd1173 i2c master ++ * @i2c: Pointer to rd1173 i2c master device ++ * @msg: Pointer to i2c message structure ++ * @cnt: Number of bytes to write (max 8) ++ * ++ * Return: Return: zero on success, else a negative error code. ++ */ ++static int rd1173_write(struct rd1173_i2c_adapter *i2c, struct i2c_msg *msg, u8 cnt) ++{ ++ u8 hdr[] = { RD1173_CMD_I2C_WR_N | (i2c->i2c_master << 1), ++ cnt, msg->addr }; ++ u8 start[] = { RD1173_CMD_REG_WR | (i2c->offset + RD1173_CONFIG_REG), ++ RD1173_CONFIG_START }; ++ struct spi_transfer xfer[2] = { 0 }; ++ int rc; ++ ++ xfer[0].tx_buf = hdr; ++ xfer[0].len = sizeof(hdr); ++ ++ xfer[1].tx_buf = msg->buf; ++ xfer[1].len = msg->len; ++ ++ rc = spi_sync_transfer(i2c->rd1173dev->spi, xfer, 2); ++ if (rc) ++ return rc; ++ ++ return spi_write(i2c->rd1173dev->spi, start, sizeof(start)); ++} ++ ++/** ++ * rd1173_read_buffer - Read rd1173 i2c master receive fifo ++ * @i2c: Pointer to rd1173 i2c master device ++ * @buf: Pointer to receive buffer address ++ * @cnt: Number of bytes to read from rx fifo (max 8) ++ * ++ * Return: zero on success, else a negative error code. ++ */ ++static int rd1173_read_buffer(struct rd1173_i2c_adapter *i2c, u8 *buf, u8 cnt) ++{ ++ u8 txbuffer[1] = { RD1173_CMD_RD_RX_FIFO | (i2c->i2c_master << 1) }; ++ ++ return spi_write_then_read(i2c->rd1173dev->spi, ++ txbuffer, sizeof(txbuffer), ++ buf, cnt + 1); ++} ++ ++/** ++ * rd1173_clear_cmdstat - Clear rd1173 command/status register ++ * @i2c: Pointer to rd1173 i2c master device ++ * ++ * Return: zero on success, else a negative error code. ++ */ ++static int rd1173_clear_cmdstat(struct rd1173_i2c_adapter *i2c) ++{ ++ struct device *dev = &i2c->rd1173dev->spi->dev; ++ int rc; ++ ++ rc = regmap_update_bits(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_CONFIG_REG, ++ RD1173_CONFIG_INT_CLR, RD1173_CONFIG_INT_CLR); ++ if (rc) ++ dev_dbg(dev, "clear cmdstat err %d\n", rc); ++ ++ rc = regmap_update_bits(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_CONFIG_REG, ++ RD1173_CONFIG_INT_CLR, 0); ++ if (rc) ++ dev_dbg(dev, "clear cmdstat err %d\n", rc); ++ ++ return rc; ++} ++ ++/** ++ * update_i2c_stats - Update the i2c transfer state counters ++ * @i2c: Pointer to rd1173 i2c master device ++ * @readop: I2C readop or writeop ++ * ++ */ ++static void update_i2c_stats(struct rd1173_i2c_adapter *i2c, int readop) ++{ ++ struct i2c_stats *stats = &i2c->rd1173dev->stats; ++ ++ regmap_read(i2c->rd1173dev->regmap, ++ i2c->offset + RD1173_CMD_STAT_REG, ++ &i2c->state); ++ ++ if (i2c->state & RD1173_STAT_TS) { ++ if (i2c->i2c_master) ++ readop ? stats->i2c1_rx_complete++ : ++ stats->i2c1_tx_complete++; ++ else ++ readop ? stats->i2c0_rx_complete++ : ++ stats->i2c0_tx_complete++; ++ } ++ if (i2c->state & RD1173_STAT_I2C_BUSY) ++ i2c->i2c_master ? stats->i2c1_busy++ : ++ stats->i2c0_busy++; ++ if (i2c->state & RD1173_STAT_NO_ANS) ++ i2c->i2c_master ? stats->i2c1_no_answer++ : ++ stats->i2c0_no_answer++; ++ if (i2c->state & RD1173_STAT_NO_ACK) ++ i2c->i2c_master ? stats->i2c1_no_ack++ : ++ stats->i2c0_no_ack++; ++ if (i2c->state & RD1173_STAT_TX_ERR) ++ i2c->i2c_master ? stats->i2c1_tx_error++ : ++ stats->i2c0_tx_error++; ++ if (i2c->state & RD1173_STAT_RX_ERR) ++ i2c->i2c_master ? stats->i2c1_rx_error++ : ++ stats->i2c0_rx_error++; ++ if (i2c->state & RD1173_STAT_ABORT_ACK) ++ i2c->i2c_master ? stats->i2c1_abort_ack++ : ++ stats->i2c0_abort_ack++; ++} ++ ++/** ++ * rd1173_xfer - The master spi to i2c transfer function ++ * @adap: Pointer to the i2c_adapter structure ++ * @msgs: Pointer to the messages to be processed ++ * @num: Length of the MSGS array ++ * ++ * Returns the number of messages processed, or a negative errno on failure. ++ */ ++static int rd1173_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) ++{ ++ struct rd1173_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap); ++ struct device *dev = &i2c->rd1173dev->spi->dev; ++ u8 max_len = i2c->rd1173dev->chip->buffer_size; ++ int read_operation = 0; ++ u16 bytes_remaining; ++ u16 bytes_transferred; ++ u8 num_transfers; ++ u8 rx_fifo[9]; ++ u8 cnt; ++ int rc = 0; ++ int i, j; ++ ++ if (num > 2) ++ return -EOPNOTSUPP; ++ ++ for (i = 0; i < num; i++) { ++ if (msgs[i].flags & I2C_M_RD) { ++ if (msgs[i].len > 256) ++ return -EOPNOTSUPP; ++ read_operation++; ++ } else { ++ /* Writes limited buffer size */ ++ if (msgs[i].len > max_len) ++ return -EOPNOTSUPP; ++ } ++ dev_dbg(dev, "i2c%d msgs[%d]: addr 0x%x flags 0x%x len %d\n", ++ i2c->i2c_adap.nr - 1, i, msgs[i].addr, msgs[i].flags, ++ msgs[i].len); ++ } ++ ++ if (mutex_lock_interruptible(&i2c->rd1173dev->xfer_active)) ++ return -ERESTARTSYS; ++ ++ reinit_completion(&i2c->rd1173dev->completion); ++ i2c->rd1173dev->active_port = i2c->i2c_adap.nr; ++ ++ for (i = 0; i < num; i++) { ++ rd1173_fifo_clear(i2c); ++ rd1173_clear_cmdstat(i2c); ++ ++ bytes_transferred = 0; ++ bytes_remaining = msgs[i].len; ++ num_transfers = (bytes_remaining + max_len - 1) / max_len; ++ ++ for (j = 0; j < num_transfers; j++) { ++ rd1173_fifo_clear(i2c); ++ rd1173_clear_cmdstat(i2c); ++ ++ if (bytes_remaining > max_len) ++ cnt = max_len; ++ else ++ cnt = bytes_remaining; ++ ++ if (msgs[i].flags & I2C_M_RD) { ++ dev_dbg(dev, "i2c%d read addr 0x%x len %d\n", ++ i2c->i2c_adap.nr - 1, msgs[i].addr, ++ msgs[i].len); ++ rc = rd1173_read(i2c, &msgs[i], cnt); ++ } else { ++ dev_dbg(dev, "i2c%d write addr 0x%x len %d\n", ++ i2c->i2c_adap.nr - 1, msgs[i].addr, ++ msgs[i].len); ++ rc = rd1173_write(i2c, &msgs[i], cnt); ++ } ++ ++ rc = wait_for_completion_timeout(&i2c->rd1173dev->completion, ++ i2c->i2c_adap.timeout); ++ ++ update_i2c_stats(i2c, msgs[i].flags & I2C_M_RD); ++ ++ if (!rc && i2c->state != RD1173_STAT_TS) ++ dev_dbg(dev, "i2c%d cmdstat 0x%02x\n", ++ i2c->i2c_adap.nr - 1, i2c->state); ++ ++ if (!rc || i2c->state != RD1173_STAT_TS) ++ goto done; ++ ++ if (msgs[i].flags & I2C_M_RD) { ++ rc = rd1173_read_buffer(i2c, rx_fifo, cnt); ++ if (rc) ++ goto done; ++ ++ memcpy((u8 *)(msgs[i].buf + bytes_transferred), ++ &rx_fifo[1], cnt); ++ ++ bytes_remaining -= cnt; ++ bytes_transferred += cnt; ++ } ++ } ++ } ++ ++done: ++ rd1173_clear_cmdstat(i2c); ++ switch (i2c->state) { ++ case RD1173_STAT_TS: ++ rc = num; /* transfer complete */ ++ break; ++ case RD1173_STAT_I2C_BUSY: ++ rd1173_reset(i2c); ++ rc = -EAGAIN; ++ break; ++ case RD1173_STAT_NO_ANS: ++ rc = -ENXIO; ++ break; ++ case RD1173_STAT_NO_ACK: ++ rc = -EREMOTEIO; ++ break; ++ case RD1173_STAT_TX_ERR: ++ case RD1173_STAT_RX_ERR: ++ case RD1173_STAT_ABORT_ACK: ++ rd1173_reset(i2c); ++ rc = -EAGAIN; ++ default: ++ rc = -EAGAIN; ++ } ++ ++ i2c->rd1173dev->active_port = 0; ++ mutex_unlock(&i2c->rd1173dev->xfer_active); ++ return rc; ++} ++ ++static u32 rd1173_func(struct i2c_adapter *adap) ++{ ++ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; ++} ++ ++static const struct i2c_algorithm rd1173_algo = { ++ .master_xfer = rd1173_xfer, ++ .functionality = rd1173_func, ++}; ++ ++static int rd1173_probe(struct spi_device *spi) ++{ ++ struct rd1173dev *rd1173dev; ++ struct rd1173_i2c_adapter *i2c; ++ int i, rc; ++ ++ rd1173dev = devm_kzalloc(&spi->dev, sizeof(*rd1173dev), GFP_KERNEL); ++ if (!rd1173dev) ++ return -ENOMEM; ++ ++ spi_set_drvdata(spi, rd1173dev); ++ init_completion(&rd1173dev->completion); ++ rd1173dev->spi = spi; ++ spi->bits_per_word = 8; ++ spi->mode = SPI_MODE_0; ++ ++ for (i = 0; i < 2; i++) { ++ i2c = &rd1173dev->i2c_adap[i]; ++ i2c_set_adapdata(&i2c->i2c_adap, i2c); ++ i2c->rd1173dev = rd1173dev; ++ i2c->i2c_master = i; ++ i2c->i2c_adap.algo = &rd1173_algo; ++ i2c->i2c_adap.algo_data = NULL; ++ i2c->i2c_adap.dev.parent = &spi->dev; ++ i2c->i2c_adap.owner = THIS_MODULE; ++ i2c->i2c_adap.class = I2C_CLASS_DEPRECATED; ++ i2c->i2c_adap.timeout = msecs_to_jiffies(100); ++ i2c->i2c_adap.nr = PORT1_I2C_BUS_NUM + i; ++ ++ if (i == 0) ++ i2c->offset = RD1173_I2C0_CONFIG_REG; ++ else ++ i2c->offset = RD1173_I2C1_CONFIG_REG; ++ ++ snprintf(i2c->i2c_adap.name, sizeof(i2c->i2c_adap.name), ++ "CPLD Lattice RD1173 I2C%d", i); ++ ++ dev_info(&spi->dev, "%s mode %d irq %d\n", i2c->i2c_adap.name, ++ spi->mode, spi->irq); ++ } ++ ++ rd1173dev->chip = of_device_get_match_data(&spi->dev); ++ if (!rd1173dev->chip) { ++ dev_info(&spi->dev, "No matching chip %d\n", -ENODEV); ++ return -ENODEV; ++ } ++ ++ rd1173dev->regmap = devm_regmap_init(&spi->dev, ®map_rd1173_bus, ++ &spi->dev, ++ rd1173dev->chip->regmap_cfg); ++ if (IS_ERR(rd1173dev->regmap)) { ++ rc = PTR_ERR(rd1173dev->regmap); ++ dev_err(&spi->dev, "Failed to init regmap, %d\n", rc); ++ return rc; ++ } ++ ++ ++ mutex_init(&rd1173dev->xfer_active); ++ ++ rc = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, ++ rd1173_irq_handler, ++ IRQF_TRIGGER_FALLING | IRQF_ONESHOT, ++ "cpld-rd1173", rd1173dev); ++ if (rc) { ++ dev_err(&spi->dev, "Failed to request irq, err %d\n", rc); ++ return rc; ++ } ++ ++ rc = spi_setup(spi); ++ if (rc) { ++ dev_info(&spi->dev, "spi setup error %d\n", rc); ++ return rc; ++ } ++ ++ for (i = 0; i < 2; i++) { ++ i2c = &rd1173dev->i2c_adap[i]; ++ rd1173_reset(i2c); ++ rd1173_fifo_clear(i2c); ++ ++ rc = i2c_add_numbered_adapter(&i2c->i2c_adap); ++ if (rc) { ++ dev_err(&spi->dev, "error adding i2c adapter: %d\n", rc); ++ return rc; ++ } ++ dev_info(&spi->dev, "registered I2C bus number %d\n", ++ i2c->i2c_adap.nr); ++ } ++ ++ rc = sysfs_create_group(&spi->dev.kobj, &i2c_attr_group); ++ if (rc) ++ dev_warn(&spi->dev, "failed to create i2c sysfs files\n"); ++ ++ rc = sysfs_create_group(&spi->dev.kobj, &rd1173_attr_group); ++ if (rc) ++ dev_warn(&spi->dev, "failed to create rd1173 sysfs files\n"); ++ ++ return 0; ++} ++ ++static void rd1173_remove(struct spi_device *spi) ++{ ++ struct rd1173dev *rd1173dev = spi_get_drvdata(spi); ++ ++ i2c_del_adapter(&rd1173dev->i2c_adap[0].i2c_adap); ++ i2c_del_adapter(&rd1173dev->i2c_adap[1].i2c_adap); ++ ++ return; ++} ++ ++static const struct spi_device_id rd1173_spi_ids[] = { ++ { .name = "cpld-rd1173" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(spi, rd1173_spi_ids); ++ ++#ifdef CONFIG_OF ++static const struct of_device_id rd1173_of_match[] = { ++ { .compatible = "pensando,cpld-rd1173", .data = &chip_rd1173 }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, rd1173_of_match); ++#endif ++ ++static struct spi_driver rd1173_driver = { ++ .probe = rd1173_probe, ++ .remove = rd1173_remove, ++ .id_table = rd1173_spi_ids, ++ .driver = { ++ .name = "i2c-rd1173", ++ .of_match_table = of_match_ptr(rd1173_of_match), ++ }, ++}; ++module_spi_driver(rd1173_driver); ++ ++MODULE_AUTHOR("Brad Larson "); ++MODULE_DESCRIPTION("Pensando CPLD Lattice RD1173 SPI to I2C bus adapter"); ++MODULE_LICENSE("GPL"); +-- +1.8.3.1 + diff --git a/patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch b/patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch new file mode 100644 index 000000000..40a6a8b01 --- /dev/null +++ b/patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch @@ -0,0 +1,62 @@ +From ef1be57f590ab3df679fb591f22065561c796c56 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 14:37:56 +0300 +Subject: [PATCH 6.12 4/5] mctp: driver: net: Fix MCTP over VDM/PCIe + +Fix two issues in the MCTP over VDM/PCIe transport +(CONFIG_MCTP_TRANSPORT_PCIE_VDM): + +- mctp-pcie-vdm.c was missing MODULE_LICENSE() and MODULE_DESCRIPTION(), + which causes a module taint/warning when the transport is built and + loaded as a module. Add both. +- The header guarded its declarations with + "#ifdef CONFIG_MCTP_TRANSPORT_PCIE_VDM", which does not handle the + option being built as a module (=m). Include and use + IS_ENABLED() so the declarations are visible in both built-in and + module configurations. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/mctp-pcie-vdm.c | 3 +++ + include/linux/mctp-pcie-vdm.h | 7 ++++--- + 2 files changed, 7 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/mctp/mctp-pcie-vdm.c b/drivers/net/mctp/mctp-pcie-vdm.c +index f715fc085..43f35c46e 100644 +--- a/drivers/net/mctp/mctp-pcie-vdm.c ++++ b/drivers/net/mctp/mctp-pcie-vdm.c +@@ -359,3 +359,6 @@ void mctp_pcie_vdm_remove_dev(struct net_device *vdm_dev) + } + } + EXPORT_SYMBOL_GPL(mctp_pcie_vdm_remove_dev); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MCTP PCIe VDM transport (DSP0238) helpers"); +diff --git a/include/linux/mctp-pcie-vdm.h b/include/linux/mctp-pcie-vdm.h +index 25fac3499..1aee02126 100644 +--- a/include/linux/mctp-pcie-vdm.h ++++ b/include/linux/mctp-pcie-vdm.h +@@ -8,10 +8,11 @@ + #ifndef __LINUX_MCTP_PCIE_VDM_H + #define __LINUX_MCTP_PCIE_VDM_H + ++#include + #include + #include + +-#ifdef CONFIG_MCTP_TRANSPORT_PCIE_VDM ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) + + /** + * @send_packet: referenced to send packets with PCIe VDM header packed. +@@ -32,5 +33,5 @@ struct net_device *mctp_pcie_vdm_add_dev(struct device *dev, + void mctp_pcie_vdm_receive_packet(struct net_device *ndev); + void mctp_pcie_vdm_remove_dev(struct net_device *ndev); + +-#endif /* CONFIG_MCTP_TRANSPORT_PCIE_VDM */ +-#endif /* __LINUX_MCTP_PCIE_VDM_H */ ++#endif /* IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) */ ++#endif /* __LINUX_MCTP_PCIE_VDM_H */ +-- +2.34.1 + diff --git a/patches-sonic/0004-platform-mellanox-mlxbf-bootctl-use-sysfs_emit-inste.patch b/patches-sonic/0004-platform-mellanox-mlxbf-bootctl-use-sysfs_emit-inste.patch new file mode 100644 index 000000000..2b8bd68f0 --- /dev/null +++ b/patches-sonic/0004-platform-mellanox-mlxbf-bootctl-use-sysfs_emit-inste.patch @@ -0,0 +1,77 @@ +From 6e425aad9eb78283afa517ef648d7539596bc41b Mon Sep 17 00:00:00 2001 +From: Ai Chao +Date: Thu, 16 Jan 2025 16:08:35 +0800 +Subject: [PATCH 04/78] platform/mellanox: mlxbf-bootctl: use sysfs_emit() + instead of sprintf() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Follow the advice in Documentation/filesystems/sysfs.rst: +show() should only use sysfs_emit() or sysfs_emit_at() when formatting +the value to be returned to user space. + +Signed-off-by: Ai Chao +Acked-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250116080836.2890442-1-aichao@kylinos.cn +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxbf-bootctl.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c +index 7e246dd..e8530ef 100644 +--- a/drivers/platform/mellanox/mlxbf-bootctl.c ++++ b/drivers/platform/mellanox/mlxbf-bootctl.c +@@ -177,7 +177,7 @@ static ssize_t post_reset_wdog_show(struct device *dev, + if (ret < 0) + return ret; + +- return sprintf(buf, "%d\n", ret); ++ return sysfs_emit(buf, "%d\n", ret); + } + + static ssize_t post_reset_wdog_store(struct device *dev, +@@ -206,7 +206,7 @@ static ssize_t mlxbf_bootctl_show(int smc_op, char *buf) + if (action < 0) + return action; + +- return sprintf(buf, "%s\n", mlxbf_bootctl_action_to_string(action)); ++ return sysfs_emit(buf, "%s\n", mlxbf_bootctl_action_to_string(action)); + } + + static int mlxbf_bootctl_store(int smc_op, const char *buf, size_t count) +@@ -274,14 +274,14 @@ static ssize_t lifecycle_state_show(struct device *dev, + * due to using the test bits. + */ + if (test_state) { +- return sprintf(buf, "%s(test)\n", ++ return sysfs_emit(buf, "%s(test)\n", + mlxbf_bootctl_lifecycle_states[lc_state]); + } else if (use_dev_key && + (lc_state == MLXBF_BOOTCTL_SB_LIFECYCLE_GA_SECURE)) { +- return sprintf(buf, "Secured (development)\n"); ++ return sysfs_emit(buf, "Secured (development)\n"); + } + +- return sprintf(buf, "%s\n", mlxbf_bootctl_lifecycle_states[lc_state]); ++ return sysfs_emit(buf, "%s\n", mlxbf_bootctl_lifecycle_states[lc_state]); + } + + static ssize_t secure_boot_fuse_state_show(struct device *dev, +@@ -332,9 +332,9 @@ static ssize_t secure_boot_fuse_state_show(struct device *dev, + else + status = valid ? "Invalid" : "Free"; + } +- buf_len += sprintf(buf + buf_len, "%d:%s ", key, status); ++ buf_len += sysfs_emit(buf + buf_len, "%d:%s ", key, status); + } +- buf_len += sprintf(buf + buf_len, "\n"); ++ buf_len += sysfs_emit(buf + buf_len, "\n"); + + return buf_len; + } +-- +2.8.4 + diff --git a/patches-sonic/0004-xilinx-spi-platform_get_irq_optional.patch b/patches-sonic/0004-xilinx-spi-platform_get_irq_optional.patch new file mode 100644 index 000000000..171d7b1df --- /dev/null +++ b/patches-sonic/0004-xilinx-spi-platform_get_irq_optional.patch @@ -0,0 +1,35 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v3 2/3] spi: xilinx: make irq optional +From: Abdurrahman Hussain +Date: Mon, 19 Jan 2026 07:06:23 +0000 +Message-Id: <20260119-spi-xilinx-v3-2-4566c33bac0d@nexthop.ai> +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Both the hardware and driver already support polling mode. By removing +the mandatory IRQ requirement during probe, the driver can now fall +back to polling when an interrupt is unavailable, ensuring +compatibility with a wider range of systems. + +Signed-off-by: Abdurrahman Hussain +--- + drivers/spi/spi-xilinx.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/spi/spi-xilinx.c b/drivers/spi/spi-xilinx.c +index c86dc56f38b4..4aa67fc61cb6 100644 +--- a/drivers/spi/spi-xilinx.c ++++ b/drivers/spi/spi-xilinx.c +@@ -471,7 +471,7 @@ static int xilinx_spi_probe(struct platform_device *pdev) + xspi->bytes_per_word = bits_per_word / 8; + xspi->buffer_size = xilinx_spi_find_buffer_size(xspi); + +- xspi->irq = platform_get_irq(pdev, 0); ++ xspi->irq = platform_get_irq_optional(pdev, 0); + if (xspi->irq < 0 && xspi->irq != -ENXIO) { + return xspi->irq; + } else if (xspi->irq >= 0) { + +-- +2.52.0 diff --git a/patches-sonic/0005-PCI-AER-Check-log-level-once-and-remember-it.patch b/patches-sonic/0005-PCI-AER-Check-log-level-once-and-remember-it.patch new file mode 100644 index 000000000..4d5dec043 --- /dev/null +++ b/patches-sonic/0005-PCI-AER-Check-log-level-once-and-remember-it.patch @@ -0,0 +1,128 @@ +From 4355136d6587aa7ce7729aa8f35f898993c5f8a4 Mon Sep 17 00:00:00 2001 +From: Karolina Stolarek +Date: Thu, 22 May 2025 18:21:18 -0500 +Subject: [PATCH 05/12] PCI/AER: Check log level once and remember it +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When reporting an AER error, we check its type multiple times to determine +the log level for each message. Do this check only in the top-level +functions (aer_isr_one_error(), pci_print_aer()) and save the level in +struct aer_err_info. + +[bhelgaas: save log level in struct aer_err_info instead of passing it +as a parameter] + +Signed-off-by: Karolina Stolarek +Signed-off-by: Bjorn Helgaas +Tested-by: Krzysztof Wilczyński +Reviewed-by: Ilpo Järvinen +Reviewed-by: Kuppuswamy Sathyanarayanan +Reviewed-by: Jonathan Cameron +Link: https://patch.msgid.link/20250522232339.1525671-13-helgaas@kernel.org +(cherry picked from commit c8f6791e33a7757025285db26f3b382cdcb7f7cd) +--- + drivers/pci/pci.h | 1 + + drivers/pci/pcie/aer.c | 18 +++++++++--------- + drivers/pci/pcie/dpc.c | 1 + + 3 files changed, 11 insertions(+), 9 deletions(-) + +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index 65df6d2ac003..ce78241b6302 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -505,6 +505,7 @@ static inline bool pci_dev_is_added(const struct pci_dev *dev) + struct aer_err_info { + struct pci_dev *dev[AER_MAX_MULTI_ERR_DEVICES]; + int error_dev_num; ++ const char *level; /* printk level */ + + unsigned int id:16; + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index a10a005ca6e9..82acb7580fd4 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -675,16 +675,14 @@ static void __aer_print_error(struct pci_dev *dev, + { + const char **strings; + unsigned long status = info->status & ~info->mask; +- const char *level, *errmsg; ++ const char *level = info->level; ++ const char *errmsg; + int i; + +- if (info->severity == AER_CORRECTABLE) { ++ if (info->severity == AER_CORRECTABLE) + strings = aer_correctable_error_string; +- level = KERN_WARNING; +- } else { ++ else + strings = aer_uncorrectable_error_string; +- level = KERN_ERR; +- } + + for_each_set_bit(i, &status, 32) { + errmsg = strings[i]; +@@ -700,7 +698,7 @@ void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) + { + int layer, agent; + int id = pci_dev_id(dev); +- const char *level; ++ const char *level = info->level; + + pci_dev_aer_stats_incr(dev, info); + trace_aer_event(pci_name(dev), (info->status & ~info->mask), +@@ -715,8 +713,6 @@ void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) + layer = AER_GET_LAYER_ERROR(info->severity, info->status); + agent = AER_GET_AGENT(info->severity, info->status); + +- level = (info->severity == AER_CORRECTABLE) ? KERN_WARNING : KERN_ERR; +- + pci_printk(level, dev, "PCIe Bus Error: severity=%s, type=%s, (%s)\n", + aer_error_severity_string[info->severity], + aer_error_layer[layer], aer_agent_string[agent]); +@@ -774,9 +770,11 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + if (aer_severity == AER_CORRECTABLE) { + status = aer->cor_status; + mask = aer->cor_mask; ++ info.level = KERN_WARNING; + } else { + status = aer->uncor_status; + mask = aer->uncor_mask; ++ info.level = KERN_ERR; + tlp_header_valid = status & AER_LOG_TLP_MASKS; + } + +@@ -1291,6 +1289,7 @@ static void aer_isr_one_error(struct aer_rpc *rpc, + if (e_src->status & PCI_ERR_ROOT_COR_RCV) { + e_info.id = ERR_COR_ID(e_src->id); + e_info.severity = AER_CORRECTABLE; ++ e_info.level = KERN_WARNING; + + if (e_src->status & PCI_ERR_ROOT_MULTI_COR_RCV) + e_info.multi_error_valid = 1; +@@ -1304,6 +1303,7 @@ static void aer_isr_one_error(struct aer_rpc *rpc, + + if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) { + e_info.id = ERR_UNCOR_ID(e_src->id); ++ e_info.level = KERN_ERR; + + if (e_src->status & PCI_ERR_ROOT_FATAL_RCV) + e_info.severity = AER_FATAL; +diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c +index cdc54315d879..d830696dccec 100644 +--- a/drivers/pci/pcie/dpc.c ++++ b/drivers/pci/pcie/dpc.c +@@ -254,6 +254,7 @@ static int dpc_get_aer_uncorrect_severity(struct pci_dev *dev, + else + info->severity = AER_NONFATAL; + ++ info->level = KERN_ERR; + return 1; + } + +-- +2.47.0 + diff --git a/patches-sonic/0005-arm64-traps-Call-platform-handler-for-do_serror.patch b/patches-sonic/0005-arm64-traps-Call-platform-handler-for-do_serror.patch new file mode 100644 index 000000000..77d9790c5 --- /dev/null +++ b/patches-sonic/0005-arm64-traps-Call-platform-handler-for-do_serror.patch @@ -0,0 +1,53 @@ +From abe9ac7ed658c3a09e40fd3f4cd3644a94b393bf Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 08:48:56 +0000 +Subject: [PATCH 05/27] arm64/traps: Call platform handler for do_serror + +When taking a bad_mode exception, give platform code an opportunity +to decide whether the error the event can be ignored or not. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/include/asm/traps.h | 2 +- + arch/arm64/kernel/traps.c | 9 +++++++++ + 2 files changed, 10 insertions(+), 1 deletion(-) + +diff --git a/arch/arm64/include/asm/traps.h b/arch/arm64/include/asm/traps.h +index 82cf1f8..1b22756 100644 +--- a/arch/arm64/include/asm/traps.h ++++ b/arch/arm64/include/asm/traps.h +@@ -28,7 +28,7 @@ + int reserved_fault_brk_handler(struct pt_regs *regs, unsigned long esr); + int kasan_brk_handler(struct pt_regs *regs, unsigned long esr); + int ubsan_brk_handler(struct pt_regs *regs, unsigned long esr); +- ++int platform_serror(struct pt_regs *regs, unsigned int esr); + int early_brk64(unsigned long addr, unsigned long esr, struct pt_regs *regs); + + /* +diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c +index 563cbce..ab684ec 100644 +--- a/arch/arm64/kernel/traps.c ++++ b/arch/arm64/kernel/traps.c +@@ -965,8 +965,17 @@ bool arm64_is_fatal_ras_serror(struct pt_regs *regs, unsigned long esr) + } + } + ++int __weak platform_serror(struct pt_regs *regs, unsigned int esr) ++{ ++ return 0; ++} ++ + void do_serror(struct pt_regs *regs, unsigned long esr) + { ++ /* give the platform a chance to ignore the event */ ++ if (platform_serror(regs, esr)) ++ return; ++ + /* non-RAS errors are not containable */ + if (!arm64_is_ras_serror(esr) || arm64_is_fatal_ras_serror(regs, esr)) + arm64_serror_panic(regs, esr); +-- +1.8.3.1 + diff --git a/patches-sonic/0005-drop_monitor-Do-not-block-a-configuration-change-if-it-is-a.patch b/patches-sonic/0005-drop_monitor-Do-not-block-a-configuration-change-if-it-is-a.patch new file mode 100644 index 000000000..512dc01a1 --- /dev/null +++ b/patches-sonic/0005-drop_monitor-Do-not-block-a-configuration-change-if-it-is-a.patch @@ -0,0 +1,103 @@ +From 1581ee077219eaa6cca2aec1cdc190da14ebd6ab Mon Sep 17 00:00:00 2001 +From: Ido Schimmel +Date: Thu, 12 Feb 2026 10:46:23 +0200 +Subject: [PATCH] drop_monitor: Do not block a configuration change if it is a + NOP + +Currently, drop_monitor blocks configuration changes if tracing is +enabled. Relax this condition and do not block a configuration change if +it is a NOP, even if tracing is enabled. + +Signed-off-by: Ido Schimmel +--- + net/core/drop_monitor.c | 60 ++++++++++++++--------------------------- + 1 file changed, 20 insertions(+), 40 deletions(-) + +diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c +index e56d3e166df0..5bdf8ca0caba 100644 +--- a/net/core/drop_monitor.c ++++ b/net/core/drop_monitor.c +@@ -1279,60 +1279,40 @@ static int net_dm_alert_mode_get_from_info(struct genl_info *info, + return 0; + } + +-static int net_dm_alert_mode_set(struct genl_info *info) ++static int net_dm_cmd_config(struct sk_buff *skb, struct genl_info *info) + { ++ enum net_dm_alert_mode alert_mode = net_dm_alert_mode; + struct netlink_ext_ack *extack = info->extack; +- enum net_dm_alert_mode alert_mode; ++ u32 trunc_len = net_dm_trunc_len; ++ u32 queue_len = net_dm_queue_len; + int rc; + +- if (!info->attrs[NET_DM_ATTR_ALERT_MODE]) +- return 0; +- +- rc = net_dm_alert_mode_get_from_info(info, &alert_mode); +- if (rc) { +- NL_SET_ERR_MSG_MOD(extack, "Invalid alert mode"); +- return -EINVAL; ++ if (info->attrs[NET_DM_ATTR_ALERT_MODE]) { ++ rc = net_dm_alert_mode_get_from_info(info, &alert_mode); ++ if (rc) { ++ NL_SET_ERR_MSG_MOD(extack, "Invalid alert mode"); ++ return -EINVAL; ++ } + } + +- net_dm_alert_mode = alert_mode; +- +- return 0; +-} +- +-static void net_dm_trunc_len_set(struct genl_info *info) +-{ +- if (!info->attrs[NET_DM_ATTR_TRUNC_LEN]) +- return; +- +- net_dm_trunc_len = nla_get_u32(info->attrs[NET_DM_ATTR_TRUNC_LEN]); +-} +- +-static void net_dm_queue_len_set(struct genl_info *info) +-{ +- if (!info->attrs[NET_DM_ATTR_QUEUE_LEN]) +- return; ++ if (info->attrs[NET_DM_ATTR_TRUNC_LEN]) ++ trunc_len = nla_get_u32(info->attrs[NET_DM_ATTR_TRUNC_LEN]); + +- net_dm_queue_len = nla_get_u32(info->attrs[NET_DM_ATTR_QUEUE_LEN]); +-} ++ if (info->attrs[NET_DM_ATTR_QUEUE_LEN]) ++ queue_len = nla_get_u32(info->attrs[NET_DM_ATTR_QUEUE_LEN]); + +-static int net_dm_cmd_config(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct netlink_ext_ack *extack = info->extack; +- int rc; ++ if (alert_mode == net_dm_alert_mode && trunc_len == net_dm_trunc_len && ++ queue_len == net_dm_queue_len) ++ return 0; + + if (net_dm_is_monitoring()) { + NL_SET_ERR_MSG_MOD(extack, "Cannot configure drop monitor during monitoring"); + return -EBUSY; + } + +- rc = net_dm_alert_mode_set(info); +- if (rc) +- return rc; +- +- net_dm_trunc_len_set(info); +- +- net_dm_queue_len_set(info); ++ net_dm_alert_mode = alert_mode; ++ net_dm_trunc_len = trunc_len; ++ net_dm_queue_len = queue_len; + + return 0; + } +-- +GitLab + diff --git a/patch/0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch b/patches-sonic/0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch similarity index 85% rename from patch/0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch rename to patches-sonic/0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch index 541d10bcc..6c5e50a02 100644 --- a/patch/0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch +++ b/patches-sonic/0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch @@ -1,6 +1,7 @@ From de27b34d7fc91ed109c78ed89442f42427ac0c5c Mon Sep 17 00:00:00 2001 + From: Iker Perez del Palomar Sustatxa -Date: Fri, 3 May 2019 17:15:01 +0100 + Subject: [PATCH 5/6] dt-bindings: hwmon: Add tmp75b to lm75.txt Update the LM75's devicetree definition to allow Texas Instruments @@ -9,11 +10,11 @@ TMP75B be probed. Signed-off-by: Iker Perez del Palomar Sustatxa Signed-off-by: Guenter Roeck --- - Documentation/devicetree/bindings/hwmon/lm75.txt | 1 + + Documentation/devicetree/bindings/hwmon/lm75.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/lm75.txt b/Documentation/devicetree/bindings/hwmon/lm75.txt -index 12d8cf7cf592..586b5ed70be7 100644 +index 12d8cf7cf..586b5ed70 100644 --- a/Documentation/devicetree/bindings/hwmon/lm75.txt +++ b/Documentation/devicetree/bindings/hwmon/lm75.txt @@ -25,6 +25,7 @@ Required properties: @@ -24,6 +25,3 @@ index 12d8cf7cf592..586b5ed70be7 100644 "ti,tmp75c", - reg: I2C bus address of the device --- -2.17.1 - diff --git a/patches-sonic/0005-dts-ac5-marvell-Add-switching-watchdog-node.patch b/patches-sonic/0005-dts-ac5-marvell-Add-switching-watchdog-node.patch new file mode 100644 index 000000000..64603a931 --- /dev/null +++ b/patches-sonic/0005-dts-ac5-marvell-Add-switching-watchdog-node.patch @@ -0,0 +1,88 @@ +From: Pavan Naregundi +Date: Wed, 6 Mar 2024 06:34:40 +0000 +Subject: dts: ac5: marvell: Add switching/watchdog node + +This patch adds following changes to 98DX25xx and 98DX35xx SoC, + - SDMA(Subsystem DMA engine) node for control path Rx/Tx. + - prestera node for interupt handing from switching ASIC. + - arm watchdog node. + - clock-frequency in timer node. + - ac5-ehci compatible string in usb. + +Signed-off-by: Yuval Shaia +Tested-by: Raz Adashi +Reviewed-by: Raz Adashi + +Signed-off-by: Pavan Naregundi +--- + arch/arm64/boot/dts/marvell/ac5-98dx25xx.dtsi | 14 +++++++++++++- + arch/arm64/boot/dts/marvell/ac5-98dx35xx-rd.dts | 10 +++++++++- + 2 files changed, 22 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/marvell/ac5-98dx25xx.dtsi b/arch/arm64/boot/dts/marvell/ac5-98dx25xx.dtsi +index 21ef78f8c..3c01fdc35 100644 +--- a/arch/arm64/boot/dts/marvell/ac5-98dx25xx.dtsi ++++ b/arch/arm64/boot/dts/marvell/ac5-98dx25xx.dtsi +@@ -64,6 +64,7 @@ timer { + , + , + ; ++ clock-frequency = <25000000>; + }; + + pmu { +@@ -258,7 +259,7 @@ eth1: ethernet@24000 { + }; + + usb0: usb@80000 { +- compatible = "marvell,orion-ehci"; ++ compatible = "marvell,ac5-ehci", "marvell,orion-ehci"; + reg = <0x0 0x80000 0x0 0x500>; + interrupts = ; + status = "disabled"; +@@ -329,6 +330,17 @@ nand: nand-controller@805b0000 { + status = "disabled"; + }; + ++ prestera { ++ compatible = "marvell,armada-ac5-switch"; ++ interrupts = ; ++ status = "okay"; ++ }; ++ ++ wdog { ++ compatible = "arm,smc-wdt"; ++ status = "okay"; ++ }; ++ + gic: interrupt-controller@80600000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <3>; +diff --git a/arch/arm64/boot/dts/marvell/ac5-98dx35xx-rd.dts b/arch/arm64/boot/dts/marvell/ac5-98dx35xx-rd.dts +index 0c973d7a2..68887449a 100644 +--- a/arch/arm64/boot/dts/marvell/ac5-98dx35xx-rd.dts ++++ b/arch/arm64/boot/dts/marvell/ac5-98dx35xx-rd.dts +@@ -29,7 +29,7 @@ aliases { + + memory@0 { + device_type = "memory"; +- reg = <0x2 0x00000000 0x0 0x40000000>; ++ reg = <0x2 0x00000000 0x1 0x00000000>; + }; + + usb1phy: usb-phy { +@@ -103,3 +103,11 @@ parition@2 { + &sdhci { + status = "okay"; + }; ++ ++/{ ++ sdma_drv { ++ compatible = "marvell,mvppnd"; ++ interrupts = ; ++ status = "okay"; ++ }; ++}; +-- +2.25.1 + diff --git a/patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch b/patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch new file mode 100644 index 000000000..994bdae92 --- /dev/null +++ b/patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch @@ -0,0 +1,882 @@ +From 7d8b2029f67b9f0d082b5310f5e73e540863c1e5 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 18:58:36 +0300 +Subject: [PATCH 6.12 5/5] mctp: driver: net: Fix MCTP over USB + +Fix and extend the MCTP over USB transport (CONFIG_MCTP_TRANSPORT_USB). + +This reworks the USB TX error statistics so that errors which cannot be +attributed to a single destination EID (for example batched transfers) +are accounted under a dedicated unknown-EID bucket rather than a +specific EID, removes a stale Makefile reference to a USB +error-injection object, and adds a USB gadget function (f_mctp) so the +device side can expose an MCTP-over-USB interface. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/Makefile | 1 - + drivers/net/mctp/mctp-usb.c | 43 +- + drivers/usb/gadget/function/Makefile | 2 + + drivers/usb/gadget/function/f_mctp.c | 714 +++++++++++++++++++++++++++ + include/uapi/linux/usb/ch9.h | 1 + + 5 files changed, 740 insertions(+), 21 deletions(-) + create mode 100644 drivers/usb/gadget/function/f_mctp.c + +diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile +index 996808975..5a740413e 100644 +--- a/drivers/net/mctp/Makefile ++++ b/drivers/net/mctp/Makefile +@@ -6,6 +6,5 @@ obj-$(CONFIG_NVIDIA_AST27XX_IROT) += nvidia-ast27xx-irot.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o +-obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_PCIE_VDM) += mctp-pcie-vdm.o + obj-$(CONFIG_NVIDIA_OPTEE_VROT) += nvidia-optee-vrot.o +diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c +index b9bca7067..1217d7e29 100644 +--- a/drivers/net/mctp/mctp-usb.c ++++ b/drivers/net/mctp/mctp-usb.c +@@ -138,67 +138,67 @@ static void mctp_usb_handle_tx_urb_status(struct mctp_usb *mctp_usb, + /** + * mctp_usb_handle_tx_sync_error - Handle synchronous TX error statistics + * @mctp_usb: MCTP USB device +- * @dest_eid: Destination EID from MCTP header ++ * @stat_eid: EID for stats (0-255) or MCTP_EID_UNKNOWN (256) for batch/unknown + * @error_code: Error code from URB submission + * + * Maps synchronous TX errors (URB submission failures) to per-EID statistics. +- * Unlike async URB completion errors, these can be attributed to a specific +- * destination EID since the packet header is still available. ++ * For single-packet TX, use the MCTP destination EID from the header. ++ * For batched TX, pass MCTP_EID_UNKNOWN when no per-packet EID applies. + * Only handles valid USB error codes per USB subsystem specification. + */ + static void mctp_usb_handle_tx_sync_error(struct mctp_usb *mctp_usb, +- u8 dest_eid, ++ unsigned int stat_eid, + int error_code) + { + switch (error_code) { + /* Valid USB error codes only */ + case -ENOMEM: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_no_memory); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_no_memory); + break; + case -EBUSY: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ebusy); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_ebusy); + break; + case -ENODEV: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enodev); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enodev); + break; + case -ENOENT: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoent); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enoent); + break; + case -ENXIO: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enxio); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enxio); + break; + case -EINVAL: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_einval); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_einval); + break; + case -EXDEV: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_exdev); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_exdev); + break; + case -EFBIG: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_efbig); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_efbig); + break; + case -EPIPE: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_epipe); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_epipe); + break; + case -EMSGSIZE: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_emsgsize); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_emsgsize); + break; + case -ENOSPC: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enospc); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enospc); + break; + case -ESHUTDOWN: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eshutdown); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_eshutdown); + break; + case -EPERM: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eperm); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_eperm); + break; + case -EHOSTUNREACH: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ehostunreach); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_ehostunreach); + break; + case -ENOEXEC: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoexec); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enoexec); + break; + default: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_urb_error); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_urb_error); + break; + } + } +@@ -1277,6 +1277,9 @@ static struct usb_driver mctp_usb_driver = { + .disconnect = mctp_usb_disconnect, + }; + ++/* One .ko when CONFIG_MCTP_TRANSPORT_USB=m: error-inject is not a separate module. */ ++#include "mctp-usb-error-inject.c" ++ + static int __init mctp_usb_init(void) + { + int rc; +diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile +index 87917a7d4..036a94e0a 100644 +--- a/drivers/usb/gadget/function/Makefile ++++ b/drivers/usb/gadget/function/Makefile +@@ -52,3 +52,5 @@ usb_f_printer-y := f_printer.o + obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o + usb_f_tcm-y := f_tcm.o + obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o ++usb_f_mctp-y := f_mctp.o ++obj-$(CONFIG_USB_F_MCTP) += usb_f_mctp.o +diff --git a/drivers/usb/gadget/function/f_mctp.c b/drivers/usb/gadget/function/f_mctp.c +new file mode 100644 +index 000000000..065f00b87 +--- /dev/null ++++ b/drivers/usb/gadget/function/f_mctp.c +@@ -0,0 +1,714 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * f_mctp.c - USB peripheral MCTP driver ++ * ++ * Copyright (C) 2024 Code Construct Pty Ltd ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#define MCTP_USB_PREALLOC 4 ++ ++struct f_mctp { ++ struct usb_function function; ++ ++ struct usb_ep *in_ep; ++ struct usb_ep *out_ep; ++ ++ struct net_device *dev; ++ ++ /* Updates to skb_free_list and the req lists are performed under ++ * ->lock ++ */ ++ spinlock_t lock; ++ struct sk_buff_head skb_free_list; ++ struct list_head rx_reqs; ++ struct list_head tx_reqs; ++ ++ struct work_struct prealloc_work; ++}; ++ ++struct f_mctp_opts { ++ struct usb_function_instance function_instance; ++}; ++ ++static inline struct f_mctp *func_to_mctp(struct usb_function *f) ++{ ++ return container_of(f, struct f_mctp, function); ++} ++ ++static struct usb_interface_descriptor mctp_usbg_intf = { ++ .bLength = sizeof(mctp_usbg_intf), ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bNumEndpoints = 2, ++ .bInterfaceClass = USB_CLASS_MCTP, ++ .bInterfaceSubClass = 0x0, /* todo: allow host-interface mode? */ ++ .bInterfaceProtocol = 0x1, /* MCTP version 1 */ ++ /* .iInterface = DYNAMIC */ ++}; ++ ++/* descriptors, full speed only */ ++ ++static struct usb_endpoint_descriptor hs_mctp_source_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE), ++ ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_endpoint_descriptor hs_mctp_sink_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE), ++ ++ .bEndpointAddress = USB_DIR_OUT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_descriptor_header *hs_mctp_descs[] = { ++ (struct usb_descriptor_header *) &mctp_usbg_intf, ++ (struct usb_descriptor_header *) &hs_mctp_sink_desc, ++ (struct usb_descriptor_header *) &hs_mctp_source_desc, ++ NULL, ++}; ++ ++/* strings */ ++static struct usb_string mctp_usbg_strings[] = { ++ { .s = "MCTP over USB" }, ++ { 0 } ++}; ++ ++static struct usb_gadget_strings mctp_usbg_stringtab = { ++ .language = 0x0409, /* en-us */ ++ .strings = mctp_usbg_strings, ++}; ++ ++static struct usb_gadget_strings *mctp_usbg_gadget_strings[] = { ++ &mctp_usbg_stringtab, ++ NULL, ++}; ++ ++static int mctp_usbg_bind(struct usb_configuration *c, struct usb_function *f) ++{ ++ struct usb_composite_dev *cdev = c->cdev; ++ struct f_mctp *mctp = func_to_mctp(f); ++ int id, rc; ++ ++ id = usb_interface_id(c, f); ++ if (id < 0) ++ return id; ++ mctp_usbg_intf.bInterfaceNumber = id; ++ ++ id = usb_string_id(cdev); ++ if (id < 0) ++ return id; ++ ++ mctp_usbg_strings[0].id = id; ++ ++ mctp->in_ep = usb_ep_autoconfig(cdev->gadget, &hs_mctp_source_desc); ++ if (!mctp->in_ep) { ++ ERROR(cdev, "%s in_ep autoconfig failed\n", f->name); ++ return -ENODEV; ++ } ++ hs_mctp_source_desc.wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE); ++ ++ mctp->out_ep = usb_ep_autoconfig(cdev->gadget, &hs_mctp_sink_desc); ++ if (!mctp->out_ep) { ++ ERROR(cdev, "%s out_ep autoconfig failed\n", f->name); ++ return -ENODEV; ++ } ++ hs_mctp_sink_desc.wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE); ++ ++ rc = usb_assign_descriptors(f, NULL, hs_mctp_descs, NULL, NULL); ++ if (rc) { ++ ERROR(cdev, "assign_descriptors failed %d\n", rc); ++ return rc; ++ } ++ ++ DBG(cdev, "%s: in %s, out %s\n", f->name, mctp->in_ep->name, ++ mctp->out_ep->name); ++ ++ return 0; ++} ++ ++static void mctp_usbg_prealloc(struct f_mctp *mctp) ++{ ++ struct sk_buff_head skbs; ++ struct usb_request *req; ++ struct list_head reqs; ++ struct sk_buff *skb; ++ unsigned long flags; ++ unsigned int n_skb; ++ ++ /* allocate SKBs for requests that have been consumed and don't ++ * have an associated skb, then requeue. ++ */ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_replace_init(&mctp->rx_reqs, &reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ while (!list_empty(&reqs)) { ++ req = list_first_entry(&reqs, struct usb_request, list); ++ list_del(&req->list); ++ ++ skb = __netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE, ++ GFP_KERNEL); ++ if (!skb) { ++ /* Put it back for next time */ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_add(&req->list, &mctp->rx_reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ break; ++ } ++ ++ req->buf = skb->data; ++ req->context = skb; ++ usb_ep_queue(mctp->out_ep, req, GFP_KERNEL); ++ } ++ ++ /* next, allocate our pool of spare skbs */ ++ n_skb = skb_queue_len_lockless(&mctp->skb_free_list); ++ __skb_queue_head_init(&skbs); ++ ++ for (; n_skb < MCTP_USB_PREALLOC; n_skb++) { ++ skb = __netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE, ++ GFP_KERNEL); ++ if (!skb) ++ break; ++ ++ __skb_queue_tail(&skbs, skb); ++ } ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ skb_queue_splice_tail(&skbs, &mctp->skb_free_list); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++} ++ ++static void mctp_usbg_prealloc_work(struct work_struct *work) ++{ ++ struct f_mctp *mctp = container_of(work, struct f_mctp, prealloc_work); ++ ++ mctp_usbg_prealloc(mctp); ++} ++ ++static int mctp_usbg_requeue(struct f_mctp *mctp, struct usb_ep *ep, ++ struct usb_request *req) ++{ ++ unsigned long flags; ++ struct sk_buff *skb; ++ int rc = 0; ++ ++ req->buf = NULL; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ ++ /* Do we have a preallocated skb available? if so, we can requeue ++ * immediately; otherwise wait for the workqueue to populate. ++ */ ++ skb = __skb_dequeue(&mctp->skb_free_list); ++ if (skb) { ++ req->buf = skb->data; ++ req->context = skb; ++ rc = usb_ep_queue(ep, req, GFP_ATOMIC); ++ } else { ++ /* keep for later allocation */ ++ list_add_tail(&req->list, &mctp->rx_reqs); ++ } ++ ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ schedule_work(&mctp->prealloc_work); ++ ++ return rc; ++} ++ ++static void mctp_usbg_handle_rx_urb(struct f_mctp *mctp, ++ struct usb_request *req) ++{ ++ struct device *dev = &mctp->function.config->cdev->gadget->dev; ++ struct net_device *netdev = mctp->dev; ++ struct sk_buff *skb = req->context; ++ struct mctp_usb_hdr *hdr; ++ struct mctp_skb_cb *cb; ++ unsigned int len; ++ u16 id; ++ ++ len = req->actual; ++ __skb_put(skb, len); ++ ++ hdr = skb_pull_data(skb, sizeof(*hdr)); ++ if (!hdr) ++ goto err; ++ ++ id = be16_to_cpu(hdr->id); ++ if (id != MCTP_USB_DMTF_ID) { ++ dev_dbg(dev, "%s: invalid id %04x\n", __func__, id); ++ goto err; ++ } ++ ++ if (hdr->len < sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { ++ dev_dbg(dev, "%s: short packet (hdr) %d\n", ++ __func__, hdr->len); ++ goto err; ++ } ++ ++ /* todo: multi-packet transfers */ ++ if (hdr->len - sizeof(struct mctp_usb_hdr) < skb->len) { ++ dev_dbg(dev, "%s: short packet (xfer) %d, actual %d\n", ++ __func__, hdr->len, skb->len); ++ goto err; ++ } ++ ++ skb->protocol = htons(ETH_P_MCTP); ++ skb_reset_network_header(skb); ++ cb = __mctp_cb(skb); ++ cb->halen = 0; ++ netdev->stats.rx_packets++; ++ netdev->stats.rx_bytes += skb->len; ++ netif_rx(skb); ++ ++ return; ++ ++err: ++ /* todo: return to free list */ ++ kfree_skb(skb); ++} ++static void mctp_usbg_out_ep_complete(struct usb_ep *ep, ++ struct usb_request *req) ++{ ++ struct f_mctp *mctp = ep->driver_data; ++ struct usb_composite_dev *cdev = mctp->function.config->cdev; ++ int rc; ++ ++ switch (req->status) { ++ case 0: ++ mctp_usbg_handle_rx_urb(mctp, req); ++ ++ /* re-queue out request */ ++ rc = mctp_usbg_requeue(mctp, ep, req); ++ if (rc) { ++ WARNING(cdev, "%s: unable to re-queue out req\n", ++ __func__); ++ usb_ep_free_request(ep, req); ++ } ++ ++ break; ++ ++ case -ECONNABORTED: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ kfree_skb(req->context); ++ usb_ep_free_request(ep, req); ++ break; ++ ++ default: ++ WARNING(cdev, "%s: invalid status %d?", __func__, req->status); ++ usb_ep_free_request(ep, req); ++ } ++} ++ ++static void mctp_usbg_in_ep_complete(struct usb_ep *ep, ++ struct usb_request *req) ++{ ++ struct f_mctp *mctp = ep->driver_data; ++ struct usb_composite_dev *cdev = mctp->function.config->cdev; ++ struct net_device *netdev = mctp->dev; ++ struct sk_buff *skb = req->context; ++ unsigned long flags; ++ ++ kfree_skb(skb); ++ req->context = NULL; ++ req->buf = NULL; ++ ++ /* todo: tx stats */ ++ ++ switch (req->status) { ++ case 0: ++ netdev->stats.tx_bytes += skb->len; ++ netdev->stats.tx_packets++; ++ spin_lock_irqsave(&mctp->lock, flags); ++ if (list_empty(&mctp->tx_reqs)) ++ netif_wake_queue(mctp->dev); ++ list_add(&req->list, &mctp->tx_reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ break; ++ default: ++ WARNING(cdev, "%s: invalid status %d?", __func__, req->status); ++ fallthrough; ++ case -ECONNABORTED: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ netdev->stats.tx_dropped++; ++ usb_ep_free_request(ep, req); ++ break; ++ } ++} ++ ++static int mctp_usbg_enable_ep(struct usb_gadget *gadget, struct f_mctp *mctp, ++ struct usb_ep *ep) ++{ ++ int rc; ++ ++ rc = config_ep_by_speed(gadget, &mctp->function, ep); ++ if (rc) ++ return rc; ++ ++ rc = usb_ep_enable(ep); ++ if (rc) ++ return rc; ++ ++ ep->driver_data = mctp; ++ ++ return 0; ++} ++ ++static int mctp_usbg_enable(struct usb_composite_dev *cdev, struct f_mctp *mctp) ++{ ++ struct usb_request *out_req, *in_req; ++ unsigned long flags; ++ struct sk_buff *skb; ++ int rc; ++ ++ rc = mctp_usbg_enable_ep(cdev->gadget, mctp, mctp->out_ep); ++ if (rc) { ++ ERROR(cdev, "%s: out ep enable failed %d\n", __func__, rc); ++ return rc; ++ } ++ ++ rc = mctp_usbg_enable_ep(cdev->gadget, mctp, mctp->in_ep); ++ if (rc) { ++ ERROR(cdev, "%s: in ep enable failed %d\n", __func__, rc); ++ goto err_disable_out; ++ } ++ ++ /* todo: just one out queued req for now */ ++ out_req = alloc_ep_req(mctp->out_ep, MCTP_USB_XFER_SIZE); ++ if (!out_req) { ++ ERROR(cdev, "%s: out req alloc failed\n", __func__); ++ goto err_disable_in; ++ } ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ skb = __skb_dequeue(&mctp->skb_free_list); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ if (!skb) ++ skb = netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE); ++ ++ if (!skb) ++ goto err_free_req; ++ ++ out_req->context = skb; ++ out_req->buf = skb->data; ++ out_req->complete = mctp_usbg_out_ep_complete; ++ ++ rc = usb_ep_queue(mctp->out_ep, out_req, GFP_ATOMIC); ++ if (rc) { ++ ERROR(cdev, "%s: out req queue failed %d\b", __func__, rc); ++ goto err_free_skb; ++ } ++ ++ /* todo: and just one in the in queue too */ ++ in_req = usb_ep_alloc_request(mctp->in_ep, GFP_ATOMIC); ++ if (!in_req) { ++ ERROR(cdev, "%s: out req alloc failed\n", __func__); ++ goto err_disable_in; ++ } ++ in_req->complete = mctp_usbg_in_ep_complete; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_add(&in_req->list, &mctp->tx_reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ netif_carrier_on(mctp->dev); ++ netif_wake_queue(mctp->dev); ++ dev_info(&cdev->gadget->dev, "mctp-usb: enabled\n"); ++ ++ return 0; ++ ++ ++err_free_skb: ++ kfree_skb(skb); ++err_free_req: ++ free_ep_req(mctp->out_ep, out_req); ++err_disable_in: ++ usb_ep_disable(mctp->in_ep); ++err_disable_out: ++ usb_ep_disable(mctp->out_ep); ++ ++ return rc; ++} ++ ++static netdev_tx_t mctp_usbg_start_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct f_mctp *mctp = netdev_priv(dev); ++ struct net_device *netdev = mctp->dev; ++ struct mctp_usb_hdr *hdr; ++ struct usb_request *req; ++ unsigned long flags; ++ unsigned int plen; ++ int rc; ++ ++ if (skb->len + sizeof(*hdr) > MCTP_USB_XFER_SIZE) ++ goto drop; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ req = list_first_entry_or_null(&mctp->tx_reqs, struct usb_request, list); ++ ++ if (req) ++ list_del(&req->list); ++ if (list_empty(&mctp->tx_reqs)) ++ netif_stop_queue(dev); ++ ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ if (!req) { ++ netdev_warn(dev, "no tx reqs available\n"); ++ goto drop; ++ } ++ ++ plen = skb->len; ++ hdr = skb_push(skb, sizeof(*hdr)); ++ hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); ++ hdr->rsvd = 0; ++ hdr->len = plen + sizeof(*hdr); ++ ++ /* todo: just one skb per transfer.. */ ++ req->context = skb; ++ req->buf = skb->data; ++ req->length = skb->len; ++ ++ rc = usb_ep_queue(mctp->in_ep, req, GFP_ATOMIC); ++ if (rc) { ++ netdev_warn(dev, "tx queue failed: %d\n", rc); ++ req->context = NULL; ++ req->buf = NULL; ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_add(&req->list, &mctp->tx_reqs); ++ netif_wake_queue(dev); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ goto drop; ++ } ++ ++ netdev->stats.tx_bytes += skb->len; ++ netdev->stats.tx_packets++; ++ ++ return NETDEV_TX_OK; ++ ++drop: ++ kfree_skb(skb); ++ netdev->stats.tx_dropped++; ++ return NETDEV_TX_OK; ++} ++ ++static int mctp_usbg_open(struct net_device *net) ++{ ++ return 0; ++} ++ ++static int mctp_usbg_stop(struct net_device *net) ++{ ++ return 0; ++} ++ ++static const struct net_device_ops mctp_usbg_netdev_ops = { ++ .ndo_open = mctp_usbg_open, ++ .ndo_stop = mctp_usbg_stop, ++ .ndo_start_xmit = mctp_usbg_start_xmit, ++}; ++ ++static void __mctp_usbg_disable(struct f_mctp *mctp) ++{ ++ usb_ep_disable(mctp->in_ep); ++ usb_ep_disable(mctp->out_ep); ++} ++ ++static void mctp_usbg_purge_tx_reqs(struct f_mctp *mctp) ++{ ++ LIST_HEAD(reqs); ++ struct usb_request *req, *tmp; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_splice_init(&mctp->tx_reqs, &reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ list_for_each_entry_safe(req, tmp, &reqs, list) { ++ list_del(&req->list); ++ usb_ep_free_request(mctp->in_ep, req); ++ } ++} ++ ++static void mctp_usbg_disable(struct usb_function *f) ++{ ++ struct f_mctp *mctp = func_to_mctp(f); ++ struct usb_composite_dev *cdev = mctp->function.config->cdev; ++ ++ __mctp_usbg_disable(mctp); ++ ++ cancel_work_sync(&mctp->prealloc_work); ++ mctp_usbg_purge_tx_reqs(mctp); ++ ++ netif_stop_queue(mctp->dev); ++ netif_carrier_off(mctp->dev); ++ dev_info(&cdev->gadget->dev, "mctp-usb: disabled\n"); ++} ++ ++static int mctp_usbg_set_alt(struct usb_function *f, ++ unsigned intf, unsigned alt) ++{ ++ struct usb_composite_dev *cdev = f->config->cdev; ++ struct f_mctp *mctp = func_to_mctp(f); ++ ++ __mctp_usbg_disable(mctp); ++ return mctp_usbg_enable(cdev, mctp); ++} ++ ++static void mctp_usbg_free_func(struct usb_function *f) ++{ ++ struct f_mctp *mctp = func_to_mctp(f); ++ ++ /* Cancel pending work before cleanup */ ++ cancel_work_sync(&mctp->prealloc_work); ++ ++ /* Free any pending SKBs in the queues */ ++ __skb_queue_purge(&mctp->skb_free_list); ++ ++ /* The netdev (and f_mctp) will be freed automatically ++ * by the network core since needs_free_netdev = true. ++ */ ++ unregister_netdev(mctp->dev); ++} ++ ++static void mctp_usbg_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ dev->mtu = MCTP_USB_MTU_MIN; ++ dev->min_mtu = MCTP_USB_MTU_MIN; ++ dev->max_mtu = MCTP_USB_MTU_MAX; ++ ++ dev->hard_header_len = 0; ++ dev->addr_len = 0; ++ dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; ++ dev->flags = IFF_NOARP; ++ dev->netdev_ops = &mctp_usbg_netdev_ops; ++ dev->needs_free_netdev = true; ++} ++ ++static struct usb_function ++*mctp_usbg_alloc_func(struct usb_function_instance *fi) ++{ ++ struct f_mctp_opts *opts; ++ struct net_device *dev; ++ struct f_mctp *mctp; ++ int rc; ++ ++ opts = container_of(fi, struct f_mctp_opts, function_instance); ++ ++ dev = alloc_netdev(sizeof(*mctp), "mctpusbg%d", NET_NAME_ENUM, ++ mctp_usbg_netdev_setup); ++ if (!dev) ++ return ERR_PTR(-ENOMEM); ++ ++ mctp = netdev_priv(dev); ++ mctp->dev = dev; ++ ++ spin_lock_init(&mctp->lock); ++ INIT_LIST_HEAD(&mctp->rx_reqs); ++ INIT_LIST_HEAD(&mctp->tx_reqs); ++ __skb_queue_head_init(&mctp->skb_free_list); ++ INIT_WORK(&mctp->prealloc_work, mctp_usbg_prealloc_work); ++ ++ mctp->function.name = "mctp"; ++ mctp->function.bind = mctp_usbg_bind; ++ mctp->function.set_alt = mctp_usbg_set_alt; ++ mctp->function.disable = mctp_usbg_disable; ++ mctp->function.strings = mctp_usbg_gadget_strings; ++ mctp->function.free_func = mctp_usbg_free_func; ++ ++ /* this will allocate our first pool of out (rx) skbs */ ++ mctp_usbg_prealloc(mctp); ++ ++ rc = register_netdev(dev); ++ if (rc) { ++ free_netdev(dev); ++ return ERR_PTR(rc); ++ } ++ ++ return &mctp->function; ++} ++ ++static struct f_mctp_opts *to_f_mctp_opts(struct config_item *item) ++{ ++ return container_of(to_config_group(item), struct f_mctp_opts, ++ function_instance.group); ++} ++ ++static void mctp_usbg_attr_release(struct config_item *item) ++{ ++ struct f_mctp_opts *opts = to_f_mctp_opts(item); ++ ++ usb_put_function_instance(&opts->function_instance); ++} ++ ++static struct configfs_item_operations mctp_usbg_item_ops = { ++ .release = mctp_usbg_attr_release, ++}; ++ ++static struct configfs_attribute *mctp_usbg_attrs[] = { ++ NULL, ++}; ++ ++static const struct config_item_type mctp_usbg_func_type = { ++ .ct_item_ops = &mctp_usbg_item_ops, ++ .ct_attrs = mctp_usbg_attrs, ++ .ct_owner = THIS_MODULE, ++}; ++ ++static void mctp_usbg_free_instance(struct usb_function_instance *fi) ++{ ++ struct f_mctp_opts *opts; ++ ++ opts = container_of(fi, struct f_mctp_opts, function_instance); ++ kfree(opts); ++} ++ ++static struct usb_function_instance *mctp_usbg_alloc_instance(void) ++{ ++ struct f_mctp_opts *opts; ++ ++ opts = kzalloc(sizeof(*opts), GFP_KERNEL); ++ if (!opts) ++ return ERR_PTR(-ENOMEM); ++ ++ opts->function_instance.free_func_inst = mctp_usbg_free_instance; ++ ++ config_group_init_type_name(&opts->function_instance.group, "", ++ &mctp_usbg_func_type); ++ ++ return &opts->function_instance; ++} ++ ++DECLARE_USB_FUNCTION_INIT(mctp, mctp_usbg_alloc_instance, mctp_usbg_alloc_func); ++MODULE_LICENSE("GPL"); +diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h +index 91f0f7e21..052290652 100644 +--- a/include/uapi/linux/usb/ch9.h ++++ b/include/uapi/linux/usb/ch9.h +@@ -330,6 +330,7 @@ struct usb_device_descriptor { + #define USB_CLASS_AUDIO_VIDEO 0x10 + #define USB_CLASS_BILLBOARD 0x11 + #define USB_CLASS_USB_TYPE_C_BRIDGE 0x12 ++#define USB_CLASS_MCTP 0x14 + #define USB_CLASS_MISC 0xef + #define USB_CLASS_APP_SPEC 0xfe + #define USB_SUBCLASS_DFU 0x01 +-- +2.34.1 + diff --git a/patches-sonic/0005-platform-mellanox-mlxreg-hotplug-use-sysfs_emit-inst.patch b/patches-sonic/0005-platform-mellanox-mlxreg-hotplug-use-sysfs_emit-inst.patch new file mode 100644 index 000000000..813a66ca8 --- /dev/null +++ b/patches-sonic/0005-platform-mellanox-mlxreg-hotplug-use-sysfs_emit-inst.patch @@ -0,0 +1,38 @@ +From 9d23974f2c561e438fdf48266b327e1424884ff3 Mon Sep 17 00:00:00 2001 +From: Ai Chao +Date: Thu, 16 Jan 2025 16:10:00 +0800 +Subject: [PATCH 05/78] platform/mellanox: mlxreg-hotplug: use sysfs_emit() + instead of sprintf() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Follow the advice in Documentation/filesystems/sysfs.rst: +show() should only use sysfs_emit() or sysfs_emit_at() when formatting +the value to be returned to user space. + +Signed-off-by: Ai Chao +Acked-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250116081000.2900435-1-aichao@kylinos.cn +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxreg-hotplug.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c +index 0ce9fff..40d5b99 100644 +--- a/drivers/platform/mellanox/mlxreg-hotplug.c ++++ b/drivers/platform/mellanox/mlxreg-hotplug.c +@@ -232,7 +232,7 @@ static ssize_t mlxreg_hotplug_attr_show(struct device *dev, + regval = !!(regval & data->mask); + } + +- return sprintf(buf, "%u\n", regval); ++ return sysfs_emit(buf, "%u\n", regval); + } + + #define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i] +-- +2.8.4 + diff --git a/patches-sonic/0005-xilinx-spi-use-device-property.patch b/patches-sonic/0005-xilinx-spi-use-device-property.patch new file mode 100644 index 000000000..6f7b1fabd --- /dev/null +++ b/patches-sonic/0005-xilinx-spi-use-device-property.patch @@ -0,0 +1,41 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v3 3/3] spi: xilinx: use device property accessors. +From: Abdurrahman Hussain +Date: Mon, 19 Jan 2026 07:06:24 +0000 +Message-Id: <20260119-spi-xilinx-v3-3-4566c33bac0d@nexthop.ai> +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Make the driver work on non-OF platforms. + +Signed-off-by: Abdurrahman Hussain +--- + drivers/spi/spi-xilinx.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/drivers/spi/spi-xilinx.c b/drivers/spi/spi-xilinx.c +index 4aa67fc61cb6..9fb1da2fcce4 100644 +--- a/drivers/spi/spi-xilinx.c ++++ b/drivers/spi/spi-xilinx.c +@@ -405,11 +405,11 @@ static int xilinx_spi_probe(struct platform_device *pdev) + bits_per_word = pdata->bits_per_word; + force_irq = pdata->force_irq; + } else { +- of_property_read_u32(pdev->dev.of_node, "xlnx,num-ss-bits", +- &num_cs); +- ret = of_property_read_u32(pdev->dev.of_node, +- "xlnx,num-transfer-bits", +- &bits_per_word); ++ device_property_read_u32(&pdev->dev, "xlnx,num-ss-bits", ++ &num_cs); ++ ret = device_property_read_u32(&pdev->dev, ++ "xlnx,num-transfer-bits", ++ &bits_per_word); + if (ret) + bits_per_word = 8; + } + +-- +2.52.0 + diff --git a/patches-sonic/0006-Initial-support-for-the-Pensando-Elba-SoC.patch b/patches-sonic/0006-Initial-support-for-the-Pensando-Elba-SoC.patch new file mode 100644 index 000000000..bb6d28cc6 --- /dev/null +++ b/patches-sonic/0006-Initial-support-for-the-Pensando-Elba-SoC.patch @@ -0,0 +1,650 @@ +From 5410fd4bcc6ca29a9c3b1ce843c67514a6a72e21 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 09:10:14 +0000 +Subject: [PATCH 06/27] Initial support for the Pensando Elba SoC + +This patch adds initial support for Elba that allows a kernel to boot, +along with an initial device-tree that is compatible with common +Pensando PCI cards. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/Makefile | 1 + + arch/arm64/boot/dts/pensando/Makefile | 7 + + .../boot/dts/pensando/elba-16core-spin-table.dtsi | 50 +++++ + arch/arm64/boot/dts/pensando/elba-16core.dtsi | 170 +++++++++++++++++ + arch/arm64/boot/dts/pensando/elba-asic-common.dtsi | 80 ++++++++ + arch/arm64/boot/dts/pensando/elba-asic.dts | 23 +++ + arch/arm64/boot/dts/pensando/elba.dtsi | 208 +++++++++++++++++++++ + drivers/soc/Kconfig | 1 + + drivers/soc/pensando/Kconfig | 13 ++ + 9 files changed, 553 insertions(+) + create mode 100644 arch/arm64/boot/dts/pensando/Makefile + create mode 100644 arch/arm64/boot/dts/pensando/elba-16core-spin-table.dtsi + create mode 100644 arch/arm64/boot/dts/pensando/elba-16core.dtsi + create mode 100644 arch/arm64/boot/dts/pensando/elba-asic-common.dtsi + create mode 100644 arch/arm64/boot/dts/pensando/elba-asic.dts + create mode 100644 arch/arm64/boot/dts/pensando/elba.dtsi + create mode 100644 drivers/soc/pensando/Kconfig + +diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile +index 21cd3a8..0ea1438 100644 +--- a/arch/arm64/boot/dts/Makefile ++++ b/arch/arm64/boot/dts/Makefile +@@ -22,6 +22,7 @@ subdir-y += mediatek + subdir-y += microchip + subdir-y += nuvoton + subdir-y += nvidia ++subdir-y += pensando + subdir-y += qcom + subdir-y += realtek + subdir-y += renesas +diff --git a/arch/arm64/boot/dts/pensando/Makefile b/arch/arm64/boot/dts/pensando/Makefile +new file mode 100644 +index 0000000..8971be4 +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0 ++dtb-$(CONFIG_ARCH_PENSANDO_ELBA_SOC) += \ ++ elba-asic.dtb ++ ++always-y := $(dtb-y) ++subdir-y := $(dts-dirs) ++clean-files := *.dtb +diff --git a/arch/arm64/boot/dts/pensando/elba-16core-spin-table.dtsi b/arch/arm64/boot/dts/pensando/elba-16core-spin-table.dtsi +new file mode 100644 +index 0000000..ef783fc +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-16core-spin-table.dtsi +@@ -0,0 +1,50 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++&cpu0 { ++ enable-method = "spin-table"; ++}; ++&cpu1 { ++ enable-method = "spin-table"; ++}; ++&cpu2 { ++ enable-method = "spin-table"; ++}; ++&cpu3 { ++ enable-method = "spin-table"; ++}; ++&cpu4 { ++ enable-method = "spin-table"; ++}; ++&cpu5 { ++ enable-method = "spin-table"; ++}; ++&cpu6 { ++ enable-method = "spin-table"; ++}; ++&cpu7 { ++ enable-method = "spin-table"; ++}; ++&cpu8 { ++ enable-method = "spin-table"; ++}; ++&cpu9 { ++ enable-method = "spin-table"; ++}; ++&cpu10 { ++ enable-method = "spin-table"; ++}; ++&cpu11 { ++ enable-method = "spin-table"; ++}; ++&cpu12 { ++ enable-method = "spin-table"; ++}; ++&cpu13 { ++ enable-method = "spin-table"; ++}; ++&cpu14 { ++ enable-method = "spin-table"; ++}; ++&cpu15 { ++ enable-method = "spin-table"; ++}; +diff --git a/arch/arm64/boot/dts/pensando/elba-16core.dtsi b/arch/arm64/boot/dts/pensando/elba-16core.dtsi +new file mode 100644 +index 0000000..bf5da7b +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-16core.dtsi +@@ -0,0 +1,170 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++/ { ++ cpus { ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ cpu-map { ++ cluster0 { ++ core0 { cpu = <&cpu0>; }; ++ core1 { cpu = <&cpu1>; }; ++ core2 { cpu = <&cpu2>; }; ++ core3 { cpu = <&cpu3>; }; ++ }; ++ ++ cluster1 { ++ core0 { cpu = <&cpu4>; }; ++ core1 { cpu = <&cpu5>; }; ++ core2 { cpu = <&cpu6>; }; ++ core3 { cpu = <&cpu7>; }; ++ }; ++ ++ cluster2 { ++ core0 { cpu = <&cpu8>; }; ++ core1 { cpu = <&cpu9>; }; ++ core2 { cpu = <&cpu10>; }; ++ core3 { cpu = <&cpu11>; }; ++ }; ++ ++ cluster3 { ++ core0 { cpu = <&cpu12>; }; ++ core1 { cpu = <&cpu13>; }; ++ core2 { cpu = <&cpu14>; }; ++ core3 { cpu = <&cpu15>; }; ++ }; ++ }; ++ ++ /* CLUSTER 0 */ ++ cpu0: cpu@0 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x0>; ++ next-level-cache = <&l2_0>; ++ }; ++ ++ cpu1: cpu@1 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x1>; ++ next-level-cache = <&l2_0>; ++ }; ++ ++ cpu2: cpu@2 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x2>; ++ next-level-cache = <&l2_0>; ++ }; ++ ++ cpu3: cpu@3 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x3>; ++ next-level-cache = <&l2_0>; ++ }; ++ ++ l2_0: l2-cache0 { ++ compatible = "cache"; ++ }; ++ ++ /* CLUSTER 1 */ ++ cpu4: cpu@100 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x100>; ++ next-level-cache = <&l2_1>; ++ }; ++ ++ cpu5: cpu@101 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x101>; ++ next-level-cache = <&l2_1>; ++ }; ++ ++ cpu6: cpu@102 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x102>; ++ next-level-cache = <&l2_1>; ++ }; ++ ++ cpu7: cpu@103 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x103>; ++ next-level-cache = <&l2_1>; ++ }; ++ ++ l2_1: l2-cache1 { ++ compatible = "cache"; ++ }; ++ ++ /* CLUSTER 2 */ ++ cpu8: cpu@200 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x200>; ++ next-level-cache = <&l2_2>; ++ }; ++ ++ cpu9: cpu@201 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x201>; ++ next-level-cache = <&l2_2>; ++ }; ++ ++ cpu10: cpu@202 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x202>; ++ next-level-cache = <&l2_2>; ++ }; ++ ++ cpu11: cpu@203 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x203>; ++ next-level-cache = <&l2_2>; ++ }; ++ ++ l2_2: l2-cache2 { ++ compatible = "cache"; ++ }; ++ ++ /* CLUSTER 3 */ ++ cpu12: cpu@300 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x300>; ++ next-level-cache = <&l2_3>; ++ }; ++ ++ cpu13: cpu@301 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x301>; ++ next-level-cache = <&l2_3>; ++ }; ++ ++ cpu14: cpu@302 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x302>; ++ next-level-cache = <&l2_3>; ++ }; ++ ++ cpu15: cpu@303 { ++ device_type = "cpu"; ++ compatible = "arm,cortex-a72", "arm,armv8"; ++ reg = <0 0x303>; ++ next-level-cache = <&l2_3>; ++ }; ++ ++ l2_3: l2-cache3 { ++ compatible = "cache"; ++ }; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi b/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi +new file mode 100644 +index 0000000..81057f1 +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi +@@ -0,0 +1,80 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++&ahb_clk { ++ clock-frequency = <400000000>; ++}; ++ ++&emmc_clk { ++ clock-frequency = <200000000>; ++}; ++ ++&flash_clk { ++ clock-frequency = <400000000>; ++}; ++ ++&ref_clk { ++ clock-frequency = <156250000>; ++}; ++ ++&qspi { ++ status = "okay"; ++ flash0: flash@0 { ++ compatible = "jedec,spi-nor"; ++ reg = <0>; ++ spi-max-frequency = <40000000>; ++ spi-rx-bus-width = <2>; ++ m25p,fast-read; ++ cdns,read-delay = <0>; ++ cdns,tshsl-ns = <0>; ++ cdns,tsd2d-ns = <0>; ++ cdns,tchsh-ns = <0>; ++ cdns,tslch-ns = <0>; ++ }; ++}; ++ ++&gpio0 { ++ status = "ok"; ++}; ++ ++&emmc { ++ bus-width = <8>; ++ status = "ok"; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ clock-frequency = <100000>; ++ status = "okay"; ++ temp-sensor@4c { ++ compatible = "ti,tmp451"; ++ reg = <0x4c>; ++ }; ++ ++ rtc@51 { ++ compatible = "nxp,pcf85263"; ++ reg = <0x51>; ++ }; ++ ++ regulator@62 { ++ compatible = "ti,tps53659"; ++ reg = <0x62>; ++ }; ++}; ++ ++&spi0 { ++ num-cs = <4>; ++ cs-gpios = <0>, <0>, ++ <&porta 1 GPIO_ACTIVE_LOW>, <&porta 7 GPIO_ACTIVE_LOW>; ++ status = "okay"; ++ ++ spi0_cs2@2 { ++ compatible = "pensando,cpld-rd1173"; ++ reg = <2>; ++ spi-max-frequency = <12000000>; ++ interrupt-parent = <&porta>; ++ interrupts = <0 IRQ_TYPE_LEVEL_LOW>; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/pensando/elba-asic.dts b/arch/arm64/boot/dts/pensando/elba-asic.dts +new file mode 100644 +index 0000000..382b89e +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-asic.dts +@@ -0,0 +1,23 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++/dts-v1/; ++ ++/ { ++ model = "Elba ASIC Board"; ++ compatible = "pensando,elba"; ++ ++ aliases { ++ serial0 = &uart0; ++ spi0 = &spi0; ++ spi1 = &qspi; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++}; ++ ++#include "elba.dtsi" ++#include "elba-16core.dtsi" ++#include "elba-16core-spin-table.dtsi" ++#include "elba-asic-common.dtsi" +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +new file mode 100644 +index 0000000..b3fa5b1 +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -0,0 +1,208 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright (c) 2019-2021, Pensando Systems Inc. */ ++ ++#include ++#include "dt-bindings/interrupt-controller/arm-gic.h" ++ ++/ { ++ interrupt-parent = <&gic>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ dma-coherent; ++ ++ ahb_clk: oscillator0 { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ }; ++ ++ emmc_clk: oscillator2 { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ }; ++ ++ flash_clk: oscillator3 { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ }; ++ ++ ref_clk: oscillator4 { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ }; ++ ++ timer { ++ compatible = "arm,armv8-timer"; ++ interrupts = , ++ , ++ , ++ ; ++ }; ++ ++ pmu { ++ compatible = "arm,cortex-a72-pmu"; ++ interrupts = ; ++ }; ++ ++ soc: soc { ++ compatible = "simple-bus"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ i2c0: i2c@400 { ++ compatible = "snps,designware-i2c"; ++ reg = <0x0 0x400 0x0 0x100>; ++ clocks = <&ahb_clk>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ i2c-sda-hold-time-ns = <480>; ++ snps,sda-timeout-ms = <750>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ wdt0: watchdog@1400 { ++ compatible = "snps,dw-wdt"; ++ reg = <0x0 0x1400 0x0 0x100>; ++ clocks = <&ahb_clk>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ wdt1: watchdog@1800 { ++ compatible = "snps,dw-wdt"; ++ reg = <0x0 0x1800 0x0 0x100>; ++ clocks = <&ahb_clk>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ wdt2: watchdog@1c00 { ++ compatible = "snps,dw-wdt"; ++ reg = <0x0 0x1c00 0x0 0x100>; ++ clocks = <&ahb_clk>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ wdt3: watchdog@2000 { ++ compatible = "snps,dw-wdt"; ++ reg = <0x0 0x2000 0x0 0x100>; ++ clocks = <&ahb_clk>; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ qspi: spi@2400 { ++ compatible = "amd,pensando-elba-qspi", "cdns,qspi-nor"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x0 0x2400 0x0 0x400>, ++ <0x0 0x7fff0000 0x0 0x1000>; ++ interrupts = ; ++ clocks = <&flash_clk>; ++ cdns,fifo-depth = <1024>; ++ cdns,fifo-width = <4>; ++ cdns,trigger-address = <0x7fff0000>; ++ status = "disabled"; ++ }; ++ ++ spi0: spi@2800 { ++ compatible = "amd,pensando-elba-spi"; ++ reg = <0x0 0x2800 0x0 0x100>; ++ amd,pensando-elba-syscon = <&mssoc>; ++ pensando,spics = <&mssoc 0x2468 0>; ++ clocks = <&ahb_clk>; ++ interrupts = ; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ num-cs = <2>; ++ status = "disabled"; ++ }; ++ ++ gpio0: gpio@4000 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "snps,dw-apb-gpio"; ++ reg = <0x0 0x4000 0x0 0x78>; ++ status = "disabled"; ++ ++ porta: gpio-port@0 { ++ compatible = "snps,dw-apb-gpio-port"; ++ reg = <0>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ ngpios = <8>; ++ interrupts = ; ++ interrupt-controller; ++ interrupt-parent = <&gic>; ++ #interrupt-cells = <2>; ++ }; ++ ++ portb: gpio-port@1 { ++ compatible = "snps,dw-apb-gpio-port"; ++ reg = <1>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ ngpios = <8>; ++ }; ++ }; ++ ++ uart0: serial@4800 { ++ compatible = "ns16550a"; ++ reg = <0x0 0x4800 0x0 0x100>; ++ clocks = <&ref_clk>; ++ interrupts = ; ++ reg-shift = <2>; ++ reg-io-width = <4>; ++ }; ++ ++ gic: interrupt-controller@800000 { ++ compatible = "arm,gic-v3"; ++ #interrupt-cells = <3>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ interrupt-controller; ++ reg = <0x0 0x800000 0x0 0x200000>, /* GICD */ ++ <0x0 0xa00000 0x0 0x200000>; /* GICR */ ++ interrupts = ; ++ ++ its: interrupt-controller@820000 { ++ compatible = "arm,gic-v3-its"; ++ msi-controller; ++ #msi-cells = <1>; ++ reg = <0x0 0x820000 0x0 0x10000>; ++ socionext,synquacer-pre-its = ++ <0xc00000 0x1000000>; ++ }; ++ }; ++ ++ emmc: mmc@30440000 { ++ compatible = "amd,pensando-elba-sd4hc", "cdns,sd4hc"; ++ clocks = <&emmc_clk>; ++ interrupts = ; ++ reg = <0x0 0x30440000 0x0 0x10000 ++ 0x0 0x30480044 0x0 0x4>; ++ cdns,phy-input-delay-sd-highspeed = <0x4>; ++ cdns,phy-input-delay-legacy = <0x4>; ++ cdns,phy-input-delay-sd-uhs-sdr50 = <0x6>; ++ cdns,phy-input-delay-sd-uhs-ddr50 = <0x16>; ++ cdns,phy-dll-delay-sdclk = <0>; ++ cdns,phy-dll-delay-sdclk-hsmmc = <0>; ++ cdns,mmc-ddr-1_8v; ++ status = "disabled"; ++ }; ++ ++ mssoc: mssoc@307c0000 { ++ compatible = "amd,pensando-elba-syscon", "pensando,mssoc", "syscon"; ++ reg = <0x0 0x307c0000 0x0 0x3000>; ++ }; ++ }; ++}; +diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig +index 6a8daeb..a212e3f 100644 +--- a/drivers/soc/Kconfig ++++ b/drivers/soc/Kconfig +@@ -18,6 +18,7 @@ source "drivers/soc/loongson/Kconfig" + source "drivers/soc/mediatek/Kconfig" + source "drivers/soc/microchip/Kconfig" + source "drivers/soc/nuvoton/Kconfig" ++source "drivers/soc/pensando/Kconfig" + source "drivers/soc/pxa/Kconfig" + source "drivers/soc/qcom/Kconfig" + source "drivers/soc/renesas/Kconfig" +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +new file mode 100644 +index 0000000..da71e35 +--- /dev/null ++++ b/drivers/soc/pensando/Kconfig +@@ -0,0 +1,13 @@ ++if ARCH_PENSANDO ++ ++menu "Pensando SoC drivers" ++ ++config ARCH_PENSANDO_ELBA_SOC ++ bool "Elba SoC" ++ default y ++ help ++ Support for the Pensando Elba SoC ++ ++endmenu ++ ++endif +-- +1.8.3.1 + diff --git a/patches-sonic/0006-PCI-AER-Convert-aer_get_device_error_info-aer_print_.patch b/patches-sonic/0006-PCI-AER-Convert-aer_get_device_error_info-aer_print_.patch new file mode 100644 index 000000000..9b28ef3a7 --- /dev/null +++ b/patches-sonic/0006-PCI-AER-Convert-aer_get_device_error_info-aer_print_.patch @@ -0,0 +1,150 @@ +From a53102a3566e6a29bcd447334791e5515c1e92a3 Mon Sep 17 00:00:00 2001 +From: Bjorn Helgaas +Date: Thu, 22 May 2025 18:21:22 -0500 +Subject: [PATCH 06/12] PCI/AER: Convert aer_get_device_error_info(), + aer_print_error() to index +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Previously aer_get_device_error_info() and aer_print_error() took a pointer +to struct aer_err_info and a pointer to a pci_dev. Typically the pci_dev +was one of the elements of the aer_err_info.dev[] array (DPC was an +exception, where the dev[] array was unused). + +Convert aer_get_device_error_info() and aer_print_error() to take an index +into the aer_err_info.dev[] array instead. A future patch will add +per-device ratelimit information, so the index makes it convenient to find +the ratelimit associated with the device. + +To accommodate DPC, set info->dev[0] to the DPC port before using these +interfaces. + +Signed-off-by: Bjorn Helgaas +Reviewed-by: Ilpo Järvinen +Reviewed-by: Kuppuswamy Sathyanarayanan +Link: https://patch.msgid.link/20250522232339.1525671-17-helgaas@kernel.org +(cherry picked from commit 94bc15c3484aa1dbbd01aee8f9eaa5dc347a01a8) +--- + drivers/pci/pci.h | 5 +++-- + drivers/pci/pcie/aer.c | 33 +++++++++++++++++++++++---------- + drivers/pci/pcie/dpc.c | 8 ++++++-- + 3 files changed, 32 insertions(+), 14 deletions(-) + +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index ce78241b6302..4f64f3f9cdf5 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -522,8 +522,9 @@ struct aer_err_info { + struct pcie_tlp_log tlp; /* TLP Header */ + }; + +-int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info); +-void aer_print_error(struct pci_dev *dev, struct aer_err_info *info); ++int aer_get_device_error_info(struct aer_err_info *info, int i); ++void aer_print_error(struct aer_err_info *info, int i); ++ + #endif /* CONFIG_PCIEAER */ + + #ifdef CONFIG_PCIEPORTBUS +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 82acb7580fd4..94cdf2e0d329 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -694,12 +694,18 @@ static void __aer_print_error(struct pci_dev *dev, + } + } + +-void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) ++void aer_print_error(struct aer_err_info *info, int i) + { +- int layer, agent; +- int id = pci_dev_id(dev); ++ struct pci_dev *dev; ++ int layer, agent, id; + const char *level = info->level; + ++ if (WARN_ON_ONCE(i >= AER_MAX_MULTI_ERR_DEVICES)) ++ return; ++ ++ dev = info->dev[i]; ++ id = pci_dev_id(dev); ++ + pci_dev_aer_stats_incr(dev, info); + trace_aer_event(pci_name(dev), (info->status & ~info->mask), + info->severity, info->tlp_header_valid, &info->tlp); +@@ -1200,19 +1206,26 @@ EXPORT_SYMBOL_GPL(aer_recover_queue); + + /** + * aer_get_device_error_info - read error status from dev and store it to info +- * @dev: pointer to the device expected to have a error record + * @info: pointer to structure to store the error record ++ * @i: index into info->dev[] + * + * Return 1 on success, 0 on error. + * + * Note that @info is reused among all error devices. Clear fields properly. + */ +-int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) ++int aer_get_device_error_info(struct aer_err_info *info, int i) + { +- int type = pci_pcie_type(dev); +- int aer = dev->aer_cap; ++ struct pci_dev *dev; ++ int type, aer; + u32 aercc; + ++ if (i >= AER_MAX_MULTI_ERR_DEVICES) ++ return 0; ++ ++ dev = info->dev[i]; ++ aer = dev->aer_cap; ++ type = pci_pcie_type(dev); ++ + /* Must reset in this function */ + info->status = 0; + info->tlp_header_valid = 0; +@@ -1260,11 +1273,11 @@ static inline void aer_process_err_devices(struct aer_err_info *e_info) + + /* Report all before handle them, not to lost records by reset etc. */ + for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { +- if (aer_get_device_error_info(e_info->dev[i], e_info)) +- aer_print_error(e_info->dev[i], e_info); ++ if (aer_get_device_error_info(e_info, i)) ++ aer_print_error(e_info, i); + } + for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { +- if (aer_get_device_error_info(e_info->dev[i], e_info)) ++ if (aer_get_device_error_info(e_info, i)) + handle_error_source(e_info->dev[i], e_info); + } + } +diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c +index d830696dccec..2f147cdf298d 100644 +--- a/drivers/pci/pcie/dpc.c ++++ b/drivers/pci/pcie/dpc.c +@@ -255,6 +255,10 @@ static int dpc_get_aer_uncorrect_severity(struct pci_dev *dev, + info->severity = AER_NONFATAL; + + info->level = KERN_ERR; ++ ++ info->dev[0] = dev; ++ info->error_dev_num = 1; ++ + return 1; + } + +@@ -272,8 +276,8 @@ void dpc_process_error(struct pci_dev *pdev) + pci_warn(pdev, "containment event, status:%#06x: unmasked uncorrectable error detected\n", + status); + if (dpc_get_aer_uncorrect_severity(pdev, &info) && +- aer_get_device_error_info(pdev, &info)) { +- aer_print_error(pdev, &info); ++ aer_get_device_error_info(&info, 0)) { ++ aer_print_error(&info, 0); + pci_aer_clear_nonfatal_status(pdev); + pci_aer_clear_fatal_status(pdev); + } +-- +2.47.0 + diff --git a/patch/0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch b/patches-sonic/0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch similarity index 85% rename from patch/0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch rename to patches-sonic/0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch index b8094da5f..10a0b2a7c 100644 --- a/patch/0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch +++ b/patches-sonic/0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch @@ -1,6 +1,7 @@ From 248bae1d862533e1e1b6a1a0ba32f8b73bbfe57e Mon Sep 17 00:00:00 2001 + From: Daniel Mack -Date: Thu, 11 Jul 2019 14:45:03 +0200 + Subject: [PATCH 6/6] device-tree: bindinds: add NXP PCT2075 as compatible device to LM75 @@ -11,11 +12,11 @@ Signed-off-by: Daniel Mack Link: https://lore.kernel.org/r/20190711124504.7580-1-daniel@zonque.org Signed-off-by: Guenter Roeck --- - Documentation/devicetree/bindings/hwmon/lm75.txt | 1 + + Documentation/devicetree/bindings/hwmon/lm75.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/lm75.txt b/Documentation/devicetree/bindings/hwmon/lm75.txt -index 586b5ed70be7..273616702c51 100644 +index 586b5ed70..273616702 100644 --- a/Documentation/devicetree/bindings/hwmon/lm75.txt +++ b/Documentation/devicetree/bindings/hwmon/lm75.txt @@ -15,6 +15,7 @@ Required properties: @@ -26,6 +27,3 @@ index 586b5ed70be7..273616702c51 100644 "st,stds75", "st,stlm75", "microchip,tcn75", --- -2.17.1 - diff --git a/patches-sonic/0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch b/patches-sonic/0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch new file mode 100644 index 000000000..776c080a9 --- /dev/null +++ b/patches-sonic/0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch @@ -0,0 +1,1657 @@ +From: SONiC JTAG patch +Subject: [PATCH] jtag: Add JTAG core headers, GPIO mux, and locking support + +Add the JTAG core framework headers (include/linux/jtag.h and +include/uapi/linux/jtag.h) that define the kernel-internal and +userspace-visible JTAG interfaces. + +Add GPIO mux control to the Aspeed JTAG driver so a board-level +GPIO can gate JTAG bus ownership on enable/disable. + +Add single-open locking to the core JTAG character device so only one +userspace client can hold the device at a time. + +Add the jtag-aspeed-internal driver for Aspeed SoC internal JTAG +controller support. + +Signed-off-by: SONiC JTAG patch +--- +diff --git a/include/linux/jtag.h b/include/linux/jtag.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/jtag.h +@@ -0,0 +1,49 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (c) 2018 Mellanox Technologies. All rights reserved. */ ++/* Copyright (c) 2018 Oleksandr Shamray */ ++/* Copyright (c) 2019 Intel Corporation */ ++ ++#ifndef __LINUX_JTAG_H ++#define __LINUX_JTAG_H ++ ++#include ++#include ++ ++#define JTAG_MAX_XFER_DATA_LEN (0xFFFFFFFF) ++ ++struct jtag; ++/** ++ * struct jtag_ops - callbacks for JTAG control functions: ++ * ++ * @freq_get: get frequency function. Filled by dev driver ++ * @freq_set: set frequency function. Filled by dev driver ++ * @status_get: get JTAG TAPC state function. Mandatory, Filled by dev driver ++ * @status_set: set JTAG TAPC state function. Mandatory, Filled by dev driver ++ * @xfer: send JTAG xfer function. Mandatory func. Filled by dev driver ++ * @mode_set: set specific work mode for JTAG. Filled by dev driver ++ * @trst_set: set TRST pin active(pull low) for JTAG. Filled by dev driver ++ * @bitbang: set low level bitbang operations. Filled by dev driver ++ * @enable: enables JTAG interface in controller mode. Filled by dev driver ++ * @disable: disables JTAG interface controller mode. Filled by dev driver ++ */ ++struct jtag_ops { ++ int (*freq_get)(struct jtag *jtag, u32 *freq); ++ int (*freq_set)(struct jtag *jtag, u32 freq); ++ int (*status_get)(struct jtag *jtag, u32 *state); ++ int (*status_set)(struct jtag *jtag, struct jtag_tap_state *endst); ++ int (*xfer)(struct jtag *jtag, struct jtag_xfer *xfer, u8 *xfer_data); ++ int (*mode_set)(struct jtag *jtag, struct jtag_mode *jtag_mode); ++ int (*trst_set)(struct jtag *jtag, u32 active); ++ int (*bitbang)(struct jtag *jtag, struct bitbang_packet *bitbang, ++ struct tck_bitbang *bitbang_data); ++ int (*enable)(struct jtag *jtag); ++ int (*disable)(struct jtag *jtag); ++}; ++ ++void *jtag_priv(struct jtag *jtag); ++int devm_jtag_register(struct device *dev, struct jtag *jtag); ++struct jtag *jtag_alloc(struct device *host, size_t priv_size, ++ const struct jtag_ops *ops); ++void jtag_free(struct jtag *jtag); ++ ++#endif /* __LINUX_JTAG_H */ + +diff --git a/include/uapi/linux/jtag.h b/include/uapi/linux/jtag.h +new file mode 100644 +--- /dev/null ++++ b/include/uapi/linux/jtag.h +@@ -0,0 +1,378 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (c) 2018 Mellanox Technologies. All rights reserved. */ ++/* Copyright (c) 2018 Oleksandr Shamray */ ++/* Copyright (c) 2019 Intel Corporation */ ++ ++#ifndef __UAPI_LINUX_JTAG_H ++#define __UAPI_LINUX_JTAG_H ++ ++#include ++#include ++ ++/* ++ * JTAG_XFER_MODE: JTAG transfer mode. Used to set JTAG controller transfer mode ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_MODE 0 ++/* ++ * JTAG_CONTROL_MODE: JTAG controller mode. Used to set JTAG controller mode ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_CONTROL_MODE 1 ++/* ++ * JTAG_TCK_CYCLE_DELAY_COUNT: JTAG delay counter for aspeed_jtag_tck_cycle. Used ++ * set the number of jtag_tck_cycle delays repetitions. ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_TCK_CYCLE_DELAY_COUNT 2 ++/* ++ * JTAG_CONTROLLER_OUTPUT_DISABLE: JTAG controller mode output disable, it is ++ * used to enable other devices to own the JTAG bus. ++ * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_CONTROLLER_OUTPUT_DISABLE 0 ++/* ++ * JTAG_CONTROLLER_MODE: JTAG controller mode. Used to set JTAG controller in ++ * host mode. ++ * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_CONTROLLER_MODE 1 ++/* ++ * JTAG_XFER_HW_MODE: JTAG hardware mode. Used to set HW drived or bitbang ++ * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_HW_MODE 1 ++/* ++ * JTAG_XFER_SW_MODE: JTAG software mode. Used to set SW drived or bitbang ++ * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_SW_MODE 0 ++ ++/** ++ * enum jtag_tapstate: ++ * ++ * @JTAG_STATE_TLRESET: JTAG state machine Test Logic Reset state ++ * @JTAG_STATE_IDLE: JTAG state machine IDLE state ++ * @JTAG_STATE_SELECTDR: JTAG state machine SELECT_DR state ++ * @JTAG_STATE_CAPTUREDR: JTAG state machine CAPTURE_DR state ++ * @JTAG_STATE_SHIFTDR: JTAG state machine SHIFT_DR state ++ * @JTAG_STATE_EXIT1DR: JTAG state machine EXIT-1 DR state ++ * @JTAG_STATE_PAUSEDR: JTAG state machine PAUSE_DR state ++ * @JTAG_STATE_EXIT2DR: JTAG state machine EXIT-2 DR state ++ * @JTAG_STATE_UPDATEDR: JTAG state machine UPDATE DR state ++ * @JTAG_STATE_SELECTIR: JTAG state machine SELECT_IR state ++ * @JTAG_STATE_CAPTUREIR: JTAG state machine CAPTURE_IR state ++ * @JTAG_STATE_SHIFTIR: JTAG state machine SHIFT_IR state ++ * @JTAG_STATE_EXIT1IR: JTAG state machine EXIT-1 IR state ++ * @JTAG_STATE_PAUSEIR: JTAG state machine PAUSE_IR state ++ * @JTAG_STATE_EXIT2IR: JTAG state machine EXIT-2 IR state ++ * @JTAG_STATE_UPDATEIR: JTAG state machine UPDATE IR state ++ * @JTAG_STATE_CURRENT: JTAG current state, saved by driver ++ */ ++enum jtag_tapstate { ++ JTAG_STATE_TLRESET, ++ JTAG_STATE_IDLE, ++ JTAG_STATE_SELECTDR, ++ JTAG_STATE_CAPTUREDR, ++ JTAG_STATE_SHIFTDR, ++ JTAG_STATE_EXIT1DR, ++ JTAG_STATE_PAUSEDR, ++ JTAG_STATE_EXIT2DR, ++ JTAG_STATE_UPDATEDR, ++ JTAG_STATE_SELECTIR, ++ JTAG_STATE_CAPTUREIR, ++ JTAG_STATE_SHIFTIR, ++ JTAG_STATE_EXIT1IR, ++ JTAG_STATE_PAUSEIR, ++ JTAG_STATE_EXIT2IR, ++ JTAG_STATE_UPDATEIR, ++ JTAG_STATE_CURRENT ++}; ++ ++/** ++ * enum jtag_reset: ++ * ++ * @JTAG_NO_RESET: JTAG run TAP from current state ++ * @JTAG_FORCE_RESET: JTAG force TAP to reset state ++ */ ++enum jtag_reset { ++ JTAG_NO_RESET = 0, ++ JTAG_FORCE_RESET = 1, ++}; ++ ++/** ++ * enum jtag_xfer_type: ++ * ++ * @JTAG_SIR_XFER: SIR transfer ++ * @JTAG_SDR_XFER: SDR transfer ++ */ ++enum jtag_xfer_type { ++ JTAG_SIR_XFER = 0, ++ JTAG_SDR_XFER = 1, ++}; ++ ++/** ++ * enum jtag_xfer_direction: ++ * ++ * @JTAG_READ_XFER: read transfer ++ * @JTAG_WRITE_XFER: write transfer ++ * @JTAG_READ_WRITE_XFER: read & write transfer ++ */ ++enum jtag_xfer_direction { ++ JTAG_READ_XFER = 1, ++ JTAG_WRITE_XFER = 2, ++ JTAG_READ_WRITE_XFER = 3, ++}; ++ ++/** ++ * struct jtag_tap_state - forces JTAG state machine to go into a TAPC ++ * state ++ * ++ * @reset: 0 - run IDLE/PAUSE from current state ++ * 1 - go through TEST_LOGIC/RESET state before IDLE/PAUSE ++ * @from: initital jtag state ++ * @endstate: jtag end state ++ * @tck: clock counter ++ * ++ * Structure provide interface to JTAG device for JTAG set state execution. ++ */ ++struct jtag_tap_state { ++ __u8 reset; ++ __u8 from; ++ __u8 endstate; ++ __u32 tck; ++}; ++ ++/** ++ * union pad_config - Padding Configuration: ++ * ++ * @type: transfer type ++ * @pre_pad_number: Number of prepadding bits bit[11:0] ++ * @post_pad_number: Number of prepadding bits bit[23:12] ++ * @pad_data : Bit value to be used by pre and post padding bit[24] ++ * @int_value: unsigned int packed padding configuration value bit[32:0] ++ * ++ * Structure provide pre and post padding configuration in a single __u32 ++ */ ++union pad_config { ++ struct { ++ __u32 pre_pad_number : 12; ++ __u32 post_pad_number : 12; ++ __u32 pad_data : 1; ++ __u32 rsvd : 7; ++ }; ++ __u32 int_value; ++}; ++ ++/** ++ * struct jtag_xfer - jtag xfer: ++ * ++ * @type: transfer type ++ * @direction: xfer direction ++ * @from: xfer current state ++ * @endstate: xfer end state ++ * @padding: xfer padding ++ * @length: xfer bits length ++ * @tdio : xfer data array ++ * ++ * Structure provide interface to JTAG device for JTAG SDR/SIR xfer execution. ++ */ ++struct jtag_xfer { ++ __u8 type; ++ __u8 direction; ++ __u8 from; ++ __u8 endstate; ++ __u32 padding; ++ __u32 length; ++ __u64 tdio; ++}; ++ ++/** ++ * struct bitbang_packet - jtag bitbang array packet: ++ * ++ * @data: JTAG Bitbang struct array pointer(input/output) ++ * @length: array size (input) ++ * ++ * Structure provide interface to JTAG device for JTAG bitbang bundle execution ++ */ ++struct bitbang_packet { ++ struct tck_bitbang *data; ++ __u32 length; ++} __attribute__((__packed__)); ++ ++/** ++ * struct jtag_bitbang - jtag bitbang: ++ * ++ * @tms: JTAG TMS ++ * @tdi: JTAG TDI (input) ++ * @tdo: JTAG TDO (output) ++ * ++ * Structure provide interface to JTAG device for JTAG bitbang execution. ++ */ ++struct tck_bitbang { ++ __u8 tms; ++ __u8 tdi; ++ __u8 tdo; ++} __attribute__((__packed__)); ++ ++/** ++ * struct jtag_mode - jtag mode: ++ * ++ * @feature: 0 - JTAG feature setting selector for JTAG controller HW/SW ++ * 1 - JTAG feature setting selector for controller bus mode ++ * output (enable / disable). ++ * @mode: (0 - SW / 1 - HW) for JTAG_XFER_MODE feature(0) ++ * (0 - output disable / 1 - output enable) for JTAG_CONTROL_MODE ++ * feature(1) ++ * ++ * Structure provide configuration modes to JTAG device. ++ */ ++struct jtag_mode { ++ __u32 feature; ++ __u32 mode; ++}; ++ ++/* ioctl interface */ ++#define __JTAG_IOCTL_MAGIC 0xb2 ++ ++#define JTAG_SIOCSTATE _IOW(__JTAG_IOCTL_MAGIC, 0, struct jtag_tap_state) ++#define JTAG_SIOCFREQ _IOW(__JTAG_IOCTL_MAGIC, 1, unsigned int) ++#define JTAG_GIOCFREQ _IOR(__JTAG_IOCTL_MAGIC, 2, unsigned int) ++#define JTAG_IOCXFER _IOWR(__JTAG_IOCTL_MAGIC, 3, struct jtag_xfer) ++#define JTAG_GIOCSTATUS _IOWR(__JTAG_IOCTL_MAGIC, 4, enum jtag_tapstate) ++#define JTAG_SIOCMODE _IOW(__JTAG_IOCTL_MAGIC, 5, unsigned int) ++#define JTAG_IOCBITBANG _IOW(__JTAG_IOCTL_MAGIC, 6, unsigned int) ++#define JTAG_SIOCTRST _IOW(__JTAG_IOCTL_MAGIC, 7, unsigned int) ++ ++/** ++ * struct tms_cycle - This structure represents a tms cycle state. ++ * ++ * @tmsbits: is the bitwise representation of the needed tms transitions to ++ * move from one state to another. ++ * @count: number of jumps needed to move to the needed state. ++ * ++ */ ++struct tms_cycle { ++ unsigned char tmsbits; ++ unsigned char count; ++}; ++ ++/* ++ * This is the complete set TMS cycles for going from any TAP state to any ++ * other TAP state, following a "shortest path" rule. ++ */ ++static const struct tms_cycle _tms_cycle_lookup[][16] = { ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* TLR */{{0x00, 0}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x02, 4}, {0x0a, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0a, 5}, {0x2a, 6}, {0x1a, 5}, {0x06, 3}, {0x06, 4}, {0x06, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x16, 5}, {0x16, 6}, {0x56, 7}, {0x36, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* RTI */{{0x07, 3}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SelDR*/{{0x03, 2}, {0x03, 3}, {0x00, 0}, {0x00, 1}, {0x00, 2}, {0x02, 2}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x02, 3}, {0x0a, 4}, {0x06, 3}, {0x01, 1}, {0x01, 2}, {0x01, 3}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* CapDR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x00, 0}, {0x00, 1}, {0x01, 1}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x00, 0}, {0x01, 1}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex1DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x02, 3}, {0x00, 0}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x00, 1}, {0x02, 2}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* PDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x01, 2}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x00, 0}, {0x01, 1}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex2DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x00, 1}, {0x02, 2}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x02, 3}, {0x00, 0}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* UpdDR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x00, 0}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SelIR*/{{0x01, 1}, {0x01, 2}, {0x05, 3}, {0x05, 4}, {0x05, 5}, {0x15, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x15, 6}, {0x55, 7}, {0x35, 6}, {0x00, 0}, {0x00, 1}, {0x00, 2}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x02, 2}, {0x02, 3}, {0x0a, 4}, {0x06, 3} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* CapIR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x00, 0}, {0x00, 1}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x00, 0}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex1IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x02, 3}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x00, 0}, {0x00, 1}, {0x02, 2}, {0x01, 1} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* PIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x01, 2}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x05, 3}, {0x00, 0}, {0x01, 1}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex2IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x00, 1}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x02, 2}, {0x02, 3}, {0x00, 0}, {0x01, 1} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* UpdIR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x00, 0} }, ++}; ++ ++#endif /* __UAPI_LINUX_JTAG_H */ + +diff --git a/drivers/jtag/jtag.c b/drivers/jtag/jtag.c +--- a/drivers/jtag/jtag.c ++++ b/drivers/jtag/jtag.c +@@ -23,6 +23,7 @@ + struct miscdevice miscdev; + const struct jtag_ops *ops; + int id; ++ bool locked; + unsigned long *priv; + }; + +@@ -247,17 +248,23 @@ + struct jtag *jtag = container_of(file->private_data, + struct jtag, + miscdev); ++ int ret = 0; + + file->private_data = jtag; +- if (jtag->ops->enable(jtag)) ++ if (jtag->ops->enable(jtag) || jtag->locked) + return -EBUSY; +- return nonseekable_open(inode, file); ++ ++ ret = nonseekable_open(inode, file); ++ if (!ret) ++ jtag->locked = true; ++ return ret; + } + + static int jtag_release(struct inode *inode, struct file *file) + { + struct jtag *jtag = file->private_data; + ++ jtag->locked = false; + if (jtag->ops->disable(jtag)) + return -EBUSY; + return 0; +@@ -333,6 +340,7 @@ + dev_err(jtag->miscdev.parent, "Unable to register device\n"); + goto err_jtag_name; + } ++ jtag->locked = false; + return 0; + + err_jtag_name: + +diff --git a/drivers/jtag/jtag-aspeed.c b/drivers/jtag/jtag-aspeed.c +--- a/drivers/jtag/jtag-aspeed.c ++++ b/drivers/jtag/jtag-aspeed.c +@@ -5,6 +5,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -199,6 +200,7 @@ + const struct jtag_low_level_functions *llops; + u32 pad_data_one[ASPEED_JTAG_MAX_PAD_SIZE / 32]; + u32 pad_data_zero[ASPEED_JTAG_MAX_PAD_SIZE / 32]; ++ struct gpio_desc *mux_gpiod; + }; + + /* +@@ -1393,6 +1395,9 @@ + { + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + ++ if (!IS_ERR_OR_NULL(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 1); ++ + aspeed_jtag->llops->master_enable(aspeed_jtag); + return 0; + } +@@ -1401,6 +1406,9 @@ + { + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + ++ if (!IS_ERR_OR_NULL(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 0); ++ + aspeed_jtag->llops->output_disable(aspeed_jtag); + return 0; + } +@@ -1612,6 +1620,13 @@ + aspeed_jtag = jtag_priv(jtag); + aspeed_jtag->dev = &pdev->dev; + ++ /* Optional GPIO mux control */ ++ aspeed_jtag->mux_gpiod = devm_gpiod_get_index_optional(&pdev->dev, ++ "mux", 0, ++ GPIOD_OUT_LOW); ++ if (IS_ERR(aspeed_jtag->mux_gpiod)) ++ return PTR_ERR(aspeed_jtag->mux_gpiod); ++ + aspeed_jtag->llops = jtag_functions->aspeed_jtag_llops; + + /* Initialize device*/ + +diff --git a/drivers/jtag/jtag-aspeed-internal.c b/drivers/jtag/jtag-aspeed-internal.c +new file mode 100644 +--- /dev/null ++++ b/drivers/jtag/jtag-aspeed-internal.c +@@ -0,0 +1,1095 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * JTAG driver for the Aspeed SoC ++ * ++ * Copyright (C) 2021 ASPEED Technology Inc. ++ * Ryan Chen ++ * ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++/******************************************************************************/ ++#define ASPEED_JTAG_DATA 0x00 ++#define ASPEED_JTAG_INST 0x04 ++#define ASPEED_JTAG_CTRL 0x08 ++#define ASPEED_JTAG_ISR 0x0C ++#define ASPEED_JTAG_SW 0x10 ++#define ASPEED_JTAG_TCK 0x14 ++#define ASPEED_JTAG_IDLE 0x18 ++ ++/* ASPEED_JTAG_CTRL - 0x08 : Engine Control */ ++#define JTAG_ENG_EN BIT(31) ++#define JTAG_ENG_OUT_EN BIT(30) ++#define JTAG_FORCE_TMS BIT(29) ++ ++#define JTAG_IR_UPDATE BIT(26) //AST2500 only ++ ++#define JTAG_G6_RESET_FIFO BIT(21) //AST2600 only ++#define JTAG_G6_CTRL_MODE BIT(20) //AST2600 only ++#define JTAG_G6_XFER_LEN_MASK (0x3ff << 8) //AST2600 only ++#define JTAG_G6_SET_XFER_LEN(x) (x << 8) ++#define JTAG_G6_MSB_FIRST BIT(6) //AST2600 only ++#define JTAG_G6_TERMINATE_XFER BIT(5) //AST2600 only ++#define JTAG_G6_LAST_XFER BIT(4) //AST2600 only ++#define JTAG_G6_INST_EN BIT(1) ++ ++#define JTAG_INST_LEN_MASK (0x3f << 20) ++#define JTAG_SET_INST_LEN(x) (x << 20) ++#define JTAG_SET_INST_MSB BIT(19) ++#define JTAG_TERMINATE_INST BIT(18) ++#define JTAG_LAST_INST BIT(17) ++#define JTAG_INST_EN BIT(16) ++#define JTAG_DATA_LEN_MASK (0x3f << 4) ++ ++#define JTAG_DR_UPDATE BIT(10) //AST2500 only ++#define JTAG_DATA_LEN(x) (x << 4) ++#define JTAG_MSB_FIRST BIT(3) ++#define JTAG_TERMINATE_DATA BIT(2) ++#define JTAG_LAST_DATA BIT(1) ++#define JTAG_DATA_EN BIT(0) ++ ++/* ASPEED_JTAG_ISR - 0x0C : INterrupt status and enable */ ++#define JTAG_INST_PAUSE BIT(19) ++#define JTAG_INST_COMPLETE BIT(18) ++#define JTAG_DATA_PAUSE BIT(17) ++#define JTAG_DATA_COMPLETE BIT(16) ++ ++#define JTAG_INST_PAUSE_EN BIT(3) ++#define JTAG_INST_COMPLETE_EN BIT(2) ++#define JTAG_DATA_PAUSE_EN BIT(1) ++#define JTAG_DATA_COMPLETE_EN BIT(0) ++ ++/* ASPEED_JTAG_SW - 0x10 : Software Mode and Status */ ++#define JTAG_SW_MODE_EN BIT(19) ++#define JTAG_SW_MODE_TCK BIT(18) ++#define JTAG_SW_MODE_TMS BIT(17) ++#define JTAG_SW_MODE_TDIO BIT(16) ++// ++#define JTAG_STS_INST_PAUSE BIT(2) ++#define JTAG_STS_DATA_PAUSE BIT(1) ++#define JTAG_STS_ENG_IDLE (0x1) ++ ++/* ASPEED_JTAG_TCK - 0x14 : TCK Control */ ++#define JTAG_TCK_INVERSE BIT(31) ++#define JTAG_TCK_DIVISOR_MASK (0x7ff) ++#define JTAG_GET_TCK_DIVISOR(x) (x & 0x7ff) ++ ++/* ASPEED_JTAG_IDLE - 0x18 : Ctroller set for go to IDLE */ ++#define JTAG_CTRL_TRSTn_HIGH BIT(31) ++#define JTAG_GO_IDLE BIT(0) ++ ++#define TCK_FREQ 1000000 ++#define ASPEED_JTAG_MAX_PAD_SIZE 1024 ++/******************************************************************************/ ++#define ASPEED_JTAG_DEBUG ++ ++#ifdef ASPEED_JTAG_DEBUG ++#define JTAG_DBUG(fmt, args...) \ ++ pr_debug("%s() " fmt, __func__, ##args) ++#else ++#define JTAG_DBUG(fmt, args...) ++#endif ++ ++static char *end_status_str[] = { "tlr", "idle", "selDR", "capDR", ++ "sDR", "ex1DR", "pDR", "ex2DR", ++ "updDR", "selIR", "capIR", "sIR", ++ "ex1IR", "pIR", "ex2IR", "updIR" }; ++ ++struct aspeed_jtag_config { ++ u8 jtag_version; ++ u32 jtag_buff_len; ++}; ++ ++struct aspeed_jtag_info { ++ void __iomem *reg_base; ++ struct device *dev; ++ struct aspeed_jtag_config *config; ++ enum jtag_tapstate sts; ++ int irq; ++ struct reset_control *reset; ++ struct clk *clk; ++ u32 clkin; ++ u32 tck_period; ++ u32 sw_delay; ++ u32 flag; ++ wait_queue_head_t jtag_wq; ++ u32 mode; ++ u8 pad_data_one[ASPEED_JTAG_MAX_PAD_SIZE]; ++ u8 pad_data_zero[ASPEED_JTAG_MAX_PAD_SIZE]; ++ struct gpio_desc *mux_gpiod; ++}; ++/******************************************************************************/ ++static inline u32 ++aspeed_jtag_read(struct aspeed_jtag_info *aspeed_jtag, u32 reg) ++{ ++ int val; ++ ++ val = readl(aspeed_jtag->reg_base + reg); ++ return val; ++} ++ ++static inline void ++aspeed_jtag_write(struct aspeed_jtag_info *aspeed_jtag, u32 val, u32 reg) ++{ ++ writel(val, aspeed_jtag->reg_base + reg); ++} ++ ++/******************************************************************************/ ++static int aspeed_jtag_set_freq(struct jtag *jtag, u32 freq) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ u32 div; ++ ++ /* SW mode frequency setting */ ++ aspeed_jtag->sw_delay = DIV_ROUND_UP(NSEC_PER_SEC, freq); ++ /* ++ * HW mode frequency setting ++ * AST2600: TCK period = Period of HCLK * (JTAG14[10:0] + 1) ++ * AST2500: TCK period = Period of PCLK * (JTAG14[10:0] + 1) * 2 ++ */ ++ if (aspeed_jtag->config->jtag_version == 6) ++ div = DIV_ROUND_UP(aspeed_jtag->clkin, freq) - 1; ++ else ++ div = DIV_ROUND_UP(aspeed_jtag->clkin, freq * 2) - 1; ++ if (div > JTAG_TCK_DIVISOR_MASK) { ++ pr_warn("The actual frequency will faster than required\n"); ++ div = JTAG_TCK_DIVISOR_MASK; ++ } ++ /* ++ * HW constraint: ++ * AST2600 minimal TCK divisor = 7 ++ * AST2500 minimal TCK divisor = 1 ++ */ ++ if (aspeed_jtag->config->jtag_version == 6) { ++ if (div < 7) ++ div = 7; ++ aspeed_jtag->tck_period = DIV_ROUND_UP_ULL( ++ (u64)NSEC_PER_SEC * (div + 1), aspeed_jtag->clkin); ++ } else if (aspeed_jtag->config->jtag_version == 0) { ++ if (div < 1) ++ div = 1; ++ aspeed_jtag->tck_period = DIV_ROUND_UP_ULL( ++ (u64)NSEC_PER_SEC * (div + 1) << 2, aspeed_jtag->clkin); ++ } ++ /* ++ * At ast2500: Change clock divider may cause hardware logic confusion. ++ * Enable software mode to assert the jtag hw logical before change ++ * clock divider. ++ */ ++ if (aspeed_jtag->config->jtag_version == 0) ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_SW_MODE_EN | ++ aspeed_jtag_read(aspeed_jtag, ++ ASPEED_JTAG_SW), ++ ASPEED_JTAG_SW); ++ aspeed_jtag_write(aspeed_jtag, ++ ((aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK) & ++ ~JTAG_TCK_DIVISOR_MASK) | ++ div), ++ ASPEED_JTAG_TCK); ++ if (aspeed_jtag->config->jtag_version == 0) { ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ } ++ JTAG_DBUG("Operation freq = %d / %d\n", aspeed_jtag->clkin, div + 1); ++ return 0; ++} ++ ++static int aspeed_jtag_get_freq(struct jtag *jtag, u32 *freq) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ if (aspeed_jtag->config->jtag_version == 6) { ++ /* TCK period = Period of HCLK * (JTAG14[10:0] + 1) */ ++ *freq = aspeed_jtag->clkin / ++ (JTAG_GET_TCK_DIVISOR(aspeed_jtag_read( ++ aspeed_jtag, ASPEED_JTAG_TCK)) + 1); ++ } else if (aspeed_jtag->config->jtag_version == 0) { ++ /* TCK period = Period of PCLK * (JTAG14[10:0] + 1) * 2 */ ++ *freq = (aspeed_jtag->clkin / ++ (JTAG_GET_TCK_DIVISOR(aspeed_jtag_read( ++ aspeed_jtag, ASPEED_JTAG_TCK)) + 1)) >> 1; ++ } else { ++ /* unknown jtag version */ ++ *freq = 0; ++ } ++ return 0; ++} ++/******************************************************************************/ ++static u8 TCK_Cycle(struct aspeed_jtag_info *aspeed_jtag, u8 TMS, u8 TDI) ++{ ++ u8 tdo; ++ ++ /* IEEE 1149.1 ++ * TMS & TDI shall be sampled by the test logic on the rising edge ++ * test logic shall change TDO on the falling edge ++ */ ++ // TCK = 0 ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_SW_MODE_EN | (TMS * JTAG_SW_MODE_TMS) | ++ (TDI * JTAG_SW_MODE_TDIO), ++ ASPEED_JTAG_SW); ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); ++ ++ /* Target device have their operating frequency*/ ++ ndelay(aspeed_jtag->sw_delay >> 1); ++ ++ // TCK = 1 ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_SW_MODE_EN | JTAG_SW_MODE_TCK | ++ (TMS * JTAG_SW_MODE_TMS) | ++ (TDI * JTAG_SW_MODE_TDIO), ++ ASPEED_JTAG_SW); ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); ++ ++ ndelay(aspeed_jtag->sw_delay >> 1); ++ /* Sampled TDI(slave, master's TDO) on the rising edge */ ++ if (aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW) & JTAG_SW_MODE_TDIO) ++ tdo = 1; ++ else ++ tdo = 0; ++ ++ return tdo; ++} ++ ++static int aspeed_jtag_sw_set_tap_state(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate) ++{ ++ int i = 0; ++ enum jtag_tapstate from, to; ++ ++ from = aspeed_jtag->sts; ++ to = endstate; ++ /* Send 8 TMS high to ensure jtag tap state go to TLRESET */ ++ if (endstate == JTAG_STATE_TLRESET) ++ for (i = 0; i < 8 ; i++) ++ TCK_Cycle(aspeed_jtag, ((0xff >> i) & 0x1), 0); ++ else ++ for (i = 0; i < _tms_cycle_lookup[from][to].count; i++) ++ TCK_Cycle(aspeed_jtag, ++ ((_tms_cycle_lookup[from][to].tmsbits >> i) & ++ 0x1), ++ 0); ++ aspeed_jtag->sts = endstate; ++ return 0; ++} ++ ++/******************************************************************************/ ++static void aspeed_jtag_wait_instruction_pause_complete( ++ struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_INST_PAUSE)); ++ aspeed_jtag->flag &= ~JTAG_INST_PAUSE; ++} ++static void ++aspeed_jtag_wait_instruction_complete(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_INST_COMPLETE)); ++ aspeed_jtag->flag &= ~JTAG_INST_COMPLETE; ++} ++static void ++aspeed_jtag_wait_data_pause_complete(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_DATA_PAUSE)); ++ aspeed_jtag->flag &= ~JTAG_DATA_PAUSE; ++} ++static void aspeed_jtag_wait_data_complete(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_DATA_COMPLETE)); ++ aspeed_jtag->flag &= ~JTAG_DATA_COMPLETE; ++} ++static int aspeed_jtag_run_to_tlr(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ else if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | JTAG_FORCE_TMS, ++ ASPEED_JTAG_CTRL); // x TMS high + 1 TMS low ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_wait_instruction_complete(aspeed_jtag); ++ else if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ /* After that the fsm will go to idle state: hw constraint */ ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ return 0; ++} ++ ++static int aspeed_jtag_run_to_idle(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ if (aspeed_jtag->sts == JTAG_STATE_IDLE) { ++ /* nothing to do */ ++ } else if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_TERMINATE_XFER | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_TERMINATE_DATA | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ } else if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_TERMINATE_XFER | ++ JTAG_G6_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_TERMINATE_INST | ++ JTAG_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_instruction_complete(aspeed_jtag); ++ } else { ++ pr_err("Should not get here unless aspeed_jtag->sts error!"); ++ return -EFAULT; ++ } ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ return 0; ++} ++ ++static int aspeed_jtag_hw_set_tap_state(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate) ++{ ++ int ret; ++ ++ if (endstate == JTAG_STATE_TLRESET) { ++ ret = aspeed_jtag_run_to_tlr(aspeed_jtag); ++ } else if (endstate == JTAG_STATE_IDLE) { ++ ret = aspeed_jtag_run_to_idle(aspeed_jtag); ++ } else { ++ /* other stable state will auto handle by hardware */ ++ return 0; ++ } ++ return ret; ++} ++ ++/******************************************************************************/ ++/* JTAG_reset() is to generate at leaspeed 9 TMS high and ++ * 1 TMS low to force devices into Run-Test/Idle State ++ */ ++static int aspeed_jtag_status_set(struct jtag *jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ int ret; ++ uint32_t i; ++ ++ if (tapstate->from == JTAG_STATE_CURRENT) ++ tapstate->from = aspeed_jtag->sts; ++ if (tapstate->endstate == JTAG_STATE_CURRENT) ++ tapstate->endstate = aspeed_jtag->sts; ++ JTAG_DBUG("reset:%d from:%s end:%s tck:%d", tapstate->reset, ++ end_status_str[tapstate->from], ++ end_status_str[tapstate->endstate], tapstate->tck); ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) { ++ if (tapstate->reset == JTAG_FORCE_RESET) ++ aspeed_jtag_hw_set_tap_state(aspeed_jtag, ++ JTAG_STATE_TLRESET); ++ ret = aspeed_jtag_hw_set_tap_state(aspeed_jtag, ++ tapstate->endstate); ++ for (i = 0; i < tapstate->tck; i++) ++ ndelay(aspeed_jtag->tck_period); ++ } else { ++ if (tapstate->reset == JTAG_FORCE_RESET) ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, ++ JTAG_STATE_TLRESET); ++ ret = aspeed_jtag_sw_set_tap_state(aspeed_jtag, ++ tapstate->endstate); ++ if (tapstate->endstate == JTAG_STATE_TLRESET || ++ tapstate->endstate == JTAG_STATE_IDLE || ++ tapstate->endstate == JTAG_STATE_PAUSEDR || ++ tapstate->endstate == JTAG_STATE_PAUSEIR) ++ for (i = 0; i < tapstate->tck; i++) ++ TCK_Cycle(aspeed_jtag, 0, 0); ++ } ++ if (ret) ++ return ret; ++ return 0; ++} ++ ++static int aspeed_jtag_status_get(struct jtag *jtag, u32 *status) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ *status = aspeed_jtag->sts; ++ return 0; ++} ++static void aspeed_sw_jtag_xfer(struct aspeed_jtag_info *aspeed_jtag, ++ struct jtag_xfer *xfer, u8 *xfer_data) ++{ ++ unsigned int index = 0; ++ u32 shift_bits = 0; ++ u8 tdi = 0, tdo = 0, tdo_buff = 0; ++ u32 remain_xfer = xfer->length; ++ ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, JTAG_STATE_SHIFTIR); ++ else ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, JTAG_STATE_SHIFTDR); ++ ++ while (remain_xfer) { ++ tdi = (xfer_data[index]) >> (shift_bits % 8) & (0x1); ++ if (remain_xfer == 1 && ++ xfer->endstate != (xfer->type == JTAG_SIR_XFER ? ++ JTAG_STATE_SHIFTIR : ++ JTAG_STATE_SHIFTDR)) { ++ tdo = TCK_Cycle(aspeed_jtag, 1, tdi); // go to Exit1-XR ++ aspeed_jtag->sts = xfer->type == JTAG_SIR_XFER ? ++ JTAG_STATE_EXIT1IR : ++ JTAG_STATE_EXIT1DR; ++ } else ++ tdo = TCK_Cycle(aspeed_jtag, 0, tdi); // go to XRShift ++ tdo_buff |= (tdo << (shift_bits % 8)); ++ shift_bits++; ++ remain_xfer--; ++ if ((shift_bits % 8) == 0) { ++ if (xfer->direction & JTAG_READ_XFER) ++ xfer_data[index] = tdo_buff; ++ tdo_buff = 0; ++ index++; ++ } ++ } ++ if (xfer->direction & JTAG_READ_XFER && (shift_bits % 8)) ++ xfer_data[index] = tdo_buff; ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, xfer->endstate); ++} ++static int aspeed_hw_ir_scan(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate, u32 shift_bits) ++{ ++ if (endstate == JTAG_STATE_PAUSEIR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_PAUSE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_G6_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_INST_PAUSE_EN | ++ JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_SET_INST_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_SET_INST_LEN(shift_bits) | ++ JTAG_INST_EN, ++ ASPEED_JTAG_CTRL); ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ } ++ aspeed_jtag_wait_instruction_pause_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_PAUSEIR; ++ } else if (endstate == JTAG_STATE_IDLE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_G6_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_LAST_INST | ++ JTAG_SET_INST_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | JTAG_LAST_INST | ++ JTAG_SET_INST_LEN(shift_bits) | ++ JTAG_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_instruction_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ } else { ++ pr_err("End state %d not support", endstate); ++ return -EFAULT; ++ } ++ return 0; ++} ++static int aspeed_hw_dr_scan(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate, u32 shift_bits) ++{ ++ if (endstate == JTAG_STATE_PAUSEDR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_PAUSE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_DATA_PAUSE_EN | ++ JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_DATA_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_DATA_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_wait_instruction_complete( ++ aspeed_jtag); ++ } ++ aspeed_jtag_wait_data_pause_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_PAUSEDR; ++ } else if (endstate == JTAG_STATE_IDLE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_LAST_DATA | ++ JTAG_DATA_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_LAST_DATA | ++ JTAG_DATA_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ } else { ++ pr_err("End state %d not support", endstate); ++ return -EFAULT; ++ } ++ return 0; ++} ++static void aspeed_hw_jtag_xfer(struct aspeed_jtag_info *aspeed_jtag, ++ struct jtag_xfer *xfer, u8 *xfer_data) ++{ ++ unsigned int index = 0; ++ u32 shift_bits = 0; ++ u32 remain_xfer = xfer->length; ++ int i, tmp_idx = 0; ++ u32 fifo_reg = xfer->type ? ASPEED_JTAG_DATA : ASPEED_JTAG_INST; ++ u32 *xfer_data_32 = (u32 *)xfer_data; ++ enum jtag_tapstate endstate; ++ ++ /* Translate the end tap status to the stable tap status for hw mode */ ++ if (xfer->endstate == JTAG_STATE_PAUSEDR || ++ xfer->endstate == JTAG_STATE_SHIFTDR) ++ endstate = JTAG_STATE_PAUSEDR; ++ else if (xfer->endstate == JTAG_STATE_PAUSEIR || ++ xfer->endstate == JTAG_STATE_SHIFTIR) ++ endstate = JTAG_STATE_PAUSEIR; ++ else ++ endstate = JTAG_STATE_IDLE; ++ ++ while (remain_xfer) { ++ if (remain_xfer > aspeed_jtag->config->jtag_buff_len) { ++ shift_bits = aspeed_jtag->config->jtag_buff_len; ++ tmp_idx = shift_bits / 32; ++ for (i = 0; i < tmp_idx; i++) ++ aspeed_jtag_write(aspeed_jtag, ++ xfer_data_32[index + i], ++ fifo_reg); ++ /* ++ * Add 1 tck period delay to avoid jtag hardware ++ * transfer will get wrong fifo pointer issue. ++ */ ++ ndelay(aspeed_jtag->tck_period); ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_hw_ir_scan(aspeed_jtag, ++ JTAG_STATE_PAUSEIR, ++ shift_bits); ++ else ++ aspeed_hw_dr_scan(aspeed_jtag, ++ JTAG_STATE_PAUSEDR, ++ shift_bits); ++ } else { ++ shift_bits = remain_xfer; ++ tmp_idx = shift_bits / 32; ++ if (shift_bits % 32) ++ tmp_idx += 1; ++ for (i = 0; i < tmp_idx; i++) ++ aspeed_jtag_write(aspeed_jtag, ++ xfer_data_32[index + i], ++ fifo_reg); ++ ndelay(aspeed_jtag->tck_period); ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_hw_ir_scan(aspeed_jtag, endstate, ++ shift_bits); ++ else ++ aspeed_hw_dr_scan(aspeed_jtag, endstate, ++ shift_bits); ++ } ++ ++ remain_xfer = remain_xfer - shift_bits; ++ ++ //handle tdo data ++ if (xfer->direction & JTAG_READ_XFER) { ++ tmp_idx = shift_bits / 32; ++ if (shift_bits % 32) ++ tmp_idx += 1; ++ for (i = 0; i < tmp_idx; i++) { ++ if (shift_bits < 32) ++ xfer_data_32[index + i] = ++ aspeed_jtag_read(aspeed_jtag, ++ fifo_reg) >> ++ (32 - shift_bits); ++ else ++ xfer_data_32[index + i] = ++ aspeed_jtag_read(aspeed_jtag, ++ fifo_reg); ++ shift_bits -= 32; ++ } ++ } ++ index += tmp_idx; ++ } ++} ++ ++static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer, ++ u8 *xfer_data) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ union pad_config padding; ++ struct jtag_xfer pre_xfer, post_xfer; ++ struct jtag_xfer peri_xfer = { ++ .type = xfer->type, ++ .direction = xfer->direction, ++ .from = xfer->from, ++ .endstate = xfer->endstate, ++ .padding = 0, ++ .length = xfer->length, ++ }; ++ ++ padding.int_value = xfer->padding; ++ JTAG_DBUG( ++ "%s mode, type: %s direction: %d, END : %s, padding: (value: %d) pre_pad: %d post_pad: %d, len: %d\n", ++ aspeed_jtag->mode ? "HW" : "SW", xfer->type ? "DR" : "IR", ++ xfer->direction, end_status_str[xfer->endstate], ++ padding.pad_data, padding.pre_pad_number, ++ padding.post_pad_number, xfer->length); ++ if (padding.pre_pad_number) { ++ pre_xfer.type = xfer->type; ++ pre_xfer.direction = JTAG_WRITE_XFER; ++ pre_xfer.from = xfer->from; ++ pre_xfer.endstate = ++ xfer->type ? JTAG_STATE_PAUSEDR : JTAG_STATE_PAUSEIR; ++ pre_xfer.padding = xfer->padding; ++ pre_xfer.length = padding.pre_pad_number; ++ ++ peri_xfer.from = pre_xfer.endstate; ++ } ++ ++ if (padding.post_pad_number) { ++ peri_xfer.endstate = ++ xfer->type ? JTAG_STATE_PAUSEDR : JTAG_STATE_PAUSEIR; ++ ++ post_xfer.type = xfer->type; ++ post_xfer.direction = JTAG_WRITE_XFER; ++ post_xfer.from = peri_xfer.endstate; ++ post_xfer.endstate = xfer->endstate; ++ post_xfer.padding = xfer->padding; ++ post_xfer.length = padding.post_pad_number; ++ } ++ if (padding.pre_pad_number) { ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_hw_jtag_xfer(aspeed_jtag, &pre_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ else ++ aspeed_sw_jtag_xfer(aspeed_jtag, &pre_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ } ++ ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_hw_jtag_xfer(aspeed_jtag, &peri_xfer, xfer_data); ++ else ++ aspeed_sw_jtag_xfer(aspeed_jtag, &peri_xfer, xfer_data); ++ ++ if (padding.post_pad_number) { ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_hw_jtag_xfer(aspeed_jtag, &post_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ else ++ aspeed_sw_jtag_xfer(aspeed_jtag, &post_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ } ++ ++ return 0; ++} ++ ++static irqreturn_t aspeed_jtag_isr(int this_irq, void *dev_id) ++{ ++ u32 status; ++ struct aspeed_jtag_info *aspeed_jtag = dev_id; ++ ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++ ++ if (status & JTAG_INST_PAUSE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_PAUSE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_INST_PAUSE; ++ } ++ ++ if (status & JTAG_INST_COMPLETE) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_INST_COMPLETE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_INST_COMPLETE; ++ } ++ ++ if (status & JTAG_DATA_PAUSE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_PAUSE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_DATA_PAUSE; ++ } ++ ++ if (status & JTAG_DATA_COMPLETE) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_DATA_COMPLETE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_DATA_COMPLETE; ++ } ++ ++ if (aspeed_jtag->flag) { ++ wake_up_interruptible(&aspeed_jtag->jtag_wq); ++ return IRQ_HANDLED; ++ } ++ pr_err("TODO Check JTAG's interrupt %x\n", ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR)); ++ return IRQ_NONE; ++} ++ ++ ++static struct aspeed_jtag_config jtag_config = { ++ .jtag_version = 0, ++ .jtag_buff_len = 32, ++}; ++ ++static struct aspeed_jtag_config jtag_g6_config = { ++ .jtag_version = 6, ++ .jtag_buff_len = 32, ++}; ++ ++static const struct of_device_id aspeed_jtag_of_matches[] = { ++ { ++ .compatible = "aspeed,ast2400-jtag", ++ .data = &jtag_config, ++ }, ++ { ++ .compatible = "aspeed,ast2500-jtag", ++ .data = &jtag_config, ++ }, ++ { ++ .compatible = "aspeed,ast2600-jtag", ++ .data = &jtag_g6_config, ++ }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_jtag_of_matches); ++ ++static int aspeed_jtag_bitbang(struct jtag *jtag, ++ struct bitbang_packet *bitbang, ++ struct tck_bitbang *bitbang_data) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ int i = 0; ++ ++ for (i = 0; i < bitbang->length; i++) { ++ bitbang_data[i].tdo = ++ TCK_Cycle(aspeed_jtag, bitbang_data[i].tms, ++ bitbang_data[i].tdi); ++ } ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ ++ return 0; ++} ++ ++static inline void aspeed_jtag_xfer_mode_set(struct aspeed_jtag_info *aspeed_jtag, u32 mode) ++{ ++ if (mode == JTAG_XFER_HW_MODE) ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ aspeed_jtag->mode = mode; ++} ++ ++static int aspeed_jtag_mode_set(struct jtag *jtag, struct jtag_mode *jtag_mode) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ switch (jtag_mode->feature) { ++ case JTAG_XFER_MODE: ++ aspeed_jtag_xfer_mode_set(aspeed_jtag, jtag_mode->mode); ++ break; ++ case JTAG_CONTROL_MODE: ++ return -ENOTSUPP; ++ default: ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int aspeed_jtag_trst_set(struct jtag *jtag, u32 active) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ aspeed_jtag_write(aspeed_jtag, active ? 0 : JTAG_CTRL_TRSTn_HIGH, ++ ASPEED_JTAG_IDLE); ++ return 0; ++} ++ ++static int aspeed_jtag_enable(struct jtag *jtag) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ if (!IS_ERR(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 1); ++ return 0; ++} ++ ++static int aspeed_jtag_disable(struct jtag *jtag) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ if (!IS_ERR(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 0); ++ ++ return 0; ++} ++ ++static const struct jtag_ops aspeed_jtag_ops = { ++ .freq_get = aspeed_jtag_get_freq, ++ .freq_set = aspeed_jtag_set_freq, ++ .status_get = aspeed_jtag_status_get, ++ .status_set = aspeed_jtag_status_set, ++ .xfer = aspeed_jtag_xfer, ++ .mode_set = aspeed_jtag_mode_set, ++ .trst_set = aspeed_jtag_trst_set, ++ .bitbang = aspeed_jtag_bitbang, ++ .enable = aspeed_jtag_enable, ++ .disable = aspeed_jtag_disable, ++}; ++ ++static int aspeed_jtag_probe(struct platform_device *pdev) ++{ ++ struct aspeed_jtag_info *aspeed_jtag; ++ struct jtag *jtag; ++ const struct of_device_id *jtag_dev_id; ++ struct resource *res; ++ int ret = 0; ++ ++ jtag = jtag_alloc(&pdev->dev, sizeof(*aspeed_jtag), ++ &aspeed_jtag_ops); ++ if (!jtag) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, jtag); ++ aspeed_jtag = jtag_priv(jtag); ++ aspeed_jtag->dev = &pdev->dev; ++ ++ jtag_dev_id = of_match_device(aspeed_jtag_of_matches, &pdev->dev); ++ if (!jtag_dev_id) ++ return -EINVAL; ++ ++ aspeed_jtag->mux_gpiod = devm_gpiod_get_index(&pdev->dev, "mux", 0, GPIOD_OUT_LOW); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (res == NULL) { ++ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n"); ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ aspeed_jtag->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (!aspeed_jtag->reg_base) { ++ ret = -EIO; ++ goto out; ++ } ++ ++ aspeed_jtag->irq = platform_get_irq(pdev, 0); ++ if (aspeed_jtag->irq < 0) { ++ dev_err(&pdev->dev, "no irq specified\n"); ++ ret = -ENOENT; ++ goto out; ++ } ++ aspeed_jtag->reset = ++ devm_reset_control_get_exclusive(&pdev->dev, NULL); ++ if (IS_ERR(aspeed_jtag->reset)) { ++ dev_err(&pdev->dev, "can't get jtag reset\n"); ++ return PTR_ERR(aspeed_jtag->reset); ++ } ++ ++ aspeed_jtag->clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(aspeed_jtag->clk)) { ++ dev_err(&pdev->dev, "no clock defined\n"); ++ return -ENODEV; ++ } ++ ++ aspeed_jtag->clkin = clk_get_rate(aspeed_jtag->clk); ++ dev_dbg(&pdev->dev, "aspeed_jtag->clkin %d\n", aspeed_jtag->clkin); ++ ++ aspeed_jtag->config = (struct aspeed_jtag_config *)jtag_dev_id->data; ++ // SCU init ++ reset_control_assert(aspeed_jtag->reset); ++ udelay(3); ++ reset_control_deassert(aspeed_jtag->reset); ++ ++ ret = devm_request_irq(&pdev->dev, aspeed_jtag->irq, aspeed_jtag_isr, ++ 0, dev_name(&pdev->dev), aspeed_jtag); ++ if (ret) { ++ dev_dbg(&pdev->dev, "JTAG Unable to get IRQ"); ++ goto out; ++ } ++ ++ // clear interrupt ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_INST_PAUSE | JTAG_INST_COMPLETE | ++ JTAG_DATA_PAUSE | JTAG_DATA_COMPLETE, ++ ASPEED_JTAG_ISR); ++ ++ aspeed_jtag_xfer_mode_set(aspeed_jtag, JTAG_XFER_HW_MODE); ++ aspeed_jtag->flag = 0; ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ init_waitqueue_head(&aspeed_jtag->jtag_wq); ++ ++ aspeed_jtag_set_freq(jtag, TCK_FREQ); ++ /* Enable jtag clock */ ++ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_OUT_EN, ASPEED_JTAG_CTRL); ++ ++ /* Initialize JTAG core structure*/ ++ ret = devm_jtag_register(aspeed_jtag->dev, jtag); ++ if (ret) ++ goto out; ++ ++ memset(aspeed_jtag->pad_data_one, ~0, ++ sizeof(aspeed_jtag->pad_data_one)); ++ memset(aspeed_jtag->pad_data_zero, 0, ++ sizeof(aspeed_jtag->pad_data_zero)); ++ ++ dev_info(&pdev->dev, "aspeed_jtag: driver successfully loaded.\n"); ++ ++ return 0; ++ ++out: ++ reset_control_assert(aspeed_jtag->reset); ++ jtag_free(jtag); ++ dev_warn(&pdev->dev, "aspeed_jtag: driver init failed (ret=%d)!\n", ++ ret); ++ return ret; ++} ++ ++static void aspeed_jtag_remove(struct platform_device *pdev) ++{ ++ struct jtag *jtag = platform_get_drvdata(pdev); ++ struct aspeed_jtag_info *aspeed_jtag; ++ ++ aspeed_jtag = jtag_priv(jtag); ++ reset_control_assert(aspeed_jtag->reset); ++ jtag_free(jtag); ++} ++ ++static struct platform_driver aspeed_jtag_driver = { ++ .probe = aspeed_jtag_probe, ++ .remove = aspeed_jtag_remove, ++ .driver = { ++ .name = "aspeed-jtag", ++ .of_match_table = aspeed_jtag_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_jtag_driver); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_DESCRIPTION("AST JTAG LIB Driver"); ++MODULE_LICENSE("GPL"); diff --git a/patches-sonic/0006-platform-mellanox-mlxreg-io-use-sysfs_emit-instead-o.patch b/patches-sonic/0006-platform-mellanox-mlxreg-io-use-sysfs_emit-instead-o.patch new file mode 100644 index 000000000..ec8b073c5 --- /dev/null +++ b/patches-sonic/0006-platform-mellanox-mlxreg-io-use-sysfs_emit-instead-o.patch @@ -0,0 +1,38 @@ +From 2f6b30b57d84721ea1f31a22686b57106037a964 Mon Sep 17 00:00:00 2001 +From: Ai Chao +Date: Thu, 16 Jan 2025 16:11:29 +0800 +Subject: [PATCH 06/78] platform/mellanox: mlxreg-io: use sysfs_emit() instead + of sprintf() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Follow the advice in Documentation/filesystems/sysfs.rst: +show() should only use sysfs_emit() or sysfs_emit_at() when formatting +the value to be returned to user space. + +Signed-off-by: Ai Chao +Acked-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250116081129.2902274-1-aichao@kylinos.cn +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxreg-io.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/mellanox/mlxreg-io.c b/drivers/platform/mellanox/mlxreg-io.c +index ee7bd62..c502f2b 100644 +--- a/drivers/platform/mellanox/mlxreg-io.c ++++ b/drivers/platform/mellanox/mlxreg-io.c +@@ -126,7 +126,7 @@ mlxreg_io_attr_show(struct device *dev, struct device_attribute *attr, + + mutex_unlock(&priv->io_lock); + +- return sprintf(buf, "%u\n", regval); ++ return sysfs_emit(buf, "%u\n", regval); + + access_error: + mutex_unlock(&priv->io_lock); +-- +2.8.4 + diff --git a/patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch b/patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch new file mode 100644 index 000000000..d0a6e6ca8 --- /dev/null +++ b/patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch @@ -0,0 +1,46 @@ +From 7e6b94349b5fb51f0792b31cb186585f6466d8c0 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 23 Apr 2026 21:40:40 +0300 +Subject: [PATCH 6.12 1/1] JTAG: Aspeed: Fix kernel configuration + +The JTAG subsystem was not properly wired into the kernel build: +drivers/jtag/Kconfig was not sourced from the top-level drivers/Kconfig, +so its configuration options were not exposed in menuconfig, and +drivers/Makefile descended into drivers/jtag/ only when +CONFIG_JTAG_ASPEED was set instead of for the generic CONFIG_JTAG +option. + +Source drivers/jtag/Kconfig from drivers/Kconfig so the JTAG options +become selectable, and build drivers/jtag/ based on CONFIG_JTAG so the +subsystem is compiled whenever JTAG support is enabled. + +Signed-off-by: Vadim Pasternak +--- + drivers/Kconfig | 2 ++ + drivers/Makefile | 2 +- + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/Kconfig b/drivers/Kconfig +index 7bdad836f..c13e0a575 100644 +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -245,4 +245,6 @@ source "drivers/cdx/Kconfig" + + source "drivers/dpll/Kconfig" + ++source "drivers/jtag/Kconfig" ++ + endmenu +diff --git a/drivers/Makefile b/drivers/Makefile +index 47ae5636e..8e1dc078f 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -195,4 +195,4 @@ obj-$(CONFIG_CDX_BUS) += cdx/ + obj-$(CONFIG_DPLL) += dpll/ + + obj-$(CONFIG_S390) += s390/ +-obj-$(CONFIG_JTAG_ASPEED) += jtag/ ++obj-$(CONFIG_JTAG) += jtag/ +-- +2.34.1 + diff --git a/patches-sonic/0007-PCI-AER-Factor-COR-UNCOR-error-handling-out-from-aer.patch b/patches-sonic/0007-PCI-AER-Factor-COR-UNCOR-error-handling-out-from-aer.patch new file mode 100644 index 000000000..780e460b6 --- /dev/null +++ b/patches-sonic/0007-PCI-AER-Factor-COR-UNCOR-error-handling-out-from-aer.patch @@ -0,0 +1,104 @@ +From 10d088c074f57db7579af27da91cf0f040215b05 Mon Sep 17 00:00:00 2001 +From: Bjorn Helgaas +Date: Thu, 22 May 2025 18:21:09 -0500 +Subject: [PATCH 07/12] PCI/AER: Factor COR/UNCOR error handling out from + aer_isr_one_error() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +aer_isr_one_error() duplicates the Error Source ID logging and AER error +processing for Correctable Errors and Uncorrectable Errors. Factor out the +duplicated code to aer_isr_one_error_type(). + +aer_isr_one_error() doesn't need the struct aer_rpc pointer, so pass it the +Root Port or RCEC pci_dev pointer instead. + +Signed-off-by: Bjorn Helgaas +Reviewed-by: Ilpo Järvinen +Reviewed-by: Jonathan Cameron +Reviewed-by: Kuppuswamy Sathyanarayanan +Link: https://patch.msgid.link/20250522232339.1525671-4-helgaas@kernel.org +(cherry picked from commit 6fc4dae74afcf29ef82afbaaa9b082893871eda4) +--- + drivers/pci/pcie/aer.c | 36 +++++++++++++++++++++++------------- + 1 file changed, 23 insertions(+), 13 deletions(-) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 94cdf2e0d329..6ec628965cce 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -1283,17 +1283,32 @@ static inline void aer_process_err_devices(struct aer_err_info *e_info) + } + + /** +- * aer_isr_one_error - consume an error detected by root port +- * @rpc: pointer to the root port which holds an error ++ * aer_isr_one_error_type - consume a Correctable or Uncorrectable Error ++ * detected by Root Port or RCEC ++ * @root: pointer to Root Port or RCEC that signaled AER interrupt ++ * @info: pointer to AER error info ++ */ ++static void aer_isr_one_error_type(struct pci_dev *root, ++ struct aer_err_info *info) ++{ ++ aer_print_port_info(root, info); ++ ++ if (find_source_device(root, info)) ++ aer_process_err_devices(info); ++} ++ ++/** ++ * aer_isr_one_error - consume error(s) signaled by an AER interrupt from ++ * Root Port or RCEC ++ * @root: pointer to Root Port or RCEC that signaled AER interrupt + * @e_src: pointer to an error source + */ +-static void aer_isr_one_error(struct aer_rpc *rpc, ++static void aer_isr_one_error(struct pci_dev *root, + struct aer_err_source *e_src) + { +- struct pci_dev *pdev = rpc->rpd; + struct aer_err_info e_info; + +- pci_rootport_aer_stats_incr(pdev, e_src); ++ pci_rootport_aer_stats_incr(root, e_src); + + /* + * There is a possibility that both correctable error and +@@ -1308,10 +1323,8 @@ static void aer_isr_one_error(struct aer_rpc *rpc, + e_info.multi_error_valid = 1; + else + e_info.multi_error_valid = 0; +- aer_print_port_info(pdev, &e_info); + +- if (find_source_device(pdev, &e_info)) +- aer_process_err_devices(&e_info); ++ aer_isr_one_error_type(root, &e_info); + } + + if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) { +@@ -1328,10 +1341,7 @@ static void aer_isr_one_error(struct aer_rpc *rpc, + else + e_info.multi_error_valid = 0; + +- aer_print_port_info(pdev, &e_info); +- +- if (find_source_device(pdev, &e_info)) +- aer_process_err_devices(&e_info); ++ aer_isr_one_error_type(root, &e_info); + } + } + +@@ -1352,7 +1362,7 @@ static irqreturn_t aer_isr(int irq, void *context) + return IRQ_NONE; + + while (kfifo_get(&rpc->aer_fifo, &e_src)) +- aer_isr_one_error(rpc, &e_src); ++ aer_isr_one_error(rpc->rpd, &e_src); + return IRQ_HANDLED; + } + +-- +2.47.0 + diff --git a/patches-sonic/0007-drivers-reset-Add-emmc-hardware-reset.patch b/patches-sonic/0007-drivers-reset-Add-emmc-hardware-reset.patch new file mode 100644 index 000000000..db21d5d73 --- /dev/null +++ b/patches-sonic/0007-drivers-reset-Add-emmc-hardware-reset.patch @@ -0,0 +1,1390 @@ +From bc2035e4e90ad8a5c4bc1410a34538c652daae7c Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 09:24:20 +0000 +Subject: [PATCH 07/27] drivers/reset: Add emmc hardware reset + +Add multi-function driver with sub-device reset driver to +execute emmc hardware reset. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + .../bindings/mfd/amd,pensando-elbasr.yaml | 93 +++ + .../bindings/reset/amd,pensando-elbasr-reset.yaml | 62 ++ + arch/arm64/boot/dts/pensando/elba-asic-common.dtsi | 30 + + drivers/mfd/Kconfig | 14 + + drivers/mfd/Makefile | 1 + + drivers/mfd/pensando-elbasr.c | 880 +++++++++++++++++++++ + drivers/reset/Kconfig | 9 + + drivers/reset/Makefile | 1 + + drivers/reset/reset-elbasr.c | 106 +++ + .../dt-bindings/reset/amd,pensando-elba-reset.h | 11 + + include/linux/mfd/pensando-elbasr.h | 41 + + 11 files changed, 1248 insertions(+) + create mode 100644 Documentation/devicetree/bindings/mfd/amd,pensando-elbasr.yaml + create mode 100644 Documentation/devicetree/bindings/reset/amd,pensando-elbasr-reset.yaml + create mode 100644 drivers/mfd/pensando-elbasr.c + create mode 100644 drivers/reset/reset-elbasr.c + create mode 100644 include/dt-bindings/reset/amd,pensando-elba-reset.h + create mode 100644 include/linux/mfd/pensando-elbasr.h + +diff --git a/Documentation/devicetree/bindings/mfd/amd,pensando-elbasr.yaml b/Documentation/devicetree/bindings/mfd/amd,pensando-elbasr.yaml +new file mode 100644 +index 0000000..1335680 +--- /dev/null ++++ b/Documentation/devicetree/bindings/mfd/amd,pensando-elbasr.yaml +@@ -0,0 +1,93 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/mfd/amd,pensando-elbasr.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: AMD Pensando Elba SoC Resource Controller bindings ++ ++description: | ++ AMD Pensando Elba SoC Resource Controller bindings attached to a SPI bus. ++ ++maintainers: ++ - Brad Larson ++ ++properties: ++ compatible: ++ items: ++ - enum: ++ - amd,pensando-elbasr ++ - const: simple-mfd ++ ++ spi-max-frequency: ++ description: Maximum SPI frequency of the device in Hz. ++ ++ reg: ++ maxItems: 1 ++ ++ '#address-cells': ++ const: 1 ++ ++ '#size-cells': ++ const: 0 ++ ++ interrupts: ++ maxItems: 1 ++ ++required: ++ - compatible ++ - reg ++ - spi-max-frequency ++ ++patternProperties: ++ '^reset-controller@[a-f0-9]+$': ++ $ref: ../reset/amd,pensando-elbasr-reset.yaml ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ ++ spi0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ num-cs = <4>; ++ ++ spi@0 { ++ compatible = "amd,pensando-elbasr", "simple-mfd"; ++ reg = <0>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ spi-max-frequency = <12000000>; ++ ++ rstc: reset-controller@0 { ++ compatible = "amd,pensando-elbasr-reset"; ++ reg = <0>; ++ #reset-cells = <1>; ++ }; ++ }; ++ ++ spi@1 { ++ compatible = "amd,pensando-elbasr", "simple-mfd"; ++ reg = <1>; ++ spi-max-frequency = <12000000>; ++ }; ++ ++ spi@2 { ++ compatible = "amd,pensando-elbasr", "simple-mfd"; ++ reg = <2>; ++ spi-max-frequency = <12000000>; ++ interrupt-parent = <&porta>; ++ interrupts = <0 IRQ_TYPE_LEVEL_LOW>; ++ }; ++ ++ spi@3 { ++ compatible = "amd,pensando-elbasr", "simple-mfd"; ++ reg = <3>; ++ spi-max-frequency = <12000000>; ++ }; ++ }; ++ ++... +diff --git a/Documentation/devicetree/bindings/reset/amd,pensando-elbasr-reset.yaml b/Documentation/devicetree/bindings/reset/amd,pensando-elbasr-reset.yaml +new file mode 100644 +index 0000000..03bb86e +--- /dev/null ++++ b/Documentation/devicetree/bindings/reset/amd,pensando-elbasr-reset.yaml +@@ -0,0 +1,62 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/reset/amd,pensando-elbasr-reset.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: AMD Pensando Elba SoC Reset Controller Device Tree Bindings ++ ++maintainers: ++ - Brad Larson ++ ++description: | ++ AMD Pensando Elba SoC reset controller driver which supports a resource ++ controller connected to the Elba SoC over a SPI bus. The Elba reset ++ controller must be defined as a child node of the Elba SPI bus ++ chip-select 0 node. ++ ++ See also: ++ - dt-bindings/reset/amd,pensando-elba-reset.h ++ ++properties: ++ $nodename: ++ pattern: "^reset-controller@[0-9a-f]+$" ++ ++ compatible: ++ const: amd,pensando-elbasr-reset ++ ++ reg: ++ const: 0 ++ ++ '#reset-cells': ++ const: 1 ++ ++required: ++ - compatible ++ - reg ++ - '#reset-cells' ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ spi0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ num-cs = <4>; ++ ++ spi@0 { ++ reg = <0>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ rstc: reset-controller@0 { ++ compatible = "amd,pensando-elbasr-reset"; ++ reg = <0>; ++ #reset-cells = <1>; ++ }; ++ }; ++ }; ++ ++... +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi b/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi +index 81057f1..b41ca86 100644 +--- a/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0 + ++#include ++ + &ahb_clk { + clock-frequency = <400000000>; + }; +@@ -38,6 +40,9 @@ + + &emmc { + bus-width = <8>; ++ cap-mmc-hw-reset; ++ reset-names = "hw"; ++ resets = <&rstc EMMC_HW_RESET>; + status = "ok"; + }; + +@@ -69,6 +74,25 @@ + cs-gpios = <0>, <0>, + <&porta 1 GPIO_ACTIVE_LOW>, <&porta 7 GPIO_ACTIVE_LOW>; + status = "okay"; ++ spi0_cs0@0 { ++ compatible = "amd,pensando-elbasr", "simple-mfd"; ++ reg = <0>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ spi-max-frequency = <12000000>; ++ ++ rstc: reset-controller@0 { ++ compatible = "amd,pensando-elbasr-reset"; ++ reg = <0>; ++ #reset-cells = <1>; ++ }; ++ }; ++ ++ spi0_cs1@1 { ++ compatible = "amd,pensando-elbasr", "simple-mfd"; ++ reg = <1>; ++ spi-max-frequency = <12000000>; ++ }; + + spi0_cs2@2 { + compatible = "pensando,cpld-rd1173"; +@@ -77,4 +101,10 @@ + interrupt-parent = <&porta>; + interrupts = <0 IRQ_TYPE_LEVEL_LOW>; + }; ++ ++ spi0_cs3@3 { ++ compatible = "amd,pensando-elbasr", "simple-mfd"; ++ reg = <3>; ++ spi-max-frequency = <12000000>; ++ }; + }; +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index f9325bc..356b1ac 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -1131,6 +1131,20 @@ config PCF50633_GPIO + Say yes here if you want to include support GPIO for pins on + the PCF50633 chip. + ++config MFD_PENSANDO_ELBASR ++ bool "AMD Pensando Elba System Resource chip" ++ depends on SPI_MASTER=y ++ depends on (ARCH_PENSANDO && OF) || COMPILE_TEST ++ select REGMAP_SPI ++ select MFD_CORE ++ select MFD_SYSCON ++ help ++ Support for the AMD Pensando Elba SoC System Resource chip using the ++ SPI interface. This driver provides userspace access to four device ++ functions to include CS0 device registers, CS1 smbus interface for ++ FRU and board peripherals, CS2 dual Lattice I2C masters for ++ transceiver management, and CS3 flash for firmware update. ++ + config MFD_PM8XXX + tristate "Qualcomm PM8xxx PMIC chips driver" + depends on ARM || HEXAGON || COMPILE_TEST +diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile +index 2a9f91e..ee9d180 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -224,6 +224,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o + obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o + obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o + obj-$(CONFIG_MFD_PALMAS) += palmas.o ++obj-$(CONFIG_MFD_PENSANDO_ELBASR) += pensando-elbasr.o + obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o + obj-$(CONFIG_MFD_NTXEC) += ntxec.o + obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o +diff --git a/drivers/mfd/pensando-elbasr.c b/drivers/mfd/pensando-elbasr.c +new file mode 100644 +index 0000000..868cb98 +--- /dev/null ++++ b/drivers/mfd/pensando-elbasr.c +@@ -0,0 +1,880 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * AMD Pensando Elba System Resource MFD Driver ++ * ++ * Userspace interface and reset driver support for SPI connected ++ * Pensando Elba System Resource Chip. ++ * ++ * Adapted from spidev.c ++ * ++ * Copyright (C) 2006 SWAPP ++ * Andrea Paterniani ++ * Copyright (C) 2007 David Brownell (simplification, cleanup) ++ * Copyright (C) 2022 Advanced Micro Devices, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ELBASR_SPI_CMD_REGRD 0x0b ++#define ELBASR_SPI_CMD_REGWR 0x02 ++#define ELBASR_MAX_DEVS 4 ++ ++/* The main reason to have this class is to make mdev/udev create the ++ * /dev/spidevB.C character device nodes exposing our userspace API. ++ * It also simplifies memory management. The device nodes ++ * /dev/spidevB.C are used for backward compatibility. ++ */ ++static struct class *elbasr_class; ++ ++static dev_t elbasr_devt; ++static DECLARE_BITMAP(minors, ELBASR_MAX_DEVS); ++static unsigned int bufsiz = 4096; ++ ++static LIST_HEAD(device_list); ++static DEFINE_MUTEX(device_list_lock); ++ ++static const struct mfd_cell pensando_elbasr_subdev_info[] = { ++ { ++ .name = "pensando_elbasr_reset", ++ .of_compatible = "amd,pensando-elbasr-reset", ++ }, ++}; ++ ++/* Bit masks for spi_device.mode management. Note that incorrect ++ * settings for some settings can cause *lots* of trouble for other ++ * devices on a shared bus: ++ * ++ * - CS_HIGH ... this device will be active when it shouldn't be ++ * - 3WIRE ... when active, it won't behave as it should ++ * - NO_CS ... there will be no explicit message boundaries; this ++ * is completely incompatible with the shared bus model ++ * - READY ... transfers may proceed when they shouldn't. ++ */ ++#define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \ ++ | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ ++ | SPI_NO_CS | SPI_READY | SPI_TX_DUAL \ ++ | SPI_TX_QUAD | SPI_TX_OCTAL | SPI_RX_DUAL \ ++ | SPI_RX_QUAD | SPI_RX_OCTAL) ++ ++static ssize_t ++elbasr_spi_sync(struct elbasr_data *elbasr_spi, struct spi_message *message) ++{ ++ int status; ++ struct spi_device *spi; ++ ++ spin_lock_irq(&elbasr_spi->spi_lock); ++ spi = elbasr_spi->spi; ++ spin_unlock_irq(&elbasr_spi->spi_lock); ++ ++ if (spi == NULL) ++ status = -ESHUTDOWN; ++ else ++ status = spi_sync(spi, message); ++ ++ if (status == 0) ++ status = message->actual_length; ++ ++ return status; ++} ++ ++static inline ssize_t ++elbasr_spi_sync_write(struct elbasr_data *elbasr, size_t len) ++{ ++ struct spi_transfer t = { ++ .tx_buf = elbasr->tx_buffer, ++ .len = len, ++ .speed_hz = elbasr->speed_hz, ++ }; ++ struct spi_message m; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ return elbasr_spi_sync(elbasr, &m); ++} ++ ++static inline ssize_t ++elbasr_spi_sync_read(struct elbasr_data *elbasr, size_t len) ++{ ++ struct spi_transfer t = { ++ .rx_buf = elbasr->rx_buffer, ++ .len = len, ++ .speed_hz = elbasr->speed_hz, ++ }; ++ struct spi_message m; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ return elbasr_spi_sync(elbasr, &m); ++} ++ ++/* Read-only message with current device setup */ ++static ssize_t ++elbasr_spi_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) ++{ ++ struct elbasr_data *elbasr; ++ ssize_t status; ++ ++ /* chipselect only toggles at start or end of operation */ ++ if (count > bufsiz) ++ return -EMSGSIZE; ++ ++ elbasr = filp->private_data; ++ ++ mutex_lock(&elbasr->buf_lock); ++ status = elbasr_spi_sync_read(elbasr, count); ++ if (status > 0) { ++ unsigned long missing; ++ ++ missing = copy_to_user(buf, elbasr->rx_buffer, status); ++ if (missing == status) ++ status = -EFAULT; ++ else ++ status = status - missing; ++ } ++ mutex_unlock(&elbasr->buf_lock); ++ ++ return status; ++} ++ ++/* Write-only message with current device setup */ ++static ssize_t ++elbasr_spi_write(struct file *filp, const char __user *buf, ++ size_t count, loff_t *f_pos) ++{ ++ struct elbasr_data *elbasr; ++ ssize_t status; ++ unsigned long missing; ++ ++ /* chipselect only toggles at start or end of operation */ ++ if (count > bufsiz) ++ return -EMSGSIZE; ++ ++ elbasr = filp->private_data; ++ ++ mutex_lock(&elbasr->buf_lock); ++ missing = copy_from_user(elbasr->tx_buffer, buf, count); ++ if (missing == 0) ++ status = elbasr_spi_sync_write(elbasr, count); ++ else ++ status = -EFAULT; ++ mutex_unlock(&elbasr->buf_lock); ++ ++ return status; ++} ++ ++static int elbasr_spi_message(struct elbasr_data *elbasr, ++ struct spi_ioc_transfer *u_xfers, ++ unsigned int n_xfers) ++{ ++ struct spi_message msg; ++ struct spi_transfer *k_xfers; ++ struct spi_transfer *k_tmp; ++ struct spi_ioc_transfer *u_tmp; ++ unsigned int n, total, tx_total, rx_total; ++ u8 *tx_buf, *rx_buf; ++ int status = -EFAULT; ++ ++ spi_message_init(&msg); ++ k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); ++ if (k_xfers == NULL) ++ return -ENOMEM; ++ ++ /* Construct spi_message, copying any tx data to bounce buffer. ++ * We walk the array of user-provided transfers, using each one ++ * to initialize a kernel version of the same transfer. ++ */ ++ tx_buf = elbasr->tx_buffer; ++ rx_buf = elbasr->rx_buffer; ++ total = 0; ++ tx_total = 0; ++ rx_total = 0; ++ for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; ++ n; ++ n--, k_tmp++, u_tmp++) { ++ /* Ensure that also following allocations from rx_buf/tx_buf will meet ++ * DMA alignment requirements. ++ */ ++ unsigned int len_aligned = ALIGN(u_tmp->len, ++ ARCH_KMALLOC_MINALIGN); ++ ++ k_tmp->len = u_tmp->len; ++ ++ total += k_tmp->len; ++ /* Since the function returns the total length of transfers ++ * on success, restrict the total to positive int values to ++ * avoid the return value looking like an error. Also check ++ * each transfer length to avoid arithmetic overflow. ++ */ ++ if (total > INT_MAX || k_tmp->len > INT_MAX) { ++ status = -EMSGSIZE; ++ goto done; ++ } ++ ++ if (u_tmp->rx_buf) { ++ /* this transfer needs space in RX bounce buffer */ ++ rx_total += len_aligned; ++ if (rx_total > bufsiz) { ++ status = -EMSGSIZE; ++ goto done; ++ } ++ k_tmp->rx_buf = rx_buf; ++ rx_buf += len_aligned; ++ } ++ if (u_tmp->tx_buf) { ++ /* this transfer needs space in TX bounce buffer */ ++ tx_total += len_aligned; ++ if (tx_total > bufsiz) { ++ status = -EMSGSIZE; ++ goto done; ++ } ++ k_tmp->tx_buf = tx_buf; ++ if (copy_from_user(tx_buf, (const u8 __user *) ++ (uintptr_t) u_tmp->tx_buf, ++ u_tmp->len)) ++ goto done; ++ tx_buf += len_aligned; ++ } ++ ++ k_tmp->cs_change = !!u_tmp->cs_change; ++ k_tmp->tx_nbits = u_tmp->tx_nbits; ++ k_tmp->rx_nbits = u_tmp->rx_nbits; ++ k_tmp->bits_per_word = u_tmp->bits_per_word; ++ k_tmp->delay.value = u_tmp->delay_usecs; ++ k_tmp->delay.unit = SPI_DELAY_UNIT_USECS; ++ k_tmp->speed_hz = u_tmp->speed_hz; ++ k_tmp->word_delay.value = u_tmp->word_delay_usecs; ++ k_tmp->word_delay.unit = SPI_DELAY_UNIT_USECS; ++ if (!k_tmp->speed_hz) ++ k_tmp->speed_hz = elbasr->speed_hz; ++#ifdef VERBOSE ++ dev_dbg(&elbasr->spi->dev, ++ " xfer len %u %s%s%s%dbits %u usec %u usec %uHz (%u)\n", ++ k_tmp->len, ++ k_tmp->rx_buf ? "rx " : "", ++ k_tmp->tx_buf ? "tx " : "", ++ k_tmp->cs_change ? "cs " : "", ++ k_tmp->bits_per_word ? : elbasr->spi->bits_per_word, ++ k_tmp->delay.value, ++ k_tmp->word_delay.value, ++ k_tmp->speed_hz ? : elbasr->spi->max_speed_hz); ++#endif ++ spi_message_add_tail(k_tmp, &msg); ++ } ++ ++ status = elbasr_spi_sync(elbasr, &msg); ++ if (status < 0) ++ goto done; ++ ++ /* copy any rx data out of bounce buffer */ ++ for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; ++ n; ++ n--, k_tmp++, u_tmp++) { ++ if (u_tmp->rx_buf) { ++ if (copy_to_user((u8 __user *) ++ (uintptr_t) u_tmp->rx_buf, k_tmp->rx_buf, ++ u_tmp->len)) { ++ status = -EFAULT; ++ goto done; ++ } ++ } ++ } ++ status = total; ++ ++done: ++ kfree(k_xfers); ++ return status; ++} ++ ++static struct spi_ioc_transfer * ++elbasr_spi_get_ioc_message(unsigned int cmd, ++ struct spi_ioc_transfer __user *u_ioc, ++ unsigned int *n_ioc) ++{ ++ u32 tmp; ++ ++ /* Check type, command number and direction */ ++ if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC ++ || _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) ++ || _IOC_DIR(cmd) != _IOC_WRITE) ++ return ERR_PTR(-ENOTTY); ++ ++ tmp = _IOC_SIZE(cmd); ++ if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) ++ return ERR_PTR(-EINVAL); ++ *n_ioc = tmp / sizeof(struct spi_ioc_transfer); ++ if (*n_ioc == 0) ++ return NULL; ++ ++ /* copy into scratch area */ ++ return memdup_user(u_ioc, tmp); ++} ++ ++static long ++elbasr_spi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ int retval = 0; ++ struct elbasr_data *elbasr; ++ struct spi_device *spi; ++ u32 tmp; ++ unsigned int n_ioc; ++ struct spi_ioc_transfer *ioc; ++ ++ /* Check type and command number */ ++ if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) ++ return -ENOTTY; ++ ++ /* guard against device removal before, or while, ++ * we issue this ioctl. ++ */ ++ elbasr = filp->private_data; ++ spin_lock_irq(&elbasr->spi_lock); ++ spi = spi_dev_get(elbasr->spi); ++ spin_unlock_irq(&elbasr->spi_lock); ++ ++ if (spi == NULL) ++ return -ESHUTDOWN; ++ ++ /* use the buffer lock here for triple duty: ++ * - prevent I/O (from us) so calling spi_setup() is safe; ++ * - prevent concurrent SPI_IOC_WR_* from morphing ++ * data fields while SPI_IOC_RD_* reads them; ++ * - SPI_IOC_MESSAGE needs the buffer locked "normally". ++ */ ++ mutex_lock(&elbasr->buf_lock); ++ ++ switch (cmd) { ++ /* read requests */ ++ case SPI_IOC_RD_MODE: ++ retval = put_user(spi->mode & SPI_MODE_MASK, ++ (__u8 __user *)arg); ++ break; ++ case SPI_IOC_RD_MODE32: ++ retval = put_user(spi->mode & SPI_MODE_MASK, ++ (__u32 __user *)arg); ++ break; ++ case SPI_IOC_RD_LSB_FIRST: ++ retval = put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, ++ (__u8 __user *)arg); ++ break; ++ case SPI_IOC_RD_BITS_PER_WORD: ++ retval = put_user(spi->bits_per_word, (__u8 __user *)arg); ++ break; ++ case SPI_IOC_RD_MAX_SPEED_HZ: ++ retval = put_user(elbasr->speed_hz, (__u32 __user *)arg); ++ break; ++ ++ /* write requests */ ++ case SPI_IOC_WR_MODE: ++ case SPI_IOC_WR_MODE32: ++ if (cmd == SPI_IOC_WR_MODE) ++ retval = get_user(tmp, (u8 __user *)arg); ++ else ++ retval = get_user(tmp, (u32 __user *)arg); ++ if (retval == 0) { ++ struct spi_controller *ctlr = spi->controller; ++ u32 save = spi->mode; ++ ++ if (tmp & ~SPI_MODE_MASK) { ++ retval = -EINVAL; ++ break; ++ } ++ ++ unsigned int cs = spi_get_chipselect(spi, 0); ++ ++ if (ctlr->use_gpio_descriptors && ++ ctlr->cs_gpiods && ++ cs < ctlr->num_chipselect && ++ ctlr->cs_gpiods[cs]) ++ tmp |= SPI_CS_HIGH; ++ ++ tmp |= spi->mode & ~SPI_MODE_MASK; ++ spi->mode = (u16)tmp; ++ retval = spi_setup(spi); ++ if (retval < 0) ++ spi->mode = save; ++ else ++ dev_dbg(&spi->dev, "spi mode %x\n", tmp); ++ } ++ break; ++ case SPI_IOC_WR_LSB_FIRST: ++ retval = get_user(tmp, (__u8 __user *)arg); ++ if (retval == 0) { ++ u32 save = spi->mode; ++ ++ if (tmp) ++ spi->mode |= SPI_LSB_FIRST; ++ else ++ spi->mode &= ~SPI_LSB_FIRST; ++ retval = spi_setup(spi); ++ if (retval < 0) ++ spi->mode = save; ++ else ++ dev_dbg(&spi->dev, "%csb first\n", ++ tmp ? 'l' : 'm'); ++ } ++ break; ++ case SPI_IOC_WR_BITS_PER_WORD: ++ retval = get_user(tmp, (__u8 __user *)arg); ++ if (retval == 0) { ++ u8 save = spi->bits_per_word; ++ ++ spi->bits_per_word = tmp; ++ retval = spi_setup(spi); ++ if (retval < 0) ++ spi->bits_per_word = save; ++ else ++ dev_dbg(&spi->dev, "%d bits per word\n", tmp); ++ } ++ break; ++ case SPI_IOC_WR_MAX_SPEED_HZ: ++ retval = get_user(tmp, (__u32 __user *)arg); ++ if (retval == 0) { ++ u32 save = spi->max_speed_hz; ++ ++ spi->max_speed_hz = tmp; ++ retval = spi_setup(spi); ++ if (retval == 0) { ++ elbasr->speed_hz = tmp; ++ dev_dbg(&spi->dev, "%d Hz (max)\n", ++ elbasr->speed_hz); ++ } ++ spi->max_speed_hz = save; ++ } ++ break; ++ ++ default: ++ /* segmented and/or full-duplex I/O request */ ++ /* Check message and copy into scratch area */ ++ ioc = elbasr_spi_get_ioc_message(cmd, ++ (struct spi_ioc_transfer __user *)arg, &n_ioc); ++ if (IS_ERR(ioc)) { ++ retval = PTR_ERR(ioc); ++ break; ++ } ++ if (!ioc) ++ break; /* n_ioc is also 0 */ ++ ++ /* translate to spi_message, execute */ ++ retval = elbasr_spi_message(elbasr, ioc, n_ioc); ++ kfree(ioc); ++ break; ++ } ++ ++ mutex_unlock(&elbasr->buf_lock); ++ spi_dev_put(spi); ++ return retval; ++} ++ ++#ifdef CONFIG_COMPAT ++static long ++elbasr_spi_compat_ioc_message(struct file *filp, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct spi_ioc_transfer __user *u_ioc; ++ int retval = 0; ++ struct elbasr_data *elbasr; ++ struct spi_device *spi; ++ unsigned int n_ioc, n; ++ struct spi_ioc_transfer *ioc; ++ ++ u_ioc = (struct spi_ioc_transfer __user *) compat_ptr(arg); ++ ++ /* guard against device removal before, or while, ++ * we issue this ioctl. ++ */ ++ elbasr = filp->private_data; ++ spin_lock_irq(&elbasr->spi_lock); ++ spi = spi_dev_get(elbasr->spi); ++ spin_unlock_irq(&elbasr->spi_lock); ++ ++ if (spi == NULL) ++ return -ESHUTDOWN; ++ ++ /* SPI_IOC_MESSAGE needs the buffer locked "normally" */ ++ mutex_lock(&elbasr->buf_lock); ++ ++ /* Check message and copy into scratch area */ ++ ioc = elbasr_spi_get_ioc_message(cmd, u_ioc, &n_ioc); ++ if (IS_ERR(ioc)) { ++ retval = PTR_ERR(ioc); ++ goto done; ++ } ++ if (!ioc) ++ goto done; /* n_ioc is also 0 */ ++ ++ /* Convert buffer pointers */ ++ for (n = 0; n < n_ioc; n++) { ++ ioc[n].rx_buf = (uintptr_t) compat_ptr(ioc[n].rx_buf); ++ ioc[n].tx_buf = (uintptr_t) compat_ptr(ioc[n].tx_buf); ++ } ++ ++ /* translate to spi_message, execute */ ++ retval = elbasr_spi_message(elbasr, ioc, n_ioc); ++ kfree(ioc); ++ ++done: ++ mutex_unlock(&elbasr->buf_lock); ++ spi_dev_put(spi); ++ return retval; ++} ++ ++static long ++elbasr_spi_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ if (_IOC_TYPE(cmd) == SPI_IOC_MAGIC ++ && _IOC_NR(cmd) == _IOC_NR(SPI_IOC_MESSAGE(0)) ++ && _IOC_DIR(cmd) == _IOC_WRITE) ++ return elbasr_spi_compat_ioc_message(filp, cmd, arg); ++ ++ return elbasr_spi_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); ++} ++#else ++#define elbasr_spi_compat_ioctl NULL ++#endif /* CONFIG_COMPAT */ ++ ++static int elbasr_spi_open(struct inode *inode, struct file *filp) ++{ ++ struct elbasr_data *elbasr; ++ int status = -ENXIO; ++ ++ mutex_lock(&device_list_lock); ++ ++ list_for_each_entry(elbasr, &device_list, device_entry) { ++ if (elbasr->devt == inode->i_rdev) { ++ status = 0; ++ break; ++ } ++ } ++ ++ if (status) { ++ pr_debug("elbasr_spi: nothing for minor %d\n", iminor(inode)); ++ goto err_find_dev; ++ } ++ ++ if (!elbasr->tx_buffer) { ++ elbasr->tx_buffer = kmalloc(bufsiz, GFP_KERNEL); ++ if (!elbasr->tx_buffer) { ++ status = -ENOMEM; ++ goto err_find_dev; ++ } ++ } ++ ++ if (!elbasr->rx_buffer) { ++ elbasr->rx_buffer = kmalloc(bufsiz, GFP_KERNEL); ++ if (!elbasr->rx_buffer) { ++ status = -ENOMEM; ++ goto err_alloc_rx_buf; ++ } ++ } ++ ++ elbasr->users++; ++ filp->private_data = elbasr; ++ stream_open(inode, filp); ++ ++ mutex_unlock(&device_list_lock); ++ return 0; ++ ++err_alloc_rx_buf: ++ kfree(elbasr->tx_buffer); ++ elbasr->tx_buffer = NULL; ++err_find_dev: ++ mutex_unlock(&device_list_lock); ++ return status; ++} ++ ++static int elbasr_spi_release(struct inode *inode, struct file *filp) ++{ ++ struct elbasr_data *elbasr; ++ int dofree; ++ ++ mutex_lock(&device_list_lock); ++ elbasr = filp->private_data; ++ filp->private_data = NULL; ++ ++ spin_lock_irq(&elbasr->spi_lock); ++ /* ... after we unbound from the underlying device? */ ++ dofree = (elbasr->spi == NULL); ++ spin_unlock_irq(&elbasr->spi_lock); ++ ++ /* last close? */ ++ elbasr->users--; ++ if (!elbasr->users) { ++ ++ kfree(elbasr->tx_buffer); ++ elbasr->tx_buffer = NULL; ++ ++ kfree(elbasr->rx_buffer); ++ elbasr->rx_buffer = NULL; ++ ++ if (dofree) ++ kfree(elbasr); ++ else ++ elbasr->speed_hz = elbasr->spi->max_speed_hz; ++ } ++#ifdef CONFIG_SPI_SLAVE ++ if (!dofree) ++ spi_slave_abort(elbasr->spi); ++#endif ++ mutex_unlock(&device_list_lock); ++ ++ return 0; ++} ++ ++static const struct file_operations elbasr_spi_fops = { ++ .owner = THIS_MODULE, ++ .write = elbasr_spi_write, ++ .read = elbasr_spi_read, ++ .unlocked_ioctl = elbasr_spi_ioctl, ++ .compat_ioctl = elbasr_spi_compat_ioctl, ++ .open = elbasr_spi_open, ++ .release = elbasr_spi_release, ++ .llseek = noop_llseek, ++}; ++ ++static bool ++elbasr_reg_readable(struct device *dev, unsigned int reg) ++{ ++ return reg <= ELBASR_MAX_REG; ++} ++ ++static bool ++elbasr_reg_writeable(struct device *dev, unsigned int reg) ++{ ++ return reg <= ELBASR_MAX_REG; ++} ++ ++static int ++elbasr_regs_read(void *ctx, u32 reg, u32 *val) ++{ ++ struct elbasr_data *elbasr = dev_get_drvdata(ctx); ++ struct spi_message m; ++ struct spi_transfer t[2] = { { 0 } }; ++ int ret; ++ u8 txbuf[3]; ++ u8 rxbuf[1]; ++ ++ spi_message_init(&m); ++ ++ txbuf[0] = ELBASR_SPI_CMD_REGRD; ++ txbuf[1] = reg; ++ txbuf[2] = 0x0; ++ t[0].tx_buf = (u8 *)txbuf; ++ t[0].len = 3; ++ ++ rxbuf[0] = 0x0; ++ t[1].rx_buf = rxbuf; ++ t[1].len = 1; ++ ++ spi_message_add_tail(&t[0], &m); ++ spi_message_add_tail(&t[1], &m); ++ ++ ret = elbasr_spi_sync(elbasr, &m); ++ if (ret == 4) { ++ // 3 Tx + 1 Rx = 4 ++ *val = rxbuf[0]; ++ return 0; ++ } ++ return -EIO; ++} ++ ++static int ++elbasr_regs_write(void *ctx, u32 reg, u32 val) ++{ ++ struct elbasr_data *elbasr = dev_get_drvdata(ctx); ++ struct spi_message m; ++ struct spi_transfer t[1] = { { 0 } }; ++ u8 txbuf[4]; ++ ++ spi_message_init(&m); ++ txbuf[0] = ELBASR_SPI_CMD_REGWR; ++ txbuf[1] = reg; ++ txbuf[2] = val; ++ txbuf[3] = 0; ++ ++ t[0].tx_buf = txbuf; ++ t[0].len = 4; ++ ++ spi_message_add_tail(&t[0], &m); ++ ++ return elbasr_spi_sync(elbasr, &m); ++} ++ ++static const struct regmap_config pensando_elbasr_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .cache_type = REGCACHE_NONE, ++ .readable_reg = elbasr_reg_readable, ++ .writeable_reg = elbasr_reg_writeable, ++ .reg_read = elbasr_regs_read, ++ .reg_write = elbasr_regs_write, ++ .max_register = ELBASR_MAX_REG ++}; ++ ++/* ++ * Setup Elba SPI access to System Resource Chip registers on CS0 ++ */ ++static int ++elbasr_regs_setup(struct spi_device *spi, struct elbasr_data *elbasr) ++{ ++ int ret; ++ ++ spi->bits_per_word = 8; ++ spi_setup(spi); ++ spi->dev.dma_mask = &spi->dev.coherent_dma_mask; ++ elbasr->elbasr_regs = devm_regmap_init(&spi->dev, NULL, spi, ++ &pensando_elbasr_regmap_config); ++ if (IS_ERR(elbasr->elbasr_regs)) { ++ ret = PTR_ERR(elbasr->elbasr_regs); ++ dev_err(&spi->dev, "Failed to allocate register map: %d\n", ret); ++ return ret; ++ } ++ ++ ret = devm_mfd_add_devices(&spi->dev, PLATFORM_DEVID_NONE, ++ pensando_elbasr_subdev_info, ++ ARRAY_SIZE(pensando_elbasr_subdev_info), ++ NULL, 0, NULL); ++ if (ret) ++ dev_err(&spi->dev, "Failed to register sub-devices: %d\n", ret); ++ ++ return ret; ++} ++ ++static int elbasr_spi_probe(struct spi_device *spi) ++{ ++ struct elbasr_data *elbasr; ++ unsigned long minor; ++ int status; ++ ++ if (spi_get_chipselect(spi, 0) == 0) { ++ status = alloc_chrdev_region(&elbasr_devt, 0, ELBASR_MAX_DEVS, ++ "elbasr"); ++ if (status < 0) ++ return status; ++ ++ elbasr_class = class_create("elbasr"); ++ if (IS_ERR(elbasr_class)) { ++ unregister_chrdev(MAJOR(elbasr_devt), "elbasr"); ++ return PTR_ERR(elbasr_class); ++ } ++ } ++ ++ ++ elbasr = kzalloc(sizeof(*elbasr), GFP_KERNEL); ++ if (!elbasr) { ++ if (spi_get_chipselect(spi, 0) == 0) ++ unregister_chrdev(MAJOR(elbasr_devt), "elbasr"); ++ ++ return -ENOMEM; ++ } ++ ++ /* Initialize the driver data */ ++ elbasr->spi = spi; ++ elbasr->speed_hz = spi->max_speed_hz; ++ spin_lock_init(&elbasr->spi_lock); ++ mutex_init(&elbasr->buf_lock); ++ ++ INIT_LIST_HEAD(&elbasr->device_entry); ++ ++ mutex_lock(&device_list_lock); ++ minor = find_first_zero_bit(minors, ELBASR_MAX_DEVS); ++ if (minor < ELBASR_MAX_DEVS) { ++ struct device *dev; ++ ++ elbasr->devt = MKDEV(MAJOR(elbasr_devt), minor); ++ dev = device_create(elbasr_class, ++ &spi->dev, ++ elbasr->devt, ++ elbasr, ++ "spidev%d.%d", ++ spi->controller->bus_num, ++ spi_get_chipselect(spi, 0)); ++ ++ status = PTR_ERR_OR_ZERO(dev); ++ } else { ++ dev_dbg(&spi->dev, "no minor number available\n"); ++ status = -ENODEV; ++ mutex_unlock(&device_list_lock); ++ goto minor_failed; ++ } ++ ++ set_bit(minor, minors); ++ list_add(&elbasr->device_entry, &device_list); ++ dev_dbg(&spi->dev, ++ "created device for major %d, minor %lu\n", ++ MAJOR(elbasr_devt), minor); ++ mutex_unlock(&device_list_lock); ++ ++ /* Create cdev */ ++ elbasr->cdev = cdev_alloc(); ++ if (!elbasr->cdev) { ++ dev_err(elbasr->dev, "allocation of cdev failed"); ++ status = -ENOMEM; ++ goto cdev_failed; ++ } ++ elbasr->cdev->owner = THIS_MODULE; ++ cdev_init(elbasr->cdev, &elbasr_spi_fops); ++ ++ status = cdev_add(elbasr->cdev, elbasr->devt, 1); ++ if (status) { ++ dev_err(elbasr->dev, "register of cdev failed"); ++ goto cdev_delete; ++ } ++ spi_set_drvdata(spi, elbasr); ++ ++ /* Add Elba reset driver sub-device */ ++ if (spi_get_chipselect(spi, 0) == 0) ++ elbasr_regs_setup(spi, elbasr); ++ ++ return 0; ++ ++cdev_delete: ++ if (spi_get_chipselect(spi, 0) == 0) ++ cdev_del(elbasr->cdev); ++cdev_failed: ++ if (spi_get_chipselect(spi, 0) == 0) ++ device_destroy(elbasr_class, elbasr->devt); ++minor_failed: ++ kfree(elbasr); ++ ++ return status; ++} ++ ++static const struct spi_device_id elbasr_spi_ids[] = { ++ { .name = "pensando-elbasr" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(spi, elbasr_spi_ids); ++ ++static const struct of_device_id elbasr_spi_of_match[] = { ++ { .compatible = "amd,pensando-elbasr" }, ++ { /* sentinel */ }, ++}; ++ ++static struct spi_driver elbasr_spi_driver = { ++ .probe = elbasr_spi_probe, ++ .id_table = elbasr_spi_ids, ++ .driver = { ++ .name = "elbasr", ++ .of_match_table = of_match_ptr(elbasr_spi_of_match), ++ }, ++}; ++module_spi_driver(elbasr_spi_driver); +diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig +index 5484a65..2762946 100644 +--- a/drivers/reset/Kconfig ++++ b/drivers/reset/Kconfig +@@ -89,6 +89,15 @@ config RESET_GPIO + + If compiled as module, it will be called reset-gpio. + ++config RESET_ELBASR ++ tristate "Pensando Elba System Resource reset controller" ++ depends on MFD_PENSANDO_ELBASR || COMPILE_TEST ++ help ++ This option enables support for the external reset functions ++ on the Pensando Elba System Resource Chip. Reset control ++ of peripherals is accessed over SPI to the system resource ++ chip device registers using CS0. ++ + config RESET_HSDK + bool "Synopsys HSDK Reset Driver" + depends on HAS_IOMEM +diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile +index 4411a2a..c2a0d75 100644 +--- a/drivers/reset/Makefile ++++ b/drivers/reset/Makefile +@@ -11,6 +11,7 @@ obj-$(CONFIG_RESET_BCM6345) += reset-bcm6345.o + obj-$(CONFIG_RESET_BERLIN) += reset-berlin.o + obj-$(CONFIG_RESET_BRCMSTB) += reset-brcmstb.o + obj-$(CONFIG_RESET_BRCMSTB_RESCAL) += reset-brcmstb-rescal.o ++obj-$(CONFIG_RESET_ELBASR) += reset-elbasr.o + obj-$(CONFIG_RESET_EYEQ) += reset-eyeq.o + obj-$(CONFIG_RESET_GPIO) += reset-gpio.o + obj-$(CONFIG_RESET_HSDK) += reset-hsdk.o +diff --git a/drivers/reset/reset-elbasr.c b/drivers/reset/reset-elbasr.c +new file mode 100644 +index 0000000..4b7db6b +--- /dev/null ++++ b/drivers/reset/reset-elbasr.c +@@ -0,0 +1,106 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (c) 2022 Advanced Micro Devices, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct elbasr_reset { ++ struct reset_controller_dev rcdev; ++ struct regmap *regmap; ++}; ++ ++static inline struct elbasr_reset *to_elbasr_rst(struct reset_controller_dev *rc) ++{ ++ return container_of(rc, struct elbasr_reset, rcdev); ++} ++ ++static inline int elbasr_reset_shift(unsigned long id) ++{ ++ switch (id) { ++ case EMMC_HW_RESET: ++ return 6; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int elbasr_reset_assert(struct reset_controller_dev *rcdev, ++ unsigned long id) ++{ ++ struct elbasr_reset *elbar = to_elbasr_rst(rcdev); ++ int val; ++ u32 mask; ++ ++ val = elbasr_reset_shift(id); ++ if (val < 0) ++ return val; ++ mask = 1 << val; ++ ++ return regmap_update_bits(elbar->regmap, ELBASR_CTRL0_REG, mask, mask); ++} ++ ++static int elbasr_reset_deassert(struct reset_controller_dev *rcdev, ++ unsigned long id) ++{ ++ struct elbasr_reset *elbar = to_elbasr_rst(rcdev); ++ int val; ++ u32 mask; ++ ++ val = elbasr_reset_shift(id); ++ if (val < 0) ++ return val; ++ mask = 1 << val; ++ ++ return regmap_update_bits(elbar->regmap, ELBASR_CTRL0_REG, mask, 0); ++} ++ ++static const struct reset_control_ops elbasr_reset_ops = { ++ .assert = elbasr_reset_assert, ++ .deassert = elbasr_reset_deassert, ++}; ++ ++static int elbasr_reset_probe(struct platform_device *pdev) ++{ ++ struct elbasr_data *elbasr = dev_get_drvdata(pdev->dev.parent); ++ struct elbasr_reset *elbar; ++ int ret; ++ ++ elbar = devm_kzalloc(&pdev->dev, sizeof(struct elbasr_reset), ++ GFP_KERNEL); ++ if (!elbar) ++ return -ENOMEM; ++ ++ elbar->rcdev.owner = THIS_MODULE; ++ elbar->rcdev.nr_resets = ELBASR_NR_RESETS; ++ elbar->rcdev.ops = &elbasr_reset_ops; ++ elbar->rcdev.of_node = pdev->dev.of_node; ++ elbar->regmap = elbasr->elbasr_regs; ++ ++ platform_set_drvdata(pdev, elbar); ++ ++ ret = devm_reset_controller_register(&pdev->dev, &elbar->rcdev); ++ ++ return ret; ++} ++ ++static const struct of_device_id elba_reset_dt_match[] = { ++ { .compatible = "amd,pensando-elbasr-reset", }, ++ { /* sentinel */ }, ++}; ++ ++static struct platform_driver elbasr_reset_driver = { ++ .probe = elbasr_reset_probe, ++ .driver = { ++ .name = "pensando_elbasr_reset", ++ .of_match_table = elba_reset_dt_match, ++ }, ++}; ++builtin_platform_driver(elbasr_reset_driver); +diff --git a/include/dt-bindings/reset/amd,pensando-elba-reset.h b/include/dt-bindings/reset/amd,pensando-elba-reset.h +new file mode 100644 +index 0000000..2411ed4 +--- /dev/null ++++ b/include/dt-bindings/reset/amd,pensando-elba-reset.h +@@ -0,0 +1,11 @@ ++/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ ++/* ++ * Copyright (c) 2022 Advanced Micro Devices, Inc. ++ */ ++ ++#ifndef _DT_BINDINGS_RESET_AMD_PENSANDO_ELBA_RESET_H ++#define _DT_BINDINGS_RESET_AMD_PENSANDO_ELBA_RESET_H ++ ++#define EMMC_HW_RESET 0 ++ ++#endif +diff --git a/include/linux/mfd/pensando-elbasr.h b/include/linux/mfd/pensando-elbasr.h +new file mode 100644 +index 0000000..75103d0 +--- /dev/null ++++ b/include/linux/mfd/pensando-elbasr.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ ++/* ++ * Copyright (c) 2022 Advanced Micro Devices, Inc. ++ * ++ * Declarations for AMD Pensando Elba System Resource Chip ++ */ ++ ++#ifndef __MFD_AMD_PENSANDO_ELBA_H ++#define __MFD_AMD_PENSANDO_ELBA_H ++ ++#include ++#include ++ ++#define ELBASR_CTRL0_REG 0x10 ++#define ELBASR_MAX_REG 0xff ++#define ELBASR_NR_RESETS 1 ++ ++/* ++ * Pensando Elba System Resource MFD device private data structure ++ */ ++struct elbasr_data { ++ dev_t devt; ++ int minor; ++ struct device *dev; ++ struct cdev *cdev; ++ struct spi_device *spi; ++ struct list_head device_entry; ++ spinlock_t spi_lock; ++ ++ /* TX/RX buffers are NULL unless this device is open (users > 0) */ ++ struct mutex buf_lock; ++ unsigned int users; ++ u8 *tx_buffer; ++ u8 *rx_buffer; ++ u32 speed_hz; ++ ++ /* System Resource Chip CS0 register access */ ++ struct regmap *elbasr_regs; ++}; ++ ++#endif /* __MFD_AMD_PENSANDO_ELBA_H */ +-- +1.8.3.1 + diff --git a/patches-sonic/0007-i2c-xiic-Relocate-xiic_i2c_runtime_suspend-and-xiic_.patch b/patches-sonic/0007-i2c-xiic-Relocate-xiic_i2c_runtime_suspend-and-xiic_.patch new file mode 100644 index 000000000..a104a850e --- /dev/null +++ b/patches-sonic/0007-i2c-xiic-Relocate-xiic_i2c_runtime_suspend-and-xiic_.patch @@ -0,0 +1,84 @@ +From a085b9385bfbc6b6887a9fa21f3cd40bd3811b37 Mon Sep 17 00:00:00 2001 +From: Manikanta Guntupalli +Date: Tue, 10 Dec 2024 15:22:41 +0530 +Subject: [PATCH 07/22] i2c: xiic: Relocate xiic_i2c_runtime_suspend and + xiic_i2c_runtime_resume to facilitate atomic mode + +Relocate xiic_i2c_runtime_suspend and xiic_i2c_runtime_resume functions +to avoid prototype statements in atomic mode changes. + +Signed-off-by: Manikanta Guntupalli +Acked-by: Michal Simek +Link: https://lore.kernel.org/r/20241210095242.1982770-2-manikanta.guntupalli@amd.com +Signed-off-by: Andi Shyti +--- + drivers/i2c/busses/i2c-xiic.c | 46 +++++++++++++++++------------------ + 1 file changed, 23 insertions(+), 23 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index 1d68177241a6..2d26b6d0721b 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -238,6 +238,29 @@ static const struct timing_regs timing_reg_values[] = { + static int xiic_start_xfer(struct xiic_i2c *i2c, struct i2c_msg *msgs, int num); + static void __xiic_start_xfer(struct xiic_i2c *i2c); + ++static int __maybe_unused xiic_i2c_runtime_suspend(struct device *dev) ++{ ++ struct xiic_i2c *i2c = dev_get_drvdata(dev); ++ ++ clk_disable(i2c->clk); ++ ++ return 0; ++} ++ ++static int __maybe_unused xiic_i2c_runtime_resume(struct device *dev) ++{ ++ struct xiic_i2c *i2c = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_enable(i2c->clk); ++ if (ret) { ++ dev_err(dev, "Cannot enable clock.\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ + /* + * For the register read and write functions, a little-endian and big-endian + * version are necessary. Endianness is detected during the probe function. +@@ -1365,29 +1388,6 @@ static void xiic_i2c_remove(struct platform_device *pdev) + pm_runtime_dont_use_autosuspend(&pdev->dev); + } + +-static int __maybe_unused xiic_i2c_runtime_suspend(struct device *dev) +-{ +- struct xiic_i2c *i2c = dev_get_drvdata(dev); +- +- clk_disable(i2c->clk); +- +- return 0; +-} +- +-static int __maybe_unused xiic_i2c_runtime_resume(struct device *dev) +-{ +- struct xiic_i2c *i2c = dev_get_drvdata(dev); +- int ret; +- +- ret = clk_enable(i2c->clk); +- if (ret) { +- dev_err(dev, "Cannot enable clock.\n"); +- return ret; +- } +- +- return 0; +-} +- + static const struct dev_pm_ops xiic_dev_pm_ops = { + SET_RUNTIME_PM_OPS(xiic_i2c_runtime_suspend, + xiic_i2c_runtime_resume, NULL) +-- +2.53.0 + diff --git a/patches-sonic/0007-mlxbf-bootctl-Support-sysfs-entries-for-RTC-battery-.patch b/patches-sonic/0007-mlxbf-bootctl-Support-sysfs-entries-for-RTC-battery-.patch new file mode 100644 index 000000000..399d696d8 --- /dev/null +++ b/patches-sonic/0007-mlxbf-bootctl-Support-sysfs-entries-for-RTC-battery-.patch @@ -0,0 +1,115 @@ +From 224f54a149e3fb3ef5125823ee6847750e05c4b9 Mon Sep 17 00:00:00 2001 +From: Xiangrong Li +Date: Fri, 24 Jan 2025 14:46:55 +0000 +Subject: [PATCH 07/78] mlxbf-bootctl: Support sysfs entries for RTC battery + status +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This patch extends the mlxbf-bootctl driver's sysfs entries +to support read access for the board's RTC battery status. +A successful read from this attribute returns the status of +the board's RTC battery. The RTC battery status register is +also cleared upon successful read operation. + +Signed-off-by: Xiangrong Li +Reviewed-by: David Thompson +Reviewed-by: Ilpo Järvinen +Link: https://lore.kernel.org/r/20250124144655.48564-1-xiangrongl@nvidia.com +Signed-off-by: Ilpo Järvinen +--- + .../ABI/testing/sysfs-platform-mellanox-bootctl | 10 ++++++++++ + drivers/platform/mellanox/mlxbf-bootctl.c | 20 ++++++++++++++++++++ + drivers/platform/mellanox/mlxbf-bootctl.h | 5 +++++ + 3 files changed, 35 insertions(+) + +diff --git a/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl b/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl +index 65ed386..09f783f 100644 +--- a/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl ++++ b/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl +@@ -150,3 +150,13 @@ Description: + The "mfg_lock" sysfs attribute is write-only. + A successful write to this attribute will latch the + board-level attributes into EEPROM, making them read-only. ++ ++What: /sys/bus/platform/devices/MLNXBF04:00/rtc_battery ++Date: June 2025 ++KernelVersion: 6.15 ++Contact: "Xiangrong Li " ++Description: ++ The "rtc_battery" sysfs attribute is read-only. ++ A successful read from this attribute returns the status of ++ the board's RTC battery. The RTC battery status register is ++ also cleared upon successful read operation. +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c +index e8530ef..25ffae1 100644 +--- a/drivers/platform/mellanox/mlxbf-bootctl.c ++++ b/drivers/platform/mellanox/mlxbf-bootctl.c +@@ -91,6 +91,7 @@ static const char * const mlxbf_rsh_log_level[] = { + static DEFINE_MUTEX(icm_ops_lock); + static DEFINE_MUTEX(os_up_lock); + static DEFINE_MUTEX(mfg_ops_lock); ++static DEFINE_MUTEX(rtc_ops_lock); + + /* + * Objects are stored within the MFG partition per type. +@@ -489,6 +490,23 @@ static ssize_t large_icm_store(struct device *dev, + return res.a0 ? -EPERM : count; + } + ++static ssize_t rtc_battery_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct arm_smccc_res res; ++ ++ mutex_lock(&rtc_ops_lock); ++ arm_smccc_smc(MLNX_HANDLE_GET_RTC_LOW_BATT, 0, 0, 0, 0, ++ 0, 0, 0, &res); ++ mutex_unlock(&rtc_ops_lock); ++ ++ if (res.a0) ++ return -EPERM; ++ ++ return sysfs_emit(buf, "0x%lx\n", res.a1); ++} ++ + static ssize_t os_up_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +@@ -906,6 +924,7 @@ static DEVICE_ATTR_RW(sn); + static DEVICE_ATTR_RW(uuid); + static DEVICE_ATTR_RW(rev); + static DEVICE_ATTR_WO(mfg_lock); ++static DEVICE_ATTR_RO(rtc_battery); + + static struct attribute *mlxbf_bootctl_attrs[] = { + &dev_attr_post_reset_wdog.attr, +@@ -925,6 +944,7 @@ static struct attribute *mlxbf_bootctl_attrs[] = { + &dev_attr_uuid.attr, + &dev_attr_rev.attr, + &dev_attr_mfg_lock.attr, ++ &dev_attr_rtc_battery.attr, + NULL + }; + +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.h b/drivers/platform/mellanox/mlxbf-bootctl.h +index 1299750..90bbbdc 100644 +--- a/drivers/platform/mellanox/mlxbf-bootctl.h ++++ b/drivers/platform/mellanox/mlxbf-bootctl.h +@@ -103,6 +103,11 @@ + */ + #define MLNX_HANDLE_OS_UP 0x82000014 + ++/* ++ * SMC function ID to get and clear the RTC low voltage bit ++ */ ++#define MLNX_HANDLE_GET_RTC_LOW_BATT 0x82000023 ++ + /* SMC function IDs for SiP Service queries */ + #define MLXBF_BOOTCTL_SIP_SVC_CALL_COUNT 0x8200ff00 + #define MLXBF_BOOTCTL_SIP_SVC_UID 0x8200ff01 +-- +2.8.4 + diff --git a/patches-sonic/0007-usb-ehci-Add-support-for-ac5-with-DMA-mask-64-bit.patch b/patches-sonic/0007-usb-ehci-Add-support-for-ac5-with-DMA-mask-64-bit.patch new file mode 100644 index 000000000..1c50c49ab --- /dev/null +++ b/patches-sonic/0007-usb-ehci-Add-support-for-ac5-with-DMA-mask-64-bit.patch @@ -0,0 +1,37 @@ +From ec02d4b124ea5dea90ce76fa3e1030f22c5f9e22 Mon Sep 17 00:00:00 2001 +From: Pavan Naregundi +Date: Mon, 13 Mar 2023 10:01:51 +0000 +Subject: [PATCH] usb:ehci: Add support for ac5 with DMA mask 64-bit + +Change DMA bit mask to 64-bit addressing like it is done in K-6.1 +for "ac5-ehci" 98DX25xx already present and supported by Kernel-6.12 + +Signed-off-by: Yuval Shaia +Tested-by: Raz Adashi +Reviewed-by: Raz Adashi +Signed-off-by: Yan Markman +--- + drivers/usb/host/ehci-orion.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c +index ad145a54c..03f04a3da 100644 +--- a/drivers/usb/host/ehci-orion.c ++++ b/drivers/usb/host/ehci-orion.c +@@ -68,11 +68,11 @@ static struct hc_driver __read_mostly ehci_orion_hc_driver; + /* + * Legacy DMA mask is 32 bit. + * AC5 has the DDR starting at 8GB, hence it requires +- * a larger (34-bit) DMA mask, in order for DMA allocations ++ * a larger (34-bit or even 64-bit) DMA mask, in order for DMA allocations + * to succeed: + */ + static const u64 dma_mask_orion = DMA_BIT_MASK(32); +-static const u64 dma_mask_ac5 = DMA_BIT_MASK(34); ++static const u64 dma_mask_ac5 = DMA_BIT_MASK(64); + + /* + * Implement Orion USB controller specification guidelines +-- +2.34.1 + diff --git a/patches-sonic/0008-PCI-AER-Ratelimit-correctable-and-non-fatal-error-lo.patch b/patches-sonic/0008-PCI-AER-Ratelimit-correctable-and-non-fatal-error-lo.patch new file mode 100644 index 000000000..09e074858 --- /dev/null +++ b/patches-sonic/0008-PCI-AER-Ratelimit-correctable-and-non-fatal-error-lo.patch @@ -0,0 +1,221 @@ +From 3fff4113090e26cb3de64388bb5e91bfb0d02118 Mon Sep 17 00:00:00 2001 +From: Jon Pan-Doh +Date: Thu, 22 May 2025 18:21:24 -0500 +Subject: [PATCH 08/12] PCI/AER: Ratelimit correctable and non-fatal error + logging + +Spammy devices can flood kernel logs with AER errors and slow/stall +execution. Add per-device ratelimits for AER correctable and non-fatal +uncorrectable errors that use the kernel defaults (10 per 5s). Logging of +fatal errors is not ratelimited. + +There are two AER logging entry points: + + - aer_print_error() is used by DPC and native AER + + - pci_print_aer() is used by GHES and CXL + +The native AER aer_print_error() case includes a loop that may log details +from multiple devices, which are ratelimited individually. If we log +details for any device, we also log the Error Source ID from the Root Port +or RCEC. + +If no such device details are found, we still log the Error Source from the +ERR_* Message, ratelimited by the Root Port or RCEC that received it. + +The DPC aer_print_error() case is not ratelimited, since this only happens +for fatal errors. + +The CXL pci_print_aer() case is ratelimited by the Error Source device. + +The GHES pci_print_aer() case is via aer_recover_work_func(), which +searches for the Error Source device. If the device is not found, there's +no per-device ratelimit, so we use a system-wide ratelimit that covers all +error types (correctable, non-fatal, and fatal). + +Sargun at Meta reported internally that a flood of AER errors causes RCU +CPU stall warnings and CSD-lock warnings. + +Tested using aer-inject[1]. Sent 11 AER errors. Observed 10 errors logged +while AER stats (cat /sys/bus/pci/devices//aer_dev_correctable) show +true count of 11. + +[1] https://git.kernel.org/pub/scm/linux/kernel/git/gong.chen/aer-inject.git + +[bhelgaas: commit log, factor out trace_aer_event() and aer_print_rp_info() +changes to previous patches, enable Error Source logging if any downstream +detail will be printed, don't ratelimit fatal errors, "aer_report" -> +"aer_info", "cor_log_ratelimit" -> "correctable_ratelimit", +"uncor_log_ratelimit" -> "nonfatal_ratelimit"] + +Reported-by: Sargun Dhillon +Signed-off-by: Jon Pan-Doh +Signed-off-by: Bjorn Helgaas +Reviewed-by: Kuppuswamy Sathyanarayanan +Link: https://patch.msgid.link/20250522232339.1525671-19-helgaas@kernel.org +(cherry picked from commit a57f2bfb4a5863f83087867c0e671f2418212d23) +--- + drivers/pci/pci.h | 4 ++- + drivers/pci/pcie/aer.c | 68 ++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 66 insertions(+), 6 deletions(-) + +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index 4f64f3f9cdf5..f4372bb39fe2 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -504,13 +504,15 @@ static inline bool pci_dev_is_added(const struct pci_dev *dev) + + struct aer_err_info { + struct pci_dev *dev[AER_MAX_MULTI_ERR_DEVICES]; ++ int ratelimit_print[AER_MAX_MULTI_ERR_DEVICES]; + int error_dev_num; + const char *level; /* printk level */ + + unsigned int id:16; + + unsigned int severity:2; /* 0:NONFATAL | 1:FATAL | 2:COR */ +- unsigned int __pad1:5; ++ unsigned int root_ratelimit_print:1; /* 0=skip, 1=print */ ++ unsigned int __pad1:4; + unsigned int multi_error_valid:1; + + unsigned int first_error:5; +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 6ec628965cce..8d0152f4fe50 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -84,6 +85,10 @@ struct aer_stats { + u64 rootport_total_cor_errs; + u64 rootport_total_fatal_errs; + u64 rootport_total_nonfatal_errs; ++ ++ /* Ratelimits for errors */ ++ struct ratelimit_state correctable_ratelimit; ++ struct ratelimit_state nonfatal_ratelimit; + }; + + #define AER_LOG_TLP_MASKS (PCI_ERR_UNC_POISON_TLP| \ +@@ -374,6 +379,11 @@ void pci_aer_init(struct pci_dev *dev) + + dev->aer_stats = kzalloc(sizeof(struct aer_stats), GFP_KERNEL); + ++ ratelimit_state_init(&dev->aer_stats->correctable_ratelimit, ++ DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); ++ ratelimit_state_init(&dev->aer_stats->nonfatal_ratelimit, ++ DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); ++ + /* + * We save/restore PCI_ERR_UNCOR_MASK, PCI_ERR_UNCOR_SEVER, + * PCI_ERR_COR_MASK, and PCI_ERR_CAP. Root and Root Complex Event +@@ -670,6 +680,18 @@ static void __print_tlp_header(struct pci_dev *dev, struct pcie_tlp_log *t) + t->dw[0], t->dw[1], t->dw[2], t->dw[3]); + } + ++static int aer_ratelimit(struct pci_dev *dev, unsigned int severity) ++{ ++ switch (severity) { ++ case AER_NONFATAL: ++ return __ratelimit(&dev->aer_stats->nonfatal_ratelimit); ++ case AER_CORRECTABLE: ++ return __ratelimit(&dev->aer_stats->correctable_ratelimit); ++ default: ++ return 1; /* Don't ratelimit fatal errors */ ++ } ++} ++ + static void __aer_print_error(struct pci_dev *dev, + struct aer_err_info *info) + { +@@ -710,6 +732,9 @@ void aer_print_error(struct aer_err_info *info, int i) + trace_aer_event(pci_name(dev), (info->status & ~info->mask), + info->severity, info->tlp_header_valid, &info->tlp); + ++ if (!info->ratelimit_print[i]) ++ return; ++ + if (!info->status) { + pci_err(dev, "PCIe Bus Error: severity=%s, type=Inaccessible, (Unregistered Agent ID)\n", + aer_error_severity_string[info->severity]); +@@ -791,6 +816,9 @@ void pci_print_aer(struct pci_dev *dev, int aer_severity, + trace_aer_event(pci_name(dev), (status & ~mask), + aer_severity, tlp_header_valid, &aer->header_log); + ++ if (!aer_ratelimit(dev, info.severity)) ++ return; ++ + layer = AER_GET_LAYER_ERROR(aer_severity, status); + agent = AER_GET_AGENT(aer_severity, status); + +@@ -823,6 +851,18 @@ static int add_error_device(struct aer_err_info *e_info, struct pci_dev *dev) + e_info->dev[i] = pci_dev_get(dev); + e_info->error_dev_num++; + ++ /* ++ * Ratelimit AER log messages. "dev" is either the source ++ * identified by the root's Error Source ID or it has an unmasked ++ * error logged in its own AER Capability. Messages are emitted ++ * when "ratelimit_print[i]" is non-zero. If we will print detail ++ * for a downstream device, make sure we print the Error Source ID ++ * from the root as well. ++ */ ++ if (aer_ratelimit(dev, e_info->severity)) { ++ e_info->ratelimit_print[i] = 1; ++ e_info->root_ratelimit_print = 1; ++ } + return 0; + } + +@@ -1150,9 +1190,10 @@ static void aer_recover_work_func(struct work_struct *work) + pdev = pci_get_domain_bus_and_slot(entry.domain, entry.bus, + entry.devfn); + if (!pdev) { +- pr_err("no pci_dev for %04x:%02x:%02x.%x\n", +- entry.domain, entry.bus, +- PCI_SLOT(entry.devfn), PCI_FUNC(entry.devfn)); ++ pr_err_ratelimited("%04x:%02x:%02x.%x: no pci_dev found\n", ++ entry.domain, entry.bus, ++ PCI_SLOT(entry.devfn), ++ PCI_FUNC(entry.devfn)); + continue; + } + pci_print_aer(pdev, entry.severity, entry.regs); +@@ -1291,9 +1332,26 @@ static inline void aer_process_err_devices(struct aer_err_info *e_info) + static void aer_isr_one_error_type(struct pci_dev *root, + struct aer_err_info *info) + { +- aer_print_port_info(root, info); ++ bool found; ++ ++ found = find_source_device(root, info); ++ ++ /* ++ * If we're going to log error messages, we've already set ++ * "info->root_ratelimit_print" and "info->ratelimit_print[i]" to ++ * non-zero (which enables printing) because this is either an ++ * ERR_FATAL or we found a device with an error logged in its AER ++ * Capability. ++ * ++ * If we didn't find the Error Source device, at least log the ++ * Requester ID from the ERR_* Message received by the Root Port or ++ * RCEC, ratelimited by the RP or RCEC. ++ */ ++ if (info->root_ratelimit_print || ++ (!found && aer_ratelimit(root, info->severity))) ++ aer_print_port_info(root, info); + +- if (find_source_device(root, info)) ++ if (found) + aer_process_err_devices(info); + } + +-- +2.47.0 + diff --git a/patches-sonic/0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch b/patches-sonic/0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch new file mode 100644 index 000000000..fdcdd3534 --- /dev/null +++ b/patches-sonic/0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch @@ -0,0 +1,160 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dvir Zaguri +Date: Sun, 31 May 2026 10:15:00 +0300 +Subject: [PATCH 6.12 1/1] arm64: dts: aspeed: spc6-bmc: phram-env, TPM init, + GPIO expander reset, I3C pull-ups + +Extend arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +(AST2700-A2 SPC6 CPU BMC) with four downstream fixes taken from +the OpenBMC Switch-26.05-1_br branch. Only the kernel DTS hunks +from each commit are taken; the matching userspace / U-Boot / +kconfig changes live in their own layers and are out of scope. + + 51f092d656fbe81d704036246fad43a186d008fb + "L1 Switch: SPC6 AST2700 BMC: move recovery counter to phram-env" + Dmitrijs Novikovs , May 12 2026 + + * Reserve a 4 KiB phram region (phram_env @ 0x4_237ff000), one + page below bmc-dev0, inside reserved-memory. fw_env now points + at /dev/mtd/by-name/phram-env so the recovery counter and the + rest of the U-Boot env survive warm boots. + + 2c97dad11108b1ceaedadd0762f3ef342b06abe9 + "L1 Switch: SPC6 AST2700 BMC: fix TPM GPIO init" + Sergejs Hatinecs , May 28 2026 + + * Add two gpio-hogs (spi-buf-oe, spi-tpm-buf-oe) on the i2c5 + pcal6524 (exp0) to drive GP_BMC_SPI_BUF_OE_L (pin 4) and + GP_BMC_SPI_TPM_BUF_OE_L (pin 5) low at gpiochip registration, + enabling the SPI and SPI/TPM level shifters before any SPI + probe touches the bus. + * Add a private buf-en-link-gpios property on tpm@1 pointing at + the same two exp0 pins. The property is intentionally NOT + consumed by any driver -- it exists solely as an fw_devlink + ordering hint so tpm@1 cannot probe until exp0 has probed and + its hogs have run. This kills the "no TPM found" race between + the i2c5 and spi0 buses. + + d406755fdf8bd1421095b6470bc1ca47e35697b6 + "L1 Switch: SPC6 AST2700 BMC: fix GPIO expander" + Sergejs Hatinecs , May 29 2026 + + * Add reset-gpios to the i2c5 pcal6524 (exp0) pointing at + GP_BMC_GPIO_EXP_RESET_L (gpio1 bank S, line 0, active-low), + so the pcal6524 driver pulse-resets the expander at probe and + starts it in a known state. Without this the expander could + stay wedged from a previous boot, breaking the SPI/TPM buffer + hogs, the mdio2 PHY reset (driven through &exp0 10) and the + WP / MUX-select lines that ride on the same expander. + + 2e6c7a8a27033a696505425384d8f5551aa86a36 + "L1 Switch: SPC6 AST2700: Enable I3C internal pull-ups via DT" + Dmitrijs Novikovs , May 15 2026 + + * Add internal-pullup = <1> to &i3c1 so the SoC drives the I3C + bus pull-ups and the controller comes up at boot without the + previous late user-space bind workaround. + +Signed-off-by: Dvir Zaguri +--- + .../boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts | 59 ++++++++++++++++++++++ + 1 file changed, 59 insertions(+) + +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +@@ -29,6 +29,15 @@ + ranges; + + #include "ast2700-reserved-mem.dtsi" ++ ++ // (before bmc-dev0 @0x423800000). ++ // Persistent phram region for fw_env variables across warm boots. ++ phram_env: phram-env@4237ff000 { ++ compatible = "phram"; ++ label = "phram-env"; ++ reg = <0x00000004 0x237ff000 0x0 0x00001000>; ++ no-map; ++ }; + }; + }; + +@@ -55,6 +64,30 @@ + compatible = "st,st33htpm-spi", "infineon,slb9670", "tcg,tpm_tis-spi"; + reg = <1>; + spi-max-frequency = <10000000>; ++ /* ++ * NOTE: this property is NOT consumed by any driver. Stock ++ * tpm_tis_spi (and all other drivers matching the compatibles ++ * above) does not call gpiod_get() for this name. That is ++ * intentional -- the SPI/TPM level shifters are enabled by the ++ * gpio-hogs on exp0 (see below), not by this device. ++ * ++ * It is kept purely as an fw_devlink ordering hint: any property ++ * whose name ends in "-gpios" is parsed by the DT core to build ++ * a supplier/consumer link. The phandle to exp0 therefore makes ++ * exp0 a supplier of tpm@1, so tpm@1 is not probed until the ++ * pcal6524 has probed and its hogs have run. Without this hint ++ * there is no ordering between the i2c5 and spi0 buses, and the ++ * TPM could be accessed before the buffers are enabled (races to ++ * "no TPM found"). Do not delete it. ++ * ++ * The property name is deliberately a private one ++ * ("buf-en-link-gpios") that no upstream driver consumes. If ++ * the property were called "enable-gpios" and a future kernel ++ * added gpiod_get(dev, "enable", ...) to tpm_tis_spi, it would ++ * collide with the hogs and fail -EBUSY. ++ */ ++ buf-en-link-gpios = <&exp0 4 GPIO_ACTIVE_LOW>, ++ <&exp0 5 GPIO_ACTIVE_LOW>; + }; + }; + +@@ -243,6 +276,7 @@ + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; ++ reset-gpios = <&gpio1 ASPEED_GPIO(S, 0) GPIO_ACTIVE_LOW>; /*GPIO_BMC_GPIO_EXP_RESET_L*/ + + gpio-line-names = + "RSRVD_0", "GP_EROT_RST_IN_L", +@@ -257,6 +291,30 @@ + "RSRVD_7", "GP_PROD_CS_FLASH0_EN", + "RSRVD_8", "GP_PROD_CS_FLASH1_EN", + "RSRVD_9", "RSRVD_10"; ++ /* ++ * Enable the SPI/TPM level shifters at gpiochip registration, ++ * i.e. during this node's probe -- before tpm@1 (ordered after ++ * us via the buf-en-link-gpios phandle above) touches ++ * the bus. ++ * ++ * POLARITY: these nets are active-low (..._OE_L), so physical low ++ * enables the buffer. In a gpio-hog, output-low/output-high are ++ * LOGICAL values and are inverted by GPIO_ACTIVE_LOW. Therefore ++ * "output-high" = logical 1 = physical low = buffer ENABLED. ++ * Using "output-low" here would drive the pin high and DISABLE ++ * the buffers -- the opposite of what we want. ++ */ ++ spi-buf-oe { ++ gpio-hog; ++ gpios = <4 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; ++ ++ spi-tpm-buf-oe { ++ gpio-hog; ++ gpios = <5 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_TPM_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; + }; + }; + +@@ -463,5 +521,6 @@ + status = "okay"; + pinctrl-names = "default"; + i3c-scl-hz = <12500000>; ++ internal-pullup = <1>; + mctp-controller; + }; +-- +2.34.1 diff --git a/patches-sonic/0008-dts-pensnado-Elba-flash-partitions.patch b/patches-sonic/0008-dts-pensnado-Elba-flash-partitions.patch new file mode 100644 index 000000000..7254298d5 --- /dev/null +++ b/patches-sonic/0008-dts-pensnado-Elba-flash-partitions.patch @@ -0,0 +1,142 @@ +From 01e8ec50b3de738622c904491e986ca20967a339 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 09:32:50 +0000 +Subject: [PATCH 08/27] dts/pensnado: Elba flash partitions + +Pensando Elba-based PCIE cards have a common flash layout. +This patch adds the partitions to the dts files. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba-asic.dts | 1 + + arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi | 108 +++++++++++++++++++++ + 2 files changed, 109 insertions(+) + create mode 100644 arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi + +diff --git a/arch/arm64/boot/dts/pensando/elba-asic.dts b/arch/arm64/boot/dts/pensando/elba-asic.dts +index 382b89e..8c482bd 100644 +--- a/arch/arm64/boot/dts/pensando/elba-asic.dts ++++ b/arch/arm64/boot/dts/pensando/elba-asic.dts +@@ -21,3 +21,4 @@ + #include "elba-16core.dtsi" + #include "elba-16core-spin-table.dtsi" + #include "elba-asic-common.dtsi" ++#include "elba-flash-parts.dtsi" +diff --git a/arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi b/arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi +new file mode 100644 +index 0000000..a9c5d25 +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi +@@ -0,0 +1,108 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++&flash0 { ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ partition@0 { ++ label = "flash"; ++ reg = <0x10000 0xfff0000>; ++ }; ++ ++ partition@f0000 { ++ label = "golduenv"; ++ reg = <0xf0000 0x10000>; ++ }; ++ ++ partition@100000 { ++ label = "boot0"; ++ reg = <0x100000 0x80000>; ++ }; ++ ++ partition@180000 { ++ label = "golduboot"; ++ reg = <0x180000 0x200000>; ++ }; ++ ++ partition@380000 { ++ label = "brdcfg0"; ++ reg = <0x380000 0x10000>; ++ }; ++ ++ partition@390000 { ++ label = "brdcfg1"; ++ reg = <0x390000 0x10000>; ++ }; ++ ++ partition@400000 { ++ label = "goldfw"; ++ reg = <0x400000 0x3c00000>; ++ }; ++ ++ partition@4010000 { ++ label = "fwmap"; ++ reg = <0x4010000 0x20000>; ++ }; ++ ++ partition@4030000 { ++ label = "fwsel"; ++ reg = <0x4030000 0x20000>; ++ }; ++ ++ partition@4090000 { ++ label = "bootlog"; ++ reg = <0x4090000 0x20000>; ++ }; ++ ++ partition@40b0000 { ++ label = "panicbuf"; ++ reg = <0x40b0000 0x20000>; ++ }; ++ ++ partition@40d0000 { ++ label = "uservars"; ++ reg = <0x40d0000 0x20000>; ++ }; ++ ++ partition@4200000 { ++ label = "uboota"; ++ reg = <0x4200000 0x400000>; ++ }; ++ ++ partition@4600000 { ++ label = "ubootb"; ++ reg = <0x4600000 0x400000>; ++ }; ++ ++ partition@4a00000 { ++ label = "mainfwa"; ++ reg = <0x4a00000 0x1000000>; ++ }; ++ ++ partition@5a00000 { ++ label = "mainfwb"; ++ reg = <0x5a00000 0x1000000>; ++ }; ++ ++ partition@6a00000 { ++ label = "diaguboot"; ++ reg = <0x6a00000 0x400000>; ++ }; ++ ++ partition@6e00000 { ++ label = "flashobfl"; ++ reg = <0x6e00000 0x1200000>; ++ }; ++ ++ partition@8000000 { ++ label = "diagfw"; ++ reg = <0x8000000 0x7fe0000>; ++ }; ++ ++ partition@ffe0000 { ++ label = "ubootenv"; ++ reg = <0xffe0000 0x10000>; ++ }; ++ }; ++}; +-- +1.8.3.1 + diff --git a/patches-sonic/0008-i2c-xiic-Add-atomic-transfer-support.patch b/patches-sonic/0008-i2c-xiic-Add-atomic-transfer-support.patch new file mode 100644 index 000000000..d0526846d --- /dev/null +++ b/patches-sonic/0008-i2c-xiic-Add-atomic-transfer-support.patch @@ -0,0 +1,464 @@ +From d6daa038c128013607b5fae67bb4fb737b5009e2 Mon Sep 17 00:00:00 2001 +From: Manikanta Guntupalli +Date: Tue, 10 Dec 2024 15:22:42 +0530 +Subject: [PATCH 08/22] i2c: xiic: Add atomic transfer support + +Rework the read and write code paths in the driver to support operation +in atomic contexts. + +Similar changes have been implemented in other drivers, including: +commit 3a5ee18d2a32 ("i2c: imx: implement master_xfer_atomic callback") +commit 445094c8a9fb ("i2c: exynos5: add support for atomic transfers") +commit ede2299f7101 ("i2c: tegra: Support atomic transfers") +commit fe402bd09049 ("i2c: meson: implement the master_xfer_atomic +callback") + +Signed-off-by: Manikanta Guntupalli +Acked-by: Michal Simek +Link: https://lore.kernel.org/r/20241210095242.1982770-3-manikanta.guntupalli@amd.com +Signed-off-by: Andi Shyti +--- + drivers/i2c/busses/i2c-xiic.c | 239 +++++++++++++++++++++++++++++----- + 1 file changed, 206 insertions(+), 33 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index 2d26b6d0721b..b142380ed251 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -30,6 +30,8 @@ + #include + #include + #include ++#include ++#include + + #define DRIVER_NAME "xiic-i2c" + #define DYNAMIC_MODE_READ_BROKEN_BIT BIT(0) +@@ -74,6 +76,9 @@ enum i2c_scl_freq { + * @smbus_block_read: Flag to handle block read + * @input_clk: Input clock to I2C controller + * @i2c_clk: I2C SCL frequency ++ * @atomic: Mode of transfer ++ * @atomic_lock: Lock for atomic transfer mode ++ * @atomic_xfer_state: See STATE_ + */ + struct xiic_i2c { + struct device *dev; +@@ -96,6 +101,9 @@ struct xiic_i2c { + bool smbus_block_read; + unsigned long input_clk; + unsigned int i2c_clk; ++ bool atomic; ++ spinlock_t atomic_lock; /* Lock for atomic transfer mode */ ++ enum xilinx_i2c_state atomic_xfer_state; + }; + + struct xiic_version_data { +@@ -224,6 +232,8 @@ static const struct timing_regs timing_reg_values[] = { + #define XIIC_I2C_TIMEOUT (msecs_to_jiffies(1000)) + /* timeout waiting for the controller finish transfers */ + #define XIIC_XFER_TIMEOUT (msecs_to_jiffies(10000)) ++/* timeout waiting for the controller finish transfers in micro seconds */ ++#define XIIC_XFER_TIMEOUT_US 10000000 + + /* + * The following constant is used for the device global interrupt enable +@@ -238,7 +248,7 @@ static const struct timing_regs timing_reg_values[] = { + static int xiic_start_xfer(struct xiic_i2c *i2c, struct i2c_msg *msgs, int num); + static void __xiic_start_xfer(struct xiic_i2c *i2c); + +-static int __maybe_unused xiic_i2c_runtime_suspend(struct device *dev) ++static int xiic_i2c_runtime_suspend(struct device *dev) + { + struct xiic_i2c *i2c = dev_get_drvdata(dev); + +@@ -247,7 +257,7 @@ static int __maybe_unused xiic_i2c_runtime_suspend(struct device *dev) + return 0; + } + +-static int __maybe_unused xiic_i2c_runtime_resume(struct device *dev) ++static int xiic_i2c_runtime_resume(struct device *dev) + { + struct xiic_i2c *i2c = dev_get_drvdata(dev); + int ret; +@@ -397,9 +407,10 @@ static int xiic_setclk(struct xiic_i2c *i2c) + unsigned int index = 0; + u32 reg_val; + +- dev_dbg(i2c->adap.dev.parent, +- "%s entry, i2c->input_clk: %ld, i2c->i2c_clk: %d\n", +- __func__, i2c->input_clk, i2c->i2c_clk); ++ if (!i2c->atomic) ++ dev_dbg(i2c->adap.dev.parent, ++ "%s entry, i2c->input_clk: %ld, i2c->i2c_clk: %d\n", ++ __func__, i2c->input_clk, i2c->i2c_clk); + + /* If not specified in DT, do not configure in SW. Rely only on Vivado design */ + if (!i2c->i2c_clk || !i2c->input_clk) +@@ -490,7 +501,8 @@ static int xiic_reinit(struct xiic_i2c *i2c) + return ret; + + /* Enable interrupts */ +- xiic_setreg32(i2c, XIIC_DGIER_OFFSET, XIIC_GINTR_ENABLE_MASK); ++ if (!i2c->atomic) ++ xiic_setreg32(i2c, XIIC_DGIER_OFFSET, XIIC_GINTR_ENABLE_MASK); + + xiic_irq_clr_en(i2c, XIIC_INTR_ARB_LOST_MASK); + +@@ -572,11 +584,12 @@ static void xiic_read_rx(struct xiic_i2c *i2c) + + bytes_in_fifo = xiic_getreg8(i2c, XIIC_RFO_REG_OFFSET) + 1; + +- dev_dbg(i2c->adap.dev.parent, +- "%s entry, bytes in fifo: %d, rem: %d, SR: 0x%x, CR: 0x%x\n", +- __func__, bytes_in_fifo, xiic_rx_space(i2c), +- xiic_getreg8(i2c, XIIC_SR_REG_OFFSET), +- xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); ++ if (!i2c->atomic) ++ dev_dbg(i2c->adap.dev.parent, ++ "%s entry, bytes in fifo: %d, rem: %d, SR: 0x%x, CR: 0x%x\n", ++ __func__, bytes_in_fifo, xiic_rx_space(i2c), ++ xiic_getreg8(i2c, XIIC_SR_REG_OFFSET), ++ xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); + + if (bytes_in_fifo > xiic_rx_space(i2c)) + bytes_in_fifo = xiic_rx_space(i2c); +@@ -635,6 +648,26 @@ static void xiic_read_rx(struct xiic_i2c *i2c) + } + } + ++static bool xiic_error_check(struct xiic_i2c *i2c) ++{ ++ bool status = false; ++ u32 pend, isr, ier; ++ ++ isr = xiic_getreg32(i2c, XIIC_IISR_OFFSET); ++ ier = xiic_getreg32(i2c, XIIC_IIER_OFFSET); ++ pend = isr & ier; ++ ++ if ((pend & XIIC_INTR_ARB_LOST_MASK) || ++ ((pend & XIIC_INTR_TX_ERROR_MASK) && ++ !(pend & XIIC_INTR_RX_FULL_MASK))) { ++ xiic_reinit(i2c); ++ status = true; ++ if (i2c->tx_msg || i2c->rx_msg) ++ i2c->atomic_xfer_state = STATE_ERROR; ++ } ++ return status; ++} ++ + static int xiic_tx_fifo_space(struct xiic_i2c *i2c) + { + /* return the actual space left in the FIFO */ +@@ -648,8 +681,9 @@ static void xiic_fill_tx_fifo(struct xiic_i2c *i2c) + + len = (len > fifo_space) ? fifo_space : len; + +- dev_dbg(i2c->adap.dev.parent, "%s entry, len: %d, fifo space: %d\n", +- __func__, len, fifo_space); ++ if (!i2c->atomic) ++ dev_dbg(i2c->adap.dev.parent, "%s entry, len: %d, fifo space: %d\n", ++ __func__, len, fifo_space); + + while (len--) { + u16 data = i2c->tx_msg->buf[i2c->tx_pos++]; +@@ -672,9 +706,13 @@ static void xiic_fill_tx_fifo(struct xiic_i2c *i2c) + xiic_setreg8(i2c, XIIC_CR_REG_OFFSET, cr & + ~XIIC_CR_MSMS_MASK); + } +- dev_dbg(i2c->adap.dev.parent, "%s TX STOP\n", __func__); ++ if (!i2c->atomic) ++ dev_dbg(i2c->adap.dev.parent, "%s TX STOP\n", __func__); + } + xiic_setreg16(i2c, XIIC_DTR_REG_OFFSET, data); ++ ++ if (i2c->atomic && xiic_error_check(i2c)) ++ return; + } + } + +@@ -877,22 +915,51 @@ static int xiic_wait_not_busy(struct xiic_i2c *i2c) + */ + err = xiic_bus_busy(i2c); + while (err && tries--) { +- msleep(1); ++ if (i2c->atomic) ++ udelay(1000); ++ else ++ usleep_range(1000, 1100); + err = xiic_bus_busy(i2c); + } + + return err; + } + ++static void xiic_recv_atomic(struct xiic_i2c *i2c) ++{ ++ while (xiic_rx_space(i2c)) { ++ if (xiic_getreg32(i2c, XIIC_IISR_OFFSET) & XIIC_INTR_RX_FULL_MASK) { ++ xiic_read_rx(i2c); ++ ++ /* Clear Rx full and Tx error interrupts. */ ++ xiic_irq_clr_en(i2c, XIIC_INTR_RX_FULL_MASK | ++ XIIC_INTR_TX_ERROR_MASK); ++ } ++ if (xiic_error_check(i2c)) ++ return; ++ } ++ ++ i2c->rx_msg = NULL; ++ xiic_irq_clr_en(i2c, XIIC_INTR_TX_ERROR_MASK); ++ ++ /* send next message if this wasn't the last. */ ++ if (i2c->nmsgs > 1) { ++ i2c->nmsgs--; ++ i2c->tx_msg++; ++ __xiic_start_xfer(i2c); ++ } ++} ++ + static void xiic_start_recv(struct xiic_i2c *i2c) + { + u16 rx_watermark; + u8 cr = 0, rfd_set = 0; + struct i2c_msg *msg = i2c->rx_msg = i2c->tx_msg; + +- dev_dbg(i2c->adap.dev.parent, "%s entry, ISR: 0x%x, CR: 0x%x\n", +- __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), +- xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); ++ if (!i2c->atomic) ++ dev_dbg(i2c->adap.dev.parent, "%s entry, ISR: 0x%x, CR: 0x%x\n", ++ __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), ++ xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); + + /* Disable Tx interrupts */ + xiic_irq_dis(i2c, XIIC_INTR_TX_HALF_MASK | XIIC_INTR_TX_EMPTY_MASK); +@@ -990,9 +1057,10 @@ static void xiic_start_recv(struct xiic_i2c *i2c) + XIIC_CR_MSMS_MASK) + & ~(XIIC_CR_DIR_IS_TX_MASK)); + } +- dev_dbg(i2c->adap.dev.parent, "%s end, ISR: 0x%x, CR: 0x%x\n", +- __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), +- xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); ++ if (!i2c->atomic) ++ dev_dbg(i2c->adap.dev.parent, "%s end, ISR: 0x%x, CR: 0x%x\n", ++ __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), ++ xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); + } + + if (i2c->nmsgs == 1) +@@ -1002,10 +1070,55 @@ static void xiic_start_recv(struct xiic_i2c *i2c) + /* the message is tx:ed */ + i2c->tx_pos = msg->len; + ++ i2c->prev_msg_tx = false; ++ + /* Enable interrupts */ +- xiic_setreg32(i2c, XIIC_DGIER_OFFSET, XIIC_GINTR_ENABLE_MASK); ++ if (!i2c->atomic) ++ xiic_setreg32(i2c, XIIC_DGIER_OFFSET, XIIC_GINTR_ENABLE_MASK); ++ else ++ xiic_recv_atomic(i2c); ++} + +- i2c->prev_msg_tx = false; ++static void xiic_send_rem_atomic(struct xiic_i2c *i2c) ++{ ++ while (xiic_tx_space(i2c)) { ++ if (xiic_tx_fifo_space(i2c)) { ++ u16 data; ++ ++ data = i2c->tx_msg->buf[i2c->tx_pos]; ++ i2c->tx_pos++; ++ if (!xiic_tx_space(i2c) && i2c->nmsgs == 1) { ++ /* last message in transfer -> STOP */ ++ if (i2c->dynamic) { ++ data |= XIIC_TX_DYN_STOP_MASK; ++ } else { ++ u8 cr; ++ int status; ++ ++ /* Wait till FIFO is empty so STOP is sent last */ ++ status = xiic_wait_tx_empty(i2c); ++ if (status) ++ return; ++ ++ /* Write to CR to stop */ ++ cr = xiic_getreg8(i2c, XIIC_CR_REG_OFFSET); ++ xiic_setreg8(i2c, XIIC_CR_REG_OFFSET, cr & ++ ~XIIC_CR_MSMS_MASK); ++ } ++ } ++ xiic_setreg16(i2c, XIIC_DTR_REG_OFFSET, data); ++ } ++ if (xiic_error_check(i2c)) ++ return; ++ } ++ ++ if (i2c->nmsgs > 1) { ++ i2c->nmsgs--; ++ i2c->tx_msg++; ++ __xiic_start_xfer(i2c); ++ } else { ++ xiic_irq_dis(i2c, XIIC_INTR_TX_HALF_MASK); ++ } + } + + static void xiic_start_send(struct xiic_i2c *i2c) +@@ -1014,11 +1127,13 @@ static void xiic_start_send(struct xiic_i2c *i2c) + u16 data; + struct i2c_msg *msg = i2c->tx_msg; + +- dev_dbg(i2c->adap.dev.parent, "%s entry, msg: %p, len: %d", +- __func__, msg, msg->len); +- dev_dbg(i2c->adap.dev.parent, "%s entry, ISR: 0x%x, CR: 0x%x\n", +- __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), +- xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); ++ if (!i2c->atomic) { ++ dev_dbg(i2c->adap.dev.parent, "%s entry, msg: %p, len: %d", ++ __func__, msg, msg->len); ++ dev_dbg(i2c->adap.dev.parent, "%s entry, ISR: 0x%x, CR: 0x%x\n", ++ __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), ++ xiic_getreg8(i2c, XIIC_CR_REG_OFFSET)); ++ } + + if (i2c->dynamic) { + /* write the address */ +@@ -1083,19 +1198,27 @@ static void xiic_start_send(struct xiic_i2c *i2c) + XIIC_INTR_TX_ERROR_MASK | + XIIC_INTR_BNB_MASK); + } ++ + i2c->prev_msg_tx = true; ++ ++ if (i2c->atomic && !i2c->atomic_xfer_state) ++ xiic_send_rem_atomic(i2c); + } + + static void __xiic_start_xfer(struct xiic_i2c *i2c) + { + int fifo_space = xiic_tx_fifo_space(i2c); + +- dev_dbg(i2c->adap.dev.parent, "%s entry, msg: %p, fifos space: %d\n", +- __func__, i2c->tx_msg, fifo_space); ++ if (!i2c->atomic) ++ dev_dbg(i2c->adap.dev.parent, "%s entry, msg: %p, fifos space: %d\n", ++ __func__, i2c->tx_msg, fifo_space); + + if (!i2c->tx_msg) + return; + ++ if (i2c->atomic && xiic_error_check(i2c)) ++ return; ++ + i2c->rx_pos = 0; + i2c->tx_pos = 0; + i2c->state = STATE_START; +@@ -1112,7 +1235,10 @@ static int xiic_start_xfer(struct xiic_i2c *i2c, struct i2c_msg *msgs, int num) + bool broken_read, max_read_len, smbus_blk_read; + int ret, count; + +- mutex_lock(&i2c->lock); ++ if (i2c->atomic) ++ spin_lock(&i2c->atomic_lock); ++ else ++ mutex_lock(&i2c->lock); + + if (i2c->tx_msg || i2c->rx_msg) { + dev_err(i2c->adap.dev.parent, +@@ -1121,6 +1247,8 @@ static int xiic_start_xfer(struct xiic_i2c *i2c, struct i2c_msg *msgs, int num) + goto out; + } + ++ i2c->atomic_xfer_state = STATE_DONE; ++ + /* In single master mode bus can only be busy, when in use by this + * driver. If the register indicates bus being busy for some reason we + * should ignore it, since bus will never be released and i2c will be +@@ -1147,7 +1275,9 @@ static int xiic_start_xfer(struct xiic_i2c *i2c, struct i2c_msg *msgs, int num) + i2c->tx_msg = msgs; + i2c->rx_msg = NULL; + i2c->nmsgs = num; +- init_completion(&i2c->completion); ++ ++ if (!i2c->atomic) ++ init_completion(&i2c->completion); + + /* Decide standard mode or Dynamic mode */ + i2c->dynamic = true; +@@ -1182,7 +1312,10 @@ static int xiic_start_xfer(struct xiic_i2c *i2c, struct i2c_msg *msgs, int num) + __xiic_start_xfer(i2c); + + out: +- mutex_unlock(&i2c->lock); ++ if (i2c->atomic) ++ spin_unlock(&i2c->atomic_lock); ++ else ++ mutex_unlock(&i2c->lock); + + return ret; + } +@@ -1221,6 +1354,44 @@ static int xiic_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) + return err; + } + ++static int xiic_xfer_atomic(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) ++{ ++ struct xiic_i2c *i2c = i2c_get_adapdata(adap); ++ u32 status_reg; ++ int err; ++ ++ err = xiic_i2c_runtime_resume(i2c->dev); ++ if (err) ++ return err; ++ ++ i2c->atomic = true; ++ err = xiic_start_xfer(i2c, msgs, num); ++ if (err < 0) ++ return err; ++ ++ err = readl_poll_timeout_atomic(i2c->base + XIIC_SR_REG_OFFSET, ++ status_reg, !(status_reg & XIIC_SR_BUS_BUSY_MASK), ++ 1, XIIC_XFER_TIMEOUT_US); ++ ++ if (err) /* Timeout */ ++ err = -ETIMEDOUT; ++ ++ spin_lock(&i2c->atomic_lock); ++ if (err || i2c->state) { ++ i2c->tx_msg = NULL; ++ i2c->rx_msg = NULL; ++ i2c->nmsgs = 0; ++ } ++ ++ err = (i2c->atomic_xfer_state == STATE_DONE) ? num : -EIO; ++ spin_unlock(&i2c->atomic_lock); ++ ++ i2c->atomic = false; ++ xiic_i2c_runtime_suspend(i2c->dev); ++ ++ return err; ++} ++ + static u32 xiic_func(struct i2c_adapter *adap) + { + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; +@@ -1228,6 +1399,7 @@ static u32 xiic_func(struct i2c_adapter *adap) + + static const struct i2c_algorithm xiic_algorithm = { + .master_xfer = xiic_xfer, ++ .master_xfer_atomic = xiic_xfer_atomic, + .functionality = xiic_func, + }; + +@@ -1291,6 +1463,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + DRIVER_NAME " %s", pdev->name); + + mutex_init(&i2c->lock); ++ spin_lock_init(&i2c->atomic_lock); + + i2c->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(i2c->clk)) +-- +2.53.0 + diff --git a/patches-sonic/0008-mlxbf-bootctl-use-sysfs_emit_at-in-secure_boot_fuse_.patch b/patches-sonic/0008-mlxbf-bootctl-use-sysfs_emit_at-in-secure_boot_fuse_.patch new file mode 100644 index 000000000..8afc38aa6 --- /dev/null +++ b/patches-sonic/0008-mlxbf-bootctl-use-sysfs_emit_at-in-secure_boot_fuse_.patch @@ -0,0 +1,47 @@ +From 23b2eeacd3bfb7365002f623cbce2fadda8175ac Mon Sep 17 00:00:00 2001 +From: David Thompson +Date: Mon, 7 Apr 2025 13:25:58 +0000 +Subject: [PATCH 08/78] mlxbf-bootctl: use sysfs_emit_at() in + secure_boot_fuse_state_show() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +A warning is seen when running the latest kernel on a BlueField SOC: +[251.512704] ------------[ cut here ]------------ +[251.512711] invalid sysfs_emit: buf:0000000003aa32ae +[251.512720] WARNING: CPU: 1 PID: 705264 at fs/sysfs/file.c:767 sysfs_emit+0xac/0xc8 + +The warning is triggered because the mlxbf-bootctl driver invokes +"sysfs_emit()" with a buffer pointer that is not aligned to the +start of the page. The driver should instead use "sysfs_emit_at()" +to support non-zero offsets into the destination buffer. + +Fixes: 9886f575de5a ("platform/mellanox: mlxbf-bootctl: use sysfs_emit() instead of sprintf()") +Signed-off-by: David Thompson +Link: https://lore.kernel.org/r/20250407132558.2418719-1-davthompson@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxbf-bootctl.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c +index 25ffae1..fd7fb42 100644 +--- a/drivers/platform/mellanox/mlxbf-bootctl.c ++++ b/drivers/platform/mellanox/mlxbf-bootctl.c +@@ -333,9 +333,9 @@ static ssize_t secure_boot_fuse_state_show(struct device *dev, + else + status = valid ? "Invalid" : "Free"; + } +- buf_len += sysfs_emit(buf + buf_len, "%d:%s ", key, status); ++ buf_len += sysfs_emit_at(buf, buf_len, "%d:%s ", key, status); + } +- buf_len += sysfs_emit(buf + buf_len, "\n"); ++ buf_len += sysfs_emit_at(buf, buf_len, "\n"); + + return buf_len; + } +-- +2.8.4 + diff --git a/patches-sonic/0008-mv6xxx-Fix-i2c-lock-due-to-arb-loss.patch b/patches-sonic/0008-mv6xxx-Fix-i2c-lock-due-to-arb-loss.patch new file mode 100644 index 000000000..df5cdb015 --- /dev/null +++ b/patches-sonic/0008-mv6xxx-Fix-i2c-lock-due-to-arb-loss.patch @@ -0,0 +1,174 @@ +From f8044a555a9994bbf401ce028450d285942d9dde Mon Sep 17 00:00:00 2001 +From: Keshav Gupta +Date: Mon, 16 Oct 2023 15:14:03 +0000 +Subject: [PATCH] mv6xxx: Fix i2c lock due to arb-loss + +Some i2c slaves, mainly SFPs, might cause the bus to lost arbitration +while slave is in the middle of responding. + +The solution is to change the I2C mpps to gpios, and toggle the i2c_scl +gpio to emulate bus toggling, so slave will finish its transimission. + +Tested-by: Raz Adashi +Reviewed-by: Raz Adashi +Signed-off-by: Noam Liron +Signed-off-by: Yan Markman +--- + drivers/i2c/busses/i2c-mv64xxx.c | 85 ++++++++++++++++++++++++++++++++ + 1 file changed, 85 insertions(+) + +diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c +index 29f94efed..13e9bdeb4 100644 +--- a/drivers/i2c/busses/i2c-mv64xxx.c ++++ b/drivers/i2c/busses/i2c-mv64xxx.c +@@ -9,6 +9,8 @@ + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ ++#undef UI_I2C_DEBUG ++ + #include + #include + #include +@@ -26,6 +28,7 @@ + #include + #include + #include ++#include + + #define MV64XXX_I2C_ADDR_ADDR(val) ((val & 0x7f) << 1) + #define MV64XXX_I2C_BAUD_DIV_N(val) (val & 0x7) +@@ -104,6 +107,7 @@ enum { + MV64XXX_I2C_ACTION_RCV_DATA, + MV64XXX_I2C_ACTION_RCV_DATA_STOP, + MV64XXX_I2C_ACTION_SEND_STOP, ++ MV64XXX_I2C_ACTION_UNLOCK_BUS + }; + + struct mv64xxx_i2c_regs { +@@ -150,6 +154,11 @@ struct mv64xxx_i2c_data { + bool clk_n_base_0; + struct i2c_bus_recovery_info rinfo; + bool atomic; ++ /* I2C mpp states & gpios needed for ARB lost recovery */ ++ int scl_gpio, sda_gpio; ++ bool arb_lost_reovery_ena; ++ struct pinctrl_state *i2c_mpp_state; ++ struct pinctrl_state *i2c_gpio_state; + }; + + static struct mv64xxx_i2c_regs mv64xxx_i2c_regs_mv64xxx = { +@@ -318,6 +327,11 @@ mv64xxx_i2c_fsm(struct mv64xxx_i2c_data *drv_data, u32 status) + drv_data->state = MV64XXX_I2C_STATE_IDLE; + break; + ++ case MV64XXX_I2C_STATUS_MAST_LOST_ARB: /*0x38*/ ++ drv_data->action = MV64XXX_I2C_ACTION_UNLOCK_BUS; ++ drv_data->state = MV64XXX_I2C_STATE_IDLE; ++ break; ++ + case MV64XXX_I2C_STATUS_MAST_WR_ADDR_NO_ACK: /* 0x20 */ + case MV64XXX_I2C_STATUS_MAST_WR_NO_ACK: /* 30 */ + case MV64XXX_I2C_STATUS_MAST_RD_ADDR_NO_ACK: /* 48 */ +@@ -356,6 +370,9 @@ static void mv64xxx_i2c_send_start(struct mv64xxx_i2c_data *drv_data) + static void + mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data) + { ++ struct pinctrl *pc; ++ int i, ret, ret2; ++ + switch(drv_data->action) { + case MV64XXX_I2C_ACTION_SEND_RESTART: + /* We should only get here if we have further messages */ +@@ -409,6 +426,50 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data) + drv_data->reg_base + drv_data->reg_offsets.control); + break; + ++ case MV64XXX_I2C_ACTION_UNLOCK_BUS: ++ ++ if (!drv_data->arb_lost_reovery_ena) ++ break; ++ ++ pc = devm_pinctrl_get(drv_data->adapter.dev.parent); ++ if (IS_ERR(pc)) ++ break; ++ ++ /* Move i2c MPPs to GPIOs */ ++ if (pinctrl_select_state(pc, drv_data->i2c_gpio_state) >= 0) { ++ /* Get GPIO for temporary usage */ ++ ret = devm_gpio_request_one(drv_data->adapter.dev.parent, ++ drv_data->scl_gpio, GPIOF_OUT_INIT_LOW, NULL); ++ ret2 = devm_gpio_request_one(drv_data->adapter.dev.parent, ++ drv_data->sda_gpio, GPIOF_OUT_INIT_LOW, NULL); ++ if (!ret && !ret2) { ++ /* toggle i2c scl 10 times, for the slave that occupies ++ * the bus Tx its remaining data, and release the bus ++ */ ++ for (i = 0; i < 10; i++) { ++ gpio_set_value(drv_data->scl_gpio, 1); ++ mdelay(1); ++ gpio_set_value(drv_data->scl_gpio, 0); ++ }; ++ /* Free temporary GPIO */ ++ gpio_free(drv_data->scl_gpio); ++ gpio_free(drv_data->sda_gpio); ++ } else { ++ if (!ret) ++ gpio_free(drv_data->scl_gpio); ++ if (!ret2) ++ gpio_free(drv_data->sda_gpio); ++ } ++ ++ /* restore i2c MPPs */ ++ pinctrl_select_state(pc, drv_data->i2c_mpp_state); ++ } ++ ++ /* Trigger controller soft reset and restore MPPs */ ++ writel(0x1, drv_data->reg_base + drv_data->reg_offsets.soft_reset); ++ mdelay(1); ++ fallthrough; ++ + case MV64XXX_I2C_ACTION_RCV_DATA_STOP: + drv_data->msg->buf[drv_data->byte_posn++] = + readl(drv_data->reg_base + drv_data->reg_offsets.data); +@@ -985,6 +1046,7 @@ mv64xxx_i2c_probe(struct platform_device *pd) + { + struct mv64xxx_i2c_data *drv_data; + struct mv64xxx_i2c_pdata *pdata = dev_get_platdata(&pd->dev); ++ struct pinctrl *pc; + int rc; + + if ((!pdata && !pd->dev.of_node)) +@@ -1040,6 +1102,29 @@ mv64xxx_i2c_probe(struct platform_device *pd) + if (rc == -EPROBE_DEFER) + return rc; + ++ drv_data->arb_lost_reovery_ena = false; ++ pc = devm_pinctrl_get(&pd->dev); ++ if (!IS_ERR(pc)) { ++ drv_data->i2c_mpp_state = ++ pinctrl_lookup_state(pc, "default"); ++ drv_data->i2c_gpio_state = ++ pinctrl_lookup_state(pc, "gpio"); ++ drv_data->scl_gpio = ++ of_get_named_gpio(pd->dev.of_node, "scl-gpios", 0); ++ drv_data->sda_gpio = ++ of_get_named_gpio(pd->dev.of_node, "sda-gpios", 0); ++ ++ if (!IS_ERR(drv_data->i2c_gpio_state) && ++ !IS_ERR(drv_data->i2c_mpp_state) && ++ gpio_is_valid(drv_data->scl_gpio) && ++ gpio_is_valid(drv_data->sda_gpio)) ++ drv_data->arb_lost_reovery_ena = true; ++ } ++ ++ if (!drv_data->arb_lost_reovery_ena) ++ dev_info(&pd->dev, ++ "mv64xxx: missing ARB-lost recovery defs in dts file\n"); ++ + drv_data->adapter.dev.parent = &pd->dev; + drv_data->adapter.algo = &mv64xxx_i2c_algo; + drv_data->adapter.owner = THIS_MODULE; +-- +2.34.1 + diff --git a/patches-sonic/0009-PCI-AER-Add-sysfs-attributes-for-log-ratelimits.patch b/patches-sonic/0009-PCI-AER-Add-sysfs-attributes-for-log-ratelimits.patch new file mode 100644 index 000000000..70b3ce45c --- /dev/null +++ b/patches-sonic/0009-PCI-AER-Add-sysfs-attributes-for-log-ratelimits.patch @@ -0,0 +1,272 @@ +From d9396d86c78fee6c5eab356abb7e4b86dce1be32 Mon Sep 17 00:00:00 2001 +From: Jon Pan-Doh +Date: Thu, 22 May 2025 18:21:26 -0500 +Subject: [PATCH 09/12] PCI/AER: Add sysfs attributes for log ratelimits + +Allow userspace to read/write log ratelimits per device (including +enable/disable). Create aer/ sysfs directory to store them and any +future AER configs. + +The new sysfs files are: + + /sys/bus/pci/devices/*/aer/correctable_ratelimit_burst + /sys/bus/pci/devices/*/aer/correctable_ratelimit_interval_ms + /sys/bus/pci/devices/*/aer/nonfatal_ratelimit_burst + /sys/bus/pci/devices/*/aer/nonfatal_ratelimit_interval_ms + +The default values are ratelimit_burst=10, ratelimit_interval_ms=5000, so +if we try to emit more than 10 messages in a 5 second period, some are +suppressed. + +Update AER sysfs ABI filename to reflect the broader scope of AER sysfs +attributes (e.g. stats and ratelimits). + + Documentation/ABI/testing/sysfs-bus-pci-devices-aer_stats -> + sysfs-bus-pci-devices-aer + +Tested using aer-inject[1]. Configured correctable log ratelimit to 5. +Sent 6 AER errors. Observed 5 errors logged while AER stats +(cat /sys/bus/pci/devices//aer_dev_correctable) shows 6. + +Disabled ratelimiting and sent 6 more AER errors. Observed all 6 errors +logged and accounted in AER stats (12 total errors). + +[1] https://git.kernel.org/pub/scm/linux/kernel/git/gong.chen/aer-inject.git + +[bhelgaas: note fatal errors are not ratelimited, "aer_report" -> +"aer_info", replace ratelimit_log_enable toggle with *_ratelimit_interval_ms] + +Signed-off-by: Karolina Stolarek +Signed-off-by: Jon Pan-Doh +Signed-off-by: Bjorn Helgaas +Reviewed-by: Kuppuswamy Sathyanarayanan +Link: https://patch.msgid.link/20250522232339.1525671-21-helgaas@kernel.org +(cherry picked from commit b4fe7398def6df344442b884d2288f80205cbd2d) +--- + ...es-aer_stats => sysfs-bus-pci-devices-aer} | 44 ++++++++ + Documentation/PCI/pcieaer-howto.rst | 5 +- + drivers/pci/pci-sysfs.c | 1 + + drivers/pci/pci.h | 1 + + drivers/pci/pcie/aer.c | 105 ++++++++++++++++++ + 5 files changed, 155 insertions(+), 1 deletion(-) + rename Documentation/ABI/testing/{sysfs-bus-pci-devices-aer_stats => sysfs-bus-pci-devices-aer} (72%) + +diff --git a/Documentation/ABI/testing/sysfs-bus-pci-devices-aer_stats b/Documentation/ABI/testing/sysfs-bus-pci-devices-aer +similarity index 72% +rename from Documentation/ABI/testing/sysfs-bus-pci-devices-aer_stats +rename to Documentation/ABI/testing/sysfs-bus-pci-devices-aer +index d1f67bb81d5d..5ed284523956 100644 +--- a/Documentation/ABI/testing/sysfs-bus-pci-devices-aer_stats ++++ b/Documentation/ABI/testing/sysfs-bus-pci-devices-aer +@@ -117,3 +117,47 @@ Date: July 2018 + KernelVersion: 4.19.0 + Contact: linux-pci@vger.kernel.org, rajatja@google.com + Description: Total number of ERR_NONFATAL messages reported to rootport. ++ ++PCIe AER ratelimits ++------------------- ++ ++These attributes show up under all the devices that are AER capable. ++They represent configurable ratelimits of logs per error type. ++ ++See Documentation/PCI/pcieaer-howto.rst for more info on ratelimits. ++ ++What: /sys/bus/pci/devices//aer/correctable_ratelimit_interval_ms ++Date: May 2025 ++KernelVersion: 6.16.0 ++Contact: linux-pci@vger.kernel.org ++Description: Writing 0 disables AER correctable error log ratelimiting. ++ Writing a positive value sets the ratelimit interval in ms. ++ Default is DEFAULT_RATELIMIT_INTERVAL (5000 ms). ++ ++What: /sys/bus/pci/devices//aer/correctable_ratelimit_burst ++Date: May 2025 ++KernelVersion: 6.16.0 ++Contact: linux-pci@vger.kernel.org ++Description: Ratelimit burst for correctable error logs. Writing a value ++ changes the number of errors (burst) allowed per interval ++ before ratelimiting. Reading gets the current ratelimit ++ burst. Default is DEFAULT_RATELIMIT_BURST (10). ++ ++What: /sys/bus/pci/devices//aer/nonfatal_ratelimit_interval_ms ++Date: May 2025 ++KernelVersion: 6.16.0 ++Contact: linux-pci@vger.kernel.org ++Description: Writing 0 disables AER non-fatal uncorrectable error log ++ ratelimiting. Writing a positive value sets the ratelimit ++ interval in ms. Default is DEFAULT_RATELIMIT_INTERVAL ++ (5000 ms). ++ ++What: /sys/bus/pci/devices//aer/nonfatal_ratelimit_burst ++Date: May 2025 ++KernelVersion: 6.16.0 ++Contact: linux-pci@vger.kernel.org ++Description: Ratelimit burst for non-fatal uncorrectable error logs. ++ Writing a value changes the number of errors (burst) ++ allowed per interval before ratelimiting. Reading gets the ++ current ratelimit burst. Default is DEFAULT_RATELIMIT_BURST ++ (10). +diff --git a/Documentation/PCI/pcieaer-howto.rst b/Documentation/PCI/pcieaer-howto.rst +index f013f3b27c82..896d6aea6984 100644 +--- a/Documentation/PCI/pcieaer-howto.rst ++++ b/Documentation/PCI/pcieaer-howto.rst +@@ -85,12 +85,15 @@ In the example, 'Requester ID' means the ID of the device that sent + the error message to the Root Port. Please refer to PCIe specs for other + fields. + ++Ratelimits are exposed in the form of sysfs attributes and configurable. ++See Documentation/ABI/testing/sysfs-bus-pci-devices-aer. ++ + AER Statistics / Counters + ------------------------- + + When PCIe AER errors are captured, the counters / statistics are also exposed + in the form of sysfs attributes which are documented at +-Documentation/ABI/testing/sysfs-bus-pci-devices-aer_stats ++Documentation/ABI/testing/sysfs-bus-pci-devices-aer + + Developer Guide + =============== +diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c +index 5af4a804a4f8..bc7b9974b6d0 100644 +--- a/drivers/pci/pci-sysfs.c ++++ b/drivers/pci/pci-sysfs.c +@@ -1689,6 +1689,7 @@ const struct attribute_group *pci_dev_attr_groups[] = { + &pcie_dev_attr_group, + #ifdef CONFIG_PCIEAER + &aer_stats_attr_group, ++ &aer_attr_group, + #endif + #ifdef CONFIG_PCIEASPM + &aspm_ctrl_attr_group, +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index f4372bb39fe2..fcff31039d17 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -833,6 +833,7 @@ void pci_no_aer(void); + void pci_aer_init(struct pci_dev *dev); + void pci_aer_exit(struct pci_dev *dev); + extern const struct attribute_group aer_stats_attr_group; ++extern const struct attribute_group aer_attr_group; + void pci_aer_clear_fatal_status(struct pci_dev *dev); + int pci_aer_clear_status(struct pci_dev *dev); + int pci_aer_raw_clear_status(struct pci_dev *dev); +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 8d0152f4fe50..df636016168f 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -622,6 +622,111 @@ const struct attribute_group aer_stats_attr_group = { + .is_visible = aer_stats_attrs_are_visible, + }; + ++/* ++ * Ratelimit interval ++ * <=0: disabled with ratelimit.interval = 0 ++ * >0: enabled with ratelimit.interval in ms ++ */ ++#define aer_ratelimit_interval_attr(name, ratelimit) \ ++ static ssize_t \ ++ name##_show(struct device *dev, struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ struct pci_dev *pdev = to_pci_dev(dev); \ ++ \ ++ return sysfs_emit(buf, "%d\n", \ ++ pdev->aer_stats->ratelimit.interval); \ ++ } \ ++ \ ++ static ssize_t \ ++ name##_store(struct device *dev, struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ struct pci_dev *pdev = to_pci_dev(dev); \ ++ int interval; \ ++ \ ++ if (!capable(CAP_SYS_ADMIN)) \ ++ return -EPERM; \ ++ \ ++ if (kstrtoint(buf, 0, &interval) < 0) \ ++ return -EINVAL; \ ++ \ ++ if (interval <= 0) \ ++ interval = 0; \ ++ else \ ++ interval = msecs_to_jiffies(interval); \ ++ \ ++ pdev->aer_stats->ratelimit.interval = interval; \ ++ \ ++ return count; \ ++ } \ ++ static DEVICE_ATTR_RW(name); ++ ++#define aer_ratelimit_burst_attr(name, ratelimit) \ ++ static ssize_t \ ++ name##_show(struct device *dev, struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ struct pci_dev *pdev = to_pci_dev(dev); \ ++ \ ++ return sysfs_emit(buf, "%d\n", \ ++ pdev->aer_stats->ratelimit.burst); \ ++ } \ ++ \ ++ static ssize_t \ ++ name##_store(struct device *dev, struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ struct pci_dev *pdev = to_pci_dev(dev); \ ++ int burst; \ ++ \ ++ if (!capable(CAP_SYS_ADMIN)) \ ++ return -EPERM; \ ++ \ ++ if (kstrtoint(buf, 0, &burst) < 0) \ ++ return -EINVAL; \ ++ \ ++ pdev->aer_stats->ratelimit.burst = burst; \ ++ \ ++ return count; \ ++ } \ ++ static DEVICE_ATTR_RW(name); ++ ++#define aer_ratelimit_attrs(name) \ ++ aer_ratelimit_interval_attr(name##_ratelimit_interval_ms, \ ++ name##_ratelimit) \ ++ aer_ratelimit_burst_attr(name##_ratelimit_burst, \ ++ name##_ratelimit) ++ ++aer_ratelimit_attrs(correctable) ++aer_ratelimit_attrs(nonfatal) ++ ++static struct attribute *aer_attrs[] = { ++ &dev_attr_correctable_ratelimit_interval_ms.attr, ++ &dev_attr_correctable_ratelimit_burst.attr, ++ &dev_attr_nonfatal_ratelimit_interval_ms.attr, ++ &dev_attr_nonfatal_ratelimit_burst.attr, ++ NULL ++}; ++ ++static umode_t aer_attrs_are_visible(struct kobject *kobj, ++ struct attribute *a, int n) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct pci_dev *pdev = to_pci_dev(dev); ++ ++ if (!pdev->aer_stats) ++ return 0; ++ ++ return a->mode; ++} ++ ++const struct attribute_group aer_attr_group = { ++ .name = "aer", ++ .attrs = aer_attrs, ++ .is_visible = aer_attrs_are_visible, ++}; ++ + static void pci_dev_aer_stats_incr(struct pci_dev *pdev, + struct aer_err_info *info) + { +-- +2.47.0 + diff --git a/patches-sonic/0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch b/patches-sonic/0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch new file mode 100644 index 000000000..630769244 --- /dev/null +++ b/patches-sonic/0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch @@ -0,0 +1,620 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 4 Jun 2026 11:03:21 +0300 +Subject: [PATCH 6.12 1/1] arm64: dts: aspeed: split SPC6 BMC DTS for SONiC + and OpenBMC boot +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +SONiC and OpenBMC use different rootfs boot paths on the same board. + +OpenBMC loads rootfs from SPI via phram: +(phram.phram=ramrofs,0x403920000,… and root=/dev/ram). +The device tree therefore reserves ramrofs@403920000. + +SONiC boots from eMMC (root=UUID, squashfs loop) and does not use +phram. Keeping ramrofs in the SONiC DTB causes early boot failures +when the kernel cannot reserve that region (it overlaps the kernel +load window around 0x403000000). + +Add SONiC-specific irot/sunda fragments without the ramrofs +reservation and build aspeed-nvidia-spc6-bmc.dtb from them so +SONiC FIT configs stay unchanged. +Add aspeed-nvidia-spc6-bmc-openbmc.dts for OpenBMC (phram/SPI) +with the existing ramrofs node and irot.dtsi. + +Signed-off-by: Vadim Pasternak +--- + .../aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts | 527 ++++++++++++++++++ + .../dts/aspeed/aspeed-nvidia-spc6-bmc.dts | 2 +- + .../dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi | 14 + + .../aspeed/nvidia-ast27xx-sunda-sonic.dtsi | 8 + + 4 files changed, 549 insertions(+), 1 deletion(-) + create mode 100644 arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts + create mode 100644 arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi + create mode 100644 arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi + +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +new file mode 100644 +index 000000000..e825c106d +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +@@ -0,0 +1,527 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/dts-v1/; ++ ++#include "aspeed-nvidia-spc6-bmc-core.dtsi" ++#include ++#include ++#include ++#include "nvidia-ast27xx-sunda.dtsi" ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700-A2 SPC6 CPU BMC"; ++ compatible = "aspeed,ast2700"; ++ ++ aliases { ++ rtc0 = &ext_rtc; /* Forces PCF85053A to be rtc0 */ ++ rtc1 = &rtc; /* Moves Aspeed Internal RTC to rtc1 */ ++ }; ++ ++ chosen { ++ stdout-path = &uart12; ++ bootargs = "console=ttyS12,115200 earlyprintk"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ // (before bmc-dev0 @0x423800000). ++ // Persistent phram region for fw_env variables across warm boots. ++ phram_env: phram-env@4237ff000 { ++ compatible = "phram"; ++ label = "phram-env"; ++ reg = <0x00000004 0x237ff000 0x0 0x00001000>; ++ no-map; ++ }; ++ }; ++}; ++ ++/* ++ * SPI0: BIOS recovery (CS0, CPLD selects which flash) and TPM (CS1, AST2700 only). ++ * AST2700 uses &spi0; AST2600 uses &spi1raw. ++ * Remove default flash@0/flash@1 from dtsi so spidev and tpm are the only children. ++ */ ++&spi0 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ /delete-node/ flash@0; ++ /delete-node/ flash@1; ++ spidev@0 { ++ /* BIOS flash: CPLD (spi_chnl_select) selects which of two flashes */ ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ status = "okay"; ++ }; ++ /* TPM on chip select 1; both ST33 and Infineon SLB9672 use tpm_tis_spi */ ++ tpm@1 { ++ compatible = "st,st33htpm-spi", "infineon,slb9670", "tcg,tpm_tis-spi"; ++ reg = <1>; ++ spi-max-frequency = <10000000>; ++ /* ++ * NOTE: this property is NOT consumed by any driver. Stock ++ * tpm_tis_spi (and all other drivers matching the compatibles ++ * above) does not call gpiod_get() for this name. That is ++ * intentional -- the SPI/TPM level shifters are enabled by the ++ * gpio-hogs on exp0 (see below), not by this device. ++ * ++ * It is kept purely as an fw_devlink ordering hint: any property ++ * whose name ends in "-gpios" is parsed by the DT core to build ++ * a supplier/consumer link. The phandle to exp0 therefore makes ++ * exp0 a supplier of tpm@1, so tpm@1 is not probed until the ++ * pcal6524 has probed and its hogs have run. Without this hint ++ * there is no ordering between the i2c5 and spi0 buses, and the ++ * TPM could be accessed before the buffers are enabled (races to ++ * "no TPM found"). Do not delete it. ++ * ++ * The property name is deliberately a private one ++ * ("buf-en-link-gpios") that no upstream driver consumes. If ++ * the property were called "enable-gpios" and a future kernel ++ * added gpiod_get(dev, "enable", ...) to tpm_tis_spi, it would ++ * collide with the hogs and fail -EBUSY. ++ */ ++ buf-en-link-gpios = <&exp0 4 GPIO_ACTIVE_LOW>, ++ <&exp0 5 GPIO_ACTIVE_LOW>; ++ }; ++}; ++ ++#if 0 ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x300000>; // 3MB ++ label = "u-boot"; ++ }; ++ ++ /* TODO: move to eMMC */ ++ u-boot-env@300000 { ++ reg = <0x300000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ ++ kernel@320000 { ++ reg = <0x320000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ rofs@c20000 { ++ reg = <0xc20000 0x3000000>; // 48MB ++ label = "rofs"; ++ }; ++ rwfs@3c20000 { ++ reg = <0x3C20000 0x3E0000>; // ~4MB ++ label = "rwfs"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++#endif ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ // BMC Debug Console ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "disabled"; ++}; ++ ++&mdio1 { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "disabled"; ++}; ++ ++&mac1 { ++ status = "disabled"; ++}; ++ ++&sgmii { ++ status = "okay"; ++ #phy-cells = <0>; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ reset-gpios = <&exp0 10 GPIO_ACTIVE_LOW>; ++ reset-delay-us = <30000>; ++ reset-post-delay-us = <30000>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ marvell,reg-init = <0 0x10 0xffff 0x3060>, ++ <3 0x10 0xF000 0x0117>, ++ <3 0x11 0x00C0 0x4405>; ++ }; ++}; ++ ++&mac2 { ++ status = "okay"; ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++ phys = <&sgmii>; ++ phy-names = "sgmii-phy"; ++}; ++ ++/* ++ * Enable port A as device (via the virtual hub) and port B as ++ * host by default on the eval board. This can be easily changed ++ * by replacing the override below with &ehci0 { ... } to enable ++ * host on both ports. ++ */ ++&vhuba0 { ++ status = "okay"; ++}; ++ ++/* USB PORT B - MCU and Power bank */ ++/* This is an assembly option */ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++/* I2C bus connecting BMC to CPU for IPMI */ ++/* Note : On Chameleon SPC6 on 2600 it will be i2c-0, on 2700 i2c-16 */ ++&i2c1 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz standard mode */ ++ /* BMC as I2C slave for IPMB */ ++ ipmb@20 { ++ compatible = "ipmb-dev"; ++ reg = <0x20>; /* BMC slave address (7-bit) */ ++ slave-dev; /* This is a slave device */ ++ }; ++}; ++ ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz */ ++ aspeed,hw-timeout-ms = <1000>; ++ multi-master; ++}; ++ ++&i2c4 { ++ status = "okay"; ++ ++ ext_rtc: rtc@6f { ++ compatible = "nxp,pcf85053a"; ++ reg = <0x6f>; ++ }; ++}; ++ ++&i2c5 { ++ status = "okay"; ++ exp0: pcal6524@20 { ++ status = "okay"; ++ compatible = "nxp,pcal6524"; ++ reg = <0x20>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reset-gpios = <&gpio1 ASPEED_GPIO(S, 0) GPIO_ACTIVE_LOW>; /*GPIO_BMC_GPIO_EXP_RESET_L*/ ++ ++ gpio-line-names = ++ "RSRVD_0", "GP_EROT_RST_IN_L", ++ "GP_3V3_BMC_PHY_PG", "GP_BMC_PWR_CYCLE_3V3_MASK_L", ++ "GP_BMC_SPI_BUF_OE_L", "GP_BMC_SPI_TPM_BUF_OE_L", ++ "SE_CPLD_EROT_BOOT_COMPLETE", "GP_BMC_WP_CTRL_L", ++ "GP_BMC_REC_SPI_MUX1_SEL", "RSRVD_1", ++ "S_BMC_GPIO_PHY_RST_L", "GP_JTAG_BMC_CPLD_BURN_EN_L", ++ "GP_PWR_BANK_PRESENT_L", "RSRVD_2", ++ "RSRVD_3", "RSRVD_4", ++ "RSRVD_5", "RSRVD_6", ++ "RSRVD_7", "GP_PROD_CS_FLASH0_EN", ++ "RSRVD_8", "GP_PROD_CS_FLASH1_EN", ++ "RSRVD_9", "RSRVD_10"; ++ /* ++ * Enable the SPI/TPM level shifters at gpiochip registration, ++ * i.e. during this node's probe -- before tpm@1 (ordered after ++ * us via the buf-en-link-gpios phandle above) touches ++ * the bus. ++ * ++ * POLARITY: these nets are active-low (..._OE_L), so physical low ++ * enables the buffer. In a gpio-hog, output-low/output-high are ++ * LOGICAL values and are inverted by GPIO_ACTIVE_LOW. Therefore ++ * "output-high" = logical 1 = physical low = buffer ENABLED. ++ * Using "output-low" here would drive the pin high and DISABLE ++ * the buffers -- the opposite of what we want. ++ */ ++ spi-buf-oe { ++ gpio-hog; ++ gpios = <4 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; ++ ++ spi-tpm-buf-oe { ++ gpio-hog; ++ gpios = <5 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_TPM_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; ++ }; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c8 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++/* Used by I3C */ ++&i2c9 { ++ status = "disabled"; ++}; ++ ++&i2c10 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c11 { ++ status = "disabled"; ++}; ++ ++&i2c12 { ++ status = "disabled"; ++}; ++ ++/* CPU master to BMC slave recovery interface */ ++/* Note: On Chameleon SPC6 multi - on 2600 it will be i2c-9, on 2700 - i2c-13 */ ++&i2c13 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++ /* ++ * BMC slave device (slave-24c02 at 0x42) is to be instantiated via ++ * bmc-i2c-slave-setup.service at boot. This approach ensures the ++ * I2C_CLIENT_SLAVE flag (0x1042) is properly set, which is required ++ * for the Aspeed I2C controller to register the hardware slave address. ++ * ++ * DTS-based instantiation doesn't set the I2C_CLIENT_SLAVE flag, causing: ++ * "i2c_slave_register: client slave flag not set. You might see address collisions" ++ * and preventing proper slave address registration in hardware. ++ * ++ * Note: "multi-master" is not required. ++ * ++ * Manual instantiation: echo slave-24c02 0x1042 > /sys/bus/i2c/devices/i2c-9/new_device ++ * Consider to provide service: /etc/systemd/system/bmc-i2c-slave-setup.service ++ */ ++}; ++ ++&i2c14 { ++ status = "okay"; ++ cpld: nvsw_bmc_hid189@31 { ++ compatible = "nvidia,hid189"; ++ reg = <0x31>; ++ interrupt-parent = <&gpio1>; ++ interrupts = ; ++ }; ++}; ++ ++&i2c15 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++// Enable emmc ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++ non-removable; ++ bus-width = <4>; ++ max-frequency = <52000000>; ++}; ++ ++/* eSPI Controller - Communication with host CPU */ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++// TODO: FIXME probably N/A for in 2700 ++#if 0 ++&lpc_ctrl { ++ status = "okay"; ++ memory-region = <&espi_memory>; ++}; ++ ++&lpc_reset { ++ status = "okay"; ++}; ++#endif ++ ++/* ++ * LPC SNOOP for POST code capture (simpler alternative to PCC) ++ * ++ * SNOOP driver benefits: ++ * - Mainline Linux driver (well-tested, stable) ++ * - Two independent channels for monitoring multiple ports ++ * - Simple FIFO-based interface via /dev/aspeed-lpc-snoop0/1 ++ * ++ * Usage: ++ * modprobe aspeed-lpc-snoop ++ * hexdump -C /dev/aspeed-lpc-snoop0 # Monitor port 0x80 ++ * hexdump -C /dev/aspeed-lpc-snoop1 # Monitor port 0x81 (if configured) ++ */ ++&pcie_lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>; /* Capture POST codes from port 0x80 */ ++ /* ++ * Optional: Add second port for dual-channel monitoring ++ * snoop-ports = <0x80>, <0x81>; ++ */ ++}; ++ ++/* ++ * PCC disabled - cannot run simultaneously with SNOOP ++ * (both use same hardware registers: SNPWADR, HICR5, HICR6) ++ */ ++&pcie_lpc0_pcc { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <1>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <2>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ /* Primary IPMI interface */ ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <3>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++// TODO: FIXME ++#if 0 ++&pinctrl0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_espi0_default>, ++ <&pinctrl_espialt_default>; pinctrl_salt0_default ???? ++}; ++#endif ++ ++/* ++ * I3C Controller for MCTP communication ++ * Note: pinctrl is already configured in aspeed-g7.dtsi base DTS ++ */ ++&i3c1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ i3c-scl-hz = <12500000>; ++ internal-pullup = <1>; ++ mctp-controller; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +index b16c766ec..3cdda09d7 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +@@ -5,7 +5,7 @@ + #include + #include + #include +-#include "nvidia-ast27xx-sunda.dtsi" ++#include "nvidia-ast27xx-sunda-sonic.dtsi" + + #define PCIE0_EP 1 // 1: EP, 0: RC + +diff --git a/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi +new file mode 100644 +index 000000000..650a72359 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi +@@ -0,0 +1,14 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++/ { ++ mctpirot0 { ++ compatible = "nvidia,ast27xx,irot"; ++ mboxes = <&mbox0 0>; ++ mbox-names = "irot"; ++ status = "okay"; ++ }; ++}; ++ ++&mbox0 { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi +new file mode 100644 +index 000000000..db26b5096 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi +@@ -0,0 +1,8 @@ ++#include "nvidia-ast27xx-irot-sonic.dtsi" ++// OP-TEE vROT not used on SONiC BMC; same as meta nvidia-ast27xx-sunda.dtsi. ++//#include "nvidia-optee-vrot.dtsi" ++ ++// Enable OTP; matches OpenBMC AST2700 sund stack (DTS node + ast2700_otp.cfg). ++&otp { ++ status = "okay"; ++}; +-- +2.34.1 + diff --git a/patches-sonic/0009-drivers-edac-Add-Elba-EDAC-support.patch b/patches-sonic/0009-drivers-edac-Add-Elba-EDAC-support.patch new file mode 100644 index 000000000..cb2487185 --- /dev/null +++ b/patches-sonic/0009-drivers-edac-Add-Elba-EDAC-support.patch @@ -0,0 +1,636 @@ +From b48d88a609c8bb61d35686ebfac188d344205768 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 09:36:04 +0000 +Subject: [PATCH 09/27] drivers/edac: Add Elba EDAC support + +The edac driver reports correctable and uncorrectable errors from the +DDR controller. The driver relies on device-tree nodes installed +by u-boot to convey: +- The active DDR channels. +- The NOC DDR hashes and bypass regions. + +The active DDR channels tell the driver which channels to monitor. +The hashes allow the driver to reverse the memory address hashing so +that the reports can provide a real system address for the fault. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba-asic-common.dtsi | 4 + + arch/arm64/boot/dts/pensando/elba.dtsi | 6 + + drivers/edac/Kconfig | 7 + + drivers/edac/Makefile | 1 + + drivers/edac/elba_edac.c | 539 +++++++++++++++++++++ + 5 files changed, 557 insertions(+) + create mode 100644 drivers/edac/elba_edac.c + +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi b/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi +index b41ca86..79d25c7 100644 +--- a/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba-asic-common.dtsi +@@ -46,6 +46,10 @@ + status = "ok"; + }; + ++&edac { ++ status = "okay"; ++}; ++ + &wdt0 { + status = "okay"; + }; +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index b3fa5b1..b33dedb 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -184,6 +184,12 @@ + }; + }; + ++ edac: edac@30500000 { ++ compatible = "pensando,elba-edac-mc"; ++ reg = <0x0 0x30500000 0x0 0x100000>; ++ status = "disabled"; /* not available on HAPS */ ++ }; ++ + emmc: mmc@30440000 { + compatible = "amd,pensando-elba-sd4hc", "cdns,sd4hc"; + clocks = <&emmc_clk>; +diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig +index 81af6c3..db610ca 100644 +--- a/drivers/edac/Kconfig ++++ b/drivers/edac/Kconfig +@@ -534,6 +534,13 @@ config EDAC_DMC520 + Support for error detection and correction on the + SoCs with ARM DMC-520 DRAM controller. + ++config EDAC_ELBA ++ tristate "Pensando Elba Memory ECC" ++ depends on ARCH_PENSANDO_ELBA_SOC ++ help ++ Support for error detection and correction on the ++ Pensando Elba SoC. ++ + config EDAC_ZYNQMP + tristate "Xilinx ZynqMP OCM Controller" + depends on ARCH_ZYNQMP || COMPILE_TEST +diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile +index faf310e..8e9b7ba 100644 +--- a/drivers/edac/Makefile ++++ b/drivers/edac/Makefile +@@ -85,6 +85,7 @@ obj-$(CONFIG_EDAC_QCOM) += qcom_edac.o + obj-$(CONFIG_EDAC_ASPEED) += aspeed_edac.o + obj-$(CONFIG_EDAC_BLUEFIELD) += bluefield_edac.o + obj-$(CONFIG_EDAC_DMC520) += dmc520_edac.o ++obj-$(CONFIG_EDAC_ELBA) += elba_edac.o + obj-$(CONFIG_EDAC_NPCM) += npcm_edac.o + obj-$(CONFIG_EDAC_ZYNQMP) += zynqmp_edac.o + obj-$(CONFIG_EDAC_VERSAL) += versal_edac.o +diff --git a/drivers/edac/elba_edac.c b/drivers/edac/elba_edac.c +new file mode 100644 +index 0000000..88f0247 +--- /dev/null ++++ b/drivers/edac/elba_edac.c +@@ -0,0 +1,539 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * AMD Pensando Elba Memory Controller EDAC Driver ++ * ++ * Copyright (c) 2022, Advanced Micro Devices Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "edac_module.h" ++ ++#define ELB_MC_CHAN_STRIDE 0x80000 ++#define ELB_MC_SUBCHAN_STRIDE 0x20000 ++#define ELB_MC_NCHANS 2 ++#define ELB_MC_NSUBCHANS_DDR4 1 ++#define ELB_MC_NSUBCHANS_DDR5 2 ++ ++#define ELB_MC_CONV_MASK_0 0x3010 ++#define ELB_MC_CONV_MASK_1 0x3020 ++#define ELB_MC_MCTL 0x30000 ++ ++#define ELB_MCTL_ECC_ADDR(a, s) ((a) | ((u64)((s) & 0x3f) << 32)) ++#define ELB_MCTL_ECC_SYND(s) (((s) >> 8) & 0xff) ++#define ELB_MCTL_ECC_U_ID(n) ((n) & 0x3fff) ++#define ELB_MCTL_ECC_C_ID(n) (((n) >> 16) & 0x3fff) ++ ++#define ELB_MCTL_INT_MULTI_U_ECC BIT(3) ++#define ELB_MCTL_INT_U_ECC BIT(2) ++#define ELB_MCTL_INT_MULTI_C_ECC BIT(1) ++#define ELB_MCTL_INT_C_ECC BIT(0) ++ ++#define ELB_MCTL_INT_ACK_MASK 0xf ++ ++struct mc_edac_regs { ++ int ecc_u_addr; ++ int ecc_u_synd; ++ int ecc_c_addr; ++ int ecc_c_synd; ++ int ecc_id; ++ int int_stat_ecc; ++ int int_ack_ecc; ++}; ++ ++struct ddr_map_func { ++ u64 chan_hash; // bus addr to ddr channel hash ++ u64 subchan_hash; // bus addr to ddr subchannel hash ++ u64 conv_mask[2]; // bus addr to ddr addr conversion masks ++}; ++ ++struct elba_mcdata { ++ void __iomem *base; // memory controller registers ++ const struct mc_edac_regs *edac_regs; // EDAC registers ++ struct ddr_map_func llc_map; // ddr to bus address map for LLC ++ struct ddr_map_func ddrb_map; // ddr to bus address map for bypass ++ u64 byp_start; // bypass region start ++ u64 byp_size; // bypass region size ++ u32 chanmask; // channel mask (b01, b10, or b11) ++ int map_valid; // ddr to bus map is valid ++ int nsubchans; // number of subchannels ++ int is_ddr5; // is DDR5 ++}; ++ ++static struct elba_mcdata *g_mcp; ++ ++struct elba_mcpoll_res { ++ u64 ddr_addr; // ecc address reported by the mc ++ u64 bus_addr; // system bus address (or 0 if no map) ++ u16 cnt; // ecc count (1=single, 2=multiple) ++ u16 id; // axi source id info ++ u8 synd; // ecc syndrome ++ char msg[128]; // informational message ++}; ++ ++static const struct mc_edac_regs elba_mc_edac_regs = { ++ .ecc_u_addr = 277, ++ .ecc_u_synd = 278, ++ .ecc_c_addr = 281, ++ .ecc_c_synd = 282, ++ .ecc_id = 285, ++ .int_stat_ecc = 377, ++ .int_ack_ecc = 384, ++}; ++ ++static inline u32 elba_mc_read(struct elba_mcdata *mcp, int chan, int subchan, ++ int offs) ++{ ++ return readl(mcp->base + ELB_MC_CHAN_STRIDE * chan + ++ ELB_MC_SUBCHAN_STRIDE * subchan + offs); ++} ++ ++static inline u32 elba_mctl_read(struct elba_mcdata *mcp, int chan, int subchan, ++ int reg) ++{ ++ return elba_mc_read(mcp, chan, subchan, ELB_MC_MCTL + 4 * reg); ++} ++ ++static inline void elba_mc_write(struct elba_mcdata *mcp, ++ int chan, int subchan, int offs, u32 data) ++{ ++ return writel(data, mcp->base + ELB_MC_CHAN_STRIDE * chan + ++ ELB_MC_SUBCHAN_STRIDE * subchan + offs); ++} ++ ++static inline void elba_mctl_write(struct elba_mcdata *mcp, ++ int chan, int subchan, int reg, u32 data) ++{ ++ elba_mc_write(mcp, chan, subchan, ELB_MC_MCTL + 4 * reg, data); ++} ++ ++/* ++ * Expand a DDR reported address back to a system bus address. ++ * In a 2-channel system, each ddr channel takes 1/2 of the bus address ++ * space, so one bit is dropped from the original address. ++ * In a 2-channel, 2-subchannel (4 channels effectively) system, each ddr ++ * channel takes 1/4 of the bus address, so two bits are dropped from the ++ * original bus address. ++ * This function reverses this, and provides two prospecitve addresses; ++ * one with the missing bit clear and the other with the missinog bit set. ++ * The channel-selection hash function will be applied with each address ++ * to determine the correct one. ++ * Only used for multi-channel configurations. ++ */ ++static void elba_ddr_addr_expand(u64 addr, u64 *mask, u64 *res) ++{ ++ u64 addr_o; ++ ++ addr_o = ((addr & mask[1]) << 2) | ++ ((addr & mask[0]) << 1) | ++ (addr & ~(mask[0] | mask[1])); ++ res[0] = addr_o; ++ res[1] = addr_o | ((~(mask[0] | mask[1]) + 1) & 0xfffffffffULL); ++ res[2] = addr_o | (((~(mask[0] | mask[1]) + 1) << 1) & 0xfffffffffULL); ++ res[3] = addr_o | (((~(mask[0] | mask[1]) + 1) << 1) & 0xfffffffffULL) ++ | ((~(mask[0] | mask[1]) + 1) & 0xfffffffffULL); ++} ++ ++/* ++ * Hash system address to select a ddr channel. ++ * The hash_func parameter indicates the bits from the address that ++ * should be XORed together to select the target ddr channel. ++ * Only used for 2-channel configurations. ++ */ ++static int elba_ddr_hash_func(u64 addr, u64 hash_func) ++{ ++ u64 hash_val; ++ int res; ++ ++ hash_val = addr & hash_func; ++ ++ // reduction XOR ++ res = 0; ++ while (hash_val) { ++ res ^= 1; ++ hash_val &= hash_val - 1; ++ } ++ return res; ++} ++ ++/* ++ * Map a ddr reported ecc address back to an original system bus address. ++ * For a 1-channel system, the ddr controller address is the true linear ++ * address. ++ * For a 2-channel system we first determine the two possible linear address ++ * that might map to this channel, then we apply the appropriate hash function ++ * to find the final linear address. ++ * The llc and bypass regions use different hash and convert functions, ++ * so first expand the address using the llc function and then check to see ++ * if the linear address is in the bypass range. If so, switch to the ++ * bypass conversion function and hash. ++ * Finally, linear addresses outside of 2GB..8GB range must have bit 36 set ++ * to recover the original system bus address. ++ */ ++static u64 elba_ddr_addr_to_bus(struct elba_mcdata *mcp, int chan, int subchan, ++ u64 ddr_addr) ++{ ++ struct ddr_map_func *map = &mcp->llc_map; ++ u64 bus_addr = (u64)-1; ++ u64 addr_e[4]; ++ int i; ++ ++ if (!mcp->map_valid) ++ return 0; ++ ++ if (mcp->chanmask == 0x3) { ++ elba_ddr_addr_expand(ddr_addr, map->conv_mask, addr_e); ++ if (addr_e[0] >= mcp->byp_start && ++ addr_e[0] < (mcp->byp_start + mcp->byp_size)) { ++ map = &mcp->ddrb_map; ++ elba_ddr_addr_expand(ddr_addr, map->conv_mask, addr_e); ++ } ++ ++ for (i = 0; i < ELB_MC_NCHANS * mcp->nsubchans; i++) { ++ if ((elba_ddr_hash_func(addr_e[i], map->chan_hash) == chan) && ++ (elba_ddr_hash_func(addr_e[i], map->subchan_hash) == subchan)) { ++ bus_addr = addr_e[i]; ++ break; ++ } ++ } ++ } else { ++ bus_addr = ddr_addr; ++ } ++ ++ if (bus_addr < 0x80000000 || bus_addr >= 0x200000000ULL) ++ bus_addr |= 0x1000000000ULL; ++ ++ return (bus_addr == (u64)-1) ? 0 : bus_addr; ++} ++ ++static int elba_edac_mc_c_poll(struct elba_mcdata *mcp, int chan, int subchan, ++ u32 int_stat, struct elba_mcpoll_res *res) ++{ ++ u32 addr, synd, id; ++ ++ if (!(int_stat & ELB_MCTL_INT_C_ECC)) ++ return 0; ++ ++ res->cnt = (int_stat & ELB_MCTL_INT_MULTI_C_ECC) ? 2 : 1; ++ addr = elba_mctl_read(mcp, chan, subchan, mcp->edac_regs->ecc_c_addr); ++ synd = elba_mctl_read(mcp, chan, subchan, mcp->edac_regs->ecc_c_synd); ++ id = elba_mctl_read(mcp, chan, subchan, mcp->edac_regs->ecc_id); ++ res->ddr_addr = ELB_MCTL_ECC_ADDR(addr, synd); ++ res->bus_addr = elba_ddr_addr_to_bus(mcp, chan, subchan, res->ddr_addr); ++ res->synd = ELB_MCTL_ECC_SYND(synd); ++ res->id = ELB_MCTL_ECC_C_ID(id); ++ snprintf(res->msg, sizeof(res->msg), "ddr_addr 0x%llx, id 0x%04x", ++ res->ddr_addr, res->id); ++ return 1; ++} ++ ++static int elba_edac_mc_u_poll(struct elba_mcdata *mcp, int chan, int subchan, ++ u32 int_stat, struct elba_mcpoll_res *res) ++{ ++ u32 addr, synd, id; ++ ++ if (!(int_stat & ELB_MCTL_INT_U_ECC)) ++ return 0; ++ ++ res->cnt = (int_stat & ELB_MCTL_INT_MULTI_U_ECC) ? 2 : 1; ++ addr = elba_mctl_read(mcp, chan, subchan, mcp->edac_regs->ecc_u_addr); ++ synd = elba_mctl_read(mcp, chan, subchan, mcp->edac_regs->ecc_u_synd); ++ id = elba_mctl_read(mcp, chan, subchan, mcp->edac_regs->ecc_id); ++ res->ddr_addr = ELB_MCTL_ECC_ADDR(addr, synd); ++ res->bus_addr = elba_ddr_addr_to_bus(mcp, chan, subchan, res->ddr_addr); ++ res->synd = ELB_MCTL_ECC_SYND(synd); ++ res->id = ELB_MCTL_ECC_U_ID(id); ++ snprintf(res->msg, sizeof(res->msg), "ddr_addr 0x%llx, id 0x%04x", ++ res->ddr_addr, res->id); ++ return 1; ++} ++ ++static void elba_edac_mc_poll_chan(struct mem_ctl_info *mci, int chan, int subchan) ++{ ++ struct elba_mcdata *mcp = mci->pvt_info; ++ struct elba_mcpoll_res res; ++ u32 int_stat, int_ack; ++ int slot = -1; ++ ++ if (!(mcp->chanmask & (1 << chan))) ++ return; ++ if (mcp->is_ddr5) ++ slot = subchan; ++ ++ int_stat = elba_mctl_read(mcp, chan, subchan, mcp->edac_regs->int_stat_ecc); ++ if (elba_edac_mc_c_poll(mcp, chan, subchan, int_stat, &res)) { ++ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, res.cnt, ++ res.bus_addr >> PAGE_SHIFT, res.bus_addr & ~PAGE_MASK, ++ res.synd, chan, slot, -1, res.msg, ""); ++ } ++ ++ if (elba_edac_mc_u_poll(mcp, chan, subchan, int_stat, &res)) { ++ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, res.cnt, ++ res.bus_addr >> PAGE_SHIFT, res.bus_addr & ~PAGE_MASK, ++ res.synd, chan, slot, -1, res.msg, ""); ++ } ++ ++ int_ack = int_stat & ELB_MCTL_INT_ACK_MASK; ++ if (int_ack) ++ elba_mctl_write(mcp, chan, subchan, mcp->edac_regs->int_ack_ecc, int_ack); ++} ++ ++static void elba_edac_mc_poll(struct mem_ctl_info *mci) ++{ ++ int chan, subchan; ++ struct elba_mcdata *mcp = mci->pvt_info; ++ ++ for (chan = 0; chan < ELB_MC_NCHANS; chan++) ++ for (subchan = 0; subchan < mcp->nsubchans; subchan++) ++ elba_edac_mc_poll_chan(mci, chan, subchan); ++} ++ ++/* ++ * A read error (SError) that causes a kernel panic won't have been ++ * polled yet, so one final check for uncorrectable errors on the way down. ++ */ ++static int elba_edac_mc_panic_notifier(struct notifier_block *nb, ++ unsigned long event, void *ptr) ++{ ++ struct elba_mcdata *mcp = g_mcp; ++ struct elba_mcpoll_res res; ++ u32 int_stat; ++ int chan, subchan; ++ ++ for (chan = 0; chan < ELB_MC_NCHANS; chan++) { ++ if (!(mcp->chanmask & (1 << chan))) ++ continue; ++ for (subchan = 0; subchan < mcp->nsubchans; subchan++) { ++ int_stat = elba_mctl_read(mcp, chan, subchan, ++ mcp->edac_regs->int_stat_ecc); ++ if (elba_edac_mc_u_poll(mcp, chan, subchan, int_stat, ++ &res)) { ++ if (mcp->is_ddr5) ++ pr_crit("EDAC MC0: %u UE %s " ++ "on unknown memory (channel:%u " ++ "slot:%u page:0x%llx " ++ "offset:0x%llx " ++ "grain:8 syndrome:0x%02x)\n", ++ res.cnt, res.msg, ++ chan, subchan, ++ res.bus_addr >> PAGE_SHIFT, ++ res.bus_addr & ~PAGE_MASK, ++ res.synd); ++ else ++ pr_crit("EDAC MC0: %u UE %s " ++ "on unknown memory (channel:%u " ++ "page:0x%llx " ++ "offset:0x%llx " ++ "grain:8 syndrome:0x%02x)\n", ++ res.cnt, res.msg, chan, ++ res.bus_addr >> PAGE_SHIFT, ++ res.bus_addr & ~PAGE_MASK, ++ res.synd); ++ } ++ } ++ } ++ return 0; ++} ++ ++static struct notifier_block panic_block = { ++ .notifier_call = elba_edac_mc_panic_notifier ++}; ++ ++static void elba_mc_conv_mask_read(struct elba_mcdata *mcp, ++ int chan, int offs, u64 *mask) ++{ ++ u32 w[3]; ++ int i; ++ ++ for (i = 0; i < 3; i++) ++ w[i] = elba_mc_read(mcp, chan, 0, offs + 4 * i); ++ ++ /* pull the two 36-bit masks from the 3 words */ ++ mask[0] = ((u64)(w[2] & 0xff) << 28) | (w[1] >> 4); ++ mask[1] = ((u64)(w[1] & 0xf) << 32) | w[0]; ++} ++ ++static void elba_init_ddr_map(struct platform_device *pdev, ++ struct elba_mcdata *mcp) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ u64 val[2]; ++ int r = 0; ++ int chan; ++ ++ if (np == NULL) ++ return; ++ ++ r += of_property_read_u32(np, "pensando,ddrchanmask", &mcp->chanmask); ++ r += of_property_read_u64(np, "pensando,llchash", &mcp->llc_map.chan_hash); ++ if (mcp->is_ddr5) ++ r += of_property_read_u64(np, "pensando,ddrchash", &mcp->llc_map.subchan_hash); ++ ++ chan = (mcp->chanmask & 0x1) ? 0 : 1; ++ elba_mc_conv_mask_read(mcp, chan, ELB_MC_CONV_MASK_1, ++ mcp->llc_map.conv_mask); ++ ++ if (of_property_read_variable_u64_array(np, ++ "pensando,bypass", val, 2, 0) == 2) { ++ mcp->byp_start = val[0]; ++ mcp->byp_size = val[1]; ++ r += of_property_read_u64(np, "pensando,ddrbhash", ++ &mcp->ddrb_map.chan_hash); ++ if (mcp->is_ddr5) ++ r += of_property_read_u64(np, "pensando,ddrbhash0", ++ &mcp->ddrb_map.subchan_hash); ++ elba_mc_conv_mask_read(mcp, chan, ELB_MC_CONV_MASK_0, ++ mcp->ddrb_map.conv_mask); ++ } ++ ++ if (r) { ++ dev_warn(&pdev->dev, "ddr map hashes not found\n"); ++ return; ++ } ++ ++ edac_dbg(0, "llc_map = { 0x%llx, [0x%llx, 0x%llx] }\n", ++ mcp->llc_map.chan_hash, ++ mcp->llc_map.conv_mask[0], ++ mcp->llc_map.conv_mask[1]); ++ edac_dbg(0, "ddrb_map = { 0x%llx, [0x%llx, 0x%llx] }\n", ++ mcp->ddrb_map.chan_hash, ++ mcp->ddrb_map.conv_mask[0], ++ mcp->ddrb_map.conv_mask[1]); ++ edac_dbg(0, "bypass = { 0x%llx, 0x%llx }\n", ++ mcp->byp_start, mcp->byp_size); ++ edac_dbg(0, "ddr_chanmask = 0x%x\n", mcp->chanmask); ++ ++ mcp->map_valid = 1; ++} ++ ++static void elba_init_dimms(struct mem_ctl_info *mci) ++{ ++ struct elba_mcdata *mcp = mci->pvt_info; ++ struct dimm_info *dimm; ++ int chan, subchan, dimmidx; ++ ++ for (chan = 0; chan < ELB_MC_NCHANS; chan++) { ++ if (!(mcp->chanmask & (1 << chan))) ++ continue; ++ dimmidx = chan * mcp->nsubchans; ++ for (subchan = 0; subchan < mcp->nsubchans; subchan++) { ++ dimm = mci->dimms[dimmidx + subchan]; ++ dimm->grain = 8; ++ dimm->mtype = MEM_DDR4; ++ dimm->dtype = DEV_X8; ++ dimm->edac_mode = EDAC_SECDED; ++ dimm->nr_pages = 1; /* not empty */ ++ } ++ } ++} ++ ++static int elba_edac_mc_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct edac_mc_layer layers[2]; ++ struct mem_ctl_info *mci; ++ struct elba_mcdata *mcp; ++ struct resource *r; ++ void __iomem *base; ++ static int mc_idx; ++ int nlayers = 1; ++ int is_ddr5; ++ ++ r = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!r) { ++ dev_err(&pdev->dev, "Unable to get mem resource\n"); ++ return -ENODEV; ++ } ++ base = devm_ioremap_resource(&pdev->dev, r); ++ if (IS_ERR(base)) { ++ dev_err(&pdev->dev, "Unable to map regs\n"); ++ return PTR_ERR(base); ++ } ++ ++ layers[0].type = EDAC_MC_LAYER_CHANNEL; ++ layers[0].size = ELB_MC_NCHANS; ++ layers[0].is_virt_csrow = false; ++ ++ is_ddr5 = of_property_read_bool(np, "pensando,ddr5"); ++ ++ if (is_ddr5) { ++ layers[1].type = EDAC_MC_LAYER_SLOT; ++ layers[1].size = ELB_MC_NSUBCHANS_DDR5; ++ layers[1].is_virt_csrow = false; ++ nlayers += 1; ++ } ++ ++ mci = edac_mc_alloc(mc_idx, nlayers, ++ layers, sizeof(struct elba_mcdata)); ++ if (!mci) ++ return -ENXIO; ++ ++ mcp = mci->pvt_info; ++ mcp->is_ddr5 = is_ddr5; ++ mcp->nsubchans = is_ddr5 ? ELB_MC_NSUBCHANS_DDR5 : ++ ELB_MC_NSUBCHANS_DDR4; ++ mcp->base = base; ++ mcp->edac_regs = of_device_get_match_data(&pdev->dev); ++ ++ elba_init_ddr_map(pdev, mcp); ++ elba_init_dimms(mci); ++ ++ mci->pdev = &pdev->dev; ++ mci->dev_name = dev_name(&pdev->dev); ++ ++ mci->mod_name = "elba-mc"; ++ mci->ctl_name = "elba-mc-err"; ++ mci->edac_check = elba_edac_mc_poll; ++ mci->edac_cap = EDAC_FLAG_SECDED; ++ ++ if (edac_mc_add_mc(mci)) { ++ dev_err(&pdev->dev, "edac_mc_add_mc() failed\n"); ++ edac_mc_free(mci); ++ return -ENXIO; ++ } ++ ++ platform_set_drvdata(pdev, mci); ++ ++ /* hook panic() so we can check for uncorrectable errors */ ++ g_mcp = mcp; ++ atomic_notifier_chain_register(&panic_notifier_list, &panic_block); ++ ++ /* set polling mode */ ++ opstate_init(); ++ ++ return 0; ++} ++ ++static void elba_edac_mc_remove(struct platform_device *pdev) ++{ ++ struct mem_ctl_info *mci = platform_get_drvdata(pdev); ++ ++ edac_mc_del_mc(&pdev->dev); ++ atomic_notifier_chain_unregister(&panic_notifier_list, &panic_block); ++ ++ edac_mc_free(mci); ++ return; ++} ++ ++static const struct of_device_id elba_edac_mc_of_match[] = { ++ { .compatible = "pensando,elba-edac-mc", .data = &elba_mc_edac_regs }, ++ { }, ++}; ++ ++static struct platform_driver elba_edac_mc_driver = { ++ .probe = elba_edac_mc_probe, ++ .remove = elba_edac_mc_remove, ++ .driver = { ++ .name = "elba_edac_mc", ++ .of_match_table = of_match_ptr(elba_edac_mc_of_match), ++ } ++}; ++module_platform_driver(elba_edac_mc_driver); ++ ++MODULE_DESCRIPTION("AMD Pensando Elba platform EDAC memory controller driver"); ++MODULE_AUTHOR("Advanced Micro Devices, Inc."); ++MODULE_LICENSE("GPL v2"); +-- +1.8.3.1 + diff --git a/patches-sonic/0009-dt-bindings-marvell-Add-ARMADA-7K-properties.patch b/patches-sonic/0009-dt-bindings-marvell-Add-ARMADA-7K-properties.patch new file mode 100644 index 000000000..61b39c7a4 --- /dev/null +++ b/patches-sonic/0009-dt-bindings-marvell-Add-ARMADA-7K-properties.patch @@ -0,0 +1,27 @@ +From: Pavan Naregundi +Date: Tue, 14 Mar 2023 05:57:34 +0000 +dt-bindings: marvell: Add ARMADA-7K properties + +This patch adds compatible properties for the Marvell Armada7020 +Comexpress in documentation. + +Signed-off-by: Eyal Oron +--- + Documentation/devicetree/bindings/arm/marvell/armada-7k-8k.yaml | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/Documentation/devicetree/bindings/arm/marvell/armada-7k-8k.yaml b/Documentation/devicetree/bindings/arm/marvell/armada-7k-8k.yaml +index e9bf30545..26092dc11 100644 +--- a/Documentation/devicetree/bindings/arm/marvell/armada-7k-8k.yaml ++++ b/Documentation/devicetree/bindings/arm/marvell/armada-7k-8k.yaml +@@ -20,6 +20,7 @@ properties: + - const: marvell,armada7020 + - const: marvell,armada-ap806-dual + - const: marvell,armada-ap806 ++ - const: marvell,armada7020-comexpress + + - description: Armada 7040 SoC + items: +-- +2.25.1 + diff --git a/patches-sonic/0009-i2c-i2c-xiic-Replace-dev_err-with-dev_err_probe-in-p.patch b/patches-sonic/0009-i2c-i2c-xiic-Replace-dev_err-with-dev_err_probe-in-p.patch new file mode 100644 index 000000000..a0758adf8 --- /dev/null +++ b/patches-sonic/0009-i2c-i2c-xiic-Replace-dev_err-with-dev_err_probe-in-p.patch @@ -0,0 +1,40 @@ +From 4806583e1e41dbd9c8061c7c6434c263daea00b0 Mon Sep 17 00:00:00 2001 +From: Enrico Zanda +Date: Tue, 15 Apr 2025 20:34:46 +0200 +Subject: [PATCH 09/22] i2c: i2c-xiic: Replace dev_err() with dev_err_probe() + in probe function + +This simplifies the code while improving log. + +Signed-off-by: Enrico Zanda +Link: https://lore.kernel.org/r/20250415183447.396277-10-e.zanda1@gmail.com +Signed-off-by: Andi Shyti +--- + drivers/i2c/busses/i2c-xiic.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index b142380ed251..072edc6c12da 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -1489,7 +1489,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + pdev->name, i2c); + + if (ret < 0) { +- dev_err(&pdev->dev, "Cannot claim IRQ\n"); ++ dev_err_probe(&pdev->dev, ret, "Cannot claim IRQ\n"); + goto err_pm_disable; + } + +@@ -1510,7 +1510,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + + ret = xiic_reinit(i2c); + if (ret < 0) { +- dev_err(&pdev->dev, "Cannot xiic_reinit\n"); ++ dev_err_probe(&pdev->dev, ret, "Cannot xiic_reinit\n"); + goto err_pm_disable; + } + +-- +2.53.0 + diff --git a/patches-sonic/0009-platform-mellanox-mlxbf-pmc-Support-additional-PMC-b.patch b/patches-sonic/0009-platform-mellanox-mlxbf-pmc-Support-additional-PMC-b.patch new file mode 100644 index 000000000..a2011cdd4 --- /dev/null +++ b/patches-sonic/0009-platform-mellanox-mlxbf-pmc-Support-additional-PMC-b.patch @@ -0,0 +1,246 @@ +From b8a5dec9929fa888061292fb066e0566181cd3f6 Mon Sep 17 00:00:00 2001 +From: Shravan Kumar Ramani +Date: Wed, 23 Apr 2025 04:31:03 -0400 +Subject: [PATCH 09/78] platform/mellanox: mlxbf-pmc: Support additional PMC + blocks +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add list of events and counters from the following blocks: APT (ARM Processor +Tile), GGA (Global Generic Accelerator), MSN (Memory Stasher and Navigator), +EMI (External Memory Interface) and PRNF (PCIe Request Node). +If any of the fields populated from the ACPI table (like apt_num) cannot be +read, assign the corresponding block count to be 0 instead of failing probe +to maintain compatibility with older firmware. + +Signed-off-by: Shravan Kumar Ramani +Reviewed-by: David Thompson +Link: https://lore.kernel.org/r/20250423083103.5240-1-shravankr@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxbf-pmc.c | 155 ++++++++++++++++++++++++++++++++-- + 1 file changed, 150 insertions(+), 5 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c +index 771a24c..a1c529f 100644 +--- a/drivers/platform/mellanox/mlxbf-pmc.c ++++ b/drivers/platform/mellanox/mlxbf-pmc.c +@@ -33,7 +33,7 @@ + #define MLXBF_PMC_EVENT_SET_BF3 2 + #define MLXBF_PMC_EVENT_INFO_LEN 100 + +-#define MLXBF_PMC_MAX_BLOCKS 30 ++#define MLXBF_PMC_MAX_BLOCKS 40 + #define MLXBF_PMC_MAX_ATTRS 70 + #define MLXBF_PMC_INFO_SZ 4 + #define MLXBF_PMC_REG_SIZE 8 +@@ -139,6 +139,7 @@ struct mlxbf_pmc_block_info { + * @pdev: The kernel structure representing the device + * @total_blocks: Total number of blocks + * @tile_count: Number of tiles in the system ++ * @apt_enable: Info on enabled APTs + * @llt_enable: Info on enabled LLTs + * @mss_enable: Info on enabled MSSs + * @group_num: Group number assigned to each valid block +@@ -154,6 +155,7 @@ struct mlxbf_pmc_context { + struct platform_device *pdev; + u32 total_blocks; + u32 tile_count; ++ u8 apt_enable; + u8 llt_enable; + u8 mss_enable; + u32 group_num; +@@ -893,6 +895,107 @@ static const struct mlxbf_pmc_events mlxbf_pmc_clock_events[] = { + { 0x6c, "REFERENCE_WINDOW_WIDTH_REF_156" }, + }; + ++static const struct mlxbf_pmc_events mlxbf_pmc_gga_events[] = { ++ { 0, "GGA_PERF_DESC_WQE_STRB" }, ++ { 5, "GGA_PERF_DESC_CQE_STRB" }, ++ { 8, "GGA_PERF_DESC_TPT_REQUEST_STRB" }, ++ { 17, "GGA_PERF_DESC_TPT_RESPONSESTRB" }, ++ { 120, "GGA_PERF_DESC_ENGINE0_IN_DATA_STRB" }, ++ { 121, "GGA_PERF_DESC_ENGINE1_IN_DATA_STRB" }, ++ { 122, "GGA_PERF_DESC_ENGINE2_IN_DATA_STRB" }, ++ { 123, "GGA_PERF_DESC_ENGINE3_IN_DATA_STRB" }, ++ { 124, "GGA_PERF_DESC_ENGINE4_IN_DATA_STRB" }, ++ { 125, "GGA_PERF_DESC_ENGINE5_IN_DATA_STRB" }, ++ { 126, "GGA_PERF_DESC_ENGINE6_IN_DATA_STRB" }, ++ { 127, "GGA_PERF_DESC_ENGINE7_IN_DATA_STRB" }, ++ { 128, "GGA_PERF_DESC_ENGINE8_IN_DATA_STRB" }, ++ { 129, "GGA_PERF_DESC_ENGINE9_IN_DATA_STRB" }, ++ { 130, "GGA_PERF_DESC_ENGINE10_IN_DATA_STRB" }, ++ { 131, "GGA_PERF_DESC_ENGINE11_IN_DATA_STRB" }, ++ { 132, "GGA_PERF_DESC_ENGINE12_IN_DATA_STRB" }, ++ { 133, "GGA_PERF_DESC_ENGINE13_IN_DATA_STRB" }, ++ { 134, "GGA_PERF_DESC_ENGINE14_IN_DATA_STRB" }, ++ { 195, "GGA_PERF_DESC_ENGINE0_OUT_DATA_STRB" }, ++ { 196, "GGA_PERF_DESC_ENGINE1_OUT_DATA_STRB" }, ++ { 197, "GGA_PERF_DESC_ENGINE2_OUT_DATA_STRB" }, ++ { 198, "GGA_PERF_DESC_ENGINE3_OUT_DATA_STRB" }, ++ { 199, "GGA_PERF_DESC_ENGINE4_OUT_DATA_STRB" }, ++ { 200, "GGA_PERF_DESC_ENGINE5_OUT_DATA_STRB" }, ++ { 201, "GGA_PERF_DESC_ENGINE6_OUT_DATA_STRB" }, ++ { 202, "GGA_PERF_DESC_ENGINE7_OUT_DATA_STRB" }, ++ { 203, "GGA_PERF_DESC_ENGINE8_OUT_DATA_STRB" }, ++ { 204, "GGA_PERF_DESC_ENGINE9_OUT_DATA_STRB" }, ++ { 205, "GGA_PERF_DESC_ENGINE10_OUT_DATA_STRB" }, ++ { 206, "GGA_PERF_DESC_ENGINE11_OUT_DATA_STRB" }, ++ { 207, "GGA_PERF_DESC_ENGINE12_OUT_DATA_STRB" }, ++ { 208, "GGA_PERF_DESC_ENGINE13_OUT_DATA_STRB" }, ++ { 209, "GGA_PERF_DESC_ENGINE14_OUT_DATA_STRB" }, ++}; ++ ++static const struct mlxbf_pmc_events mlxbf_pmc_apt_events[] = { ++ { 0, "APT_DATA_0" }, ++ { 1, "APT_DATA_1" }, ++ { 2, "APT_DATA_2" }, ++ { 3, "APT_DATA_3" }, ++ { 4, "APT_DATA_4" }, ++ { 5, "APT_DATA_5" }, ++ { 6, "APT_DATA_6" }, ++ { 7, "APT_DATA_7" }, ++ { 8, "APT_DATA_8" }, ++ { 9, "APT_DATA_9" }, ++ { 10, "APT_DATA_10" }, ++ { 11, "APT_DATA_11" }, ++ { 12, "APT_DATA_12" }, ++ { 13, "APT_DATA_13" }, ++ { 14, "APT_DATA_14" }, ++ { 15, "APT_DATA_15" }, ++ { 16, "APT_DATA_16" }, ++ { 17, "APT_DATA_17" }, ++ { 18, "APT_DATA_18" }, ++ { 19, "APT_DATA_19" }, ++ { 20, "APT_DATA_20" }, ++ { 21, "APT_DATA_21" }, ++}; ++ ++static const struct mlxbf_pmc_events mlxbf_pmc_emi_events[] = { ++ { 0, "MCH_WR_IN_MCH_REQ_IN_STRB" }, ++ { 10, "MCH_RD_IN_MCH_REQ_IN_STRB" }, ++ { 20, "MCH_RD_RESP_DATA_MCH_RESP_OUT_STRB" }, ++ { 98, "EMI_ARBITER_EARB2CTRL_STRB" }, ++ { 99, "EMI_ARBITER_EARB2CTRL_RAS_STRB" }, ++ { 100, "EMI_ARBITER_EARB2CTRL_CAS_STRB" }, ++}; ++ ++static const struct mlxbf_pmc_events mlxbf_pmc_prnf_events[] = { ++ { 0, "PRNF_DMA_RD_TLP_REQ" }, ++ { 1, "PRNF_DMA_RD_ICMC_BYPASS_REQ" }, ++ { 8, "PRNF_DMA_RD_TLP_SENT_TO_CHI" }, ++ { 11, "PRNF_DMA_RD_CHI_RES" }, ++ { 17, "PRNF_DMA_RD_TLP_RES_SENT" }, ++ { 18, "PRNF_DMA_WR_WR0_SLICE_ALLOC_RO" }, ++ { 19, "PRNF_DMA_WR_WR0_SLICE_ALLOC_NRO" }, ++ { 24, "PRNF_DMA_WR_WR1_SLICE_ALLOC_RO" }, ++ { 25, "PRNF_DMA_WR_WR1_SLICE_ALLOC_NRO" }, ++ { 30, "PRNF_PIO_POSTED_REQ_PUSH" }, ++ { 31, "PRNF_PIO_POSTED_REQ_POP" }, ++ { 32, "PRNF_PIO_NP_REQ_PUSH" }, ++ { 33, "PRNF_PIO_NP_REQ_POP" }, ++ { 34, "PRNF_PIO_COMP_RO_PUSH" }, ++ { 35, "PRNF_PIO_COMP_RO_POP" }, ++ { 36, "PRNF_PIO_COMP_NRO_PUSH" }, ++ { 37, "PRNF_PIO_COMP_NRO_POP" }, ++}; ++ ++static const struct mlxbf_pmc_events mlxbf_pmc_msn_events[] = { ++ { 46, "MSN_CORE_MMA_WQE_DONE_PUSH_STRB" }, ++ { 116, "MSN_CORE_MSN2MMA_WQE_STRB" }, ++ { 164, "MSN_CORE_WQE_TOP_TILE_WQE_STRB" }, ++ { 168, "MSN_CORE_TPT_TOP_GGA_REQ_STRB" }, ++ { 171, "MSN_CORE_TPT_TOP_MMA_REQ_STRB" }, ++ { 174, "MSN_CORE_TPT_TOP_GGA_RES_STRB" }, ++ { 177, "MSN_CORE_TPT_TOP_MMA_RES_STRB" }, ++}; ++ + static struct mlxbf_pmc_context *pmc; + + /* UUID used to probe ATF service. */ +@@ -1069,6 +1172,21 @@ static const struct mlxbf_pmc_events *mlxbf_pmc_event_list(const char *blk, size + } else if (strstr(blk, "clock_measure")) { + events = mlxbf_pmc_clock_events; + size = ARRAY_SIZE(mlxbf_pmc_clock_events); ++ } else if (strstr(blk, "gga")) { ++ events = mlxbf_pmc_gga_events; ++ size = ARRAY_SIZE(mlxbf_pmc_gga_events); ++ } else if (strstr(blk, "apt")) { ++ events = mlxbf_pmc_apt_events; ++ size = ARRAY_SIZE(mlxbf_pmc_apt_events); ++ } else if (strstr(blk, "emi")) { ++ events = mlxbf_pmc_emi_events; ++ size = ARRAY_SIZE(mlxbf_pmc_emi_events); ++ } else if (strstr(blk, "prnf")) { ++ events = mlxbf_pmc_prnf_events; ++ size = ARRAY_SIZE(mlxbf_pmc_prnf_events); ++ } else if (strstr(blk, "msn")) { ++ events = mlxbf_pmc_msn_events; ++ size = ARRAY_SIZE(mlxbf_pmc_msn_events); + } else { + events = NULL; + size = 0; +@@ -2056,6 +2174,18 @@ static int mlxbf_pmc_map_counters(struct device *dev) + continue; + } + ++ /* Create sysfs only for enabled EMI blocks */ ++ if (strstr(pmc->block_name[i], "emi") && ++ pmc->event_set == MLXBF_PMC_EVENT_SET_BF3) { ++ unsigned int emi_num; ++ ++ if (sscanf(pmc->block_name[i], "emi%u", &emi_num) != 1) ++ continue; ++ ++ if (!((pmc->mss_enable >> (emi_num / 2)) & 0x1)) ++ continue; ++ } ++ + /* Create sysfs only for enabled LLT blocks */ + if (strstr(pmc->block_name[i], "llt_miss")) { + unsigned int llt_num; +@@ -2075,6 +2205,17 @@ static int mlxbf_pmc_map_counters(struct device *dev) + continue; + } + ++ /* Create sysfs only for enabled APT blocks */ ++ if (strstr(pmc->block_name[i], "apt")) { ++ unsigned int apt_num; ++ ++ if (sscanf(pmc->block_name[i], "apt%u", &apt_num) != 1) ++ continue; ++ ++ if (!((pmc->apt_enable >> apt_num) & 0x1)) ++ continue; ++ } ++ + ret = device_property_read_u64_array(dev, pmc->block_name[i], + info, MLXBF_PMC_INFO_SZ); + if (ret) +@@ -2171,13 +2312,17 @@ static int mlxbf_pmc_probe(struct platform_device *pdev) + return -EFAULT; + + if (device_property_read_u32(dev, "tile_num", &pmc->tile_count)) { ++ if (device_property_read_u8(dev, "apt_enable", &pmc->apt_enable)) { ++ dev_warn(dev, "Number of APTs undefined, ignoring blocks\n"); ++ pmc->apt_enable = 0; ++ } + if (device_property_read_u8(dev, "llt_enable", &pmc->llt_enable)) { +- dev_err(dev, "Number of tiles/LLTs undefined\n"); +- return -EINVAL; ++ dev_warn(dev, "Number of LLTs undefined, ignoring blocks\n"); ++ pmc->llt_enable = 0; + } + if (device_property_read_u8(dev, "mss_enable", &pmc->mss_enable)) { +- dev_err(dev, "Number of tiles/MSSs undefined\n"); +- return -EINVAL; ++ dev_warn(dev, "Number of MSSs undefined, ignoring blocks\n"); ++ pmc->mss_enable = 0; + } + } + +-- +2.8.4 + diff --git a/patches-sonic/0010-drivers-soc-pensando-dev-capmem-driver.patch b/patches-sonic/0010-drivers-soc-pensando-dev-capmem-driver.patch new file mode 100644 index 000000000..7d0694ae7 --- /dev/null +++ b/patches-sonic/0010-drivers-soc-pensando-dev-capmem-driver.patch @@ -0,0 +1,434 @@ +From cdc05d9636d8ed577e675bf660ff2f9571cf48d1 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 09:40:49 +0000 +Subject: [PATCH 10/27] drivers/soc/pensando: /dev/capmem driver + +The capmem driver provides an controlled interface for applications +to map memory (in preference to /dev/mem). + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 4 + + drivers/soc/Makefile | 1 + + drivers/soc/pensando/Kconfig | 7 + + drivers/soc/pensando/Makefile | 6 + + drivers/soc/pensando/cap_mem.c | 306 +++++++++++++++++++++++++++++++++ + drivers/soc/pensando/capmem_dev.h | 34 ++++ + 6 files changed, 358 insertions(+) + create mode 100644 drivers/soc/pensando/Makefile + create mode 100644 drivers/soc/pensando/cap_mem.c + create mode 100644 drivers/soc/pensando/capmem_dev.h + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index b33dedb..1ecd348 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -49,6 +49,10 @@ + IRQ_TYPE_LEVEL_LOW)>; + }; + ++ capmem { ++ compatible = "pensando,capmem"; ++ }; ++ + soc: soc { + compatible = "simple-bus"; + #address-cells = <2>; +diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile +index 2037a86..47ffd83 100644 +--- a/drivers/soc/Makefile ++++ b/drivers/soc/Makefile +@@ -25,6 +25,7 @@ obj-y += microchip/ + obj-y += nuvoton/ + obj-y += pxa/ + obj-y += amlogic/ ++obj-$(CONFIG_ARCH_PENSANDO) += pensando/ + obj-y += qcom/ + obj-y += renesas/ + obj-y += rockchip/ +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +index da71e35..abe01ea 100644 +--- a/drivers/soc/pensando/Kconfig ++++ b/drivers/soc/pensando/Kconfig +@@ -8,6 +8,13 @@ config ARCH_PENSANDO_ELBA_SOC + help + Support for the Pensando Elba SoC + ++config PENSANDO_SOC_CAPMEM ++ tristate "/dev/capmem driver for the Pensando SoCs" ++ depends on OF ++ default y ++ help ++ Support for the Pensando SoC memory driver ++ + endmenu + + endif +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +new file mode 100644 +index 0000000..fcfb674 +--- /dev/null ++++ b/drivers/soc/pensando/Makefile +@@ -0,0 +1,6 @@ ++# ++# Pensando SoC drivers ++# ++CFLAGS_cap_mem.o := -I$(src) ++ ++obj-$(CONFIG_PENSANDO_SOC_CAPMEM) += cap_mem.o +diff --git a/drivers/soc/pensando/cap_mem.c b/drivers/soc/pensando/cap_mem.c +new file mode 100644 +index 0000000..04b73e3 +--- /dev/null ++++ b/drivers/soc/pensando/cap_mem.c +@@ -0,0 +1,306 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2018-2022, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "capmem_dev.h" ++ ++#define DSC_MEM_ATTR_COHERENT 0x1 // Memory range is coherent ++ ++/* ++ * Memory range information provided by U-Boot on the kernel commandline: ++ * Syntax: ++ * start-end:type[,start-end:type] ++ * start: hex start address (no 0x prefix) ++ * end: hex end address (inclusive) ++ * type: address space type: coherent | noncoherent ++ * Eg: ++ * capmem=c0000000-c3f00000:coherent,c8000000-13fffffff:noncoherent ++ * ++ * Only address ranges specified are allowed to be mapped. ++ */ ++static char *ranges; ++#ifdef MODULE ++module_param(ranges, charp, 0); ++#else ++static int __init capmem_setup(char *s) ++{ ++ ranges = s; ++ return 0; ++} ++__setup("capmem=", capmem_setup); ++#endif ++ ++static struct capmem_range mem_range[CAPMEM_MAX_RANGES]; ++static int nmem_ranges; ++ ++static int capmem_add_range(uint64_t start, uint64_t len, int type) ++{ ++ struct capmem_range *p = &mem_range[nmem_ranges]; ++ ++ if (nmem_ranges == CAPMEM_MAX_RANGES) ++ return -ENOMEM; ++ p->start = start; ++ p->len = len; ++ p->type = type; ++ ++nmem_ranges; ++ return 0; ++} ++ ++static int cap_mem_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ size_t size = vma->vm_end - vma->vm_start; ++ phys_addr_t p_start = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT; ++ phys_addr_t p_end = p_start + size - 1; ++ pgprot_t pgprot = vma->vm_page_prot; ++ int i; ++ ++ // range cannot wrap ++ if (p_end <= p_start) ++ return -EINVAL; ++ ++ // must be MAP_SHARED ++ if (!(vma->vm_flags & VM_MAYSHARE)) ++ return -EINVAL; ++ ++ // find permitted range ++ for (i = 0; i < nmem_ranges; i++) ++ if (p_start >= mem_range[i].start && ++ p_end < (mem_range[i].start + mem_range[i].len)) ++ break; ++ if (i == nmem_ranges) ++ return -EPERM; ++ ++ switch (mem_range[i].type) { ++ case CAPMEM_TYPE_DEVICE: ++ /* register space must be device-mapped */ ++ pgprot = pgprot_device(pgprot); ++ vm_flags_set(vma, VM_IO); ++ break; ++ ++ case CAPMEM_TYPE_NONCOHERENT: ++ /* ++ * An inner shareable cached mapping on a noncoherence range ++ * is invalid, so only accept non-cached mapping requests. ++ */ ++ if (!(file->f_flags & O_SYNC)) ++ return -EINVAL; ++ pgprot = pgprot_writecombine(pgprot); ++ break; ++ ++ default: ++ // CAPMEM_TYPE_COHERENT - default inner shareable mapping ++ break; ++ } ++ ++ /* ++ * Clear the RDONLY bit and set the DIRTY bit to bypass the ++ * kernel's clean/dirty page tracking, which uses a page fault on ++ * first write behavior, which is undesirable for performance. ++ */ ++ if (vma->vm_flags & VM_WRITE) ++ pgprot = __pgprot_modify(pgprot, PTE_RDONLY, PTE_DIRTY); ++ ++ vma->vm_page_prot = pgprot; ++ ++ /* Remap-pfn-range will mark the range VM_IO */ ++ if (remap_pfn_range(vma, ++ vma->vm_start, ++ vma->vm_pgoff, ++ size, ++ vma->vm_page_prot)) { ++ return -EAGAIN; ++ } ++ ++ return 0; ++} ++ ++static long cap_mem_unlocked_ioctl(struct file *file, ++ unsigned int cmd, unsigned long arg) ++{ ++ void __user *p = (void __user *)arg; ++ struct capmem_range __user *rp; ++ struct capmem_ranges_args gr; ++ int i; ++ ++ switch (cmd) { ++ case CAPMEM_GET_NRANGES: ++ return put_user(nmem_ranges, (int __user *)p); ++ ++ case CAPMEM_GET_RANGES: ++ if (copy_from_user(&gr, p, sizeof(gr))) ++ return -EFAULT; ++ rp = (struct capmem_range __user *)gr.range; ++ for (i = 0; i < gr.nranges; i++) { ++ if (i >= nmem_ranges) ++ return i; ++ if (copy_to_user(rp, &mem_range[i], sizeof(*rp))) ++ return -EFAULT; ++ ++rp; ++ } ++ return i; ++ ++ default: ++ return -ENOTTY; ++ } ++} ++ ++const struct file_operations cap_mem_fops = { ++ .owner = THIS_MODULE, ++ .mmap = cap_mem_mmap, ++ .unlocked_ioctl = cap_mem_unlocked_ioctl, ++}; ++ ++static struct miscdevice cap_mem_dev = { ++ MISC_DYNAMIC_MINOR, ++ CAPMEM_NAME, ++ &cap_mem_fops ++}; ++ ++static int __init parse_memory_ranges(struct platform_device *pdev, char *s) ++{ ++ uint64_t start, end, len; ++ char *p, *q; ++ int r, type; ++ ++ if (!s) ++ return 0; ++ ++ while ((p = strsep(&s, ",")) != NULL) { ++ if (nmem_ranges == CAPMEM_MAX_RANGES) { ++ dev_err(&pdev->dev, "too many ranges\n"); ++ return -ENODEV; ++ } ++ q = strchr(p, ':'); ++ if (!q) ++ goto syntax; ++ *q++ = '\0'; ++ if (sscanf(p, "%llx-%llx", &start, &end) != 2) ++ goto syntax; ++ if (end <= start) ++ goto syntax; ++ if (strcmp(q, "coherent") == 0) ++ type = CAPMEM_TYPE_COHERENT; ++ else if (strcmp(q, "noncoherent") == 0) ++ type = CAPMEM_TYPE_NONCOHERENT; ++ else ++ goto syntax; ++ len = end - start + 1; ++ r = capmem_add_range(start, len, type); ++ if (r) ++ return r; ++ } ++ return 0; ++syntax: ++ dev_err(&pdev->dev, "invalid range syntax\n"); ++ return -EINVAL; ++} ++ ++/* ++ * Device space is mapped out here. ++ */ ++static const struct { ++ uint64_t start; ++ uint64_t len; ++} init_device_ranges[] = { ++ { 0x00200000, 0x6fe00000 }, // 00200000...6fffffff ++}; ++ ++static void load_static_entries(void) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(init_device_ranges); i++) { ++ capmem_add_range(init_device_ranges[i].start, ++ init_device_ranges[i].len, ++ CAPMEM_TYPE_DEVICE); ++ } ++} ++ ++/* ++ * Load ranges from device-tree (installed by u-boot): ++ * The pensando,capmem-ranges parameter is a table of 5 words per row. ++ * The table format is: ++ * ++ * attr is { unused:30, bypass:1, coherent:1 } ++ */ ++static int load_of_ranges(struct platform_device *pdev) ++{ ++ u32 entries[CAPMEM_MAX_RANGES][5]; ++ int r, n, i, type; ++ u64 start, len; ++ ++ n = of_property_read_variable_u32_array(pdev->dev.of_node, ++ "pensando,capmem-ranges", (u32 *)entries, ++ 0, sizeof(entries) / sizeof(u32)); ++ if (n < 0) ++ return -ENOENT; ++ if (n % 5 != 0) { ++ dev_err(&pdev->dev, "of pensando,capmem-ranges invalid\n"); ++ return -ENODEV; ++ } ++ n /= 5; ++ for (i = 0; i < n; i++) { ++ type = (entries[i][4] & DSC_MEM_ATTR_COHERENT) ? ++ CAPMEM_TYPE_COHERENT : CAPMEM_TYPE_NONCOHERENT; ++ start = ((u64)entries[i][0] << 32) | entries[i][1]; ++ len = ((u64)entries[i][2] << 32) | entries[i][3]; ++ r = capmem_add_range(start, len, type); ++ if (r) ++ return r; ++ } ++ return 0; ++} ++ ++static int capmem_probe(struct platform_device *pdev) ++{ ++ int r; ++ ++ dev_info(&pdev->dev, "Loading capmem driver\n"); ++ load_static_entries(); ++ r = load_of_ranges(pdev); ++ if (r == -ENOENT) { ++ /* fallback to the capmem= variable */ ++ r = parse_memory_ranges(pdev, ranges); ++ if (r) ++ return r; ++ } ++ return misc_register(&cap_mem_dev); ++} ++ ++static void capmem_remove(struct platform_device *pdev) ++{ ++ dev_info(&pdev->dev, "Unloading capmem driver\n"); ++ misc_deregister(&cap_mem_dev); ++} ++ ++static const struct of_device_id capmem_of_match[] = { ++ { .compatible = "pensando,capmem" }, ++ { /* end of table */ } ++}; ++ ++static struct platform_driver capmem_driver = { ++ .probe = capmem_probe, ++ .remove = capmem_remove, ++ .driver = { ++ .name = "capmem", ++ .owner = THIS_MODULE, ++ .of_match_table = capmem_of_match, ++ }, ++}; ++ ++module_platform_driver(capmem_driver); ++MODULE_DESCRIPTION("Pensando SoC Memory Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/pensando/capmem_dev.h b/drivers/soc/pensando/capmem_dev.h +new file mode 100644 +index 0000000..ac8547e +--- /dev/null ++++ b/drivers/soc/pensando/capmem_dev.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2018-2021, Pensando Systems Inc. ++ */ ++ ++#ifndef __CAPMEM_H__ ++#define __CAPMEM_H__ ++ ++#define CAPMEM_NAME "capmem" ++#define CAPMEM_DEV "/dev/capmem" ++#define CAPMEM_IOCTL_NUM 0xcc ++ ++struct capmem_range { ++ uint64_t start; ++ uint64_t len; ++ int type; ++}; ++enum { ++ CAPMEM_TYPE_DEVICE, ++ CAPMEM_TYPE_COHERENT, ++ CAPMEM_TYPE_NONCOHERENT ++}; ++ ++struct capmem_ranges_args { ++ struct capmem_range *range; ++ int nranges; ++}; ++ ++#define CAPMEM_MAX_RANGES 64 ++ ++#define CAPMEM_GET_NRANGES _IOR(CAPMEM_IOCTL_NUM, 1, int) ++#define CAPMEM_GET_RANGES _IOWR(CAPMEM_IOCTL_NUM, 2, struct capmem_ranges_args) ++ ++#endif +-- +1.8.3.1 + diff --git a/patches-sonic/0010-dts-marvell-Add-support-for-7020-comexpress.patch b/patches-sonic/0010-dts-marvell-Add-support-for-7020-comexpress.patch new file mode 100644 index 000000000..c3f28075e --- /dev/null +++ b/patches-sonic/0010-dts-marvell-Add-support-for-7020-comexpress.patch @@ -0,0 +1,198 @@ +From 392cb3d2894af3eca861b74318aa719f2218158e Mon Sep 17 00:00:00 2001 +From: Keshav Gupta +Date: Wed, 4 Oct 2023 06:46:29 +0000 +Subject: [PATCH] dts: marvell: Add support for 7020 comexpress + +This change adds device tree file for Marvell Armada 7020 ComExpress +development board. + +The Armada 7020(7K family) composed of: + + - An AP806 block that contains the CPU core and a few basic peripherals. + The AP806 is available in dual core configurations. + + - One CP110 blocks that contain all the high-speed interfaces + (SATA, PCIe, Ethernet, etc.). + +Also added SDIO mode pin control configuration for CP0. + +Signed-off-by: Eyal Oron +Tested-by: Raz Adashi +Reviewed-by: Raz Adashi +Signed-off-by: Yan Markman +--- + arch/arm64/boot/dts/marvell/Makefile | 1 + + .../dts/marvell/armada-7020-comexpress.dts | 134 ++++++++++++++++++ + arch/arm64/boot/dts/marvell/armada-70x0.dtsi | 6 + + 3 files changed, 141 insertions(+) + create mode 100644 arch/arm64/boot/dts/marvell/armada-7020-comexpress.dts + +diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile +index ce751b502..062317759 100644 +--- a/arch/arm64/boot/dts/marvell/Makefile ++++ b/arch/arm64/boot/dts/marvell/Makefile +@@ -32,3 +32,4 @@ dtb-$(CONFIG_ARCH_MVEBU) += cn9130-cf-base.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9130-cf-pro.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9131-cf-solidwan.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9132-clearfog.dtb ++dtb-$(CONFIG_ARCH_MVEBU) += armada-7020-comexpress.dtb +diff --git a/arch/arm64/boot/dts/marvell/armada-7020-comexpress.dts b/arch/arm64/boot/dts/marvell/armada-7020-comexpress.dts +new file mode 100644 +index 000000000..782ac4539 +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/armada-7020-comexpress.dts +@@ -0,0 +1,134 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (C) 2019 Marvell Technology Group Ltd. ++ * ++ * Device Tree file for Marvell Armada 7020 COMEXPRESS board. ++ */ ++ ++#include "armada-7040.dtsi" ++ ++/ { ++ model = "Marvell Armada-7020 COMEXPRESS board setup"; ++ compatible = "marvell,armada7020-comexpress", "marvell,armada7020", ++ "marvell,armada-ap806"; ++ ++ memory@0 { ++ device_type = "memory"; ++ reg = <0x0 0x0 0x0 0x40000000>; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ aliases { ++ ethernet0 = &cp0_eth0; ++ ethernet1 = &cp0_eth1; ++ }; ++}; ++ ++&i2c0 { ++ status = "okay"; ++ clock-frequency = <100000>; ++}; ++ ++&spi0 { ++ status = "okay"; ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++&cp0_mdio { ++ status = "okay"; ++ ++ phy0: ethernet-phy@10 { ++ reg = <0x10>; ++ }; ++}; ++ ++&cp0_ethernet { ++ status = "okay"; ++}; ++ ++&cp0_eth0 { ++ status = "disabled"; ++ phy-mode = "10gbase-kr"; ++ managed = "in-band-status"; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp0_comphy4 0>; ++}; ++ ++&cp0_eth1 { ++ status = "okay"; ++ phy = <&phy0>; ++ phy-mode = "rgmii-id"; ++}; ++ ++&cp0_i2c0 { ++ status = "okay"; ++ clock-frequency = <100000>; ++}; ++ ++&cp0_nand_controller { ++ status = "okay"; ++ ++ nand@0 { ++ reg = <0>; ++ label = "pxa3xx_nand-0"; ++ nand-rb = <0>; ++ nand-ecc-mode = "hw"; ++ nand-on-flash-bbt; ++ nand-ecc-strength = <8>; ++ nand-ecc-step-size = <512>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "U-Boot"; ++ reg = <0 0x400000>; ++ }; ++ partition@200000 { ++ label = "Linux"; ++ reg = <0x400000 0x100000>; ++ }; ++ partition@1000000 { ++ label = "Filesystem"; ++ reg = <0x500000 0x1e00000>; ++ }; ++ }; ++ }; ++}; ++ ++&cp0_pcie0 { ++ status = "okay"; ++ num-lanes = <4>; ++ num-viewport = <8>; ++ ++ ranges = <0x81000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x00010000 ++ 0x82000000 0x0 0x00000000 0x8 0x00000000 0x2 0x00000000>; ++}; ++ ++&cp0_sata0 { ++ /* CPM Lane 0 - U29 */ ++ status = "okay"; ++ ++ sata-port@1 { ++ status = "okay"; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp0_comphy5 1>; ++ }; ++}; ++ ++&cp0_sdhci0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdhci_pins>; ++ status = "okay"; ++ bus-width = <4>; ++ no-1-8-v; ++ broken-cd; ++}; +diff --git a/arch/arm64/boot/dts/marvell/armada-70x0.dtsi b/arch/arm64/boot/dts/marvell/armada-70x0.dtsi +index 293403a1a..95199cd99 100644 +--- a/arch/arm64/boot/dts/marvell/armada-70x0.dtsi ++++ b/arch/arm64/boot/dts/marvell/armada-70x0.dtsi +@@ -47,6 +47,12 @@ &cp0_syscon0 { + cp0_pinctrl: pinctrl { + compatible = "marvell,armada-7k-pinctrl"; + ++ sdhci_pins: sdhi-pins { ++ marvell,pins = "mpp56", "mpp57", "mpp58", ++ "mpp59", "mpp60", "mpp61", "mpp62"; ++ marvell,function = "sdio"; ++ }; ++ + nand_pins: nand-pins { + marvell,pins = + "mpp15", "mpp16", "mpp17", "mpp18", +-- +2.34.1 + diff --git a/patches-sonic/0010-fix-aer-ratelimiting-sysfs-output-units.patch b/patches-sonic/0010-fix-aer-ratelimiting-sysfs-output-units.patch new file mode 100644 index 000000000..73dada6f1 --- /dev/null +++ b/patches-sonic/0010-fix-aer-ratelimiting-sysfs-output-units.patch @@ -0,0 +1,34 @@ +From f993b05bf9e59ab4a0c41cad1ab956080c5fb027 Mon Sep 17 00:00:00 2001 +From: jamessewart +Date: Mon, 10 Nov 2025 13:45:50 +0000 +Subject: [PATCH 10/12] fix aer ratelimiting sysfs output units + +aer statistics include an interval value that determines the time period over which +to limit error logging. This is stored in jiffies, but the sysfs file configuring it +is supposed to expose milliseconds, the store function converts the input to jiffies +and this change ensures the show implementation converts back to milliseconds + +Signed-off-by: James Sewart +--- + drivers/pci/pcie/aer.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index df636016168f..067e305212eb 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -634,8 +634,9 @@ const struct attribute_group aer_stats_attr_group = { + { \ + struct pci_dev *pdev = to_pci_dev(dev); \ + \ +- return sysfs_emit(buf, "%d\n", \ +- pdev->aer_stats->ratelimit.interval); \ ++ int interval = pdev->aer_stats->ratelimit.interval; \ ++ interval = jiffies_to_msecs(interval); \ ++ return sysfs_emit(buf, "%d\n", interval); \ + } \ + \ + static ssize_t \ +-- +2.47.0 + diff --git a/patches-sonic/0010-i2c-xiic-switch-to-devres-managed-APIs.patch b/patches-sonic/0010-i2c-xiic-switch-to-devres-managed-APIs.patch new file mode 100644 index 000000000..941e92ef3 --- /dev/null +++ b/patches-sonic/0010-i2c-xiic-switch-to-devres-managed-APIs.patch @@ -0,0 +1,107 @@ +From fb4beb7d83581052bc47a326fe8e78721ed2dc4c Mon Sep 17 00:00:00 2001 +From: Abdurrahman Hussain +Date: Mon, 23 Feb 2026 15:59:16 +0000 +Subject: [PATCH 10/22] i2c: xiic: switch to devres managed APIs + +Simplify the error code paths by switching to devres managed helper +functions. + +Signed-off-by: Abdurrahman Hussain +Reviewed-by: Andy Shevchenko +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260223-i2c-xiic-v12-1-b6c9ce4e4f3c@nexthop.ai +--- + drivers/i2c/busses/i2c-xiic.c | 30 ++++++++++++------------------ + 1 file changed, 12 insertions(+), 18 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index 072edc6c12da..ea85fc396033 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -1424,6 +1424,7 @@ MODULE_DEVICE_TABLE(of, xiic_of_match); + + static int xiic_i2c_probe(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; + struct xiic_i2c *i2c; + struct xiic_i2c_platform_data *pdata; + const struct of_device_id *match; +@@ -1462,7 +1463,10 @@ static int xiic_i2c_probe(struct platform_device *pdev) + snprintf(i2c->adap.name, sizeof(i2c->adap.name), + DRIVER_NAME " %s", pdev->name); + +- mutex_init(&i2c->lock); ++ ret = devm_mutex_init(dev, &i2c->lock); ++ if (ret) ++ return ret; ++ + spin_lock_init(&i2c->atomic_lock); + + i2c->clk = devm_clk_get_enabled(&pdev->dev, NULL); +@@ -1473,8 +1477,9 @@ static int xiic_i2c_probe(struct platform_device *pdev) + i2c->dev = &pdev->dev; + pm_runtime_set_autosuspend_delay(i2c->dev, XIIC_PM_TIMEOUT); + pm_runtime_use_autosuspend(i2c->dev); +- pm_runtime_set_active(i2c->dev); +- pm_runtime_enable(i2c->dev); ++ ret = devm_pm_runtime_set_active_enabled(dev); ++ if (ret) ++ return ret; + + /* SCL frequency configuration */ + i2c->input_clk = clk_get_rate(i2c->clk); +@@ -1490,7 +1495,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + + if (ret < 0) { + dev_err_probe(&pdev->dev, ret, "Cannot claim IRQ\n"); +- goto err_pm_disable; ++ return ret; + } + + i2c->singlemaster = +@@ -1509,16 +1514,14 @@ static int xiic_i2c_probe(struct platform_device *pdev) + i2c->endianness = BIG; + + ret = xiic_reinit(i2c); +- if (ret < 0) { +- dev_err_probe(&pdev->dev, ret, "Cannot xiic_reinit\n"); +- goto err_pm_disable; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "Cannot xiic_reinit\n"); + + /* add i2c adapter to i2c tree */ + ret = i2c_add_adapter(&i2c->adap); + if (ret) { + xiic_deinit(i2c); +- goto err_pm_disable; ++ return ret; + } + + if (pdata) { +@@ -1531,12 +1534,6 @@ static int xiic_i2c_probe(struct platform_device *pdev) + (unsigned long)res->start, irq, i2c->i2c_clk); + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- pm_runtime_set_suspended(&pdev->dev); +- +- return ret; + } + + static void xiic_i2c_remove(struct platform_device *pdev) +@@ -1556,9 +1553,6 @@ static void xiic_i2c_remove(struct platform_device *pdev) + xiic_deinit(i2c); + + pm_runtime_put_sync(i2c->dev); +- pm_runtime_disable(&pdev->dev); +- pm_runtime_set_suspended(&pdev->dev); +- pm_runtime_dont_use_autosuspend(&pdev->dev); + } + + static const struct dev_pm_ops xiic_dev_pm_ops = { +-- +2.53.0 + diff --git a/patches-sonic/0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch b/patches-sonic/0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch new file mode 100644 index 000000000..1c79fadaa --- /dev/null +++ b/patches-sonic/0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch @@ -0,0 +1,48 @@ +From: Sergejs Hatinecs +Date: Thu, 12 Jun 2026 12:00:00 +0300 +Subject: [PATCH 6.12 1/1] net: ftgmac100: hard-reset MAC for SGMII to clear RX stall + +Upstream-Status: Pending + +On AST2700 + Marvell 88E151x over SGMII, an "ifconfig down/up" (or a +partner-side link flap) intermittently leaves the MAC receive path wedged: +the link comes back up and reports resolved, TX works, but RX stays frozen +(no DHCP, no ping). The MDIO/serdes registers look identical to a healthy +link in this state, so the wedge is below the register layer. + +The only reliable recovery is "modprobe -r ftgmac100 / modprobe ftgmac100", +which re-runs probe and performs a full reset_control cycle of the MAC IP +block. The ndo_open/ftgmac100_reset() path only issues the soft MACCR reset, +which does not clear the wedged RX path. + +ftgmac100_reset_and_config_mac() already does the reset_control assert/ +deassert for RMII; extend it to SGMII so every MAC reset hard-resets the IP +block and clears the RX path, matching the modprobe-reload behaviour. With +this, repeated down/up cycles and partner link flaps recover reliably. + +Signed-off-by: Sergejs Hatinecs +--- + drivers/net/ethernet/faraday/ftgmac100.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c +index 8ef2b736f23e..a3534b467aa6 100644 +--- a/drivers/net/ethernet/faraday/ftgmac100.c ++++ b/drivers/net/ethernet/faraday/ftgmac100.c +@@ -157,8 +157,14 @@ static int ftgmac100_reset_and_config_mac(struct ftgmac100 *priv) + { + u32 maccr = 0; + +- /* RMII needs SCU reset to clear status */ +- if (priv->netdev->phydev->interface == PHY_INTERFACE_MODE_RMII) { ++ /* RMII needs SCU reset to clear status. SGMII (AST2700 + 88E151x) needs ++ * it too: after ifconfig down/up the soft MACCR reset alone leaves the ++ * MAC RX path wedged (link reads resolved but RX stays frozen). Only a ++ * full reset_control cycle clears it - this is why "modprobe -r/modprobe" ++ * (which hard-resets at probe) always recovers while down/up does not. ++ */ ++ if (priv->netdev->phydev->interface == PHY_INTERFACE_MODE_RMII || ++ priv->netdev->phydev->interface == PHY_INTERFACE_MODE_SGMII) { + int err; + + err = reset_control_assert(priv->rst); diff --git a/patches-sonic/0010-platform-mellanox-nvsw-sn2200-Fix-.items-in-nvsw_sn2.patch b/patches-sonic/0010-platform-mellanox-nvsw-sn2200-Fix-.items-in-nvsw_sn2.patch new file mode 100644 index 000000000..eec14acfd --- /dev/null +++ b/patches-sonic/0010-platform-mellanox-nvsw-sn2200-Fix-.items-in-nvsw_sn2.patch @@ -0,0 +1,241 @@ +From 0eb3b624267260f95f0d126fe3a031af591c3d70 Mon Sep 17 00:00:00 2001 +From: Nathan Chancellor +Date: Fri, 9 May 2025 11:58:01 +0100 +Subject: [PATCH 10/78] platform: mellanox: nvsw-sn2200: Fix .items in + nvsw_sn2201_busbar_hotplug +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Clang warns (or errors with CONFIG_WERROR=y): + + drivers/platform/mellanox/nvsw-sn2201.c:531:32: error: variable 'nvsw_sn2201_busbar_items' is not needed and will not be emitted [-Werror,-Wunneeded-internal-declaration] + 531 | static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = { + | ^~~~~~~~~~~~~~~~~~~~~~~~ + +nvsw_sn2201_busbar_items is only used in ARRAY_SIZE(), which uses +sizeof(), so this variable is only used at compile time. It appears that +this may be a copy and paste issue, so use nvsw_sn2201_busbar_items as +the .items member in nvsw_sn2201_busbar_hotplug, clearing up the +warning. + +Fixes: 56b0bb7f9069 ("platform: mellanox: nvsw-sn2200: Add support for new system flavour") +Signed-off-by: Nathan Chancellor +Link: https://lore.kernel.org/r/20250509-nvsw-sn2200-fix-items-busbar-hotplug-v1-1-8844fff38dc8@kernel.org +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/nvsw-sn2201.c | 114 ++++++++++++++++++++++++++++++-- + 1 file changed, 109 insertions(+), 5 deletions(-) + +diff --git a/drivers/platform/mellanox/nvsw-sn2201.c b/drivers/platform/mellanox/nvsw-sn2201.c +index e708521..beb3f82 100644 +--- a/drivers/platform/mellanox/nvsw-sn2201.c ++++ b/drivers/platform/mellanox/nvsw-sn2201.c +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -104,6 +105,9 @@ + | NVSW_SN2201_CPLD_AGGR_PSU_MASK_DEF \ + | NVSW_SN2201_CPLD_AGGR_PWR_MASK_DEF \ + | NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF) ++#define NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF \ ++ (NVSW_SN2201_CPLD_AGGR_ASIC_MASK_DEF \ ++ | NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF) + + #define NVSW_SN2201_CPLD_ASIC_MASK GENMASK(3, 1) + #define NVSW_SN2201_CPLD_PSU_MASK GENMASK(1, 0) +@@ -132,6 +136,7 @@ + * @cpld_devs: I2C devices for cpld; + * @cpld_devs_num: number of I2C devices for cpld; + * @main_mux_deferred_nr: I2C adapter number must be exist prior creating devices execution; ++ * @ext_pwr_source: true if system powered by external power supply; false - by internal; + */ + struct nvsw_sn2201 { + struct device *dev; +@@ -152,6 +157,7 @@ struct nvsw_sn2201 { + struct mlxreg_hotplug_device *cpld_devs; + int cpld_devs_num; + int main_mux_deferred_nr; ++ bool ext_pwr_source; + }; + + static bool nvsw_sn2201_writeable_reg(struct device *dev, unsigned int reg) +@@ -522,6 +528,35 @@ struct mlxreg_core_hotplug_platform_data nvsw_sn2201_hotplug = { + .mask = NVSW_SN2201_CPLD_AGGR_MASK_DEF, + }; + ++static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = { ++ { ++ .data = nvsw_sn2201_fan_items_data, ++ .aggr_mask = NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF, ++ .reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET, ++ .mask = NVSW_SN2201_CPLD_FAN_MASK, ++ .count = ARRAY_SIZE(nvsw_sn2201_fan_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = nvsw_sn2201_sys_items_data, ++ .aggr_mask = NVSW_SN2201_CPLD_AGGR_ASIC_MASK_DEF, ++ .reg = NVSW_SN2201_ASIC_STATUS_OFFSET, ++ .mask = NVSW_SN2201_CPLD_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_sn2201_sys_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data nvsw_sn2201_busbar_hotplug = { ++ .items = nvsw_sn2201_busbar_items, ++ .counter = ARRAY_SIZE(nvsw_sn2201_busbar_items), ++ .cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET, ++ .mask = NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF, ++}; ++ + /* SN2201 static devices. */ + static struct i2c_board_info nvsw_sn2201_static_devices[] = { + { +@@ -557,6 +592,9 @@ static struct i2c_board_info nvsw_sn2201_static_devices[] = { + { + I2C_BOARD_INFO("pmbus", 0x40), + }, ++ { ++ I2C_BOARD_INFO("lm5066i", 0x15), ++ }, + }; + + /* SN2201 default static board info. */ +@@ -607,6 +645,58 @@ static struct mlxreg_hotplug_device nvsw_sn2201_static_brdinfo[] = { + }, + }; + ++/* SN2201 default busbar static board info. */ ++static struct mlxreg_hotplug_device nvsw_sn2201_busbar_static_brdinfo[] = { ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[0], ++ .nr = NVSW_SN2201_MAIN_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[1], ++ .nr = NVSW_SN2201_MAIN_MUX_CH0_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[2], ++ .nr = NVSW_SN2201_MAIN_MUX_CH0_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[3], ++ .nr = NVSW_SN2201_MAIN_MUX_CH0_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[4], ++ .nr = NVSW_SN2201_MAIN_MUX_CH3_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[5], ++ .nr = NVSW_SN2201_MAIN_MUX_CH5_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[6], ++ .nr = NVSW_SN2201_MAIN_MUX_CH5_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[7], ++ .nr = NVSW_SN2201_MAIN_MUX_CH5_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[8], ++ .nr = NVSW_SN2201_MAIN_MUX_CH6_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[9], ++ .nr = NVSW_SN2201_MAIN_MUX_CH6_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[10], ++ .nr = NVSW_SN2201_MAIN_MUX_CH7_NR, ++ }, ++ { ++ .brdinfo = &nvsw_sn2201_static_devices[11], ++ .nr = NVSW_SN2201_MAIN_MUX_CH1_NR, ++ }, ++}; ++ + /* LED default data. */ + static struct mlxreg_core_data nvsw_sn2201_led_data[] = { + { +@@ -981,7 +1071,10 @@ static int nvsw_sn2201_config_init(struct nvsw_sn2201 *nvsw_sn2201, void *regmap + nvsw_sn2201->io_data = &nvsw_sn2201_regs_io; + nvsw_sn2201->led_data = &nvsw_sn2201_led; + nvsw_sn2201->wd_data = &nvsw_sn2201_wd; +- nvsw_sn2201->hotplug_data = &nvsw_sn2201_hotplug; ++ if (nvsw_sn2201->ext_pwr_source) ++ nvsw_sn2201->hotplug_data = &nvsw_sn2201_busbar_hotplug; ++ else ++ nvsw_sn2201->hotplug_data = &nvsw_sn2201_hotplug; + + /* Register IO access driver. */ + if (nvsw_sn2201->io_data) { +@@ -1088,7 +1181,7 @@ static int nvsw_sn2201_i2c_completion_notify(void *handle, int id) + if (!nvsw_sn2201->main_mux_devs->adapter) { + err = -ENODEV; + dev_err(nvsw_sn2201->dev, "Failed to get adapter for bus %d\n", +- nvsw_sn2201->main_mux_devs->nr); ++ nvsw_sn2201->cpld_devs->nr); + goto i2c_get_adapter_main_fail; + } + +@@ -1198,12 +1291,18 @@ static int nvsw_sn2201_config_pre_init(struct nvsw_sn2201 *nvsw_sn2201) + static int nvsw_sn2201_probe(struct platform_device *pdev) + { + struct nvsw_sn2201 *nvsw_sn2201; ++ const char *sku; + int ret; + + nvsw_sn2201 = devm_kzalloc(&pdev->dev, sizeof(*nvsw_sn2201), GFP_KERNEL); + if (!nvsw_sn2201) + return -ENOMEM; + ++ /* Validate system powering type - only HI168 SKU supports external power. */ ++ sku = dmi_get_system_info(DMI_PRODUCT_SKU); ++ if (sku && !strcmp(sku, "HI168")) ++ nvsw_sn2201->ext_pwr_source = true; ++ + nvsw_sn2201->dev = &pdev->dev; + platform_set_drvdata(pdev, nvsw_sn2201); + ret = platform_device_add_resources(pdev, nvsw_sn2201_lpc_io_resources, +@@ -1214,8 +1313,13 @@ static int nvsw_sn2201_probe(struct platform_device *pdev) + nvsw_sn2201->main_mux_deferred_nr = NVSW_SN2201_MAIN_MUX_DEFER_NR; + nvsw_sn2201->main_mux_devs = nvsw_sn2201_main_mux_brdinfo; + nvsw_sn2201->cpld_devs = nvsw_sn2201_cpld_brdinfo; +- nvsw_sn2201->sn2201_devs = nvsw_sn2201_static_brdinfo; +- nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_static_brdinfo); ++ if (nvsw_sn2201->ext_pwr_source) { ++ nvsw_sn2201->sn2201_devs = nvsw_sn2201_busbar_static_brdinfo; ++ nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_busbar_static_brdinfo); ++ } else { ++ nvsw_sn2201->sn2201_devs = nvsw_sn2201_static_brdinfo; ++ nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_static_brdinfo); ++ } + + return nvsw_sn2201_config_pre_init(nvsw_sn2201); + } +@@ -1253,7 +1357,7 @@ MODULE_DEVICE_TABLE(acpi, nvsw_sn2201_acpi_ids); + + static struct platform_driver nvsw_sn2201_driver = { + .probe = nvsw_sn2201_probe, +- .remove_new = nvsw_sn2201_remove, ++ .remove = nvsw_sn2201_remove, + .driver = { + .name = "nvsw-sn2201", + .acpi_match_table = nvsw_sn2201_acpi_ids, +-- +2.8.4 + diff --git a/patches-sonic/0011-Interrupt-domain-controllers-for-Elba-ASIC.patch b/patches-sonic/0011-Interrupt-domain-controllers-for-Elba-ASIC.patch new file mode 100644 index 000000000..2f30a0f5a --- /dev/null +++ b/patches-sonic/0011-Interrupt-domain-controllers-for-Elba-ASIC.patch @@ -0,0 +1,1067 @@ +From f8f5f8ae9e241e72771f6c28136919cdbfbfd8b3 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 09:49:17 +0000 +Subject: [PATCH 11/27] Interrupt domain controllers for Elba ASIC + +This supports using ASIC registers as IRQ chips, one per domain. The +result is a many level interrupt controller. There are actually three +different register blocks, each of which has its own configuration of +enable and disable mechanisms. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 16 + + drivers/irqchip/Kconfig | 11 + + drivers/irqchip/Makefile | 1 + + drivers/irqchip/irq-pensando.c | 879 +++++++++++++++++++++++++++++++++ + include/linux/irqchip/irq-pensando.h | 91 ++++ + 5 files changed, 998 insertions(+) + create mode 100644 drivers/irqchip/irq-pensando.c + create mode 100644 include/linux/irqchip/irq-pensando.h + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index 1ecd348..afc9c7c 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -188,6 +188,22 @@ + }; + }; + ++ /* ++ * Until we know the interrupt domain following this, we ++ * are forced to use this is the place where interrupts from ++ * PCI converge. In the ideal case, we use one domain higher, ++ * where the PCI-ness has been shed. ++ */ ++ pxc0_intr: interrupt-controller@20102200 { ++ compatible = "pensando,soc-ictlr-csrintr"; ++ interrupt-controller; ++ reg = <0x0 0x20102200 0x0 0x4>; ++ #interrupt-cells = <3>; ++ interrupt-parent = <&gic>; ++ interrupts = ; ++ interrupt-names = "pxc0_intr"; ++ }; ++ + edac: edac@30500000 { + compatible = "pensando,elba-edac-mc"; + reg = <0x0 0x30500000 0x0 0x100000>; +diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig +index a799a89..3f0afa7 100644 +--- a/drivers/irqchip/Kconfig ++++ b/drivers/irqchip/Kconfig +@@ -198,6 +198,17 @@ config CLPS711X_IRQCHIP + select SPARSE_IRQ + default y + ++config IRQ_PENSANDO ++ bool "Support for Pensando SOC IRQ chip structure" ++ depends on ARCH_PENSANDO_ELBA_SOC ++ default "y" ++ select IRQ_DOMAIN ++ select IRQ_DOMAIN_HIERARCHY ++ help ++ This controls whether the Pensando IRQ chip code is used. This code ++ works in sync with the SOC device tree to define the topology ++ of devices controlling interrupts. ++ + config OMPIC + bool + +diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile +index e3679ec..199883e 100644 +--- a/drivers/irqchip/Makefile ++++ b/drivers/irqchip/Makefile +@@ -124,5 +124,6 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o + obj-$(CONFIG_WPCM450_AIC) += irq-wpcm450-aic.o + obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o + obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o ++obj-$(CONFIG_IRQ_PENSANDO) += irq-pensando.o + obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o + obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o +diff --git a/drivers/irqchip/irq-pensando.c b/drivers/irqchip/irq-pensando.c +new file mode 100644 +index 0000000..5d72d19 +--- /dev/null ++++ b/drivers/irqchip/irq-pensando.c +@@ -0,0 +1,879 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Pensando register-based hierarchical IRQ driver ++ * ++ * Copyright (C) 2019-2021 Pensando, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ * Thanks to other irqchip developers! ++ * ++ * There are three types of registers in the Pensando Capri and Elba ASICs: ++ * o csr - a particular device. This has enable set, enable clear, and ++ * interrupt status registers ++ * o group - a group of devices. This has an enable register and an ++ * interrupt status register ++ * o csrintr - module level interrupts. This has a single register with the ++ * interrupt status at bit 0 and the enable at bit 1. The csrintr code ++ * supports two or more address tuples, which are all considered for ++ * interrupt enable, disable, and EOI processing. ++ * ++ * Items remaining to do: ++ * o Make interrupts use two cells. Right now they have 3 with an ++ * unnecessary leading GIC_SPI. ++ * o Drop enable_csr_padddr and enable_mask: ++ * ++ * a. Change reg to have what is now in enable_csr_paddr be ++ * the first element, which will be the same as the current ++ * value of enable_csr_paddr. ++ * b. The enable mask should be the same as 1 shifted left by ++ * the second value in the interrupts property ++ * ++ * Doing these, and documenting them, should make this easier to maintain. ++ */ ++ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifndef CHAR_BIT ++#define CHAR_BIT 8 ++#endif ++ ++/* Build in debugging messages in unmask_enable, eio, etc. functions */ ++#undef TRACE_IRQ_DOMAIN_OPS ++ ++#ifdef TRACE_IRQ_DOMAIN_OPS ++#define trace_irq_domain_ops(fmt, ...) pr_err("%s: " fmt, __func__, \ ++ ##__VA_ARGS__) ++#else ++#define trace_irq_domain_ops(fmt, ...) do { } while (false) ++#endif ++ ++/* Maximum number of supported domains */ ++#define MAX_DOMAINS 25 ++ ++#define MAX_N_IRQS_PER_CHIP 32 ++ ++/* Parameters for interpreting an irq_fwspec parsed from the device tree */ ++#define IRQ_GIC_INTR_TYPE 0 ++#define IRQ_FWSPEC_HWIRQ 1 ++#define IRQ_FWSPEC_TYPE 2 ++#define N_INTERRUPT_CELLS 3 ++#define N_CHIP_TYPES 1 /* On a per-domain basis */ ++ ++struct domain_info { ++ unsigned int i; ++ struct irq_domain *domain; ++}; ++ ++/* ++ * Control access to IRQ data ++ */ ++static unsigned long info_irq_lock(struct pen_ictlr_info *info) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&info->irq_lock, flags); ++ return flags; ++} ++ ++static void info_irq_unlock(struct pen_ictlr_info *info, unsigned long flags) ++{ ++ spin_unlock_irqrestore(&info->irq_lock, flags); ++} ++ ++/* ++ * Interrupt functions with disjoint enable and disable registers. ++ * ++ * The following interrupt enable/disable functions are here for ++ * debugging only and should be replaced by the corresponding ++ * irq_gc* function. ++ */ ++void pen_irq_unmask_enable_csr_one(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_csr __iomem *data; ++ irq_hw_number_t hwirq; ++ u32 mask; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ data = info->map_base[0]; ++ mask = BIT(hwirq); ++ trace_irq_domain_ops("enable %s with %x\n", irq_data->domain->name, ++ mask); ++ writel(mask, &data->int_enable_set); ++} ++EXPORT_SYMBOL(pen_irq_unmask_enable_csr_one); ++ ++void pen_irq_unmask_enable_csr(struct irq_data *irq_data) ++{ ++ pen_irq_unmask_enable_csr_one(irq_data); ++ irq_chip_unmask_parent(irq_data); ++} ++ ++void pen_irq_mask_disable_csr_one(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct irq_chip *irq_chip; ++ void *chip_data; ++ struct pen_ictlr_csr __iomem *data; ++ irq_hw_number_t hwirq; ++ u32 mask; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ irq_chip = irq_data->chip; ++ chip_data = irq_data->chip_data; ++ info = domain->host_data; ++ data = info->map_base[0]; ++ ++ mask = BIT(hwirq); ++ trace_irq_domain_ops("disable %s with %x\n", irq_data->domain->name, ++ mask); ++ writel(mask, &data->int_enable_clear); ++} ++EXPORT_SYMBOL(pen_irq_mask_disable_csr_one); ++ ++void pen_irq_mask_disable_csr(struct irq_data *irq_data) ++{ ++ pen_irq_mask_disable_csr_one(irq_data); ++ irq_chip_mask_parent(irq_data); ++} ++ ++void pen_irq_eoi_csr(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_csr __iomem *data; ++ irq_hw_number_t hwirq; ++ u32 mask; ++ u32 intreg_before; ++ ++ trace_irq_domain_ops("eio %s\n", irq_data->domain->name); ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ data = info->map_base[0]; ++ ++ mask = BIT(hwirq); ++ intreg_before = readl(&data->intreg); ++ writel(mask, &data->intreg); ++ irq_chip_eoi_parent(irq_data); ++} ++ ++/* ++ * Interrupt functions with enable and disable in the bit of the same ++ * register but a disjoint interrupt status register ++ * ++ * The following interrupt enable/disable functions are here for ++ * debugging only and should be replaced by the corresponding ++ * irq_gc* function. ++ */ ++void pen_irq_unmask_enable_grp_one(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_grp *data; ++ irq_hw_number_t hwirq; ++ unsigned long flags; ++ u32 enable, mask; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ data = info->map_base[0]; ++ ++ mask = BIT(hwirq); ++ ++ flags = info_irq_lock(info); ++ enable = readl(&data->intreg); ++ trace_irq_domain_ops("enable %s with %x\n", irq_data->domain->name, ++ enable | mask); ++ writel(enable | mask, &data->int_enable_rw_reg); ++ info_irq_unlock(info, flags); ++} ++EXPORT_SYMBOL(pen_irq_unmask_enable_grp_one); ++ ++void pen_irq_unmask_enable_grp(struct irq_data *irq_data) ++{ ++ pen_irq_unmask_enable_grp_one(irq_data); ++ irq_chip_unmask_parent(irq_data); ++} ++ ++void pen_irq_mask_disable_grp_one(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_grp *data; ++ irq_hw_number_t hwirq; ++ unsigned long flags; ++ u32 enable, mask; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ info = domain->host_data; ++ data = info->map_base[0]; ++ ++ mask = BIT(hwirq); ++ ++ flags = info_irq_lock(info); ++ enable = readl(&data->int_enable_rw_reg); ++ trace_irq_domain_ops("disable %s with %x\n", irq_data->domain->name, ++ enable & ~mask); ++ writel(enable & ~mask, &data->int_enable_rw_reg); ++ info_irq_unlock(info, flags); ++} ++EXPORT_SYMBOL(pen_irq_mask_disable_grp_one); ++ ++void pen_irq_mask_disable_grp(struct irq_data *irq_data) ++{ ++ pen_irq_mask_disable_grp_one(irq_data); ++ irq_chip_mask_parent(irq_data); ++} ++ ++void pen_irq_eoi_grp(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_grp *data; ++ irq_hw_number_t hwirq; ++ unsigned long flags; ++ u32 before, after; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ data = info->map_base[0]; ++ ++ flags = info_irq_lock(info); ++ ++ /* No state here, so we just invoke the parent */ ++ before = readl(&data->int_enable_rw_reg); ++ irq_chip_eoi_parent(irq_data); ++ after = readl(&data->int_enable_rw_reg); ++ info_irq_unlock(info, flags); ++ trace_irq_domain_ops("eio %s before %x after %x\n", ++ irq_data->domain->name, before, after); ++} ++ ++/* ++ * Interrupt functions with enable and disable in one bit of the same ++ * register as the interrupt status bit ++ * ++ * The following interrupt enable/disable functions are here for ++ * debugging only and should be replaced by the corresponding ++ * irq_gc* function. ++ */ ++void pen_irq_unmask_enable_csrintr_one(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_csrintr *data; ++ irq_hw_number_t hwirq; ++ unsigned long flags; ++ u32 mask; ++ u32 enable; ++ unsigned int i; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ mask = BIT(1); ++ ++ flags = info_irq_lock(info); ++ ++ for (i = 0; i < info->num_bases; i++) { ++ data = info->map_base[i]; ++ enable = readl(&data->intr); ++ trace_irq_domain_ops("enable %s (base #%u) with %x\n", ++ irq_data->domain->name, i, enable | mask); ++ writel(enable | mask, &data->intr); ++ } ++ ++ info_irq_unlock(info, flags); ++} ++EXPORT_SYMBOL(pen_irq_unmask_enable_csrintr_one); ++ ++void pen_irq_unmask_enable_csrintr(struct irq_data *irq_data) ++{ ++ pen_irq_unmask_enable_csrintr_one(irq_data); ++ irq_chip_unmask_parent(irq_data); ++} ++ ++void pen_irq_mask_disable_csrintr_one(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_csrintr *data; ++ irq_hw_number_t hwirq; ++ unsigned long flags; ++ u32 mask; ++ u32 enable; ++ unsigned int i; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ mask = BIT(1); ++ ++ flags = info_irq_lock(info); ++ ++ for (i = 0; i < info->num_bases; i++) { ++ data = info->map_base[i]; ++ enable = readl(&data->intr); ++ trace_irq_domain_ops("disable %s (base #%u) with %x\n", ++ irq_data->domain->name, i, enable & ~mask); ++ writel(enable & ~mask, &data->intr); ++ } ++ ++ info_irq_unlock(info, flags); ++} ++EXPORT_SYMBOL(pen_irq_mask_disable_csrintr_one); ++ ++void pen_irq_mask_disable_csrintr(struct irq_data *irq_data) ++{ ++ pen_irq_mask_disable_csrintr_one(irq_data); ++ irq_chip_mask_parent(irq_data); ++} ++ ++/* ++ * We don't need to do any real work here, just report debugging info ++ */ ++void pen_irq_eoi_csrintr(struct irq_data *irq_data) ++{ ++ struct irq_domain *domain; ++ struct pen_ictlr_info *info; ++ struct pen_ictlr_csrintr *data; ++ irq_hw_number_t hwirq; ++ unsigned long flags; ++ u32 before, after; ++ unsigned int i; ++ ++ hwirq = irq_data->hwirq; ++ domain = irq_data->domain; ++ info = domain->host_data; ++ flags = info_irq_lock(info); ++ ++ /* No state here, so we just invoke the parent */ ++ for (i = 0; i < info->num_bases; i++) { ++ data = info->map_base[i]; ++ before = readl(&data->intr); ++ ++ irq_chip_eoi_parent(irq_data); ++ ++ after = readl(&data->intr); ++ trace_irq_domain_ops("eio %s (base #%u) before %x after %x\n", ++ irq_data->domain->name, i, before, after); ++ } ++ info_irq_unlock(info, flags); ++} ++ ++static struct irq_chip pen_irq_chip_csr = { ++ .name = "CSR", ++ .irq_mask = pen_irq_mask_disable_csr, ++ .irq_unmask = pen_irq_unmask_enable_csr, ++ .irq_eoi = pen_irq_eoi_csr, ++ .irq_set_type = irq_chip_set_type_parent, ++#ifdef CONFIG_SMP ++ .irq_set_affinity = irq_chip_set_affinity_parent, ++#endif ++}; ++ ++static struct irq_chip pen_irq_chip_grp = { ++ .name = "GRP", ++ .irq_mask = pen_irq_mask_disable_grp, ++ .irq_unmask = pen_irq_unmask_enable_grp, ++#ifdef TRACE_IRQ_DOMAIN_OPS ++ .irq_eoi = pen_irq_eoi_grp, ++#else ++ .irq_eoi = irq_chip_eoi_parent, ++#endif ++ .irq_set_type = irq_chip_set_type_parent, ++#ifdef CONFIG_SMP ++ .irq_set_affinity = irq_chip_set_affinity_parent, ++#endif ++}; ++ ++static struct irq_chip pen_irq_chip_csrintr = { ++ .name = "CSRintr", ++ .irq_mask = pen_irq_mask_disable_csrintr, ++ .irq_unmask = pen_irq_unmask_enable_csrintr, ++#ifdef TRACE_IRQ_DOMAIN_OPS ++ .irq_eoi = pen_irq_eoi_csrintr, ++#else ++ .irq_eoi = irq_chip_eoi_parent, ++#endif ++ .irq_set_type = irq_chip_set_type_parent, ++#ifdef CONFIG_SMP ++ .irq_set_affinity = irq_chip_set_affinity_parent, ++#endif ++}; ++ ++/* ++ * Pull the hardware IRQ number from the device tree information ++ * Returns the zero if successful, otherwise a negative errno value ++ */ ++static int extract_hwirq(struct irq_fwspec *fwspec, irq_hw_number_t *hwirq) ++{ ++ ++ /* Verify that the value from the interrupts property is correct */ ++ if (fwspec->param_count != N_INTERRUPT_CELLS || ++ fwspec->param[IRQ_GIC_INTR_TYPE] != GIC_SPI) { ++ pr_err("Invalid value for #interrupts property\n"); ++ return -EINVAL; ++ } ++ ++ *hwirq = fwspec->param[IRQ_FWSPEC_HWIRQ]; ++ if (*hwirq >= MAX_N_IRQS_PER_CHIP) { ++ pr_err("Hardware interrupt number too big (>%lu)", *hwirq); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++/* ++ * This will fill in the param_count and 3 elements of param of a ++ * struct irq_fwspec. It is narrowly tailored for this particular usage ++ * but could fairly easily be generalized. ++ * ++ * Returns 0 on success, or a negative errno on failure. ++ */ ++static int pen_get_parent_fwspec(struct irq_domain *d, ++ struct irq_fwspec *fwspec) ++{ ++ struct device_node *device; ++ struct device_node *parent; ++ struct pen_ictlr_info *info; ++ u32 intsize; ++ unsigned int i; ++ int err; ++ ++ info = d->host_data; ++ device = info->dn; ++ ++ /* Look for the interrupt parent. */ ++ parent = of_irq_find_parent(device); ++ if (parent == NULL) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ /* Get size of interrupt specifier */ ++ if (of_property_read_u32(parent, "#interrupt-cells", &intsize)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ /* This is not a general solution, fail if it's not one we can do */ ++ if (intsize != N_INTERRUPT_CELLS) { ++ err = -EOPNOTSUPP; ++ goto out; ++ } ++ ++ fwspec->param_count = N_INTERRUPT_CELLS; ++ for (i = 0; i < intsize; i++) { ++ err = of_property_read_u32_index(device, "interrupts", i, ++ &fwspec->param[i]); ++ if (err != 0) ++ goto out; ++ } ++ ++ fwspec->param_count = intsize; ++ ++ return 0; ++ ++out: ++ of_node_put(parent); ++ return err; ++} ++ ++/* ++ * Translate from a value in the device tree interrupts property to a ++ * hardware IRQ number. ++ * @d: Domain for which translation should be done ++ * @fwspec: Hardware IRQ information from the device tree for this ++ * domain ++ * @out_hwirq: Pointer to place to store the lowest supported hardware IRQ ++ * @out_type: Pointer to the type of interrupt, e.g. edge, or level triggered ++ */ ++static int pen_irq_domain_translate(struct irq_domain *d, ++ struct irq_fwspec *fwspec, irq_hw_number_t *out_hwirq, ++ unsigned int *out_type) ++{ ++ if (is_of_node(fwspec->fwnode)) { ++ irq_hw_number_t hwirq; ++ int rc; ++ ++ rc = extract_hwirq(fwspec, &hwirq); ++ if (rc != 0) ++ return rc; ++ ++ *out_hwirq = hwirq; ++ *out_type = fwspec->param[IRQ_FWSPEC_TYPE] & ++ IRQ_TYPE_SENSE_MASK; ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++/* ++ * Recursively allocate the IRQ structures for this domain, using information ++ * provided in the device tree ++ * @d: Domain for which IRQ information should be added ++ * @virq: Lowest available kernel IRQ number ++ * @nr_irqs: Number of IRQs to allocate ++ * @fw_data: Hardware IRQ information for this domain ++ */ ++static int pen_irq_domain_alloc(struct irq_domain *d, unsigned int virq, ++ unsigned int nr_irqs, void *fw_data) ++{ ++ struct irq_fwspec *fwspec = fw_data; ++ struct irq_fwspec parent_fwspec; ++ struct pen_ictlr_info *info; ++ irq_hw_number_t hwirq; ++ unsigned int i; ++ int rc; ++ ++ rc = extract_hwirq(fwspec, &hwirq); ++ if (rc != 0) ++ return rc; ++ ++ /* Create the irq structure */ ++ info = d->host_data; ++ for (i = 0; i < nr_irqs; i++) { ++ rc = irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i, ++ info->irq_chip, info); ++ if (rc != 0) { ++ pr_err("irq_domain_set_hwirq_and_chipd failed: %d\n", ++ rc); ++ return rc; ++ } ++ } ++ ++ /* Set up the parent information */ ++ rc = pen_get_parent_fwspec(d, &parent_fwspec); ++ parent_fwspec.fwnode = d->parent->fwnode; ++ ++ rc = irq_domain_alloc_irqs_parent(d, virq, nr_irqs, &parent_fwspec); ++ ++ return rc; ++} ++ ++static void pen_irq_domain_free(struct irq_domain *d, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *data = irq_domain_get_irq_data(d, virq); ++ ++ irq_domain_reset_irq_data(data); ++} ++ ++static const struct irq_domain_ops pen_irq_domain_generic_chip_ops = { ++ .translate = pen_irq_domain_translate, ++ .alloc = pen_irq_domain_alloc, ++ .free = pen_irq_domain_free, ++}; ++ ++/* ++ * Handle mapping I/O of a device register block ++ */ ++static int __init pen_ictlr_iomap_csr(struct device_node *dn, ++ struct pen_ictlr_info *info) ++{ ++ if (info->num_bases != 1) { ++ pr_err("%pOF: reg property needs one tuple but has %u\n", ++ dn, info->num_bases); ++ return -EIO; ++ } ++ ++ info->map_base[0] = of_iomap(dn, 0); ++ if (info->map_base[0] == NULL) { ++ pr_err("unable to map registers\n"); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++/* ++ * Handle mapping I/O of a group register block ++ */ ++static int __init pen_ictlr_iomap_grp(struct device_node *dn, ++ struct pen_ictlr_info *info) ++{ ++ if (info->num_bases != 1) { ++ pr_err("%pOF: reg property needs one tuple but has %u\n", ++ dn, info->num_bases); ++ return -EIO; ++ } ++ ++ info->map_base[0] = of_iomap(dn, 0); ++ if (info->map_base[0] == NULL) { ++ pr_err("unable to map registers\n"); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++/* ++ * Handle mapping I/O of a device register block ++ */ ++static int __init pen_ictlr_iomap_csrintr(struct device_node *dn, ++ struct pen_ictlr_info *info) ++{ ++ unsigned int i; ++ ++ if (info->num_bases < 1) { ++ pr_err("%pOF: reg property needs at least one tuple, has %u\n", ++ dn, info->num_bases); ++ return -EIO; ++ } ++ ++ for (i = 0; i < info->num_bases; i++) { ++ info->map_base[i] = of_iomap(dn, i); ++ if (info->map_base[i] == NULL) ++ break; ++ } ++ ++ if (i != info->num_bases) { ++ pr_err("only mapped %u of %u regions\n", i, info->num_bases); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static struct pen_ictlr_info *__init pen_ictlr_probe(struct device_node *dn, ++ struct device_node *parent, enum reg_type reg_type, ++ int (*of_iomap_fn)(struct device_node *, struct pen_ictlr_info *), ++ void (*set_irq_chip_info)(struct irq_chip_type *ct), ++ struct irq_chip *irq_chip) ++{ ++ struct irq_domain *parent_domain; ++ struct pen_ictlr_info *info; ++ unsigned int num_bases; ++ unsigned int info_size; ++ int ret = 0; ++ ++ pr_info("Probe IRQ domain controller %s\n", dn->full_name); ++ ++ if (parent == NULL) { ++ pr_err("%pOF has no device node parent\n", dn); ++ return ERR_PTR(-ENODEV); ++ } ++ ++ parent_domain = irq_find_host(parent); ++ if (parent_domain == NULL) { ++ pr_err("%pOF has no domain parent\n", dn); ++ return ERR_PTR(-ENXIO); ++ } ++ ++ /* Count the number of mapped areas */ ++ num_bases = 0; ++ for (num_bases = 0; of_get_address(dn, num_bases, NULL, NULL) != NULL; ++ num_bases++) { ++ } ++ if (num_bases == 0) { ++ pr_err("no addresses found in reg property for %pOF\n", ++ dn); ++ return ERR_PTR(ENXIO); ++ } ++ ++ /* Allocate a struct pen_ictlr_info and add on space for the ++ * mapped addresses at the end ++ */ ++ info_size = offsetof(struct pen_ictlr_info, map_base[num_bases]); ++ info = kzalloc(info_size, GFP_KERNEL); ++ if (info == NULL) ++ return ERR_PTR(-ENOMEM); ++ info->num_bases = num_bases; ++ ++ /* Set up the mapping to the register block */ ++ ret = of_iomap_fn(dn, info); ++ if (ret != 0) { ++ pr_err("Unable to map register block\n"); ++ goto out_free_info; ++ } ++ ++ /* Create a new domain with a linear IRQ mapping */ ++ info->reg_type = reg_type; ++ info->irq_chip = irq_chip; ++ info->domain = irq_domain_add_hierarchy(parent_domain, 0, ++ MAX_N_IRQS_PER_CHIP, dn, &pen_irq_domain_generic_chip_ops, ++ info); ++ if (info->domain == NULL) { ++ pr_err("Unable to create pensando soc domain\n"); ++ ret = -ENOMEM; ++ goto out_unmap_regs; ++ } ++ ++ info->dn = dn; ++ ++ spin_lock_init(&info->irq_lock); ++ ++ return info; ++ ++out_unmap_regs: ++ if (info->map_base) ++ iounmap(info->map_base); ++ ++out_free_info: ++ kfree(info); ++ return ERR_PTR(ret); ++} ++ ++static void set_irq_chip_info_csr(struct irq_chip_type *ct) ++{ ++ ct->chip.irq_mask = pen_irq_mask_disable_csr; ++ ct->chip.irq_unmask = pen_irq_unmask_enable_csr; ++ ++ ct->regs.enable = offsetof(struct pen_ictlr_csr, int_enable_set); ++ ct->regs.disable = offsetof(struct pen_ictlr_csr, int_enable_set); ++ ct->regs.ack = offsetof(struct pen_ictlr_csr, intreg); ++ ++ ct->chip.irq_ack = irq_gc_noop; ++} ++ ++static void set_irq_chip_info_grp(struct irq_chip_type *ct) ++{ ++ ct->chip.irq_mask = pen_irq_mask_disable_grp; ++ ct->chip.irq_unmask = pen_irq_unmask_enable_grp; ++ ++ ct->regs.enable = offsetof(struct pen_ictlr_grp, int_enable_rw_reg); ++ ct->regs.disable = offsetof(struct pen_ictlr_grp, ++ int_enable_rw_reg); ++ ct->regs.ack = offsetof(struct pen_ictlr_grp, intreg); ++ ++ ct->chip.irq_ack = irq_gc_noop; ++} ++ ++static void set_irq_chip_info_csrintr(struct irq_chip_type *ct) ++{ ++ ct->chip.irq_mask = pen_irq_mask_disable_csrintr; ++ ct->chip.irq_unmask = pen_irq_unmask_enable_csrintr; ++ ++ ct->regs.enable = offsetof(struct pen_ictlr_csrintr, intr); ++ ct->regs.disable = offsetof(struct pen_ictlr_csrintr, intr); ++ ct->regs.ack = offsetof(struct pen_ictlr_csrintr, intr); ++ ++ ct->chip.irq_ack = irq_gc_noop; ++} ++ ++static int __init pen_ictlr_probe_csr(struct device_node *dn, ++ struct device_node *parent, ++ int (*iomap_fn)(struct device_node *, ++ struct pen_ictlr_info *), struct irq_chip *irq_chip, ++ const char *intc_name) ++{ ++ struct pen_ictlr_info *info; ++ int ret = 0; ++ ++ info = pen_ictlr_probe(dn, parent, REG_TYPE_CSR, iomap_fn, ++ set_irq_chip_info_csr, &pen_irq_chip_csr); ++ if (IS_ERR(info)) ++ return PTR_ERR(info); ++ ++ return ret; ++} ++ ++static int __init pen_ictlr_probe_grp(struct device_node *dn, ++ struct device_node *parent, ++ int (*iomap_fn)(struct device_node *, ++ struct pen_ictlr_info *), struct irq_chip *irq_chip, ++ const char *intc_name) ++{ ++ struct pen_ictlr_info *info; ++ int ret = 0; ++ ++ info = pen_ictlr_probe(dn, parent, REG_TYPE_GRP, iomap_fn, ++ set_irq_chip_info_grp, &pen_irq_chip_grp); ++ if (IS_ERR(info)) ++ return PTR_ERR(info); ++ ++ return ret; ++} ++ ++static int __init pen_ictlr_probe_csrintr(struct device_node *dn, ++ struct device_node *parent, ++ int (*iomap_fn)(struct device_node *, ++ struct pen_ictlr_info *), struct irq_chip *irq_chip, ++ const char *intc_name) ++{ ++ struct pen_ictlr_info *info; ++ ++ info = pen_ictlr_probe(dn, parent, REG_TYPE_CSRINTR, iomap_fn, ++ set_irq_chip_info_csrintr, &pen_irq_chip_csrintr); ++ if (IS_ERR(info)) ++ return PTR_ERR(info); ++ ++ return 0; ++} ++ ++/* Probe for device-level interrupts */ ++static int __init soc_probe_csr(struct device_node *dn, ++ struct device_node *parent) ++{ ++ pr_info("Probe %pOF\n", dn); ++ return pen_ictlr_probe_csr(dn, parent, ++ pen_ictlr_iomap_csr, &pen_irq_chip_csr, ++ "Pensando SOC CSR"); ++} ++ ++/* Probe for group-level interrupts */ ++static int __init soc_probe_grp(struct device_node *dn, ++ struct device_node *parent) ++{ ++ pr_info("Probe %pOF\n", dn); ++ return pen_ictlr_probe_grp(dn, parent, ++ pen_ictlr_iomap_grp, &pen_irq_chip_grp, ++ "Pensando SOC GRP"); ++} ++ ++/* Probe for module-level interrupts */ ++static int __init soc_probe_csrintr(struct device_node *dn, ++ struct device_node *parent) ++{ ++ pr_info("Probe %pOF\n", dn); ++ return pen_ictlr_probe_csrintr(dn, parent, ++ pen_ictlr_iomap_csrintr, &pen_irq_chip_csrintr, ++ "Pensando SOC CSR Intr"); ++} ++ ++IRQCHIP_DECLARE(soc_ictlr_csr, "pensando,soc-ictlr-csr", ++ soc_probe_csr); ++IRQCHIP_DECLARE(soc_ictlr_grp, "pensando,soc-ictlr-grp", ++ soc_probe_grp); ++IRQCHIP_DECLARE(soc_ictlr_csrintr, "pensando,soc-ictlr-csrintr", ++ soc_probe_csrintr); +diff --git a/include/linux/irqchip/irq-pensando.h b/include/linux/irqchip/irq-pensando.h +new file mode 100644 +index 0000000..0716918 +--- /dev/null ++++ b/include/linux/irqchip/irq-pensando.h +@@ -0,0 +1,91 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2019-2021, Pensando Systems Inc. ++ * ++ * Definitions for interrupt domain controllers for Pensando Capri and Elba chip ++ */ ++ ++#ifndef _LINUX_IRQCHIP_IRQ_CAPRI_H_ ++#define _LINUX_IRQCHIP_IRQ_CAPRI_H_ ++/* Types of possible register blocks */ ++enum reg_type { ++ REG_TYPE_UNKNOWN, ++ REG_TYPE_CSR, ++ REG_TYPE_GRP, ++ REG_TYPE_CSRINTR, ++}; ++ ++/* ++ * Common format for interrupt block control registers for a CSR block ++ * @intreg: Interrupt status on read, clear interrupt on write ++ * @int_test: Test bits, write to cause corresponding interrupt ++ * @int_enable_set: Write to set enable bits, read for current enable mask ++ * @int_enable_clear: Write to clear enable bits ++ */ ++struct pen_ictlr_csr { ++ u32 intreg; ++ u32 int_test; ++ u32 int_enable_set; ++ u32 int_enable_clear; ++}; ++ ++/* ++ * Common format for interrupt block control registers for a group ++ * @intreg: Interrupt status on read, clear interrupt on write ++ * @int_enable_rw_reg: Enable bits ++ * @int_rw_reg: TBD ++ */ ++struct pen_ictlr_grp { ++ u32 intreg; ++ u32 int_enable_rw_reg; ++ u32 int_rw_reg; ++}; ++ ++/* ++ * Common format for interrupt block control registers for a CSR interrupt ++ * block ++ * @intr: Interrupt status in low order bit, i.e. bit 0. The ++ * enable bit is bit 1. ++ */ ++struct pen_ictlr_csrintr { ++ u32 intr; ++}; ++ ++/* ++ * Per domain information ++ * @reg_type: Type of register block ++ * @domain: Pointer to the associated &struct irq_domain ++ * @dn: Pointer to the device tree device node for this ++ * interrupt controller ++ * @irq_chip: Pointer to the struct irq_chip for this domain (which ++ * has just one) ++ * @num_irqs: Number of IRQs for this chip ++ * @irq_lock: Per-domain controller lock ++ * @irq_flag: Flag set by spin_lock_irqsave() when used on ++ * @irq_lock to be restored by spin_lock_irqrestore() ++ * @num_bases: Number of virtual addresses in @map_base ++ * @map_base: Virtual addresses of the beginning of the register ++ * block, mapped from the reg property. Allocation ++ * continues at the end to accommodate the values. ++ */ ++struct pen_ictlr_info { ++ enum reg_type reg_type; ++ struct irq_domain *domain; ++ struct device_node *dn; ++ struct irq_chip *irq_chip; ++ unsigned int parent_irq; ++ spinlock_t irq_lock; ++ unsigned long irq_flag; ++ unsigned int num_bases; ++ void __iomem *map_base[]; ++}; ++ ++/* Enable/disable functions that do not recurse on their parents */ ++void pen_irq_unmask_enable_csr_one(struct irq_data *irq_data); ++void pen_irq_mask_disable_csr_one(struct irq_data *irq_data); ++void pen_irq_unmask_enable_grp_one(struct irq_data *irq_data); ++void pen_irq_mask_disable_grp_one(struct irq_data *d); ++void pen_irq_unmask_enable_csrintr_one(struct irq_data *irq_data); ++void pen_irq_mask_disable_csrintr_one(struct irq_data *irq_data); ++ ++#endif /* _LINUX_IRQCHIP_IRQ_CAPRI_H_ */ +-- +1.8.3.1 + diff --git a/patches-sonic/0011-PCI-AER-Fix-NULL-pointer-access-by-aer_info.patch b/patches-sonic/0011-PCI-AER-Fix-NULL-pointer-access-by-aer_info.patch new file mode 100644 index 000000000..4ba0ce643 --- /dev/null +++ b/patches-sonic/0011-PCI-AER-Fix-NULL-pointer-access-by-aer_info.patch @@ -0,0 +1,36 @@ +From 3b9b13868b3051851850d17adf1c22ac87ece220 Mon Sep 17 00:00:00 2001 +From: Vernon Yang +Date: Fri, 5 Sep 2025 02:25:27 +0800 +Subject: [PATCH 11/12] PCI/AER: Fix NULL pointer access by aer_info + +The kzalloc(GFP_KERNEL) may return NULL, so all accesses to aer_info->xxx +will result in kernel panic. Fix it. + +Signed-off-by: Vernon Yang +Signed-off-by: Bjorn Helgaas +Link: https://patch.msgid.link/20250904182527.67371-1-vernon2gm@gmail.com +(cherry picked from commit 0a27bdb14b028fed30a10cec2f945c38cb5ca4fa) +--- + drivers/pci/pcie/aer.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 067e305212eb..f1886e60f5a2 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -377,7 +377,11 @@ void pci_aer_init(struct pci_dev *dev) + if (!dev->aer_cap) + return; + +- dev->aer_stats = kzalloc(sizeof(struct aer_stats), GFP_KERNEL); ++ dev->aer_stats = kzalloc(sizeof(*dev->aer_stats), GFP_KERNEL); ++ if (!dev->aer_stats) { ++ dev->aer_cap = 0; ++ return; ++ } + + ratelimit_state_init(&dev->aer_stats->correctable_ratelimit, + DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); +-- +2.47.0 + diff --git a/patches-sonic/0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch b/patches-sonic/0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch new file mode 100644 index 000000000..09219ec0a --- /dev/null +++ b/patches-sonic/0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch @@ -0,0 +1,60 @@ +From: Sergejs Hatinecs +Date: Thu, 12 Jun 2026 12:30:00 +0300 +Subject: [PATCH 6.12 1/1] arm64: dts: aspeed: spc6-bmc: fix mac2 SGMII phy-names + +Upstream-Status: Pending + +ftgmac100 probes the optional SGMII serdes PHY with devm_phy_optional_get() +using the name "sgmii". mac2 in the SPC6 BMC device trees used +phy-names = "sgmii-phy", so the serdes PHY was never bound and SGMII +management traffic did not run through the expected path. + +Use phy-names = "sgmii" on mac2 in the A1, SONiC, and OpenBMC SPC6 BMC +DTS variants so the name matches the driver. + +Signed-off-by: Sergejs Hatinecs +--- + arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts | 2 +- + arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts | 2 +- + arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts +index 810d50fe11ac..741bd588ec2a 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts +@@ -170,7 +170,7 @@ + phy-mode = "sgmii"; + phy-handle = <ðphy2>; + phys = <&sgmii>; +- phy-names = "sgmii-phy"; ++ phy-names = "sgmii"; + }; + + /* +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +index 0817034b8658..e16559039ed5 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +@@ -207,7 +207,7 @@ + phy-mode = "sgmii"; + phy-handle = <ðphy2>; + phys = <&sgmii>; +- phy-names = "sgmii-phy"; ++ phy-names = "sgmii"; + }; + + /* +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +index cfe5743b90fe..bcdb5a463b42 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +@@ -215,7 +215,7 @@ + phy-mode = "sgmii"; + phy-handle = <ðphy2>; + phys = <&sgmii>; +- phy-names = "sgmii-phy"; ++ phy-names = "sgmii"; + }; + + /* diff --git a/patches-sonic/0011-arm64-dts-marvell-Add-Nokia-7215-IXS-A1-board.patch b/patches-sonic/0011-arm64-dts-marvell-Add-Nokia-7215-IXS-A1-board.patch new file mode 100644 index 000000000..0c29f16ac --- /dev/null +++ b/patches-sonic/0011-arm64-dts-marvell-Add-Nokia-7215-IXS-A1-board.patch @@ -0,0 +1,258 @@ +From d0144bfbcbf78c7577f5e1330aba16f1e5bb4ebe Mon Sep 17 00:00:00 2001 +From: Natarajan Subbiramani +Date: Sun, 16 Nov 2025 12:55:20 +0200 +Subject: [PATCH] arm64: dts: marvell: Add DTS for 7215-IXS-A1 board + +7215-IXS-A1 is an aggregation switch based on Marvell AlleyCat5X. +This dts is extracted from Marvell cn9130-crb and removed unused +nodes along with platform specific changes. + +Signed-off-by: Natarajan Subbiramani +Signed-off-by: Yan Markman +Tested-by: Natarajan Subbiramani +Reviewed-by: Jon Goldberg +--- + arch/arm64/boot/dts/marvell/7215-ixs-a1.dts | 221 ++++++++++++++++++++ + arch/arm64/boot/dts/marvell/Makefile | 1 + + 2 files changed, 226 insertions(+) + create mode 100644 arch/arm64/boot/dts/marvell/7215-ixs-a1.dts + +diff --git a/arch/arm64/boot/dts/marvell/7215-ixs-a1.dts b/arch/arm64/boot/dts/marvell/7215-ixs-a1.dts +new file mode 100644 +index 000000000..fd0e6b395 +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/7215-ixs-a1.dts +@@ -0,0 +1,221 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2024 Nokia ++ * Copyright (C) 2020 Marvell International Ltd. ++ */ ++ ++#include "cn9130.dtsi" /* include SoC device tree */ ++ ++#include ++ ++/ { ++ #address-cells = <0x02>; ++ #size-cells = <0x02>; ++ model = "7215 IXS-A1"; ++ compatible = "marvell,cn9130\0marvell,armada-ap807-quad\0marvell,armada-ap807"; ++ ++ aliases { ++ i2c0 = &cp0_i2c0; ++ ethernet0 = &cp0_eth0; ++ ethernet1 = &cp0_eth1; ++ ethernet2 = &cp0_eth2; ++ gpio1 = &cp0_gpio1; ++ gpio2 = &cp0_gpio2; ++ }; ++ ++ memory@0 { ++ device_type = "memory"; ++ reg = <0x0 0x0 0x0 0x80000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <0x02>; ++ #size-cells = <0x02>; ++ ranges; ++ ++ /* keep psci-area: 04000000 size 0x00200000 */ ++ /* keep tee: 04400000 size 0x01000000 */ ++ buf1: buffer@6000000 { ++ compatible = "shared-dma-pool"; ++ reg = <0x0 0x06000000 0x0 0x02000000>; ++ no-map; ++ }; ++ }; ++ ++ mv_dma { ++ compatible = "marvell,mv_dma"; ++ memory-region = <&buf1>; ++ status = "okay"; ++ }; ++ ++ cp0_usb3_0_phy0: cp0_usb3_phy0 { ++ compatible = "usb-nop-xceiv"; ++ }; ++ ++ cp0_usb3_0_phy1: cp0_usb3_phy1 { ++ compatible = "usb-nop-xceiv"; ++ }; ++}; ++ ++&cp0_syscon0 { ++ cp0_pinctrl: pinctrl { ++ compatible = "marvell,cp115-standalone-pinctrl"; ++ ++ cp0_i2c0_pins: cp0-i2c-pins-0 { ++ marvell,pins = "mpp37", "mpp38"; ++ marvell,function = "i2c0"; ++ }; ++ cp0_i2c1_pins: cp0-i2c-pins-1 { ++ marvell,pins = "mpp35", "mpp36"; ++ marvell,function = "i2c1"; ++ }; ++ cp0_spi0_pins: cp0-spi-pins-0 { ++ marvell,pins = "mpp13", "mpp14", "mpp15", "mpp16"; ++ marvell,function = "spi1"; ++ }; ++ }; ++}; ++ ++&cp0_gpio1 { ++ status = "okay"; ++}; ++ ++&cp0_gpio2 { ++ status = "okay"; ++}; ++ ++&cp0_pcie0 { ++ status = "okay"; ++ num-lanes = <4>; ++ num-viewport = <8>; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp0_comphy0 0 ++ &cp0_comphy1 0 ++ &cp0_comphy2 0 ++ &cp0_comphy3 0>; ++ iommu-map = ++ <0x0 &smmu 0x480 0x20>, ++ <0x100 &smmu 0x4a0 0x20>, ++ <0x200 &smmu 0x4c0 0x20>; ++ iommu-map-mask = <0x031f>; ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++/* on-board eMMC U6 */ ++&ap_sdhci0 { ++ pinctrl-names = "default"; ++ bus-width = <8>; ++ status = "okay"; ++ non-removable; ++ mmc-ddr-1_8v; ++ mmc-hs200-1_8v; ++}; ++ ++/*Delete nodes that are not available*/ ++/delete-node/ &cp0_rtc; ++/delete-node/ &cp0_sdhci0; ++/delete-node/ &cp0_crypto; ++ ++&cp0_i2c0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c0_pins>; ++ status = "okay"; ++ clock-frequency = <100000>; ++ rtc@68 { ++ compatible = "st,m41t11"; ++ reg = <0x68>; ++ }; ++}; ++ ++&cp0_i2c1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c1_pins>; ++ clock-frequency = <100000>; ++ status = "okay"; ++}; ++ ++&cp0_usb3_0 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy0>; ++ phy-names = "usb"; ++}; ++ ++&cp0_usb3_1 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy1>; ++ phy-names = "usb"; ++}; ++ ++&cp0_spi1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_spi0_pins>; ++ reg = <0x700680 0x50>, /* control */ ++ <0x2000000 0x1000000>; /* CS0 */ ++ status = "okay"; ++ ++ flash@0 { ++ #address-cells = <0x1>; ++ #size-cells = <0x1>; ++ compatible = "jedec,spi-nor"; ++ reg = <0x0>; ++ /* On-board MUX does not allow higher frequencies */ ++ spi-max-frequency = <40000000>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "U-Boot"; ++ reg = <0x0 0x2f0000>; ++ }; ++ ++ partition@1 { ++ label = "U-Boot-Env"; ++ reg = <0x2f0000 0x10000>; ++ }; ++ ++ partition@2{ ++ label = "Filesystem"; ++ reg = <0x300000 0xd00000>; ++ }; ++ }; ++ }; ++}; ++ ++&cp0_mdio { ++ status = "okay"; ++ phy0: ethernet-phy@0 { ++ reg = <0>; ++ /* Management port LED blink activity*/ ++ marvell,reg-init = <0x03 0x10 0x0 0x1140>; ++ }; ++}; ++ ++&cp0_xmdio { ++ status = "disabled"; ++}; ++ ++&cp0_ethernet { ++ status = "okay"; ++}; ++ ++&cp0_eth0 { ++ status = "disabled"; ++}; ++ ++&cp0_eth1 { ++ status = "disabled"; ++}; ++ ++&cp0_eth2 { ++ /* This port uses "sgmii" phy-mode */ ++ status = "okay"; ++ phy = <&phy0>; ++ phys = <&cp0_comphy5 2>; ++ phy-mode = "sgmii"; ++}; +diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile +index 062317759..c9d4e7836 100644 +--- a/arch/arm64/boot/dts/marvell/Makefile ++++ b/arch/arm64/boot/dts/marvell/Makefile +@@ -33,3 +33,4 @@ dtb-$(CONFIG_ARCH_MVEBU) += cn9130-cf-pro.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9131-cf-solidwan.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9132-clearfog.dtb + dtb-$(CONFIG_ARCH_MVEBU) += armada-7020-comexpress.dtb ++dtb-$(CONFIG_ARCH_MVEBU) += 7215-ixs-a1.dtb +-- +2.34.1 + diff --git a/patches-sonic/0011-i2c-xiic-remove-duplicate-error-message.patch b/patches-sonic/0011-i2c-xiic-remove-duplicate-error-message.patch new file mode 100644 index 000000000..88cc8278c --- /dev/null +++ b/patches-sonic/0011-i2c-xiic-remove-duplicate-error-message.patch @@ -0,0 +1,38 @@ +From 8809c7f8154d8752a3b5ab2f571160cdc413e394 Mon Sep 17 00:00:00 2001 +From: Abdurrahman Hussain +Date: Mon, 23 Feb 2026 15:59:17 +0000 +Subject: [PATCH 11/22] i2c: xiic: remove duplicate error message + +The devm_request_threaded_irq() already prints an error message. Remove +the duplicate. + +Signed-off-by: Abdurrahman Hussain +Reviewed-by: Andy Shevchenko +Reviewed-by: Andrew Lunn +Reviewed-by: Jonathan Cameron +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260223-i2c-xiic-v12-2-b6c9ce4e4f3c@nexthop.ai +--- + drivers/i2c/busses/i2c-xiic.c | 5 +---- + 1 file changed, 1 insertion(+), 4 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index ea85fc396033..5f141acc5306 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -1492,11 +1492,8 @@ static int xiic_i2c_probe(struct platform_device *pdev) + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xiic_process, IRQF_ONESHOT, + pdev->name, i2c); +- +- if (ret < 0) { +- dev_err_probe(&pdev->dev, ret, "Cannot claim IRQ\n"); ++ if (ret) + return ret; +- } + + i2c->singlemaster = + of_property_read_bool(pdev->dev.of_node, "single-master"); +-- +2.53.0 + diff --git a/patches-sonic/0011-platform-mellanox-Fix-spelling-and-comment-clarity-i.patch b/patches-sonic/0011-platform-mellanox-Fix-spelling-and-comment-clarity-i.patch new file mode 100644 index 000000000..1aef17843 --- /dev/null +++ b/patches-sonic/0011-platform-mellanox-Fix-spelling-and-comment-clarity-i.patch @@ -0,0 +1,86 @@ +From 941fba51fff1bfeb77ad69dba08209de16e375c4 Mon Sep 17 00:00:00 2001 +From: Alok Tiwari +Date: Sun, 22 Jun 2025 00:29:11 -0700 +Subject: [PATCH 11/78] platform/mellanox: Fix spelling and comment clarity in + Mellanox drivers +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This commit corrects several minor typographical errors in comments +and error messages across multiple Mellanox platform driver. +Fixed spelling of "thresholds", "region", "platform", "default", +and removed redundant spaces in comment strings and error logs. + +These changes are cosmetic and do not affect runtime behavior. + +Signed-off-by: Alok Tiwari +Reviewed-by: Vadim Pasternak +Reviewed-by: David Thompson +Link: https://lore.kernel.org/r/20250622072921.4111552-1-alok.a.tiwari@oracle.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxbf-tmfifo.c | 2 +- + drivers/platform/mellanox/mlxreg-lc.c | 10 +++++----- + 2 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxbf-tmfifo.c b/drivers/platform/mellanox/mlxbf-tmfifo.c +index d2c27cc..c05cd93 100644 +--- a/drivers/platform/mellanox/mlxbf-tmfifo.c ++++ b/drivers/platform/mellanox/mlxbf-tmfifo.c +@@ -1288,7 +1288,7 @@ static void mlxbf_tmfifo_get_cfg_mac(u8 *mac) + ether_addr_copy(mac, mlxbf_tmfifo_net_default_mac); + } + +-/* Set TmFifo thresolds which is used to trigger interrupts. */ ++/* Set TmFifo thresholds which is used to trigger interrupts. */ + static void mlxbf_tmfifo_set_threshold(struct mlxbf_tmfifo *fifo) + { + u64 ctl; +diff --git a/drivers/platform/mellanox/mlxreg-lc.c b/drivers/platform/mellanox/mlxreg-lc.c +index 9915267..08d37ef 100644 +--- a/drivers/platform/mellanox/mlxreg-lc.c ++++ b/drivers/platform/mellanox/mlxreg-lc.c +@@ -57,9 +57,9 @@ enum mlxreg_lc_state { + * @dev: platform device; + * @lock: line card lock; + * @par_regmap: parent device regmap handle; +- * @data: pltaform core data; ++ * @data: platform core data; + * @io_data: register access platform data; +- * @led_data: LED platform data ; ++ * @led_data: LED platform data; + * @mux_data: MUX platform data; + * @led: LED device; + * @io_regs: register access device; +@@ -171,7 +171,7 @@ static int mlxreg_lc_chan[] = { + 0x4e, 0x4f + }; + +-/* Defaul mux configuration. */ ++/* Default mux configuration. */ + static struct mlxcpld_mux_plat_data mlxreg_lc_mux_data[] = { + { + .chan_ids = mlxreg_lc_chan, +@@ -181,7 +181,7 @@ static struct mlxcpld_mux_plat_data mlxreg_lc_mux_data[] = { + }, + }; + +-/* Defaul mux board info. */ ++/* Default mux board info. */ + static struct i2c_board_info mlxreg_lc_mux_brdinfo = { + I2C_BOARD_INFO("i2c-mux-mlxcpld", 0x32), + }; +@@ -758,7 +758,7 @@ mlxreg_lc_config_init(struct mlxreg_lc *mlxreg_lc, void *regmap, + platform_device_register_resndata(dev, "mlxreg-io", data->hpdev.nr, NULL, 0, + mlxreg_lc->io_data, sizeof(*mlxreg_lc->io_data)); + if (IS_ERR(mlxreg_lc->io_regs)) { +- dev_err(dev, "Failed to create regio for client %s at bus %d at addr 0x%02x\n", ++ dev_err(dev, "Failed to create region for client %s at bus %d at addr 0x%02x\n", + data->hpdev.brdinfo->type, data->hpdev.nr, + data->hpdev.brdinfo->addr); + err = PTR_ERR(mlxreg_lc->io_regs); +-- +2.8.4 + diff --git a/patches-sonic/0012-PCI-AER-Avoid-NULL-pointer-dereference-in-aer_rateli.patch b/patches-sonic/0012-PCI-AER-Avoid-NULL-pointer-dereference-in-aer_rateli.patch new file mode 100644 index 000000000..f0fb8d9d0 --- /dev/null +++ b/patches-sonic/0012-PCI-AER-Avoid-NULL-pointer-dereference-in-aer_rateli.patch @@ -0,0 +1,61 @@ +From 5034b584556f1a8fe60d10231ac9a3f687508c54 Mon Sep 17 00:00:00 2001 +From: Breno Leitao +Date: Mon, 29 Sep 2025 02:15:47 -0700 +Subject: [PATCH 12/12] PCI/AER: Avoid NULL pointer dereference in + aer_ratelimit() + +When platform firmware supplies error information to the OS, e.g., via the +ACPI APEI GHES mechanism, it may identify an error source device that +doesn't advertise an AER Capability and therefore dev->aer_info, which +contains AER stats and ratelimiting data, is NULL. + +pci_dev_aer_stats_incr() already checks dev->aer_info for NULL, but +aer_ratelimit() did not, leading to NULL pointer dereferences like this one +from the URL below: + + {1}[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 0 + {1}[Hardware Error]: event severity: corrected + {1}[Hardware Error]: device_id: 0000:00:00.0 + {1}[Hardware Error]: vendor_id: 0x8086, device_id: 0x2020 + {1}[Hardware Error]: aer_cor_status: 0x00001000, aer_cor_mask: 0x00002000 + BUG: kernel NULL pointer dereference, address: 0000000000000264 + RIP: 0010:___ratelimit+0xc/0x1b0 + pci_print_aer+0x141/0x360 + aer_recover_work_func+0xb5/0x130 + +[8086:2020] is an Intel "Sky Lake-E DMI3 Registers" device that claims to +be a Root Port but does not advertise an AER Capability. + +Add a NULL check in aer_ratelimit() to avoid the NULL pointer dereference. +Note that this also prevents ratelimiting these events from GHES. + +Fixes: a57f2bfb4a5863 ("PCI/AER: Ratelimit correctable and non-fatal error logging") +Link: https://lore.kernel.org/r/buduna6darbvwfg3aogl5kimyxkggu3n4romnmq6sozut6axeu@clnx7sfsy457/ +Signed-off-by: Breno Leitao +[bhelgaas: add crash details to commit log] +Signed-off-by: Bjorn Helgaas +Reviewed-by: Kuppuswamy Sathyanarayanan +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20250929-aer_crash_2-v1-1-68ec4f81c356@debian.org +(cherry picked from commit deb2f228388ff3a9d0623e3b59a053e9235c341d) +--- + drivers/pci/pcie/aer.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index f1886e60f5a2..ac64e5e5c95e 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -792,6 +792,9 @@ static void __print_tlp_header(struct pci_dev *dev, struct pcie_tlp_log *t) + + static int aer_ratelimit(struct pci_dev *dev, unsigned int severity) + { ++ if (!dev->aer_stats) ++ return 1; ++ + switch (severity) { + case AER_NONFATAL: + return __ratelimit(&dev->aer_stats->nonfatal_ratelimit); +-- +2.47.0 + diff --git a/patches-sonic/0012-drivers-uio-UIO-drivers-for-Elba.patch b/patches-sonic/0012-drivers-uio-UIO-drivers-for-Elba.patch new file mode 100644 index 000000000..a3deaf203 --- /dev/null +++ b/patches-sonic/0012-drivers-uio-UIO-drivers-for-Elba.patch @@ -0,0 +1,1475 @@ +From 3cd452343a6b660a56fbe0f1ab46bb668698e7fd Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:04:04 +0000 +Subject: [PATCH 12/27] drivers/uio: UIO drivers for Elba + +Support userspace interrupt drivers for Elba. PCIE events. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 30 ++ + drivers/uio/Kconfig | 34 ++ + drivers/uio/Makefile | 4 + + drivers/uio/uio_pciemac.c | 30 ++ + drivers/uio/uio_pengic.c | 661 +++++++++++++++++++++++++++++++++ + drivers/uio/uio_pengic.h | 18 + + drivers/uio/uio_penmsi.c | 539 +++++++++++++++++++++++++++ + drivers/uio/uio_penmsi.h | 17 + + drivers/uio/uio_penmsi1.c | 32 ++ + 9 files changed, 1365 insertions(+) + create mode 100644 drivers/uio/uio_pciemac.c + create mode 100644 drivers/uio/uio_pengic.c + create mode 100644 drivers/uio/uio_pengic.h + create mode 100644 drivers/uio/uio_penmsi.c + create mode 100644 drivers/uio/uio_penmsi.h + create mode 100644 drivers/uio/uio_penmsi1.c + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index afc9c7c..d87128c 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -49,6 +49,12 @@ + IRQ_TYPE_LEVEL_LOW)>; + }; + ++ /* Common UIO device for MSI drivers */ ++ uio_penmsi { ++ compatible = "pensando,uio_penmsi"; ++ name = "uio_penmsi"; ++ }; ++ + capmem { + compatible = "pensando,capmem"; + }; +@@ -167,6 +173,14 @@ + reg-io-width = <4>; + }; + ++ /* MSI UIO device 1 */ ++ uio_penmsi1@520000 { ++ compatible = "pensando,uio_penmsi1"; ++ reg = <0x0 0x520000 0x0 0x10000>; ++ msi-parent = <&its 0xa>; ++ num-interrupts = <16>; /* # MSI interrupts to get */ ++ }; ++ + gic: interrupt-controller@800000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <3>; +@@ -204,6 +218,22 @@ + interrupt-names = "pxc0_intr"; + }; + ++ /* UIO device using interrupt line PCIEMAC */ ++ pciemac@20102200 { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ #interrupt-cells = <3>; ++ ++ compatible = "pensando,uio_pciemac"; ++ register-type = "csr-interrupt"; ++ interrupt-parent = <&pxc0_intr>; ++ interrupts = ; ++ reg = <0x0 0x20102200 0x0 0x10>; /* pxc0_intr */ ++ ++ enable_csr_paddr = <0x0 0x20102200 0x0 0x10>; ++ enable_mask = <(1 << 0)>; ++ }; ++ + edac: edac@30500000 { + compatible = "pensando,elba-edac-mc"; + reg = <0x0 0x30500000 0x0 0x100000>; +diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig +index b060dcd..c4d8be6 100644 +--- a/drivers/uio/Kconfig ++++ b/drivers/uio/Kconfig +@@ -164,4 +164,38 @@ config UIO_DFL + opae-sdk/tools/libopaeuio/ + + If you compile this as a module, it will be called uio_dfl. ++ ++config UIO_PCIEMAC ++ tristate "Include PCI MAC status driver" ++ default "y" ++ select UIO_PENGIC ++ depends on ARCH_PENSANDO_ELBA_SOC ++ help ++ Driver that allows handling of ring2 on Pensando Capri systems. It ++ is useful for having a userspace driver that can handle GIC 17 ++ interrupts. ++ ++ If you compile this as a module, it will be called uio_pciemac. ++ ++config UIO_PENMSI1 ++ tristate "Driver 1 for MSI" ++ default "y" ++ select UIO_PENMSI ++ depends on ARCH_PENSANDO_ELBA_SOC ++ help ++ Driver that allows obtaining MSI data and handling MSI interrupts ++ in userspace. ++ ++ If you compile this as a module, it will be called uio_pengmsi1. ++ ++config UIO_PENGIC ++ tristate ++ depends on ARCH_PENSANDO ++ default n ++ ++config UIO_PENMSI ++ tristate ++ depends on ARCH_PENSANDO ++ default n ++ + endif +diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile +index 1c5f3b5..1acc483 100644 +--- a/drivers/uio/Makefile ++++ b/drivers/uio/Makefile +@@ -9,5 +9,9 @@ obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o + obj-$(CONFIG_UIO_NETX) += uio_netx.o + obj-$(CONFIG_UIO_MF624) += uio_mf624.o + obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o ++obj-$(CONFIG_UIO_PENGIC) += uio_pengic.o ++obj-$(CONFIG_UIO_PCIEMAC) += uio_pciemac.o ++obj-$(CONFIG_UIO_PENMSI) += uio_penmsi.o ++obj-$(CONFIG_UIO_PENMSI1) += uio_penmsi1.o + obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o + obj-$(CONFIG_UIO_DFL) += uio_dfl.o +diff --git a/drivers/uio/uio_pciemac.c b/drivers/uio/uio_pciemac.c +new file mode 100644 +index 0000000..b7d89c6 +--- /dev/null ++++ b/drivers/uio/uio_pciemac.c +@@ -0,0 +1,30 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++#include ++ ++#include "uio_pengic.h" ++ ++#define DRIVER_NAME_PCIEMAC "uio_pciemac0" ++ ++#ifdef CONFIG_OF ++static const struct of_device_id pengic_match[] = { ++ { .compatible = "pensando,uio_pciemac" }, ++ { /* Mark the end of the list */ }, ++}; ++#endif ++ ++static struct platform_driver uio_pciemac = { ++ .probe = pengic_probe_enable, ++ .remove = pengic_remove, ++ .driver = { ++ .name = DRIVER_NAME_PCIEMAC, ++ .pm = &pengic_pm_ops, ++ .of_match_table = of_match_ptr(pengic_match), ++ } ++}; ++ ++module_platform_driver(uio_pciemac); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Support PCIe MAC status reporting"); ++MODULE_AUTHOR("David VomLehn"); +diff --git a/drivers/uio/uio_pengic.c b/drivers/uio/uio_pengic.c +new file mode 100644 +index 0000000..8c15bff +--- /dev/null ++++ b/drivers/uio/uio_pengic.c +@@ -0,0 +1,661 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2020-2021, Pensando Systems Inc. ++ * ++ * UIO driver for the device using the GIC interrupts for the Pensando ++ * Capri and Elba ASIC. This includes status/error registers. ++ * ++ * To do: ++ * o Get enable_csr from the "reg" property ++ * o Get enable_mask from the device nodes "interrupts" property ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Probably should be defined in irq.h, but isn't */ ++#define NO_IRQ 0 ++ ++#define MAP_ERROR (~0) /* Invalid address */ ++ ++/* ++ * If set, will print a status message from the IRQ handler. This can be ++ * very helpful when trying to determine whether the device tree has the ++ * correct parent/child structure ++ */ ++#undef PRINT_HANDLER_STATUS ++ ++#ifdef PRINT_HANDLER_STATUS ++#define handler_status(fmt, ...) pr_err(fmt, ##__VA_ARGS__) ++#else ++#define handler_status(fmt, ...) do { } while (false) ++#endif ++ ++/* ++ * pengic_platdata - platform data for one UIO device ++ * @name: Name of the driver ++ * @reg_type: Type of register block we're dealing with ++ * @flags: ++ * @pdev: Pointer to struct platform_device ++ * @n_res: Number of entries in @res[] used ++ * @res: Struct resource that defines the address and size of ++ * the CSR ++ * @node: Pointer to the open firmware device node for this ++ * device; ++ * @enable_mask: Mask of bits to be set to enable the interrupt(s) ++ * @enable_csr: Virtual address of the pen_ictlr_csr for this child ++ * @uio_info: Pointer to the associated struct uio_info ++ * @dev: Pointer to the struct device for this device ++ * @cmd: Information for the command file ++ * @pengic_dir: Pointer to information about the pengic directory ++ * in which the command file lives ++ * @irq: Kernel interrupt number ++ * @disables_lock: Spin lock protecting the @disables member ++ * @disable: Number of outstanding disables. Incremented for ++ * each disable call, decremented for each enable call ++ */ ++struct pengic_platdata { ++ const char *name; ++ enum reg_type reg_type; ++ unsigned long flags; ++ unsigned int n_res; ++ struct resource res[MAX_UIO_MAPS]; ++ struct device_node *node; ++ u32 enable_mask; ++ struct pen_ictlr_csr *enable_csr; ++ struct uio_info *uio_info; ++ struct device *dev; ++ unsigned int irq; ++}; ++ ++static void pengic_enable_intr_nolock(struct pengic_platdata *platdata) ++{ ++ struct irq_data *irq_data; ++ ++ irq_data = irq_get_irq_data(platdata->irq); ++ ++ switch (platdata->reg_type) { ++ case REG_TYPE_CSR: ++ pen_irq_unmask_enable_csr_one(irq_data); ++ break; ++ ++ case REG_TYPE_GRP: ++ pen_irq_unmask_enable_grp_one(irq_data); ++ break; ++ ++ case REG_TYPE_CSRINTR: ++ pen_irq_unmask_enable_csrintr_one(irq_data); ++ break; ++ ++ default: ++ pr_err("%pOF: Unknown register type: %d\n", ++ platdata->node, platdata->reg_type); ++ break; ++ } ++ ++} ++ ++/* ++ * Enable the register in the bottom-most interrupt domain controller. We ++ * only call the real enable function when we have zero outstanding ++ * disables. ++ * ++ * It's ugly that this calls directly into functions for the interrupt ++ * domain controller but it's either that or duplicate code. ++ */ ++static void pengic_enable_intr(struct pengic_platdata *platdata) ++{ ++ pengic_enable_intr_nolock(platdata); ++} ++ ++static void pengic_disable_intr_nolock(struct pengic_platdata *platdata) ++{ ++ struct irq_data *irq_data; ++ ++ irq_data = irq_get_irq_data(platdata->irq); ++ ++ /* ++ * If this is the first disable call, actually do the disable. ++ * Otherwise, we are already disabled and skip the call ++ */ ++ switch (platdata->reg_type) { ++ case REG_TYPE_CSR: ++ pen_irq_mask_disable_csr_one(irq_data); ++ break; ++ ++ case REG_TYPE_GRP: ++ pen_irq_mask_disable_grp_one(irq_data); ++ break; ++ ++ case REG_TYPE_CSRINTR: ++ pen_irq_mask_disable_csrintr_one(irq_data); ++ break; ++ ++ default: ++ pr_err("Unknown register type: %d\n", ++ platdata->reg_type); ++ break; ++ } ++} ++ ++static void pengic_disable_intr(struct pengic_platdata *platdata) ++{ ++ pengic_disable_intr_nolock(platdata); ++} ++ ++static int pengic_open(struct uio_info *uioinfo, struct inode *inode) ++{ ++ struct pengic_platdata *platdata; ++ int ret; ++ ++ ret = 0; ++ platdata = uioinfo->priv; ++ pm_runtime_get_sync(platdata->dev); ++ ++ return ret; ++} ++ ++/* ++ * pengic_release - called when the device has no more open file descriptors ++ * and it was enough to use end-of-interrupt handling ++ */ ++static int pengic_release(struct uio_info *uioinfo, struct inode *inode) ++{ ++ struct pengic_platdata *platdata; ++ ++ platdata = uioinfo->priv; ++ pm_runtime_put_sync(platdata->dev); ++ ++ return 0; ++} ++ ++/* ++ * pengic_release_enable - called when the device has no more open file ++ * descriptors when the interrupt had to be disabled in addition to ++ * performing end-of-interrupt processing. This ensures that the ++ * interrupt is enabled on exit. ++ */ ++static int pengic_release_enable(struct uio_info *uioinfo, struct inode *inode) ++{ ++ struct pengic_platdata *platdata; ++ int rc; ++ ++ platdata = uioinfo->priv; ++ ++#ifdef FORCE_ENABLE_ON_RELEASE ++ /* ++ * If the interrupt chain is disabled, enable it ++ * ++ * No need to lock here, there is nobody else contending for this ++ * data structure ++ */ ++ if (platdata->disables != 0) ++ pengic_enable_intr_nolock(platdata); ++#endif ++ rc = pengic_release(uioinfo, inode); ++ ++ return rc; ++} ++ ++/* ++ * Read the register where bits are set when interrupts happen. Note that ++ * an undefined reg_type will cause a zero to be returned, which will ++ * cause an IRQ_NONE to be returned to the caller of the IRQ handler. ++ */ ++static uint32_t pengic_read_status(enum reg_type reg_type, void *p) ++{ ++ uint32_t intr_status; ++ ++ switch (reg_type) { ++ case REG_TYPE_CSR: ++ intr_status = readl(&((struct pen_ictlr_csr *)p)->intreg); ++ break; ++ ++ case REG_TYPE_GRP: ++ intr_status = readl(&((struct pen_ictlr_grp *)p)->intreg); ++ break; ++ ++ case REG_TYPE_CSRINTR: ++ intr_status = readl(&((struct pen_ictlr_csrintr *)p)->intr); ++ break; ++ ++ default: ++ intr_status = 0; /* Always causes IRQ_NONE */ ++ break; ++ } ++ ++ return intr_status; ++} ++ ++/* ++ * Handle an IRQ assuming the default EOI processing is sufficient to ++ * renable the interrupt. ++ */ ++static irqreturn_t pengic_handler(int virq, struct uio_info *uioinfo) ++{ ++ struct pengic_platdata *platdata; ++ struct irq_data *irq_data; ++ u32 intr_status, enable_mask; ++ irq_hw_number_t hwirq; ++ unsigned int i; ++ ++ platdata = uioinfo->priv; ++ irq_data = irq_get_irq_data(platdata->irq); ++ hwirq = irq_data->hwirq; ++ enable_mask = 1 << hwirq; ++ ++ for (i = 0; i < platdata->n_res; i++) { ++ void *p; ++ ++ p = uioinfo->mem[i].internal_addr + uioinfo->mem[i].offs; ++ intr_status = pengic_read_status(platdata->reg_type, p); ++ if ((intr_status & enable_mask) != 0) ++ return IRQ_HANDLED; /* This is our interrupt */ ++ } ++ ++ return IRQ_NONE; ++} ++ ++/* ++ * Handle an IRQ assuming the default EOI processing is insufficient to ++ * renable the interrupt. Instead, the interrupt is disabled here and enabled ++ * through the irqcontrol function invoked from userspace via write(). ++ */ ++static irqreturn_t pengic_handler_disable(int virq, struct uio_info *uioinfo) ++{ ++ irqreturn_t rc; ++ ++ rc = pengic_handler(virq, uioinfo); ++ if (rc == IRQ_HANDLED) { ++ struct pengic_platdata *platdata; ++ ++ platdata = uioinfo->priv; ++ pengic_disable_intr(platdata); ++ } ++ ++ return rc; ++} ++ ++/* Unmap the first n elements */ ++static void unmap_asic(struct uio_info *uioinfo, unsigned int n, ++ struct device *dev) ++{ ++ unsigned int i; ++ ++ for (i = 0; uioinfo->mem[i].size != 0; i++) { ++ struct uio_mem *mem; ++ ++ mem = &uioinfo->mem[i]; ++ ++ if (mem->internal_addr == NULL) { ++ dev_warn(dev, ++ "mem[%td].internal_addr is unexpectdly NULL\n", ++ mem - uioinfo->mem); ++ } else { ++ iounmap(mem->internal_addr); ++ mem->internal_addr = NULL; /* paranoia */ ++ } ++ ++ if (mem->name == NULL) { ++ dev_warn(dev, "mem[%td].name is unexpectdly NULL\n", ++ mem - uioinfo->mem); ++ } else { ++ kfree(mem->name); ++ mem->name = NULL; /* More paranoia */ ++ } ++ } ++} ++ ++/* ++ * Returns the number of resources on success, otherwise a negative errno ++ * value ++ */ ++static int map_asic(struct uio_info *uio_info, struct device *dev, ++ struct device_node *node, struct resource *res, unsigned int n_res) ++{ ++ unsigned int i; ++ int ret; ++ ++ for (i = 0; i < n_res; i++) { ++ struct uio_mem *mem; ++ size_t unrounded_size; ++ size_t rounded_size; ++ ++ ret = of_address_to_resource(node, i, &res[i]); ++ if (ret != 0) ++ break; ++ ++ mem = &uio_info->mem[i]; ++ ++ /* Offset with the page in which the device registers reside */ ++ mem->offs = res[i].start & ~PAGE_MASK; ++ /* Physical address, aligned on a page boundary */ ++ mem->addr = res[i].start - mem->offs; ++ if (node->name != NULL) ++ mem->name = kstrdup(node->name, GFP_KERNEL); ++ ++ /* ++ * Size of an area completely containing the device registers ++ * of interest. Must be multiple of the page size ++ */ ++ unrounded_size = mem->offs + resource_size(res); ++ rounded_size = (unrounded_size + (PAGE_SIZE - 1)) & PAGE_MASK; ++ mem->size = rounded_size; ++ ++ /* Specify we have physical memory, then map it */ ++ mem->memtype = UIO_MEM_PHYS; ++ mem->internal_addr = ioremap(mem->addr, mem->size); ++ ++ if (mem->internal_addr == NULL) ++ break; ++ } ++ ++ return i; ++} ++ ++static int pengic_irqcontrol(struct uio_info *info, s32 irq_on) ++{ ++ struct pengic_platdata *platdata; ++ ++ platdata = info->priv; ++ ++ switch (irq_on) { ++ case 0: ++ pengic_disable_intr(platdata); ++ break; ++ ++ case 1: ++ pengic_enable_intr(platdata); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int set_uio_info(struct uio_info *uio_info, ++ struct device *dev, struct device_node *node, ++ irqreturn_t (*pengic_handler)(int irq, struct uio_info *uioinfo)) ++{ ++ const char *dt_name; ++ int irq; ++ int ret; ++ ++ ret = of_property_read_string(node, "name", &dt_name); ++ if (ret != 0) ++ dt_name = "pengic"; ++ uio_info->name = kstrdup(dt_name, GFP_KERNEL); ++ if (uio_info->name == NULL) ++ return -ENOMEM; ++ uio_info->version = "0.0.1"; ++ ++ irq = irq_of_parse_and_map(node, 0); ++ if (irq == 0) { ++ dev_warn(dev, "no interrupt found for %pOF\n", node); ++ uio_info->irq = UIO_IRQ_NONE; ++ return -ENXIO; ++ } ++ ++ uio_info->irq = irq; ++ uio_info->irq_flags = IRQF_SHARED; ++ uio_info->handler = pengic_handler; ++ uio_info->irqcontrol = pengic_irqcontrol; ++ ++ return 0; ++} ++ ++static void free_uio_info(struct uio_info *uio_info, unsigned int n_res, ++ struct device *dev) ++{ ++ if (uio_info->name != NULL) { ++ kfree(uio_info->name); ++ uio_info->name = NULL; ++ } ++ ++ uio_info->handler = NULL; ++ uio_info->irqcontrol = NULL; ++ unmap_asic(uio_info, n_res, dev); ++} ++ ++static void free_platdata(struct pengic_platdata *platdata) ++{ ++ kfree(platdata); ++} ++ ++static enum reg_type get_reg_type(struct device_node *node) ++{ ++ const char *reg_type_name; ++ int rc; ++ ++ rc = of_property_read_string(node, "register-type", ®_type_name); ++ if (rc != 0) ++ return REG_TYPE_UNKNOWN; ++ if (strcmp(reg_type_name, "csr") == 0) ++ return REG_TYPE_CSR; ++ if (strcmp(reg_type_name, "group") == 0) ++ return REG_TYPE_GRP; ++ if (strcmp(reg_type_name, "csr-interrupt") == 0) ++ return REG_TYPE_CSRINTR; ++ ++ pr_warn("Unknown register type: %s\n", reg_type_name); ++ return REG_TYPE_UNKNOWN; ++} ++ ++static int set_platdata(struct pengic_platdata *platdata, ++ const struct uio_info *uioinfo, struct platform_device *pdev) ++{ ++ struct device_node *node; ++ int ret; ++ u32 fw_param; ++ ++ /* Initialize the platform data */ ++ ++ platdata->node = pdev->dev.of_node; ++ platdata->dev = &pdev->dev; ++ platdata->name = uioinfo->name; ++ ++ platdata->irq = uioinfo->irq; ++ node = platdata->node; ++ ++ platdata->reg_type = get_reg_type(platdata->node); ++ ++ ret = map_asic(platdata->uio_info, platdata->dev, node, ++ platdata->res, ARRAY_SIZE(platdata->res)); ++ if (ret < 0) { ++ dev_err(platdata->dev, "can't map ASIC registers\n"); ++ return ret; ++ } ++ ++ platdata->n_res = ret; ++ ++ /* ++ * Get the interrupt number. This must be the first of a three ++ * element irq_fwspec. Can check "#interrupt-cells" if necessary. ++ */ ++ ret = of_property_read_u32_index(platdata->node, "interrupts", 2, ++ &fw_param); ++ if (ret != 0) ++ return ret; ++ platdata->enable_mask = 1 << fw_param; ++ ++ return 0; ++} ++ ++/* ++ * pengic_probe - allocate and initialize state for device ++ * @pdev: Pointer to the platform device ++ * @handler_fn: Pointer to the IRQ handler ++ * @release_fn: Pointer to the release function (called when the last file ++ * descriptor is closed) ++ */ ++static int pengic_probe_common(struct platform_device *pdev, ++ irqreturn_t (*handler_fn)(int irq, struct uio_info *uioinfo), ++ int (*release_fn)(struct uio_info *uioinfo, struct inode *inode)) ++{ ++ struct device_node *node; ++ struct pengic_platdata *platdata; ++ struct uio_info *uio_info; ++ unsigned int i; ++ int ret; ++ ++ ret = -EINVAL; ++ node = pdev->dev.of_node; ++ ++ /* Allocate space for the platform-specific data */ ++ platdata = kzalloc(sizeof(*platdata), GFP_KERNEL); ++ if (platdata == NULL) ++ return -ENOMEM; ++ ++ uio_info = kzalloc(sizeof(*uio_info), GFP_KERNEL); ++ if (uio_info == NULL) { ++ kfree(platdata); ++ return -ENOMEM; ++ } ++ ++ uio_info->priv = platdata; ++ platdata->uio_info = uio_info; ++ ++ ret = set_uio_info(uio_info, &pdev->dev, node, handler_fn); ++ if (ret != 0) ++ goto free_name; ++ ++ ret = set_platdata(platdata, uio_info, pdev); ++ if (ret != 0) ++ goto free_uioinfo; ++ ++ platform_set_drvdata(pdev, platdata); ++ uio_info->open = pengic_open; ++ uio_info->release = release_fn; ++ uio_info->priv = platdata; ++ ++ /* Map the device */ ++ if (dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)) != 0) { ++ dev_err(&pdev->dev, "no valid coherent DMA mask"); ++ goto free_platdata; ++ } ++ ++ pm_runtime_enable(&pdev->dev); ++ ++ /* Ready to be a grown up UIO device now */ ++ ret = uio_register_device(&pdev->dev, platdata->uio_info); ++ if (ret != 0) { ++ dev_err(&pdev->dev, "can't register UIO device: ret %d", ret); ++ pm_runtime_disable(&pdev->dev); ++ goto free_platdata; ++ } ++ ++ /* Print mapping report */ ++ for (i = 0; i < platdata->n_res; i++) { ++ dev_info(platdata->dev, "0x%llx->%p\n", ++ platdata->res[i].start, ++ uio_info->mem[i].internal_addr); ++ } ++ ++ return 0; ++ ++free_platdata: ++ free_platdata(platdata); ++ ++free_uioinfo: ++ free_uio_info(uio_info, ARRAY_SIZE(platdata->res), platdata->dev); ++ ++free_name: ++ kfree(uio_info->name); ++ uio_info->name = NULL; ++ ++ dev_err(platdata->dev, "probe failed\n"); ++ return ret; ++} ++ ++/* ++ * pengic_probe - allocate and initialize state for device where normal EOI ++ * processing is sufficient to process an interrupt ++ * @pdev: Pointer to the platform device ++ */ ++int pengic_probe(struct platform_device *pdev) ++{ ++ return pengic_probe_common(pdev, pengic_handler, pengic_release); ++} ++EXPORT_SYMBOL(pengic_probe); ++ ++/* ++ * pengic_probe_enable - allocate and initialize state for device where an ++ * explicit call to irq_enable() is required to process an interrupt. ++ * @pdev: Pointer to the platform device ++ */ ++int pengic_probe_enable(struct platform_device *pdev) ++{ ++ return pengic_probe_common(pdev, pengic_handler_disable, ++ pengic_release_enable); ++} ++EXPORT_SYMBOL(pengic_probe_enable); ++ ++/* ++ * pengic_remove - free UIO-related data structures ++ * ++ * @pdev: Pointer to the platform_device structure to remove ++ */ ++void pengic_remove(struct platform_device *pdev) ++{ ++ struct pengic_platdata *platdata; ++ struct uio_info *uio_info; ++ unsigned int n_res; ++ ++ platdata = platform_get_drvdata(pdev); ++ uio_info = platdata->uio_info; ++ platdata->uio_info = NULL; ++ uio_info->priv = NULL; ++ n_res = platdata->n_res; ++ ++ /* This should not be necessary but it's defensive programing in ++ * case this driver gets called after it has been shutdown ++ */ ++ platform_set_drvdata(pdev, NULL); ++ ++ free_platdata(platdata); ++ free_uio_info(uio_info, n_res, &pdev->dev); ++ ++ pm_runtime_disable(&pdev->dev); ++ ++ return; ++} ++EXPORT_SYMBOL(pengic_remove); ++ ++/* ++ * pengic_pm_nop - Power management stub that just returns success ++ * ++ * We leave it to other drivers to handle the device power management ++ * operations, if any. ++ */ ++static int pengic_pm_nop(struct device *dev) ++{ ++ return 0; ++} ++ ++const struct dev_pm_ops pengic_pm_ops = { ++ .runtime_suspend = pengic_pm_nop, ++ .runtime_resume = pengic_pm_nop, ++}; ++EXPORT_SYMBOL(pengic_pm_ops); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Support userspace I/O for Pensando Ring interrupts"); ++MODULE_AUTHOR("David VomLehn"); +diff --git a/drivers/uio/uio_pengic.h b/drivers/uio/uio_pengic.h +new file mode 100644 +index 0000000..eabcd17 +--- /dev/null ++++ b/drivers/uio/uio_pengic.h +@@ -0,0 +1,18 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2020-2021, Pensando Systems Inc. ++ */ ++ ++#ifndef _UIO_PENGIC_H_ ++#define _UIO_PENGIC_H_ ++ ++#include ++#include ++ ++extern const struct dev_pm_ops pengic_pm_ops; ++ ++int pengic_probe(struct platform_device *pdev); ++int pengic_probe_enable(struct platform_device *pdev); ++void pengic_remove(struct platform_device *pdev); ++ ++#endif /* _UIO_PENGIC_H_ */ +diff --git a/drivers/uio/uio_penmsi.c b/drivers/uio/uio_penmsi.c +new file mode 100644 +index 0000000..28ab905 +--- /dev/null ++++ b/drivers/uio/uio_penmsi.c +@@ -0,0 +1,539 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2020-2021, Pensando Systems Inc. ++ * ++ * UIO common driver for MSIX interrupts for Pensando system. ++ * ++ * As you read this, remember that it is the common portion of what will ++ * generally be many drivers. As such, functions like the probe() function have ++ * to deal with contention, which code that is one-per-driver do not. This ++ * makes things a bit more complex. ++ * ++ * The second complexity is that we have an asynchronous callback from MSIX. ++ * Since we don't now the context of that call, we pre-allocate the ++ * needed data structures from a context known to be suitable for memory ++ * allocation. When the callback is called, it simply dequeues the memory ++ * and puts it on an "in use" list. When the probe() function needs to ++ * allocate its platform data, it just uses one of the structures on the ++ * in use list. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Optionally add attributes */ ++#define ADD_ATTRS ++#undef ENABLE_STORE ++ ++#ifdef ADD_ATTRS ++#define to_penmsi_dir(kobjp) container_of(kobjp, struct penmsi_dir, kobj) ++#endif ++ ++/* ++ * devdata - information for a UIO device ++ * @pdev: Pointer to the struct platform_device ++ * @platdata: Pointer to the containing struct platdata ++ * @have_msi_index: Indicates that @msi_index is valid ++ * @msi_index: Index used to match descriptors with data ++ * @open: Flag indicating whether this device is open ++ * @msi_msg: MSI address and data information ++ * @list Item in a list of allocated devdata items; ++ * @penmsi_dir: Pointer to information about the penmsi directory ++ * in which the command file lives ++ */ ++struct devdata { ++ struct uio_info uio_info; ++ struct platdata *platdata; ++ bool have_msi_msg; ++ bool have_msi_desc; ++ u16 msi_index; ++ bool open; ++ struct msi_msg msi_msg; ++#ifdef ADD_ATTRS ++ struct penmsi_dir *penmsi_dir; ++#endif ++}; ++ ++/* ++ * Per platform device ++ * @pdev: Associated platform device ++ * @n_pending: Remaining number of devdata items we are waiting for ++ * @n_irqs: Number of IRQs being supported ++ * @n_valid: Number of struct devdata items initialized for use ++ * @devdata: Array of struct devdata items ++ */ ++struct platdata { ++ struct platform_device *pdev; ++ unsigned int n_pending; ++ size_t n_irqs; ++ unsigned int n_valid; ++ struct devdata devdatas[]; ++}; ++/* ++ * @penmsi_sem: Protects the @init_platdata pointer and allocation of ++ * MSI IRQs ++ * @init_platdata: Pointer to the platdata currently being initialized. ++ * This is only valid while @penmsi_sem is held ++ */ ++static DEFINE_SEMAPHORE(penmsi_sem, 1); ++static DECLARE_WAIT_QUEUE_HEAD(penmsi_wq_head); ++static DEFINE_SPINLOCK(penmsi_lock); ++static struct platdata *init_platdata; ++static unsigned int num_uio_devs; ++ ++#ifdef ADD_ATTRS ++struct penmsi_dir { ++ struct kobject kobj; ++ struct msi_msg *msi_msg; ++ size_t msi_size; ++}; ++ ++struct msi { ++ int counter; ++ struct penmsi_dir *penmsi_dir; ++}; ++ ++static ssize_t msi_show(struct msi_msg *msi_msg, char *buf) ++{ ++ memcpy(buf, msi_msg, sizeof(*msi_msg)); ++ return sizeof(*msi_msg); ++} ++ ++#ifdef ENABLE_STORE ++static ssize_t msi_store(struct msi_msg *msi_msg, const char *buf, ++ size_t size) ++{ ++ if (size != sizeof(*msi_msg)) ++ return -EINVAL; ++ ++ memcpy(msi_msg, buf, sizeof(*msi_msg)); ++ return sizeof(*msi_msg); ++} ++#else ++static ssize_t msi_store(struct msi_msg *msi_msg, const char *buf, ++ size_t size) ++{ ++ return -EOPNOTSUPP; ++} ++#endif ++ ++struct msi_sysfs_entry { ++ struct attribute attr; ++ ssize_t (*show)(struct msi_msg *msg, char *buf); ++ ssize_t (*store)(struct msi_msg *msg, const char *buf, size_t sz); ++}; ++ ++static struct msi_sysfs_entry msi_attribute = ++ __ATTR(msi, 0600, msi_show, msi_store); ++ ++static struct attribute *msi_attrs[] = { ++ &msi_attribute.attr, ++ NULL, /* need to NULL terminate the list of attributes */ ++}; ++ ++ATTRIBUTE_GROUPS(msi); ++ ++static void penmsi_rel(struct kobject *kobj) ++{ ++ struct penmsi_dir *penmsi_dir = to_penmsi_dir(kobj); ++ ++ kfree(penmsi_dir); ++} ++ ++static ssize_t penmsi_show(struct kobject *kobj, struct attribute *attr, ++ char *buf) ++{ ++ struct penmsi_dir *penmsi_dir = to_penmsi_dir(kobj); ++ struct msi_msg *msi_msg = penmsi_dir->msi_msg; ++ struct msi_sysfs_entry *entry; ++ ++ entry = container_of(attr, struct msi_sysfs_entry, attr); ++ ++ if (entry->show == NULL) ++ return -EOPNOTSUPP; ++ return entry->show(msi_msg, buf); ++} ++ ++#ifdef ENABLE_STORE ++static ssize_t penmsi_store(struct kobject *kobj, struct attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct penmsi_dir *penmsi_dir = to_penmsi_dir(kobj); ++ struct msi *msi = penmsi_dir->msi; ++ struct msi_sysfs_entry *entry; ++ ++ entry = container_of(attr, struct msi_sysfs_entry, attr); ++ ++ if (entry->show == NULL) ++ return -EOPNOTSUPP; ++ ++ return entry->store(msi, buf, size); ++} ++#else ++static ssize_t penmsi_store(struct kobject *kobj, struct attribute *attr, ++ const char *buf, size_t size) ++{ ++ return -EOPNOTSUPP; ++} ++#endif ++ ++static const struct sysfs_ops penmsi_sysfs_ops = { ++ .show = penmsi_show, ++ .store = penmsi_store, ++}; ++ ++static struct kobj_type penmsi_attr_type = { ++ .release = penmsi_rel, ++ .sysfs_ops = &penmsi_sysfs_ops, ++ .default_groups = msi_groups, ++}; ++ ++static int add_attr(struct devdata *devdata) ++{ ++ struct penmsi_dir *penmsi_dir; ++ struct uio_device *uio_dev; ++ int ret; ++ ++ /* ++ * Create a kobject for the directory in which the command interface ++ * lives ++ */ ++ uio_dev = devdata->uio_info.uio_dev; ++ ++ /* Allocate storage for the command file */ ++ penmsi_dir = kzalloc(sizeof(*penmsi_dir), GFP_KERNEL); ++ if (penmsi_dir == NULL) ++ return -ENOMEM; ++ devdata->penmsi_dir = penmsi_dir; ++ devdata->penmsi_dir->msi_msg = &devdata->msi_msg; ++ devdata->penmsi_dir->msi_size = sizeof(devdata->msi_msg); ++ kobject_init(&devdata->penmsi_dir->kobj, &penmsi_attr_type); ++ ++ ret = kobject_add(&devdata->penmsi_dir->kobj, &uio_dev->dev.kobj, ++ "pensando"); ++ if (ret != 0) ++ goto remove_penmsi_dir; ++ ++ ret = kobject_uevent(&devdata->penmsi_dir->kobj, KOBJ_ADD); ++ if (ret != 0) ++ goto remove_penmsi_dir; ++ ++ return 0; ++ ++remove_penmsi_dir: ++ kobject_put(&devdata->penmsi_dir->kobj); ++ kfree(devdata->penmsi_dir); ++ devdata->penmsi_dir = NULL; ++ ++ dev_err(&uio_dev->dev, "error creating sysfiles (%d)\n", ret); ++ return -EIO; ++} ++ ++static void del_attr(struct devdata *devdata) ++{ ++ kobject_put(&devdata->penmsi_dir->kobj); ++ devdata->penmsi_dir = NULL; ++} ++ ++static int adorn_with_attrs(struct devdata *devdata) ++{ ++ int rc; ++ ++ rc = add_attr(devdata); ++ if (rc != 0) { ++ dev_err(&devdata->platdata->pdev->dev, "%s failed: %d\n", ++ __func__, rc); ++ goto remove_attrs; ++ } ++ ++ return 0; ++ ++remove_attrs: ++ del_attr(devdata); ++ return rc; ++ ++} ++ ++static void del_attrs(struct platdata *platdata) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < platdata->n_irqs; i++) ++ del_attr(&platdata->devdatas[i]); ++} ++#else ++static inline int add_attr(struct devdata *devdata) { return 0; } ++static inline void del_attr(struct devdata *devdata) { } ++static inline void del_attrs(struct platdata *platdata) { } ++#endif ++ ++static int penmsi_open(struct uio_info *uio_info, struct inode *inode) ++{ ++ struct platdata *platdata; ++ struct devdata *devdata; ++ int ret; ++ ++ devdata = uio_info->priv; ++ if (devdata->open) ++ return -EBUSY; ++ devdata->open = true; ++ ret = 0; ++ ++ platdata = devdata->platdata; ++ pm_runtime_get_sync(&platdata->pdev->dev); ++ ++ return ret; ++} ++ ++static int penmsi_release(struct uio_info *uio_info, struct inode *inode) ++{ ++ struct devdata *devdata; ++ ++ devdata = uio_info->priv; ++ devdata->open = false; ++ pm_runtime_put_sync(&devdata->platdata->pdev->dev); ++ ++ return 0; ++} ++ ++ ++/* ++ * The interrupt is edge triggered, so if we don't do anything and just ++ * return, nothing bad will happen. Yes, this is a bit unusual but ++ * it's useful. ++ */ ++static irqreturn_t penmsi_handler(int irq, struct uio_info *uio_info) ++{ ++ return IRQ_HANDLED; ++} ++ ++static void decrement_pending(struct platdata *platdata) ++{ ++ platdata->n_pending--; ++ if (platdata->n_pending == 0) ++ up(&penmsi_sem); ++} ++ ++/* ++ * Remove the device completely ++ * @pdev: Pointer to the struct platform_device we're using ++ */ ++void penmsi_remove(struct platform_device *pdev) ++{ ++ struct platdata *platdata; ++ unsigned int i; ++ ++ platdata = platform_get_drvdata(pdev); ++ msi_domain_free_irqs_all(&pdev->dev, 0); ++ del_attrs(platdata); ++ ++ // FIXME: this needs to use n_valid ++ for (i = 0; i < platdata->n_irqs; i++) { ++ struct devdata *devdata; ++ struct uio_info *uio_info; ++ ++ devdata = &platdata->devdatas[i]; ++ uio_info = &devdata->uio_info; ++ uio_unregister_device(uio_info); ++ kfree(uio_info->name); ++ ++ /* ++ * We're cheating with this, skip all the rest of the UIOs ++ * so we don't free anything we shouldn't. This is going to ++ * result in a memory leak. ++ */ ++ break; ++ } ++ ++ kfree(platdata); ++ ++ return; ++} ++EXPORT_SYMBOL(penmsi_remove); ++ ++static int penmsi_probe_one(struct devdata *devdata, ++ struct platform_device *pdev, struct msi_desc *desc) ++{ ++ struct uio_info *uio_info; ++ const char *dt_name; ++ char name_buf[32]; ++ int rc; ++ ++ uio_info = &devdata->uio_info; ++ ++ rc = of_property_read_string(pdev->dev.of_node, "name", &dt_name); ++ if (rc != 0) ++ dt_name = "penmsiX"; ++ snprintf(name_buf, sizeof(name_buf), "%s.%u", dt_name, num_uio_devs); ++ num_uio_devs++; ++ uio_info->name = kstrdup(name_buf, GFP_KERNEL); ++ if (uio_info->name == NULL) ++ return -ENOMEM; ++ ++ /* ++ * Only take one interrupt because UIO won't let us pass more than ++ * that ++ */ ++ uio_info->priv = devdata; ++ uio_info->version = "0.1"; ++ uio_info->open = penmsi_open; ++ uio_info->release = penmsi_release; ++ uio_info->handler = penmsi_handler; ++ uio_info->irq = desc->irq; ++ uio_info->irq_flags = IRQF_SHARED; ++ ++ /* Ready to be a grown up UIO device now */ ++ rc = uio_register_device(&pdev->dev, uio_info); ++ if (rc != 0) { ++ dev_err(&pdev->dev, "can't register UIO device"); ++ goto free_name; ++ } ++ ++ rc = adorn_with_attrs(devdata); ++ if (rc != 0) ++ goto unregister_uio; ++ ++ return 0; ++ ++unregister_uio: ++ uio_unregister_device(uio_info); ++ ++free_name: ++ kfree(uio_info->name); ++ uio_info->name = NULL; ++ ++ return rc; ++} ++ ++/* ++ * Allocate a struct platdata with all of its devdata structures. ++ * ++ * Returns the a pointer to the allocated platdata on success, NULL otherwise ++ */ ++static struct platdata *alloc_devdatas(unsigned int n) ++{ ++ unsigned int i; ++ struct platdata *platdata; ++ ++ platdata = kzalloc(offsetof(struct platdata, devdatas[n]), GFP_KERNEL); ++ if (platdata == NULL) ++ return platdata; ++ ++ for (i = 0; i < n; i++) ++ platdata->devdatas[i].platdata = platdata; ++ ++ platdata->n_pending = n; ++ platdata->n_irqs = n; ++ ++ return platdata; ++} ++ ++/* ++ * penmsi_probe - allocate and initialize state for device ++ */ ++int penmsi_probe(struct platform_device *pdev); ++int penmsi_probe(struct platform_device *pdev) ++{ ++ struct msi_desc *desc; ++ struct platdata *platdata; ++ struct devdata *devdata; ++ u32 num_interrupts; ++ int rc; ++ ++ num_uio_devs = 0; ++ rc = of_property_read_u32(pdev->dev.of_node, "num-interrupts", ++ &num_interrupts); ++ if (rc != 0) { ++ dev_err(&pdev->dev, "num-interrupts property missing\n"); ++ return rc; ++ } ++ if (num_interrupts == 0) { ++ dev_err(&pdev->dev, "num-interrupts property must be >0\n"); ++ return -EINVAL; ++ } ++ ++ platdata = alloc_devdatas(num_interrupts); ++ platform_set_drvdata(pdev, platdata); ++ platdata->n_irqs = num_interrupts; ++ platdata->pdev = pdev; ++ ++ /* Due to the way MSI passes back information, we want to allocate ++ * MSI IRQs one at a time. ++ */ ++ down(&penmsi_sem); ++ init_platdata = platdata; ++ ++ /* Kick off work that should result in calling the callback */ ++ rc = msi_domain_alloc_irqs(&pdev->dev, 0, num_interrupts); ++ if (rc != 0) { ++ dev_err(&pdev->dev, "msi_domain_alloc_irqs failed: %d\n", rc); ++ goto up_sem; ++ } ++ ++ msi_for_each_desc(desc, &pdev->dev, MSI_DESC_ASSOCIATED) { ++ devdata = &platdata->devdatas[desc->msi_index]; ++ devdata->have_msi_desc = true; ++ rc = penmsi_probe_one(devdata, pdev, desc); ++ if (rc != 0) ++ goto free_msis; ++ } ++ ++ init_platdata = NULL; // Don't use this again ++ up(&penmsi_sem); ++ ++ /* Map the device */ ++ rc = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); ++ if (rc != 0) { ++ dev_err(&pdev->dev, "no valid coherent DMA mask"); ++ goto free_msis; ++ } ++ ++ pm_runtime_enable(&pdev->dev); ++ ++ return 0; ++ ++free_msis: ++ msi_domain_free_irqs_all(&pdev->dev, 0); ++ ++up_sem: ++ up(&penmsi_sem); ++ kfree(platdata); ++ return rc; ++ ++} ++EXPORT_SYMBOL(penmsi_probe); ++ ++/* ++ * penmsi_pm_nop - Power management stub that just returns success ++ * ++ * We leave it to other drivers to handle the device power management ++ * operations, if any. ++ */ ++static int penmsi_pm_nop(struct device *dev) ++{ ++ return 0; ++} ++ ++const struct dev_pm_ops penmsi_pm_ops = { ++ .runtime_suspend = penmsi_pm_nop, ++ .runtime_resume = penmsi_pm_nop, ++}; ++EXPORT_SYMBOL(penmsi_pm_ops); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Support userspace I/O for Pensando MSIX interrupts"); ++MODULE_AUTHOR("David VomLehn"); +diff --git a/drivers/uio/uio_penmsi.h b/drivers/uio/uio_penmsi.h +new file mode 100644 +index 0000000..b8ce7b5 +--- /dev/null ++++ b/drivers/uio/uio_penmsi.h +@@ -0,0 +1,17 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2020-2021, Pensando Systems Inc. ++ */ ++ ++#ifndef _UIO_PENMSI_H_ ++#define _UIO_PENMSI_H_ ++ ++#include ++#include ++ ++extern const struct dev_pm_ops penmsi_pm_ops; ++ ++int penmsi_probe(struct platform_device *pdev); ++void penmsi_remove(struct platform_device *pdev); ++ ++#endif /* _UIO_PENMSI_H_ */ +diff --git a/drivers/uio/uio_penmsi1.c b/drivers/uio/uio_penmsi1.c +new file mode 100644 +index 0000000..14aec07 +--- /dev/null ++++ b/drivers/uio/uio_penmsi1.c +@@ -0,0 +1,32 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2020-2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include "uio_penmsi.h" ++ ++#define DRIVER_NAME_MSIX1 "uio_penmsi1" ++ ++#ifdef CONFIG_OF ++static const struct of_device_id penmsi_match[] = { ++ { .compatible = "pensando,uio_penmsi1" }, ++ { /* Mark the end of the list */ }, ++}; ++#endif ++ ++static struct platform_driver penmsi1 = { ++ .probe = penmsi_probe, ++ .remove = penmsi_remove, ++ .driver = { ++ .name = DRIVER_NAME_MSIX1, ++ .pm = &penmsi_pm_ops, ++ .of_match_table = of_match_ptr(penmsi_match), ++ } ++}; ++ ++module_platform_driver(penmsi1); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Support userspace I/O for Pensando MSI interrupts"); ++MODULE_AUTHOR("David VomLehn"); +-- +1.8.3.1 + diff --git a/patches-sonic/0012-i2c-xiic-switch-to-generic-device-property-accessors.patch b/patches-sonic/0012-i2c-xiic-switch-to-generic-device-property-accessors.patch new file mode 100644 index 000000000..43467523e --- /dev/null +++ b/patches-sonic/0012-i2c-xiic-switch-to-generic-device-property-accessors.patch @@ -0,0 +1,100 @@ +From aba046769d414fb5d6fb3281465e2d7eb3bea7c8 Mon Sep 17 00:00:00 2001 +From: Abdurrahman Hussain +Date: Mon, 23 Feb 2026 15:59:18 +0000 +Subject: [PATCH 12/22] i2c: xiic: switch to generic device property accessors + +Use generic device property accessors making them work for ACPI +platforms. + +Signed-off-by: Abdurrahman Hussain +Reviewed-by: Andy Shevchenko +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260223-i2c-xiic-v12-3-b6c9ce4e4f3c@nexthop.ai +--- + drivers/i2c/busses/i2c-xiic.c | 21 +++++++-------------- + 1 file changed, 7 insertions(+), 14 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index 5f141acc5306..16fd85373e46 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -27,7 +27,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -1409,7 +1408,6 @@ static const struct i2c_adapter xiic_adapter = { + .algo = &xiic_algorithm, + }; + +-#if defined(CONFIG_OF) + static const struct xiic_version_data xiic_2_00 = { + .quirks = DYNAMIC_MODE_READ_BROKEN_BIT, + }; +@@ -1420,14 +1418,14 @@ static const struct of_device_id xiic_of_match[] = { + {}, + }; + MODULE_DEVICE_TABLE(of, xiic_of_match); +-#endif + + static int xiic_i2c_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; ++ struct fwnode_handle *fwnode = dev_fwnode(dev); + struct xiic_i2c *i2c; + struct xiic_i2c_platform_data *pdata; +- const struct of_device_id *match; ++ const struct xiic_version_data *data; + struct resource *res; + int ret, irq; + u8 i; +@@ -1437,12 +1435,9 @@ static int xiic_i2c_probe(struct platform_device *pdev) + if (!i2c) + return -ENOMEM; + +- match = of_match_node(xiic_of_match, pdev->dev.of_node); +- if (match && match->data) { +- const struct xiic_version_data *data = match->data; +- ++ data = device_get_match_data(dev); ++ if (data) + i2c->quirks = data->quirks; +- } + + i2c->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2c->base)) +@@ -1459,7 +1454,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + i2c->adap = xiic_adapter; + i2c_set_adapdata(&i2c->adap, i2c); + i2c->adap.dev.parent = &pdev->dev; +- i2c->adap.dev.of_node = pdev->dev.of_node; ++ device_set_node(&i2c->adap.dev, fwnode); + snprintf(i2c->adap.name, sizeof(i2c->adap.name), + DRIVER_NAME " %s", pdev->name); + +@@ -1483,8 +1478,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + + /* SCL frequency configuration */ + i2c->input_clk = clk_get_rate(i2c->clk); +- ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", +- &i2c->i2c_clk); ++ ret = device_property_read_u32(dev, "clock-frequency", &i2c->i2c_clk); + /* If clock-frequency not specified in DT, do not configure in SW */ + if (ret || i2c->i2c_clk > I2C_MAX_FAST_MODE_PLUS_FREQ) + i2c->i2c_clk = 0; +@@ -1495,8 +1489,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + if (ret) + return ret; + +- i2c->singlemaster = +- of_property_read_bool(pdev->dev.of_node, "single-master"); ++ i2c->singlemaster = device_property_read_bool(dev, "single-master"); + + /* + * Detect endianness +-- +2.53.0 + diff --git a/patches-sonic/0012-usb-ehci-Add-Aspeed-AST2700-support.patch b/patches-sonic/0012-usb-ehci-Add-Aspeed-AST2700-support.patch new file mode 100644 index 000000000..432f1a5be --- /dev/null +++ b/patches-sonic/0012-usb-ehci-Add-Aspeed-AST2700-support.patch @@ -0,0 +1,79 @@ +From: Ryan Chen +Date: Sun, 28 Sep 2025 03:24:07 +0000 +Subject: [PATCH 6.12 1/1] usb: ehci: Add Aspeed AST2700 support + +Upstream-Status: Backport [274f2232a94f6ca626d60288044e13d9a58c7612] + +Unlike earlier Aspeed SoCs (AST2400/2500/2600) which are limited to +32-bit DMA addressing, the EHCI controller in AST2700 supports 64-bit +DMA. Update the EHCI platform driver to make use of this capability by +selecting a 64-bit DMA mask when the "aspeed,ast2700-ehci" compatible +is present in device tree. + +Without this, ehci-platform probe of usb@12063000 (EHCI1, USB Port B) +on SPC6 AST2700 BMC fails with -ENOMEM when DTS enables &ehci1. + +Signed-off-by: Ryan Chen +Reviewed-by: Alan Stern +--- + drivers/usb/host/ehci-platform.c | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c +index cdf41886e8ca..dd6a93edf4de 100644 +--- a/drivers/usb/host/ehci-platform.c ++++ b/drivers/usb/host/ehci-platform.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -238,9 +239,11 @@ static int ehci_platform_probe(struct platform_device *dev) + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); ++ const struct of_device_id *match; + struct ehci_platform_priv *priv; + struct ehci_hcd *ehci; + int err, irq, clk = 0; ++ bool dma_mask_64; + + if (usb_disabled()) + return -ENODEV; +@@ -252,8 +255,13 @@ static int ehci_platform_probe(struct platform_device *dev) + if (!pdata) + pdata = &ehci_platform_defaults; + ++ dma_mask_64 = pdata->dma_mask_64; ++ match = of_match_device(dev->dev.driver->of_match_table, &dev->dev); ++ if (match && match->data) ++ dma_mask_64 = true; ++ + err = dma_coerce_mask_and_coherent(&dev->dev, +- pdata->dma_mask_64 ? DMA_BIT_MASK(64) : DMA_BIT_MASK(32)); ++ dma_mask_64 ? DMA_BIT_MASK(64) : DMA_BIT_MASK(32)); + if (err) { + dev_err(&dev->dev, "Error: DMA mask configuration failed\n"); + return err; +@@ -297,7 +305,9 @@ static int ehci_platform_probe(struct platform_device *dev) + if (of_device_is_compatible(dev->dev.of_node, + "aspeed,ast2500-ehci") || + of_device_is_compatible(dev->dev.of_node, +- "aspeed,ast2600-ehci")) ++ "aspeed,ast2600-ehci") || ++ of_device_is_compatible(dev->dev.of_node, ++ "aspeed,ast2700-ehci")) + ehci->is_aspeed = 1; + + if (soc_device_match(quirk_poll_match)) +@@ -484,6 +494,7 @@ static const struct of_device_id vt8500_ehci_ids[] = { + { .compatible = "wm,prizm-ehci", }, + { .compatible = "generic-ehci", }, + { .compatible = "cavium,octeon-6335-ehci", }, ++ { .compatible = "aspeed,ast2700-ehci", .data = (void *)1 }, + {} + }; + MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); diff --git a/patches-sonic/0013-drivers-pensando-soc-Boot-State-Machine-integrtion.patch b/patches-sonic/0013-drivers-pensando-soc-Boot-State-Machine-integrtion.patch new file mode 100644 index 000000000..b5bc7f0e1 --- /dev/null +++ b/patches-sonic/0013-drivers-pensando-soc-Boot-State-Machine-integrtion.patch @@ -0,0 +1,307 @@ +From 49829ec689fcab6262c6c37a85ba9e26be0aada2 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:07:36 +0000 +Subject: [PATCH 13/27] drivers/pensando/soc:Boot State Machine integrtion + +The Pensando BSM interacts with the boot firmware to provide a level +of recovery should a Linux image fail to start. If the BSM is +armed and a kernel panic + reset occur, then the boot firmware +will consider the loaded image as bad and will select the alternate +image to boot next. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 5 ++ + drivers/soc/pensando/Kconfig | 13 +++ + drivers/soc/pensando/Makefile | 3 + + drivers/soc/pensando/bsm_dev.h | 24 +++++ + drivers/soc/pensando/cap_bsm.c | 159 +++++++++++++++++++++++++++++++++ + drivers/soc/pensando/cap_soc.c | 25 ++++++ + 6 files changed, 229 insertions(+) + create mode 100644 drivers/soc/pensando/bsm_dev.h + create mode 100644 drivers/soc/pensando/cap_bsm.c + create mode 100644 drivers/soc/pensando/cap_soc.c + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index d87128c..3060a7f 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -260,5 +260,10 @@ + compatible = "amd,pensando-elba-syscon", "pensando,mssoc", "syscon"; + reg = <0x0 0x307c0000 0x0 0x3000>; + }; ++ ++ bsm@307c2080 { ++ compatible = "pensando,bsm"; ++ reg = <0x0 0x307c2080 0x0 0x4>; ++ }; + }; + }; +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +index abe01ea..eb1494e 100644 +--- a/drivers/soc/pensando/Kconfig ++++ b/drivers/soc/pensando/Kconfig +@@ -15,6 +15,19 @@ config PENSANDO_SOC_CAPMEM + help + Support for the Pensando SoC memory driver + ++config PENSANDO_SOC_BSM ++ bool "Pensando SoC Boot State Machine" ++ depends on OF ++ default y ++ help ++ Support for the Pensando SoC boot state machine driver ++ ++config PENSANDO_SOC_BSM_ENABLE ++ bool "Enable the Boot State Machine in the kernel" ++ depends on PENSANDO_SOC_BSM ++ help ++ Re-enable the BSM on kernel startup ++ + endmenu + + endif +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +index fcfb674..bf4f46f 100644 +--- a/drivers/soc/pensando/Makefile ++++ b/drivers/soc/pensando/Makefile +@@ -1,6 +1,9 @@ + # + # Pensando SoC drivers + # ++obj-y += cap_soc.o ++ + CFLAGS_cap_mem.o := -I$(src) + + obj-$(CONFIG_PENSANDO_SOC_CAPMEM) += cap_mem.o ++obj-$(CONFIG_PENSANDO_SOC_BSM) += cap_bsm.o +diff --git a/drivers/soc/pensando/bsm_dev.h b/drivers/soc/pensando/bsm_dev.h +new file mode 100644 +index 0000000..7e31269 +--- /dev/null ++++ b/drivers/soc/pensando/bsm_dev.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2019-2021, Pensando Systems Inc. ++ */ ++ ++#ifndef _BSM_DEV_H_ ++#define _BSM_DEV_H_ ++ ++#define BSM_WDT_LSB 10 ++#define BSM_WDT_MASK 0x1 ++#define BSM_FWID_LSB 8 ++#define BSM_FWID_MASK 0x3 ++#define BSM_ATTEMPT_LSB 6 ++#define BSM_ATTEMPT_MASK 0x3 ++#define BSM_TRACK_LSB 4 ++#define BSM_TRACK_MASK 0x3 ++#define BSM_STAGE_LSB 2 ++#define BSM_STAGE_MASK 0x3 ++#define BSM_RUNNING_LSB 1 ++#define BSM_RUNNING_MASK 0x1 ++#define BSM_AUTOBOOT_LSB 0 ++#define BSM_AUTOBOOT_MASK 0x1 ++ ++#endif +diff --git a/drivers/soc/pensando/cap_bsm.c b/drivers/soc/pensando/cap_bsm.c +new file mode 100644 +index 0000000..a55039f +--- /dev/null ++++ b/drivers/soc/pensando/cap_bsm.c +@@ -0,0 +1,159 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2019-2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "bsm_dev.h" ++ ++extern struct kobject *pensando_fw_kobj_get(void); ++ ++struct bsm { ++ void __iomem *base; ++ uint32_t val; ++}; ++static struct bsm bsm; ++ ++#define BSM_SHOW_INT(n, s) \ ++ static ssize_t n##_show(struct device *dev, \ ++ struct device_attribute *attr, char *buf) \ ++ { \ ++ int val = (bsm.val >> BSM_##s##_LSB) & BSM_##s##_MASK; \ ++ return sprintf(buf, "%d\n", val); \ ++ } \ ++ static DEVICE_ATTR_RO(n); ++ ++BSM_SHOW_INT(wdt, WDT) ++BSM_SHOW_INT(attempt, ATTEMPT) ++BSM_SHOW_INT(stage, STAGE) ++BSM_SHOW_INT(running, RUNNING) ++BSM_SHOW_INT(autoboot, AUTOBOOT) ++ ++static const char *fwnames[4] = { ++ "mainfwa", "mainfwb", "goldfw", "diagfw" ++}; ++ ++#define BSM_SHOW_FWID(n, s) \ ++ static ssize_t n##_show(struct device *dev, \ ++ struct device_attribute *attr, char *buf) \ ++ { \ ++ int val = (bsm.val >> BSM_##s##_LSB) & BSM_##s##_MASK; \ ++ return sprintf(buf, "%s\n", fwnames[val & 0x3]); \ ++ } \ ++ static DEVICE_ATTR_RO(n); ++ ++BSM_SHOW_FWID(fwid, FWID) ++BSM_SHOW_FWID(track, TRACK) ++ ++static ssize_t success_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ long val; ++ ++ if (kstrtoul(buf, 0, &val) < 0) ++ return -EINVAL; ++ if (val) { ++ bsm.val &= ~(1 << BSM_RUNNING_LSB); ++ writel(bsm.val, bsm.base); ++ } ++ ++ return count; ++} ++static DEVICE_ATTR_WO(success); ++ ++static const struct device_attribute *bsm_attrs[] = { ++ &dev_attr_wdt, ++ &dev_attr_fwid, ++ &dev_attr_attempt, ++ &dev_attr_track, ++ &dev_attr_stage, ++ &dev_attr_running, ++ &dev_attr_autoboot, ++ &dev_attr_success, ++}; ++ ++static int bsm_probe(struct platform_device *pdev) ++{ ++ struct kobject *pensando_kobj; ++ int i, r = 0; ++ ++ if (bsm.base == NULL) { ++ /* bsm not in device-tree */ ++ return -ENODEV; ++ } ++ pensando_kobj = pensando_fw_kobj_get(); ++ if (!pensando_kobj) ++ return -ENOMEM; ++ for (i = 0; i < ARRAY_SIZE(bsm_attrs); i++) { ++ r = device_create_file(&pdev->dev, bsm_attrs[i]); ++ if (r) { ++ pr_err("failed to create sysfs file\n"); ++ return r; ++ } ++ } ++ r = sysfs_create_link(pensando_kobj, &pdev->dev.kobj, "bsm"); ++ if (r) { ++ pr_err("failed to create sysfs symlink\n"); ++ kobject_put(pensando_kobj); ++ return r; ++ } ++ return 0; ++} ++ ++static const struct of_device_id bsm_of_match[] = { ++ { .compatible = "pensando,bsm" }, ++}; ++ ++static struct platform_driver bsm_driver = { ++ .driver = { ++ .name = "capri-bsm", ++ .of_match_table = bsm_of_match, ++ .suppress_bind_attrs = true, ++ }, ++ .probe = bsm_probe, ++}; ++builtin_platform_driver(bsm_driver); ++ ++/* ++ * Boot State Machine init. ++ * If auto-booting, then set the BSM_RUNNING bit in the BSM register ++ * to continue BSM protection. The bit will be cleared when userland comes up. ++ */ ++static int __init cap_bsm_init(void) ++{ ++ const struct of_device_id *match; ++ struct device_node *np; ++ struct resource res; ++ ++ np = of_find_matching_node_and_match(NULL, bsm_of_match, &match); ++ if (!np) { ++ /* Not found in the device-tree. Quietly resign */ ++ return 0; ++ } ++ if (of_address_to_resource(np, 0, &res) < 0) { ++ pr_err("failed to get BSM registers\n"); ++ of_node_put(np); ++ return -ENXIO; ++ } ++ of_node_put(np); ++ ++ bsm.base = ioremap(res.start, resource_size(&res)); ++ if (!bsm.base) { ++ pr_err("failed to map BSM register\n"); ++ return -ENXIO; ++ } ++ ++ bsm.val = readl(bsm.base); ++#ifdef CONFIG_PENSANDO_SOC_BSM_ENABLE ++ if (bsm.val & (1 << BSM_AUTOBOOT_LSB)) { ++ bsm.val |= 1 << BSM_RUNNING_LSB; ++ writel(bsm.val, bsm.base); ++ } ++#endif ++ return 0; ++} ++early_initcall(cap_bsm_init); +diff --git a/drivers/soc/pensando/cap_soc.c b/drivers/soc/pensando/cap_soc.c +new file mode 100644 +index 0000000..9942c20 +--- /dev/null ++++ b/drivers/soc/pensando/cap_soc.c +@@ -0,0 +1,25 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++ ++static struct kobject *pensando_fw_kobj; ++ ++/* ++ * Creates a new pensando sysfs node if it does not exists. The kobj is ++ * returned after incrementing the refcnt, so a module should use kobject_put() ++ * when it is done using this koject. ++ */ ++struct kobject *pensando_fw_kobj_get(void) ++{ ++ if (!pensando_fw_kobj) { ++ pensando_fw_kobj = kobject_create_and_add("pensando", firmware_kobj); ++ if (!pensando_fw_kobj) ++ return NULL; ++ } ++ return kobject_get(pensando_fw_kobj); ++} ++EXPORT_SYMBOL_GPL(pensando_fw_kobj_get); +-- +1.8.3.1 + diff --git a/patches-sonic/0013-i2c-xiic-cosmetic-cleanup.patch b/patches-sonic/0013-i2c-xiic-cosmetic-cleanup.patch new file mode 100644 index 000000000..36225def4 --- /dev/null +++ b/patches-sonic/0013-i2c-xiic-cosmetic-cleanup.patch @@ -0,0 +1,92 @@ +From 20c1b78562c141aa7723cbf3b733ea23ae48b4f0 Mon Sep 17 00:00:00 2001 +From: Abdurrahman Hussain +Date: Mon, 23 Feb 2026 15:59:19 +0000 +Subject: [PATCH 13/22] i2c: xiic: cosmetic cleanup + +Re-use dev pointer instead of referencing &pdev->dev everywhere. + +Signed-off-by: Abdurrahman Hussain +Reviewed-by: Andy Shevchenko +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260223-i2c-xiic-v12-4-b6c9ce4e4f3c@nexthop.ai +--- + drivers/i2c/busses/i2c-xiic.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index 16fd85373e46..6de239d2cfc0 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -1431,7 +1431,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + u8 i; + u32 sr; + +- i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); ++ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + +@@ -1447,7 +1447,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + if (irq < 0) + return irq; + +- pdata = dev_get_platdata(&pdev->dev); ++ pdata = dev_get_platdata(dev); + + /* hook up driver to tree */ + platform_set_drvdata(pdev, i2c); +@@ -1469,9 +1469,10 @@ static int xiic_i2c_probe(struct platform_device *pdev) + return dev_err_probe(&pdev->dev, PTR_ERR(i2c->clk), + "failed to enable input clock.\n"); + +- i2c->dev = &pdev->dev; +- pm_runtime_set_autosuspend_delay(i2c->dev, XIIC_PM_TIMEOUT); +- pm_runtime_use_autosuspend(i2c->dev); ++ i2c->dev = dev; ++ ++ pm_runtime_set_autosuspend_delay(dev, XIIC_PM_TIMEOUT); ++ pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; +@@ -1483,9 +1484,8 @@ static int xiic_i2c_probe(struct platform_device *pdev) + if (ret || i2c->i2c_clk > I2C_MAX_FAST_MODE_PLUS_FREQ) + i2c->i2c_clk = 0; + +- ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, +- xiic_process, IRQF_ONESHOT, +- pdev->name, i2c); ++ ret = devm_request_threaded_irq(dev, irq, NULL, xiic_process, ++ IRQF_ONESHOT, pdev->name, i2c); + if (ret) + return ret; + +@@ -1528,21 +1528,21 @@ static int xiic_i2c_probe(struct platform_device *pdev) + + static void xiic_i2c_remove(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; + struct xiic_i2c *i2c = platform_get_drvdata(pdev); + int ret; + + /* remove adapter & data */ + i2c_del_adapter(&i2c->adap); + +- ret = pm_runtime_get_sync(i2c->dev); +- ++ ret = pm_runtime_get_sync(dev); + if (ret < 0) +- dev_warn(&pdev->dev, "Failed to activate device for removal (%pe)\n", ++ dev_warn(dev, "Failed to activate device for removal (%pe)\n", + ERR_PTR(ret)); + else + xiic_deinit(i2c); + +- pm_runtime_put_sync(i2c->dev); ++ pm_runtime_put_sync(dev); + } + + static const struct dev_pm_ops xiic_dev_pm_ops = { +-- +2.53.0 + diff --git a/patches-sonic/0014-drivers-soc-pensando-crash-dump-driver.patch b/patches-sonic/0014-drivers-soc-pensando-crash-dump-driver.patch new file mode 100644 index 000000000..60a8c3170 --- /dev/null +++ b/patches-sonic/0014-drivers-soc-pensando-crash-dump-driver.patch @@ -0,0 +1,322 @@ +From aca3b7015ba5fb2f3995253409d9d0ace1d5474e Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:10:44 +0000 +Subject: [PATCH 14/27] drivers/soc/pensando: crash dump driver + +The crash dump driver writes the kernel message log to +memory-mapped qspi flash. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi | 8 + + drivers/soc/pensando/Kconfig | 7 + + drivers/soc/pensando/Makefile | 1 + + drivers/soc/pensando/cap_crash.c | 251 +++++++++++++++++++++ + 4 files changed, 267 insertions(+) + create mode 100644 drivers/soc/pensando/cap_crash.c + +diff --git a/arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi b/arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi +index a9c5d25..1a25318 100644 +--- a/arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba-flash-parts.dtsi +@@ -106,3 +106,11 @@ + }; + }; + }; ++ ++&soc { ++ panicdump@740b0000 { ++ compatible = "pensando,capri-crash"; ++ pensando,crash-ctrl = <&qspi>; ++ reg = <0x0 0x740b0000 0x0 0x20000>; ++ }; ++}; +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +index eb1494e..6f0b1a8 100644 +--- a/drivers/soc/pensando/Kconfig ++++ b/drivers/soc/pensando/Kconfig +@@ -15,6 +15,13 @@ config PENSANDO_SOC_CAPMEM + help + Support for the Pensando SoC memory driver + ++config PENSANDO_SOC_CRASH ++ tristate "Pensando crash driver" ++ depends on OF ++ default y ++ help ++ Support for the Pensando SoC crash driver ++ + config PENSANDO_SOC_BSM + bool "Pensando SoC Boot State Machine" + depends on OF +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +index bf4f46f..815ae45 100644 +--- a/drivers/soc/pensando/Makefile ++++ b/drivers/soc/pensando/Makefile +@@ -6,4 +6,5 @@ obj-y += cap_soc.o + CFLAGS_cap_mem.o := -I$(src) + + obj-$(CONFIG_PENSANDO_SOC_CAPMEM) += cap_mem.o ++obj-$(CONFIG_PENSANDO_SOC_CRASH) += cap_crash.o + obj-$(CONFIG_PENSANDO_SOC_BSM) += cap_bsm.o +diff --git a/drivers/soc/pensando/cap_crash.c b/drivers/soc/pensando/cap_crash.c +new file mode 100644 +index 0000000..385b02d +--- /dev/null ++++ b/drivers/soc/pensando/cap_crash.c +@@ -0,0 +1,251 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2019-2022, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define PCRASH_NAME "pensando-crash" ++ ++struct pcrash_st { ++ struct platform_device *pdev; ++ struct kmsg_dumper dump; ++ void __iomem *ctrlbase; ++ void __iomem *flashbase; ++ resource_size_t size; ++ void *panic_buf; ++}; ++ ++struct panicbuf_header { ++ u32 magic; ++ u32 len; ++}; ++ ++static struct pcrash_st *pcrash; ++static u32 PANIC_SIGNATURE = 0x9d7a7318; ++ ++/* ++ * Prepare the Cadence Quad SPI Controller for ++ * memory mapped crash dump writes. ++ */ ++ ++#define CQSPI_REG_CONFIG 0x00 ++#define CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL BIT(7) ++ ++#define CQSPI_REG_WR_COMPLETION_CTRL 0x38 ++#define CQSPI_REG_WR_DISABLE_AUTO_POLL BIT(14) ++ ++static void pcrash_prepare_controller(void) ++{ ++ void __iomem *ctrl = pcrash->ctrlbase; ++ u32 val; ++ ++ /* ++ * Re-enable the Direct Access Controller (memory-mapped access). ++ */ ++ val = readl(ctrl + CQSPI_REG_CONFIG); ++ if (!(val & CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL)) { ++ val |= CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL; ++ writel(val, ctrl + CQSPI_REG_CONFIG); ++ } ++ ++ /* ++ * Re-enable auto-polling, if it was disabled. ++ * This is required for memory-mapped writes. ++ */ ++ val = readl(ctrl + CQSPI_REG_WR_COMPLETION_CTRL); ++ if (val & CQSPI_REG_WR_DISABLE_AUTO_POLL) { ++ val &= ~CQSPI_REG_WR_DISABLE_AUTO_POLL; ++ writel(val, ctrl + CQSPI_REG_WR_COMPLETION_CTRL); ++ } ++ ++ /* readback + barrier */ ++ (void)readl(ctrl + CQSPI_REG_CONFIG); ++ __iowmb(); ++} ++ ++static void pcrash_do_dump(struct kmsg_dumper *dumper, ++ enum kmsg_dump_reason reason) ++{ ++ int idx; ++ u32 *src; ++ size_t kmsg_dump_len; ++ struct kmsg_dump_iter iter; ++ u32 __iomem *dst = (u32 *)pcrash->flashbase; ++ struct panicbuf_header *hdr = pcrash->flashbase; ++ ++ /* ++ * Prepare the flash controller for memory-mapped writes. ++ */ ++ pcrash_prepare_controller(); ++ ++ /* ++ * read first 32bits, if all ff then the new panic data ++ * can be written to the panic buf. ++ */ ++ if (hdr->magic == 0xffffffff) { ++ kmsg_dump_rewind(&iter); ++ kmsg_dump_get_buffer(&iter, false, pcrash->panic_buf, ++ pcrash->size - sizeof(struct panicbuf_header), &kmsg_dump_len); ++ ++ /* write the signature to panic buf log */ ++ hdr->magic = PANIC_SIGNATURE; ++ hdr->len = kmsg_dump_len; ++ src = (u32 *)pcrash->panic_buf; ++ dst = (u32 *)(hdr + 1); ++ for (idx = 0; idx < roundup(kmsg_dump_len, 4) / 4; idx++) ++ *dst++ = *src++; ++ } ++} ++ ++static int cap_panic_callback(struct notifier_block *nb, ++ unsigned long reason, void *arg) ++{ ++ struct timespec64 ts; ++ struct tm tm; ++ ++ ktime_get_real_ts64(&ts); ++ time64_to_tm(ts.tv_sec, 0, &tm); ++ pr_info("Panic on %04ld-%02d-%02d %02d:%02d:%02d.%06ld\n", ++ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, ++ tm.tm_hour, tm.tm_min, tm.tm_sec, ++ ts.tv_nsec / 1000); ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block cap_panic_notifier = { ++ .notifier_call = cap_panic_callback, ++}; ++ ++static int pcrash_get_flash_controller(struct platform_device *pdev, ++ struct resource *res) ++{ ++ const struct device_node *np; ++ struct device_node *dn; ++ int err = -ENODEV; ++ ++ np = pdev->dev.of_node; ++ if (np) { ++ /* ++ * The pensando,crash-ctrl property should be a phandle ++ * to a pensando,elba-qspi controller. ++ * Extract its register space, if found. ++ */ ++ dn = of_parse_phandle(np, "pensando,crash-ctrl", 0); ++ if (of_device_is_compatible(dn, "pensando,elba-qspi")) ++ if (!of_address_to_resource(dn, 0, res)) ++ err = 0; ++ of_node_put(dn); ++ } ++ return err; ++} ++ ++static int pcrash_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct resource ctrlres; ++ struct resource *res; ++ int err = -ENODEV; ++ ++ pcrash = devm_kzalloc(dev, sizeof(*pcrash), GFP_KERNEL); ++ if (!pcrash) ++ return -ENOMEM; ++ ++ pcrash->pdev = pdev; ++ platform_set_drvdata(pdev, pcrash); ++ ++ /* get and map the flash controller */ ++ if (pcrash_get_flash_controller(pdev, &ctrlres)) { ++ dev_err(dev, "%s: Cannot find flash controller.\n", pdev->name); ++ return -ENODEV; ++ } ++ pcrash->ctrlbase = ioremap(ctrlres.start, resource_size(&ctrlres)); ++ if (!pcrash->ctrlbase) { ++ dev_err(dev, "%s: Cannot map flash controller.\n", pdev->name); ++ return -ENODEV; ++ } ++ ++ /* get and map the memory-mapped flash */ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(dev, "%s: Memory resource not found\n", pdev->name); ++ goto bail1; ++ } ++ pcrash->flashbase = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcrash->flashbase)) { ++ dev_err(dev, "%s: Cannot remap flash address.\n", pdev->name); ++ err = PTR_ERR(pcrash->flashbase); ++ goto bail1; ++ } ++ pcrash->size = resource_size(res); ++ pcrash->panic_buf = vmalloc(pcrash->size); ++ if (!pcrash->panic_buf) { ++ dev_err(dev, "%s: Failed to allocate buffer workspace\n", ++ pdev->name); ++ err = -ENOMEM; ++ goto bail1; ++ } ++ memset(pcrash->panic_buf, 0xff, pcrash->size); ++ atomic_notifier_chain_register(&panic_notifier_list, ++ &cap_panic_notifier); ++ pcrash->dump.max_reason = KMSG_DUMP_PANIC; ++ pcrash->dump.dump = pcrash_do_dump; ++ err = kmsg_dump_register(&pcrash->dump); ++ if (err) { ++ dev_err(dev, "%s: registering kmsg dumper failed, error %d\n", ++ pdev->name, err); ++ goto bail2; ++ } ++ return 0; ++bail2: ++ atomic_notifier_chain_unregister(&panic_notifier_list, ++ &cap_panic_notifier); ++ vfree(pcrash->panic_buf); ++bail1: ++ iounmap(pcrash->ctrlbase); ++ return err; ++} ++ ++static void pcrash_remove(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ ++ if (kmsg_dump_unregister(&pcrash->dump) < 0) ++ dev_err(dev, "could not unregister kmsg_dumper\n"); ++ atomic_notifier_chain_unregister(&panic_notifier_list, ++ &cap_panic_notifier); ++ vfree(pcrash->panic_buf); ++ iounmap(pcrash->ctrlbase); ++ return; ++} ++ ++static const struct of_device_id pcrash_dt_ids[] = { ++ {.compatible = "pensando,capri-crash",}, ++ { /* end of table */ } ++}; ++ ++MODULE_DEVICE_TABLE(of, pcrash_dt_ids); ++ ++static struct platform_driver pcrash_platform_driver = { ++ .probe = pcrash_probe, ++ .remove = pcrash_remove, ++ .driver = { ++ .name = PCRASH_NAME, ++ .of_match_table = pcrash_dt_ids, ++ }, ++}; ++ ++module_platform_driver(pcrash_platform_driver); ++ ++MODULE_DESCRIPTION("Pensando Panic Crash Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:" PCRASH_NAME); ++MODULE_AUTHOR("Rahul Shekhar "); +-- +1.8.3.1 + diff --git a/patches-sonic/0014-i2c-xiic-cosmetic-use-resource-format-specifier-in-d.patch b/patches-sonic/0014-i2c-xiic-cosmetic-use-resource-format-specifier-in-d.patch new file mode 100644 index 000000000..bb1e4160c --- /dev/null +++ b/patches-sonic/0014-i2c-xiic-cosmetic-use-resource-format-specifier-in-d.patch @@ -0,0 +1,34 @@ +From d902afc2c1847903bafa2d9e5a8f311c8d123fc4 Mon Sep 17 00:00:00 2001 +From: Abdurrahman Hussain +Date: Mon, 23 Feb 2026 15:59:20 +0000 +Subject: [PATCH 14/22] i2c: xiic: cosmetic: use resource format specifier in + debug log + +Use standard resource format specifier %pR in debug log. + +Signed-off-by: Abdurrahman Hussain +Reviewed-by: Andy Shevchenko +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260223-i2c-xiic-v12-5-b6c9ce4e4f3c@nexthop.ai +--- + drivers/i2c/busses/i2c-xiic.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index 6de239d2cfc0..b9880609a92d 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -1520,8 +1520,8 @@ static int xiic_i2c_probe(struct platform_device *pdev) + i2c_new_client_device(&i2c->adap, pdata->devices + i); + } + +- dev_dbg(&pdev->dev, "mmio %08lx irq %d scl clock frequency %d\n", +- (unsigned long)res->start, irq, i2c->i2c_clk); ++ dev_dbg(dev, "mmio %pR irq %d scl clock frequency %d\n", ++ res, irq, i2c->i2c_clk); + + return 0; + } +-- +2.53.0 + diff --git a/patches-sonic/0015-arm64-dts-marvell-Add-Supermicro-SSE-G3748-board.patch b/patches-sonic/0015-arm64-dts-marvell-Add-Supermicro-SSE-G3748-board.patch new file mode 100644 index 000000000..19ee8dabc --- /dev/null +++ b/patches-sonic/0015-arm64-dts-marvell-Add-Supermicro-SSE-G3748-board.patch @@ -0,0 +1,150 @@ +From fc880f237cbfba658a147f797b363ab61f3577d0 Mon Sep 17 00:00:00 2001 +From: Erich Yen +Date: Tue, 21 May 2024 20:41:37 +0000 +Subject: [PATCH] arm64: dts: marvell: Add Supermicro SSE-G3748 board + +This change adds device tree files for Supermicro SSE-G3748 board. +This dts file is derived from Marvell RD-AC5X board. +SPI flash partitions and i2c1 configurations are modified to match with SSE-G3748 board. +--- + arch/arm64/boot/dts/marvell/Makefile | 1 + + arch/arm64/boot/dts/marvell/smc_sse-g3748.dts | 118 ++++++++++++++++++ + 2 files changed, 119 insertions(+) + create mode 100644 arch/arm64/boot/dts/marvell/smc_sse-g3748.dts + +diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile +index c9d4e7836..10adf28c9 100644 +--- a/arch/arm64/boot/dts/marvell/Makefile ++++ b/arch/arm64/boot/dts/marvell/Makefile +@@ -34,3 +34,4 @@ dtb-$(CONFIG_ARCH_MVEBU) += cn9131-cf-solidwan.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9132-clearfog.dtb + dtb-$(CONFIG_ARCH_MVEBU) += armada-7020-comexpress.dtb + dtb-$(CONFIG_ARCH_MVEBU) += 7215-ixs-a1.dtb ++dtb-$(CONFIG_ARCH_MVEBU) += smc_sse-g3748.dtb +diff --git a/arch/arm64/boot/dts/marvell/smc_sse-g3748.dts b/arch/arm64/boot/dts/marvell/smc_sse-g3748.dts +new file mode 100644 +index 000000000..00fc581d7 +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/smc_sse-g3748.dts +@@ -0,0 +1,118 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Device Tree For Supermicro SSE-G3748. ++ * ++ * Copyright (C) 2021 Marvell ++ * Copyright (C) 2022 Allied Telesis Labs ++ * Copyright (C) 2023 Supermicro ++ */ ++ ++/dts-v1/; ++ ++#include "ac5-98dx35xx.dtsi" ++ ++/ { ++ model = "Supermicro SSE-G3748 Board"; ++ compatible = "marvell,rd-ac5x", "marvell,ac5x", "marvell,ac5"; ++ ++ aliases { ++ serial0 = &uart0; ++ spiflash0 = &spiflash0; ++ gpio0 = &gpio0; ++ ethernet0 = ð0; ++ ethernet1 = ð1; ++ }; ++ ++ memory@0 { ++ device_type = "memory"; ++ reg = <0x2 0x00000000 0x0 0x40000000>; ++ }; ++ ++ usb1phy: usb-phy { ++ compatible = "usb-nop-xceiv"; ++ #phy-cells = <0>; ++ }; ++}; ++ ++&mdio { ++ status = "okay"; ++ pinctrl-names = "default"; ++ phy0: ethernet-phy@0 { ++ reg = <0>; ++ }; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ /delete-property/ pinctrl-names; ++ /delete-property/ pinctrl-0; ++ /delete-property/ pinctrl-1; ++ /delete-property/ scl-gpios; ++ /delete-property/ sda-gpios; ++ status = "okay"; ++}; ++ ++ð0 { ++ status = "okay"; ++ phy-mode = "sgmii"; ++ phy-handle = <&phy0>; ++}; ++ ++/* USB0 is a host USB */ ++&usb0 { ++ status = "okay"; ++}; ++ ++/* USB1 is a peripheral USB */ ++&usb1 { ++ status = "okay"; ++ phys = <&usb1phy>; ++ phy-names = "usb-phy"; ++ dr_mode = "peripheral"; ++}; ++ ++&spi0 { ++ status = "okay"; ++ ++ spiflash0: flash@0 { ++ compatible = "jedec,spi-nor"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <1>; /* 1-single, 2-dual, 4-quad */ ++ spi-rx-bus-width = <1>; /* 1-single, 2-dual, 4-quad */ ++ reg = <0>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "spi_flash_part0"; ++ reg = <0x0 0x200000>; ++ }; ++ ++ parition@1 { ++ label = "spi_flash_part1"; ++ reg = <0x200000 0x10000>; ++ }; ++ ++ parition@2 { ++ label = "spi_flash_part2"; ++ reg = <0x210000 0xC00000>; ++ }; ++ ++ parition@3 { ++ label = "spi_flash_part3"; ++ reg = <0xE10000 0x1F0000>; ++ }; ++ }; ++}; ++ ++/{ ++ sdma_drv { ++ compatible = "marvell,mvppnd"; ++ interrupts = ; ++ status = "okay"; ++ }; ++}; +-- +2.34.1 + diff --git a/patches-sonic/0015-drivers-soc-pensando-Add-the-Reset-Cause-driver.patch b/patches-sonic/0015-drivers-soc-pensando-Add-the-Reset-Cause-driver.patch new file mode 100644 index 000000000..ddc842900 --- /dev/null +++ b/patches-sonic/0015-drivers-soc-pensando-Add-the-Reset-Cause-driver.patch @@ -0,0 +1,316 @@ +From 2e997ca00391a982934d65ee275a5bb8d69434ae Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:12:50 +0000 +Subject: [PATCH 15/27] drivers/soc/pensando: Add the Reset Cause driver + +The reset cause driver exposes sysfs nodes under +/sys/pensando/firmware/rstcause that report the reason for the last +reboot and, for software affecting a new reboot, a file in which to +write why the next reboot is occurring. This is used by the Pensando +platform userspace software. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 5 + + drivers/soc/pensando/Kconfig | 7 ++ + drivers/soc/pensando/Makefile | 1 + + drivers/soc/pensando/cap_rstcause.c | 218 +++++++++++++++++++++++++++++++++ + drivers/soc/pensando/cap_rstcause.h | 15 +++ + 5 files changed, 246 insertions(+) + create mode 100644 drivers/soc/pensando/cap_rstcause.c + create mode 100644 drivers/soc/pensando/cap_rstcause.h + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index 3060a7f..3348473 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -55,6 +55,11 @@ + name = "uio_penmsi"; + }; + ++ rstcause { ++ compatible = "pensando,rstcause"; ++ pensando,causeregs = <&mssoc 0x2084>; ++ }; ++ + capmem { + compatible = "pensando,capmem"; + }; +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +index 6f0b1a8..59c1fa4 100644 +--- a/drivers/soc/pensando/Kconfig ++++ b/drivers/soc/pensando/Kconfig +@@ -15,6 +15,13 @@ config PENSANDO_SOC_CAPMEM + help + Support for the Pensando SoC memory driver + ++config PENSANDO_SOC_RSTCAUSE ++ tristate "Pensando reset cause driver" ++ depends on OF ++ default y ++ help ++ Support for the Pensando SoC reset cause driver ++ + config PENSANDO_SOC_CRASH + tristate "Pensando crash driver" + depends on OF +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +index 815ae45..ea5062f 100644 +--- a/drivers/soc/pensando/Makefile ++++ b/drivers/soc/pensando/Makefile +@@ -6,5 +6,6 @@ obj-y += cap_soc.o + CFLAGS_cap_mem.o := -I$(src) + + obj-$(CONFIG_PENSANDO_SOC_CAPMEM) += cap_mem.o ++obj-$(CONFIG_PENSANDO_SOC_RSTCAUSE) += cap_rstcause.o + obj-$(CONFIG_PENSANDO_SOC_CRASH) += cap_crash.o + obj-$(CONFIG_PENSANDO_SOC_BSM) += cap_bsm.o +diff --git a/drivers/soc/pensando/cap_rstcause.c b/drivers/soc/pensando/cap_rstcause.c +new file mode 100644 +index 0000000..4144dc5 +--- /dev/null ++++ b/drivers/soc/pensando/cap_rstcause.c +@@ -0,0 +1,218 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Pensando restart cause driver ++ * ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "cap_rstcause.h" ++ ++struct kobject *pensando_fw_kobj_get(void); ++ ++struct cap_rstdev { ++ struct platform_device *pdev; ++ struct regmap *regs; ++ unsigned int regs_offset; ++ u32 this_cause; ++ struct notifier_block panic_nb; ++ struct notifier_block reboot_nb; ++ struct kobject *pensando_kobj; ++}; ++ ++static struct cap_rstdev *g_rdev; ++ ++static inline u32 read_cause_reg(struct cap_rstdev *rdev) ++{ ++ u32 val; ++ ++ regmap_read(rdev->regs, rdev->regs_offset, &val); ++ return val; ++} ++ ++static inline u32 read_next_cause_reg(struct cap_rstdev *rdev) ++{ ++ u32 val; ++ ++ regmap_read(rdev->regs, rdev->regs_offset + 4, &val); ++ return val; ++} ++ ++static inline void set_next_cause_reg(struct cap_rstdev *rdev, u32 mask) ++{ ++ regmap_update_bits(rdev->regs, rdev->regs_offset + 4, mask, ~0U); ++} ++ ++void cap_rstcause_set(u32 mask) ++{ ++ if (g_rdev) ++ set_next_cause_reg(g_rdev, mask); ++} ++ ++EXPORT_SYMBOL_GPL(cap_rstcause_set); ++ ++static int rstcause_reboot_handler(struct notifier_block *this, ++ unsigned long code, void *unused) ++{ ++ cap_rstcause_set(CAP_RSTCAUSE_EV_REBOOT); ++ return NOTIFY_OK; ++} ++ ++static int rstcause_panic_handler(struct notifier_block *this, ++ unsigned long code, void *unused) ++{ ++ cap_rstcause_set(CAP_RSTCAUSE_EV_PANIC); ++ return NOTIFY_OK; ++} ++ ++static ssize_t this_cause_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct cap_rstdev *rdev; ++ ++ rdev = platform_get_drvdata(to_platform_device(dev)); ++ return sprintf(buf, "0x%08x\n", rdev->this_cause); ++} ++ ++static DEVICE_ATTR_RO(this_cause); ++ ++static ssize_t next_cause_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct cap_rstdev *rdev; ++ ++ rdev = platform_get_drvdata(to_platform_device(dev)); ++ return sprintf(buf, "0x%08x\n", read_next_cause_reg(rdev)); ++} ++ ++static ssize_t next_cause_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct cap_rstdev *rdev; ++ unsigned long val; ++ ++ rdev = platform_get_drvdata(to_platform_device(dev)); ++ if (kstrtoul(buf, 0, &val) < 0) ++ return -EINVAL; ++ if (val) ++ set_next_cause_reg(rdev, val); ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(next_cause); ++ ++static const struct device_attribute *rstcause_attrs[] = { ++ &dev_attr_this_cause, ++ &dev_attr_next_cause, ++}; ++ ++static int rstcause_probe(struct platform_device *pdev) ++{ ++ struct of_phandle_args args; ++ struct cap_rstdev *rdev; ++ struct regmap *regs; ++ int r, i; ++ ++ if (g_rdev) ++ return -ENODEV; ++ ++ r = of_parse_phandle_with_fixed_args(pdev->dev.of_node, ++ "pensando,causeregs", 1, 0, &args); ++ if (r) { ++ dev_err(&pdev->dev, "could not find causeregs\n"); ++ return r; ++ } ++ ++ regs = syscon_node_to_regmap(args.np); ++ if (IS_ERR(regs)) { ++ dev_err(&pdev->dev, "could not map causeregs\n"); ++ return PTR_ERR(regs); ++ } ++ ++ rdev = devm_kzalloc(&pdev->dev, sizeof(*rdev), GFP_KERNEL); ++ if (!rdev) ++ return -ENOMEM; ++ rdev->pdev = pdev; ++ platform_set_drvdata(pdev, rdev); ++ ++ rdev->regs = regs; ++ rdev->regs_offset = args.args[0]; ++ rdev->reboot_nb.notifier_call = rstcause_reboot_handler; ++ register_reboot_notifier(&rdev->reboot_nb); ++ ++ rdev->panic_nb.notifier_call = rstcause_panic_handler; ++ atomic_notifier_chain_register(&panic_notifier_list, &rdev->panic_nb); ++ ++ rdev->this_cause = read_cause_reg(rdev); ++ ++ g_rdev = rdev; ++ ++ rdev->pensando_kobj = pensando_fw_kobj_get(); ++ if (rdev->pensando_kobj) { ++ for (i = 0; i < ARRAY_SIZE(rstcause_attrs); i++) { ++ r = device_create_file(&pdev->dev, rstcause_attrs[i]); ++ if (r) { ++ dev_err(&pdev->dev, ++ "failed to create sysfs file\n"); ++ return r; ++ } ++ } ++ r = sysfs_create_link(rdev->pensando_kobj, ++ &pdev->dev.kobj, "rstcause"); ++ if (r) { ++ dev_err(&pdev->dev, "failed to create sysfs symlink\n"); ++ kobject_put(rdev->pensando_kobj); ++ rdev->pensando_kobj = NULL; ++ } ++ } ++ return 0; ++} ++ ++static void rstcause_remove(struct platform_device *pdev) ++{ ++ struct cap_rstdev *rdev = platform_get_drvdata(pdev); ++ ++ unregister_reboot_notifier(&rdev->reboot_nb); ++ atomic_notifier_chain_unregister(&panic_notifier_list, ++ &rdev->panic_nb); ++ if (g_rdev == rdev) { ++ g_rdev = NULL; ++ if (rdev->pensando_kobj) ++ kobject_put(rdev->pensando_kobj); ++ } ++ ++ return; ++} ++ ++static const struct of_device_id rstcause_of_match[] = { ++ {.compatible = "pensando,rstcause"}, ++ { /* end of table */ } ++}; ++ ++static struct platform_driver rstcause_driver = { ++ .probe = rstcause_probe, ++ .remove = rstcause_remove, ++ .driver = { ++ .name = "pensando-rstcause", ++ .owner = THIS_MODULE, ++ .of_match_table = rstcause_of_match, ++ }, ++}; ++ ++module_platform_driver(rstcause_driver); ++MODULE_DESCRIPTION("Pensando SoC Reset Cause Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/soc/pensando/cap_rstcause.h b/drivers/soc/pensando/cap_rstcause.h +new file mode 100644 +index 0000000..12fe871 +--- /dev/null ++++ b/drivers/soc/pensando/cap_rstcause.h +@@ -0,0 +1,15 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#ifndef __CAP_RSTCAUSE_H__ ++#define __CAP_RSTCAUSE_H__ ++ ++#define CAP_RSTCAUSE_EV_REBOOT BIT(0) ++#define CAP_RSTCAUSE_EV_PANIC BIT(1) ++#define CAP_RSTCAUSE_EV_PCIE_RESET BIT(2) ++ ++void cap_rstcause_set(u32 mask); ++ ++#endif +-- +1.8.3.1 + diff --git a/patches-sonic/0015-i2c-mlxcpld-Support-PCIe-mapped-register-space.patch b/patches-sonic/0015-i2c-mlxcpld-Support-PCIe-mapped-register-space.patch new file mode 100644 index 000000000..c680fb78e --- /dev/null +++ b/patches-sonic/0015-i2c-mlxcpld-Support-PCIe-mapped-register-space.patch @@ -0,0 +1,183 @@ +From 53d028fcb527a4b8cf4c9d99b55799da872390fe Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 24 Jul 2023 12:39:33 +0000 +Subject: [PATCH 15/78] i2c: mlxcpld: Support PCIe mapped register space + +Currently driver uses constant base address of register space. +On new systems this base address and access to it are different. + +On old it is space mapped to Low Pin Count bus and accessed through +port-mapped I/O. + +On new systems base address of the register space is mapped to PCIe +space and accessed through memory-mapped I/O. + +Use base address passed to the driver through platform data. + +Implement new routines for register space access, which is limited by +two bytes of transaction size. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Michael Shych +--- + drivers/i2c/busses/i2c-mlxcpld.c | 94 ++++++++++++++++++++++++++++++++++++---- + 1 file changed, 86 insertions(+), 8 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c +index 8223f6d..ecae755 100644 +--- a/drivers/i2c/busses/i2c-mlxcpld.c ++++ b/drivers/i2c/busses/i2c-mlxcpld.c +@@ -70,6 +70,7 @@ struct mlxcpld_i2c_curr_xfer { + struct mlxcpld_i2c_priv { + struct i2c_adapter adap; + u32 base_addr; ++ void __iomem *addr; + struct mutex lock; + struct mlxcpld_i2c_curr_xfer xfer; + struct device *dev; +@@ -77,7 +78,7 @@ struct mlxcpld_i2c_priv { + int polling_time; + }; + +-static void mlxcpld_i2c_lpc_write_buf(u8 *data, u8 len, u32 addr) ++static void mlxcpld_i2c_lpc_write_buf_ioport(u8 *data, u8 len, u32 addr) + { + int i; + +@@ -87,7 +88,7 @@ static void mlxcpld_i2c_lpc_write_buf(u8 *data, u8 len, u32 addr) + outb(*(data + i), addr + i); + } + +-static void mlxcpld_i2c_lpc_read_buf(u8 *data, u8 len, u32 addr) ++static void mlxcpld_i2c_lpc_read_buf_ioport(u8 *data, u8 len, u32 addr) + { + int i; + +@@ -97,8 +98,8 @@ static void mlxcpld_i2c_lpc_read_buf(u8 *data, u8 len, u32 addr) + *(data + i) = inb(addr + i); + } + +-static void mlxcpld_i2c_read_comm(struct mlxcpld_i2c_priv *priv, u8 offs, +- u8 *data, u8 datalen) ++static void mlxcpld_i2c_read_comm_ioport(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) + { + u32 addr = priv->base_addr + offs; + +@@ -117,13 +118,13 @@ static void mlxcpld_i2c_read_comm(struct mlxcpld_i2c_priv *priv, u8 offs, + *((u32 *)data) = inl(addr); + break; + default: +- mlxcpld_i2c_lpc_read_buf(data, datalen, addr); ++ mlxcpld_i2c_lpc_read_buf_ioport(data, datalen, addr); + break; + } + } + +-static void mlxcpld_i2c_write_comm(struct mlxcpld_i2c_priv *priv, u8 offs, +- u8 *data, u8 datalen) ++static void mlxcpld_i2c_write_comm_ioport(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) + { + u32 addr = priv->base_addr + offs; + +@@ -142,11 +143,85 @@ static void mlxcpld_i2c_write_comm(struct mlxcpld_i2c_priv *priv, u8 offs, + outl(*((u32 *)data), addr); + break; + default: +- mlxcpld_i2c_lpc_write_buf(data, datalen, addr); ++ mlxcpld_i2c_lpc_write_buf_ioport(data, datalen, addr); + break; + } + } + ++static void mlxcpld_i2c_lpc_write_buf_io(u8 *data, u8 len, void __iomem *addr) ++{ ++ int i; ++ ++ for (i = 0; i < len - len % 2; i += 2) ++ iowrite16(*(u16 *)(data + i), addr + i); ++ for (; i < len; ++i) ++ iowrite8(*(data + i), addr + i); ++} ++ ++static void mlxcpld_i2c_lpc_read_buf_io(u8 *data, u8 len, void __iomem *addr) ++{ ++ int i; ++ ++ for (i = 0; i < len - len % 2; i += 2) ++ *(u16 *)(data + i) = ioread16(addr + i); ++ for (; i < len; ++i) ++ *(data + i) = ioread8(addr + i); ++} ++ ++static void mlxcpld_i2c_read_comm_io(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) ++{ ++ void __iomem *addr = priv->addr + offs; ++ ++ switch (datalen) { ++ case 1: ++ *(data) = ioread8(addr); ++ break; ++ case 2: ++ *((u16 *)data) = ioread16(addr); ++ break; ++ default: ++ mlxcpld_i2c_lpc_read_buf_io(data, datalen, addr); ++ break; ++ } ++} ++ ++static void mlxcpld_i2c_write_comm_io(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) ++{ ++ void __iomem *addr = priv->addr + offs; ++ ++ switch (datalen) { ++ case 1: ++ iowrite8(*(data), addr); ++ break; ++ case 2: ++ iowrite16(*((u16 *)data), addr); ++ break; ++ default: ++ mlxcpld_i2c_lpc_write_buf_io(data, datalen, addr); ++ break; ++ } ++} ++ ++static void mlxcpld_i2c_write_comm(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) ++{ ++ if (priv->addr) ++ mlxcpld_i2c_write_comm_io(priv, offs, data, datalen); ++ else ++ mlxcpld_i2c_write_comm_ioport(priv, offs, data, datalen); ++} ++ ++static void mlxcpld_i2c_read_comm(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) ++{ ++ if (priv->addr) ++ mlxcpld_i2c_read_comm_io(priv, offs, data, datalen); ++ else ++ mlxcpld_i2c_read_comm_ioport(priv, offs, data, datalen); ++} ++ + /* + * Check validity of received i2c messages parameters. + * Returns 0 if OK, other - in case of invalid parameters. +@@ -543,6 +618,9 @@ static int mlxcpld_i2c_probe(struct platform_device *pdev) + /* Set I2C bus frequency if platform data provides this info. */ + pdata = dev_get_platdata(&pdev->dev); + if (pdata) { ++ if (pdata->addr) ++ priv->addr = pdata->addr; ++ + err = mlxcpld_i2c_set_frequency(priv, pdata); + if (err) + goto mlxcpld_i2_probe_failed; +-- +2.8.4 + diff --git a/patches-sonic/0015-i2c-xiic-use-numbered-adapter-registration.patch b/patches-sonic/0015-i2c-xiic-use-numbered-adapter-registration.patch new file mode 100644 index 000000000..f2e6819ff --- /dev/null +++ b/patches-sonic/0015-i2c-xiic-use-numbered-adapter-registration.patch @@ -0,0 +1,45 @@ +From f7b0e31e45365fd8b5e7b71b67041eb7d3126c2b Mon Sep 17 00:00:00 2001 +From: Abdurrahman Hussain +Date: Mon, 23 Feb 2026 15:59:21 +0000 +Subject: [PATCH 15/22] i2c: xiic: use numbered adapter registration + +Switch from i2c_add_adapter() to i2c_add_numbered_adapter() to enable +platforms to specify fixed I2C bus numbers via the platform device ID. + +This allows systems to maintain consistent bus numbering across reboots. +On platforms where the device ID is PLATFORM_DEVID_NONE (the default), +the adapter falls back to dynamic allocation, preserving backward +compatibility. + +Signed-off-by: Abdurrahman Hussain +Reviewed-by: Andy Shevchenko +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260223-i2c-xiic-v12-6-b6c9ce4e4f3c@nexthop.ai +--- + drivers/i2c/busses/i2c-xiic.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index b9880609a92d..56109a38f57b 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -1452,6 +1452,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + /* hook up driver to tree */ + platform_set_drvdata(pdev, i2c); + i2c->adap = xiic_adapter; ++ i2c->adap.nr = pdev->id; + i2c_set_adapdata(&i2c->adap, i2c); + i2c->adap.dev.parent = &pdev->dev; + device_set_node(&i2c->adap.dev, fwnode); +@@ -1508,7 +1509,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) + return dev_err_probe(dev, ret, "Cannot xiic_reinit\n"); + + /* add i2c adapter to i2c tree */ +- ret = i2c_add_adapter(&i2c->adap); ++ ret = i2c_add_numbered_adapter(&i2c->adap); + if (ret) { + xiic_deinit(i2c); + return ret; +-- +2.53.0 + diff --git a/patches-sonic/0016-arm64-dts-marvell-Add-DTS-for-Wistron-ES-1227-54TS-b.patch b/patches-sonic/0016-arm64-dts-marvell-Add-DTS-for-Wistron-ES-1227-54TS-b.patch new file mode 100644 index 000000000..b9621a2ad --- /dev/null +++ b/patches-sonic/0016-arm64-dts-marvell-Add-DTS-for-Wistron-ES-1227-54TS-b.patch @@ -0,0 +1,438 @@ +From 6ef6ef01c5ecc0da55683114b10b050ab5bced95 Mon Sep 17 00:00:00 2001 +From: Owen Wu +Date: Tue, 15 Oct 2024 15:24:11 +0800 +Subject: [PATCH] arm64: dts: marvell: Add DTS for Wistron ES-1227-54TS + board + +This change adds device tree file for Wistron ES-1227-54TS +and ES-2227-54TS boards. + +This dts is extracted from Marvell cn9130-crb and removed unused +nodes along with platform specific changes. + +Signed-off-by: Owen Wu +Signed-off-by: Yan Markman +--- + arch/arm64/boot/dts/marvell/Makefile | 1 + + arch/arm64/boot/dts/marvell/es1227-54ts.dts | 74 +++++ + arch/arm64/boot/dts/marvell/es1227-54ts.dtsi | 318 +++++++++++++++++++ + 3 files changed, 393 insertions(+) + create mode 100644 arch/arm64/boot/dts/marvell/es1227-54ts.dts + create mode 100644 arch/arm64/boot/dts/marvell/es1227-54ts.dtsi + +diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile +index 10adf28c9..02cbba99b 100644 +--- a/arch/arm64/boot/dts/marvell/Makefile ++++ b/arch/arm64/boot/dts/marvell/Makefile +@@ -35,3 +35,4 @@ dtb-$(CONFIG_ARCH_MVEBU) += cn9132-clearfog.dtb + dtb-$(CONFIG_ARCH_MVEBU) += armada-7020-comexpress.dtb + dtb-$(CONFIG_ARCH_MVEBU) += 7215-ixs-a1.dtb + dtb-$(CONFIG_ARCH_MVEBU) += smc_sse-g3748.dtb ++dtb-$(CONFIG_ARCH_MVEBU) += es1227-54ts.dtb +diff --git a/arch/arm64/boot/dts/marvell/es1227-54ts.dts b/arch/arm64/boot/dts/marvell/es1227-54ts.dts +new file mode 100644 +index 000000000..c77c7d4b6 +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/es1227-54ts.dts +@@ -0,0 +1,74 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (C) 2021 Marvell International Ltd. ++ * ++ * Device tree for the CN9130-CRB-A board. ++ */ ++ ++#include "es1227-54ts.dtsi" ++/ { ++ model = "Wistron ES1227 board"; ++}; ++ ++&cp0_pcie2 { ++ status = "okay"; ++ num-lanes = <1>; ++ num-viewport = <8>; ++ ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp0_comphy5 2>; ++ iommu-map = <0x0 &smmu 0x480 0x20>, ++ <0x100 &smmu 0x4a0 0x20>, ++ <0x200 &smmu 0x4c0 0x20>; ++ iommu-map-mask = <0x031f>; ++}; ++ ++&cp0_mdio { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_ge_mdio_pins>; ++ ++ phy0: ethernet-phy@0 { ++ reg = <0>; ++ marvell,reg-init = <0x2 0x1A 0x7FFF 0x8000>, ++ <0x2 0x0 0x7FFF 0x8000>, ++ <0x1 0x10 0xFDFF 0x200>; ++ }; ++}; ++ ++&cp0_xmdio { ++ status = "disabled"; ++}; ++ ++&cp0_uart0 { ++ pinctrl-names = "default"; ++ status = "okay"; ++ pinctrl-0 = <&cp0_uart0_pins>; ++}; ++ ++&cp0_usb3_0 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy0>; ++ phys = <&cp0_comphy1 0>; ++ phy-names = "usb3"; ++}; ++ ++&cp0_eth0 { ++ status = "disabled"; ++}; ++ ++/* CON56 */ ++&cp0_eth1 { ++ status = "okay"; ++ phys = <&cp0_comphy3 1>; ++ phy-handle = <&phy0>; ++ phy-mode = "sgmii"; ++}; ++ ++&cp0_eth2 { ++ status = "disabled"; ++}; ++ ++&cp0_rtc { ++ status = "disabled"; ++}; +diff --git a/arch/arm64/boot/dts/marvell/es1227-54ts.dtsi b/arch/arm64/boot/dts/marvell/es1227-54ts.dtsi +new file mode 100644 +index 000000000..142232bba +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/es1227-54ts.dtsi +@@ -0,0 +1,318 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (C) 2021 Marvell International Ltd. ++ * ++ * Device tree for the CN9130-CRB boards. ++ */ ++ ++#include "cn9130.dtsi" ++ ++#include ++ ++/ { ++ model = "Wistron ES1227"; ++ compatible = "marvell,cn9130", "marvell,armada-ap807-quad", ++ "marvell,armada-ap807"; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ aliases { ++ gpio1 = &cp0_gpio1; ++ gpio2 = &cp0_gpio2; ++ i2c0 = &cp0_i2c0; ++ i2c1 = &cp0_i2c1; ++ ethernet0 = &cp0_eth0; ++ ethernet1 = &cp0_eth1; ++ spi1 = &cp0_spi0; ++ spi2 = &cp0_spi1; ++ }; ++ ++ /*16G */ ++ memory@0 { ++ device_type = "memory"; ++ reg = <0x0 0x0 0x4 0x00000000>; ++ }; ++ ++ cp0_usb3_0_phy0: cp0_usb3_phy@0 { ++ compatible = "usb-nop-xceiv"; ++ }; ++ ++ cp0_usb3_0_phy1: cp0_usb3_phy@1 { ++ compatible = "usb-nop-xceiv"; ++ }; ++ ++ cp0_reg_sd_vccq: cp0_sd_vccq@0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "cp0_sd_vccq"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&cp0_gpio2 18 GPIO_ACTIVE_HIGH>; ++ states = <1800000 0x1 ++ 3300000 0x0>; ++ }; ++ ++ cp0_reg_sd_vcc: cp0_sd_vcc@0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "cp0_sd_vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpio = <&cp0_gpio2 19 GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ regulator-always-on; ++ }; ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++&ap_sdhci0 { ++ pinctrl-names = "default"; ++ bus-width = <8>; ++ status = "okay"; ++}; ++ ++&cp0_crypto { ++ status = "okay"; ++}; ++ ++&cp0_ethernet { ++ status = "okay"; ++}; ++ ++&cp0_eth1 { ++ status = "okay"; ++ phy = <&phy0>; ++ phy-mode = "rgmii-id"; ++}; ++ ++&cp0_gpio1 { ++ status = "okay"; ++}; ++ ++&cp0_gpio2 { ++ status = "okay"; ++}; ++ ++&cp0_i2c0 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c0_pins>; ++ clock-frequency = <100000>; ++ ++ i2c-switch1@77 { ++ compatible = "nxp,pca9548"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x77>; ++ i2c-mux-idle-disconnect; ++ ++ i2c@0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0>; ++ ++ tmp75@49 { ++ compatible = "ti,tmp75"; ++ reg = <0x49>; ++ }; ++ tmp75@4a { ++ compatible = "ti,tmp75"; ++ reg = <0x4a>; ++ }; ++ tmp75@4b { ++ compatible = "ti,tmp75"; ++ reg = <0x4b>; ++ }; ++ expander0: pca953x@20 { ++ compatible = "nxp,pca9555"; ++ pinctrl-names = "default"; ++ gpio-controller; ++ #gpio-cells = <2>; ++ reg = <0x20>; ++ status = "okay"; ++ }; ++ }; ++ ++ i2c@1 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <1>; ++ }; ++ ++ i2c@2 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <2>; ++ }; ++ ++ i2c@3 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <3>; ++ }; ++ ++ i2c@4 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <4>; ++ ++ rtc0:rtc-ds1388@68 { ++ compatible = "dallas,ds1388"; ++ /* PDF P13 : 1101 000 R/nW , 0x68+ R/nW */ ++ reg = <0x68>; ++ }; ++ }; ++ ++ i2c@5 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <5>; ++ }; ++ ++ i2c@6 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <6>; ++ }; ++ ++ i2c@7 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <7>; ++ }; ++ }; ++ ++ eeprom@55 { ++ compatible = "atmel,24c02"; ++ reg = <0x55>; ++ }; ++}; ++ ++&cp0_i2c1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c1_pins>; ++ clock-frequency = <100000>; ++}; ++ ++ ++/* U55 */ ++&cp0_spi1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_spi0_pins>; ++ reg = <0x700680 0x50>; ++ ++ spi-flash@0 { ++ #address-cells = <0x1>; ++ #size-cells = <0x1>; ++ compatible = "jedec,spi-nor"; ++ reg = <0x0>; ++ spi-max-frequency = <50000000>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "U-Boot-0"; ++ reg = <0x0 0x200000>; ++ }; ++ ++ partition@3f0000 { ++ label = "uboot-env"; ++ reg = <0x3f0000 0x10000>; ++ env_size = <0x10000>; ++ }; ++ ++ partition@400000 { ++ label = "Filesystem-0"; ++ reg = <0x400000 0xc00000>; ++ }; ++ ++ partition@1000000 { ++ label = "onie"; ++ reg = <0x1000000 0x1000000>; ++ }; ++ }; ++ }; ++}; ++ ++&cp0_syscon0 { ++ cp0_pinctrl: pinctrl { ++ compatible = "marvell,cp115-standalone-pinctrl"; ++ ++ cp0_uart0_pins: cp0-uart0-pins-0 { ++ marvell,pins = "mpp0", "mpp1"; ++ marvell,function = "uart0"; ++ }; ++ ++ cp0_ge_mdio_pins: ge-mdio-pins { ++ marvell,pins = "mpp40", "mpp41"; ++ marvell,function = "ge"; ++ }; ++ ++ cp0_i2c0_pins: cp0-i2c-pins-0 { ++ marvell,pins = "mpp37", "mpp38"; ++ marvell,function = "i2c0"; ++ }; ++ ++ cp0_i2c1_pins: cp0-i2c-pins-1 { ++ marvell,pins = "mpp35", "mpp36"; ++ marvell,function = "i2c1"; ++ }; ++ ++ cp0_ge1_rgmii_pins: cp0-ge-rgmii-pins-0 { ++ marvell,pins = "mpp0", "mpp1", "mpp2", ++ "mpp3", "mpp4", "mpp5", ++ "mpp6", "mpp7", "mpp8", ++ "mpp9", "mpp10", "mpp11"; ++ marvell,function = "ge0"; ++ }; ++ ++ cp0_ge2_rgmii_pins: cp0-ge-rgmii-pins-1 { ++ marvell,pins = "mpp44", "mpp45", "mpp46", ++ "mpp47", "mpp48", "mpp49", ++ "mpp50", "mpp51", "mpp52", ++ "mpp53", "mpp54", "mpp55"; ++ marvell,function = "ge1"; ++ }; ++ ++ cp0_sdhci_cd_pins: cp0-sdhci-cd-pins-0 { ++ marvell,pins = "mpp43"; ++ marvell,function = "gpio"; ++ }; ++ ++ cp0_sdhci_pins: cp0-sdhi-pins-0 { ++ marvell,pins = "mpp56", "mpp57", "mpp58", ++ "mpp59", "mpp60", "mpp61"; ++ marvell,function = "sdio"; ++ }; ++ ++ cp0_spi0_pins: cp0-spi-pins-0 { ++ marvell,pins = "mpp13", "mpp14", "mpp15", "mpp16"; ++ marvell,function = "spi1"; ++ }; ++ ++ cp0_sdhci_cd_pins_crb: cp0-sdhci-cd-pins-crb { ++ marvell,pins = "mpp55"; ++ marvell,function = "gpio"; ++ }; ++ }; ++}; ++ ++&cp0_usb3_0 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy0>; ++ phy-names = "usb"; ++}; ++ ++&cp0_usb3_1 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy1>; ++ phy-names = "usb"; ++}; +-- +2.34.1 + diff --git a/patches-sonic/0016-arm64-dts-marvell-Add-Wistron-ES-1227-54TS-board.patch b/patches-sonic/0016-arm64-dts-marvell-Add-Wistron-ES-1227-54TS-board.patch new file mode 100644 index 000000000..650d49431 --- /dev/null +++ b/patches-sonic/0016-arm64-dts-marvell-Add-Wistron-ES-1227-54TS-board.patch @@ -0,0 +1,436 @@ +From f9bbb2f58b724d06ce76d63eb780c246399dc58d Mon Sep 17 00:00:00 2001 +From: Owen Wu +Date: Tue, 15 Oct 2024 15:24:11 +0800 +Subject: [PATCH] arm64: dts: marvell: Add DTS for Wistron ES-1227-54TS board + +This change adds device tree file for Wistron ES-1227-54TS +and ES-2227-54TS boards. + +This dts is extracted from Marvell cn9130-crb and removed unused +nodes along with platform specific changes. + +Signed-off-by: Owen Wu +--- + arch/arm64/boot/dts/marvell/Makefile | 1 + + arch/arm64/boot/dts/marvell/es1227-54ts.dts | 74 +++++ + arch/arm64/boot/dts/marvell/es1227-54ts.dtsi | 318 +++++++++++++++++++ + 3 files changed, 393 insertions(+) + create mode 100644 arch/arm64/boot/dts/marvell/es1227-54ts.dts + create mode 100644 arch/arm64/boot/dts/marvell/es1227-54ts.dtsi + +diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile +index 88cef592e..5fa9dbf4e 100644 +--- a/arch/arm64/boot/dts/marvell/Makefile ++++ b/arch/arm64/boot/dts/marvell/Makefile +@@ -29,3 +29,4 @@ dtb-$(CONFIG_ARCH_MVEBU) += ac5-98dx35xx-rd.dtb + dtb-$(CONFIG_ARCH_MVEBU) += armada-7020-comexpress.dtb + dtb-$(CONFIG_ARCH_MVEBU) += 7215-ixs-a1.dtb + dtb-$(CONFIG_ARCH_MVEBU) += smc_sse-g3748.dtb ++dtb-$(CONFIG_ARCH_MVEBU) += es1227-54ts.dtb +diff --git a/arch/arm64/boot/dts/marvell/es1227-54ts.dts b/arch/arm64/boot/dts/marvell/es1227-54ts.dts +new file mode 100644 +index 000000000..c77c7d4b6 +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/es1227-54ts.dts +@@ -0,0 +1,74 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (C) 2021 Marvell International Ltd. ++ * ++ * Device tree for the CN9130-CRB-A board. ++ */ ++ ++#include "es1227-54ts.dtsi" ++/ { ++ model = "Wistron ES1227 board"; ++}; ++ ++&cp0_pcie2 { ++ status = "okay"; ++ num-lanes = <1>; ++ num-viewport = <8>; ++ ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp0_comphy5 2>; ++ iommu-map = <0x0 &smmu 0x480 0x20>, ++ <0x100 &smmu 0x4a0 0x20>, ++ <0x200 &smmu 0x4c0 0x20>; ++ iommu-map-mask = <0x031f>; ++}; ++ ++&cp0_mdio { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_ge_mdio_pins>; ++ ++ phy0: ethernet-phy@0 { ++ reg = <0>; ++ marvell,reg-init = <0x2 0x1A 0x7FFF 0x8000>, ++ <0x2 0x0 0x7FFF 0x8000>, ++ <0x1 0x10 0xFDFF 0x200>; ++ }; ++}; ++ ++&cp0_xmdio { ++ status = "disabled"; ++}; ++ ++&cp0_uart0 { ++ pinctrl-names = "default"; ++ status = "okay"; ++ pinctrl-0 = <&cp0_uart0_pins>; ++}; ++ ++&cp0_usb3_0 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy0>; ++ phys = <&cp0_comphy1 0>; ++ phy-names = "usb3"; ++}; ++ ++&cp0_eth0 { ++ status = "disabled"; ++}; ++ ++/* CON56 */ ++&cp0_eth1 { ++ status = "okay"; ++ phys = <&cp0_comphy3 1>; ++ phy-handle = <&phy0>; ++ phy-mode = "sgmii"; ++}; ++ ++&cp0_eth2 { ++ status = "disabled"; ++}; ++ ++&cp0_rtc { ++ status = "disabled"; ++}; +diff --git a/arch/arm64/boot/dts/marvell/es1227-54ts.dtsi b/arch/arm64/boot/dts/marvell/es1227-54ts.dtsi +new file mode 100644 +index 000000000..e3f7c4e0b +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/es1227-54ts.dtsi +@@ -0,0 +1,318 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (C) 2021 Marvell International Ltd. ++ * ++ * Device tree for the CN9130-CRB boards. ++ */ ++ ++#include "cn9130.dtsi" ++ ++#include ++ ++/ { ++ model = "Wistron ES1227"; ++ compatible = "marvell,cn9130", "marvell,armada-ap807-quad", ++ "marvell,armada-ap807"; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ aliases { ++ gpio1 = &cp0_gpio1; ++ gpio2 = &cp0_gpio2; ++ i2c0 = &cp0_i2c0; ++ i2c1 = &cp0_i2c1; ++ ethernet0 = &cp0_eth0; ++ ethernet1 = &cp0_eth1; ++ spi1 = &cp0_spi0; ++ spi2 = &cp0_spi1; ++ }; ++ ++ /*16G */ ++ memory@00000000 { ++ device_type = "memory"; ++ reg = <0x0 0x0 0x4 0x00000000>; ++ }; ++ ++ cp0_usb3_0_phy0: cp0_usb3_phy@0 { ++ compatible = "usb-nop-xceiv"; ++ }; ++ ++ cp0_usb3_0_phy1: cp0_usb3_phy@1 { ++ compatible = "usb-nop-xceiv"; ++ }; ++ ++ cp0_reg_sd_vccq: cp0_sd_vccq@0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "cp0_sd_vccq"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&cp0_gpio2 18 GPIO_ACTIVE_HIGH>; ++ states = <1800000 0x1 ++ 3300000 0x0>; ++ }; ++ ++ cp0_reg_sd_vcc: cp0_sd_vcc@0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "cp0_sd_vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpio = <&cp0_gpio2 19 GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ regulator-always-on; ++ }; ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++&ap_sdhci0 { ++ pinctrl-names = "default"; ++ bus-width = <8>; ++ status = "okay"; ++}; ++ ++&cp0_crypto { ++ status = "okay"; ++}; ++ ++&cp0_ethernet { ++ status = "okay"; ++}; ++ ++&cp0_eth1 { ++ status = "okay"; ++ phy = <&phy0>; ++ phy-mode = "rgmii-id"; ++}; ++ ++&cp0_gpio1 { ++ status = "okay"; ++}; ++ ++&cp0_gpio2 { ++ status = "okay"; ++}; ++ ++&cp0_i2c0 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c0_pins>; ++ clock-frequency = <100000>; ++ ++ i2c-switch1@77 { ++ compatible = "nxp,pca9548"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x77>; ++ i2c-mux-idle-disconnect; ++ ++ i2c@0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0>; ++ ++ tmp75@49 { ++ compatible = "ti,tmp75"; ++ reg = <0x49>; ++ }; ++ tmp75@4a { ++ compatible = "ti,tmp75"; ++ reg = <0x4a>; ++ }; ++ tmp75@4b { ++ compatible = "ti,tmp75"; ++ reg = <0x4b>; ++ }; ++ expander0: pca953x@20 { ++ compatible = "nxp,pca9555"; ++ pinctrl-names = "default"; ++ gpio-controller; ++ #gpio-cells = <2>; ++ reg = <0x20>; ++ status = "okay"; ++ }; ++ }; ++ ++ i2c@1 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <1>; ++ }; ++ ++ i2c@2 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <2>; ++ }; ++ ++ i2c@3 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <3>; ++ }; ++ ++ i2c@4 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <4>; ++ ++ rtc0:rtc-ds1388@68 { ++ compatible = "dallas,ds1388"; ++ /* PDF P13 : 1101 000 R/nW , 0x68+ R/nW */ ++ reg = <0x68>; ++ }; ++ }; ++ ++ i2c@5 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <5>; ++ }; ++ ++ i2c@6 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <6>; ++ }; ++ ++ i2c@7 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <7>; ++ }; ++ }; ++ ++ eeprom@55 { ++ compatible = "atmel,24c02"; ++ reg = <0x55>; ++ }; ++}; ++ ++&cp0_i2c1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c1_pins>; ++ clock-frequency = <100000>; ++}; ++ ++ ++/* U55 */ ++&cp0_spi1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_spi0_pins>; ++ reg = <0x700680 0x50>; ++ ++ spi-flash@0 { ++ #address-cells = <0x1>; ++ #size-cells = <0x1>; ++ compatible = "jedec,spi-nor"; ++ reg = <0x0>; ++ spi-max-frequency = <50000000>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "U-Boot-0"; ++ reg = <0x0 0x200000>; ++ }; ++ ++ partition@3f0000 { ++ label = "uboot-env"; ++ reg = <0x3f0000 0x10000>; ++ env_size = <0x10000>; ++ }; ++ ++ partition@400000 { ++ label = "Filesystem-0"; ++ reg = <0x400000 0xc00000>; ++ }; ++ ++ partition@1000000 { ++ label = "onie"; ++ reg = <0x1000000 0x1000000>; ++ }; ++ }; ++ }; ++}; ++ ++&cp0_syscon0 { ++ cp0_pinctrl: pinctrl { ++ compatible = "marvell,cp115-standalone-pinctrl"; ++ ++ cp0_uart0_pins: cp0-uart0-pins-0 { ++ marvell,pins = "mpp0", "mpp1"; ++ marvell,function = "uart0"; ++ }; ++ ++ cp0_ge_mdio_pins: ge-mdio-pins { ++ marvell,pins = "mpp40", "mpp41"; ++ marvell,function = "ge"; ++ }; ++ ++ cp0_i2c0_pins: cp0-i2c-pins-0 { ++ marvell,pins = "mpp37", "mpp38"; ++ marvell,function = "i2c0"; ++ }; ++ ++ cp0_i2c1_pins: cp0-i2c-pins-1 { ++ marvell,pins = "mpp35", "mpp36"; ++ marvell,function = "i2c1"; ++ }; ++ ++ cp0_ge1_rgmii_pins: cp0-ge-rgmii-pins-0 { ++ marvell,pins = "mpp0", "mpp1", "mpp2", ++ "mpp3", "mpp4", "mpp5", ++ "mpp6", "mpp7", "mpp8", ++ "mpp9", "mpp10", "mpp11"; ++ marvell,function = "ge0"; ++ }; ++ ++ cp0_ge2_rgmii_pins: cp0-ge-rgmii-pins-1 { ++ marvell,pins = "mpp44", "mpp45", "mpp46", ++ "mpp47", "mpp48", "mpp49", ++ "mpp50", "mpp51", "mpp52", ++ "mpp53", "mpp54", "mpp55"; ++ marvell,function = "ge1"; ++ }; ++ ++ cp0_sdhci_cd_pins: cp0-sdhci-cd-pins-0 { ++ marvell,pins = "mpp43"; ++ marvell,function = "gpio"; ++ }; ++ ++ cp0_sdhci_pins: cp0-sdhi-pins-0 { ++ marvell,pins = "mpp56", "mpp57", "mpp58", ++ "mpp59", "mpp60", "mpp61"; ++ marvell,function = "sdio"; ++ }; ++ ++ cp0_spi0_pins: cp0-spi-pins-0 { ++ marvell,pins = "mpp13", "mpp14", "mpp15", "mpp16"; ++ marvell,function = "spi1"; ++ }; ++ ++ cp0_sdhci_cd_pins_crb: cp0-sdhci-cd-pins-crb { ++ marvell,pins = "mpp55"; ++ marvell,function = "gpio"; ++ }; ++ }; ++}; ++ ++&cp0_usb3_0 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy0>; ++ phy-names = "usb"; ++}; ++ ++&cp0_usb3_1 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy1>; ++ phy-names = "usb"; ++}; +-- +2.25.1 + diff --git a/patches-sonic/0016-hwmon-mlxreg-fan-Add-support-for-new-flavour-of-capa.patch b/patches-sonic/0016-hwmon-mlxreg-fan-Add-support-for-new-flavour-of-capa.patch new file mode 100644 index 000000000..031009197 --- /dev/null +++ b/patches-sonic/0016-hwmon-mlxreg-fan-Add-support-for-new-flavour-of-capa.patch @@ -0,0 +1,92 @@ +From dc8d6f8639f241ebce6589d6c9507e9a0bfca096 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 23 Jul 2023 06:26:09 +0000 +Subject: [PATCH 16/78] hwmon: (mlxreg-fan) Add support for new flavour of + capability register + +FAN platform data is common across the various systems, while fan +driver should be able to apply only the fan instances relevant +to specific system. + +For example, platform data might contain descriptions for fan1, +fan2, ..., fan{n}, while some systems equipped with all 'n' fans, +others with less. +Also, on some systems fan drawer can be equipped with several +tachometers and on others only with one. + +For detection of the real number of equipped drawers and tachometers +special capability registers are used. +These registers used to indicate presence of drawers and tachometers +through the bitmap. + +For some new big modular systems this register will provide presence +data by counter. + +Use slot parameter to distinct whether capability register contains +bitmask or counter. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/mlxreg-fan.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c +index a5f89aa..e54a438 100644 +--- a/drivers/hwmon/mlxreg-fan.c ++++ b/drivers/hwmon/mlxreg-fan.c +@@ -63,12 +63,14 @@ struct mlxreg_fan; + * @reg: register offset; + * @mask: fault mask; + * @prsnt: present register offset; ++ * @shift: tacho presence bit shift; + */ + struct mlxreg_fan_tacho { + bool connected; + u32 reg; + u32 mask; + u32 prsnt; ++ u32 shift; + }; + + /* +@@ -143,8 +145,10 @@ mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + /* + * Map channel to presence bit - drawer can be equipped with + * one or few FANs, while presence is indicated per drawer. ++ * Shift channle value if necessary to align with register value. + */ +- if (BIT(channel / fan->tachos_per_drwr) & regval) { ++ if (BIT(rol32(channel, tacho->shift) / fan->tachos_per_drwr) & ++ regval) { + /* FAN is not connected - return zero for FAN speed. */ + *val = 0; + return 0; +@@ -400,7 +404,7 @@ static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan, + return err; + } + +- return !!(regval & data->bit); ++ return data->slot ? (data->slot <= regval ? 1 : 0) : !!(regval & data->bit); + } + + static int mlxreg_pwm_connect_verify(struct mlxreg_fan *fan, +@@ -537,7 +541,15 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan, + return err; + } + +- drwr_avail = hweight32(regval); ++ /* ++ * The number of drawers could be specified in registers by counters for newer ++ * systems, or by bitmasks for older systems. In case the data is provided by ++ * counter, it is indicated through 'version' field. ++ */ ++ if (pdata->version) ++ drwr_avail = regval; ++ else ++ drwr_avail = hweight32(regval); + if (!tacho_avail || !drwr_avail || tacho_avail < drwr_avail) { + dev_err(fan->dev, "Configuration is invalid: drawers num %d tachos num %d\n", + drwr_avail, tacho_avail); +-- +2.8.4 + diff --git a/patches-sonic/0016-i2c-xiic-skip-input-clock-setup-on-non-OF-systems.patch b/patches-sonic/0016-i2c-xiic-skip-input-clock-setup-on-non-OF-systems.patch new file mode 100644 index 000000000..27a8d234a --- /dev/null +++ b/patches-sonic/0016-i2c-xiic-skip-input-clock-setup-on-non-OF-systems.patch @@ -0,0 +1,42 @@ +From efe64a75e59f17682a6199f0cfc596441e3318f0 Mon Sep 17 00:00:00 2001 +From: Abdurrahman Hussain +Date: Mon, 23 Feb 2026 15:59:22 +0000 +Subject: [PATCH 16/22] i2c: xiic: skip input clock setup on non-OF systems + +Currently Linux does not implement ACPI ClockInput() resource to describe +clocks, unlike DT. However the xiic driver is happy if something +magically enables the clock before the driver probes, and does not +turn it off again. The clock should always be considered optional for +ACPI. + +Signed-off-by: Abdurrahman Hussain +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260223-i2c-xiic-v12-7-b6c9ce4e4f3c@nexthop.ai +--- + drivers/i2c/busses/i2c-xiic.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c +index 56109a38f57b..e0626c39a381 100644 +--- a/drivers/i2c/busses/i2c-xiic.c ++++ b/drivers/i2c/busses/i2c-xiic.c +@@ -1465,10 +1465,12 @@ static int xiic_i2c_probe(struct platform_device *pdev) + + spin_lock_init(&i2c->atomic_lock); + +- i2c->clk = devm_clk_get_enabled(&pdev->dev, NULL); +- if (IS_ERR(i2c->clk)) +- return dev_err_probe(&pdev->dev, PTR_ERR(i2c->clk), +- "failed to enable input clock.\n"); ++ if (is_of_node(fwnode)) { ++ i2c->clk = devm_clk_get_enabled(dev, NULL); ++ if (IS_ERR(i2c->clk)) ++ return dev_err_probe(dev, PTR_ERR(i2c->clk), ++ "failed to enable input clock.\n"); ++ } + + i2c->dev = dev; + +-- +2.53.0 + diff --git a/patches-sonic/0016-soc-pensando-pcie-driver.patch b/patches-sonic/0016-soc-pensando-pcie-driver.patch new file mode 100644 index 000000000..3c42f5c93 --- /dev/null +++ b/patches-sonic/0016-soc-pensando-pcie-driver.patch @@ -0,0 +1,721 @@ +From 56c8ff098ace73f3817c6568f508d5fb340b9e35 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:15:28 +0000 +Subject: [PATCH 16/27] soc/pensando: pcie driver + +pcie driver provides these services on elba +1) panic blink handler runs after panic, restart when host restarts +2) sysfs panic_reboot node to restart immediately on panic +3) /dev/penpcie controlled access to pcie clock domain registers + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 7 + + drivers/soc/pensando/Kconfig | 7 + + drivers/soc/pensando/Makefile | 1 + + drivers/soc/pensando/cap_pcie.c | 437 +++++++++++++++++++++++++++++++++ + drivers/soc/pensando/cap_pcie_elba.h | 54 ++++ + drivers/soc/pensando/cap_reboot.c | 85 +++++++ + drivers/soc/pensando/cap_reboot.h | 11 + + drivers/soc/pensando/penpcie_dev.h | 26 ++ + 8 files changed, 628 insertions(+) + create mode 100644 drivers/soc/pensando/cap_pcie.c + create mode 100644 drivers/soc/pensando/cap_pcie_elba.h + create mode 100644 drivers/soc/pensando/cap_reboot.c + create mode 100644 drivers/soc/pensando/cap_reboot.h + create mode 100644 drivers/soc/pensando/penpcie_dev.h + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index 3348473..b6bf8fa 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -270,5 +270,12 @@ + compatible = "pensando,bsm"; + reg = <0x0 0x307c2080 0x0 0x4>; + }; ++ ++ pcie@307c2480 { ++ compatible = "pensando,pcie"; ++ reg = <0x0 0x307c2480 0x0 0x4 /* MS CFG_WDT */ ++ 0x0 0x1400 0x0 0x10 /* WDT0 */ ++ 0x0 0x20000000 0x0 0x380000>; /* PXB Base */ ++ }; + }; + }; +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +index 59c1fa4..be2d3db 100644 +--- a/drivers/soc/pensando/Kconfig ++++ b/drivers/soc/pensando/Kconfig +@@ -15,6 +15,13 @@ config PENSANDO_SOC_CAPMEM + help + Support for the Pensando SoC memory driver + ++config PENSANDO_SOC_PCIE ++ tristate "PCIe driver for Pensando SoCs" ++ depends on OF ++ default y ++ help ++ Provide a PCIe driver for pciemgr kernel assist ++ + config PENSANDO_SOC_RSTCAUSE + tristate "Pensando reset cause driver" + depends on OF +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +index ea5062f..675e84d 100644 +--- a/drivers/soc/pensando/Makefile ++++ b/drivers/soc/pensando/Makefile +@@ -6,6 +6,7 @@ obj-y += cap_soc.o + CFLAGS_cap_mem.o := -I$(src) + + obj-$(CONFIG_PENSANDO_SOC_CAPMEM) += cap_mem.o ++obj-$(CONFIG_PENSANDO_SOC_PCIE) += cap_pcie.o cap_reboot.o + obj-$(CONFIG_PENSANDO_SOC_RSTCAUSE) += cap_rstcause.o + obj-$(CONFIG_PENSANDO_SOC_CRASH) += cap_crash.o + obj-$(CONFIG_PENSANDO_SOC_BSM) += cap_bsm.o +diff --git a/drivers/soc/pensando/cap_pcie.c b/drivers/soc/pensando/cap_pcie.c +new file mode 100644 +index 0000000..bbfd71c +--- /dev/null ++++ b/drivers/soc/pensando/cap_pcie.c +@@ -0,0 +1,437 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2019-2022, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "cap_reboot.h" ++#include "cap_rstcause.h" ++#include "penpcie_dev.h" ++#include "cap_pcie_elba.h" ++ ++#define DRV_NAME "cap_pcie" ++#define PFX DRV_NAME ": " ++ ++/* device resource indexes */ ++#define MS_CFG_WDT_IDX 0 ++#define WDT_IDX 1 ++#define PCIE_IDX 2 ++ ++struct pciedev_info { ++ u32 __iomem *ms_cfg_wdt; ++ u32 __iomem *wdt; ++ void __iomem *pcieva; ++ u64 pcie_base; ++ u64 pcie_size; ++ u64 pciep_access_address; ++ int pciep_access_error; ++ spinlock_t pciep_access_lock; ++ long (*saved_panic_blink)(int state); ++}; ++ ++static struct pciedev_info pciedev_info; ++ ++static void *pcie_ptov(const u64 pciepa) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ const u64 pcieoff = pciepa - pi->pcie_base; ++ ++ return pi->pcieva + pcieoff; ++} ++ ++static u32 pcie_readl(const u64 pciepa) ++{ ++ return readl(pcie_ptov(pciepa)); ++} ++ ++static void pcie_writel(const u32 val, const u64 pciepa) ++{ ++ writel(val, pcie_ptov(pciepa)); ++} ++ ++static int pciep_access_in_progress(void) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ ++ if (pi->pciep_access_address) { ++ pi->pciep_access_error++; ++ return 1; ++ } ++ return 0; ++} ++ ++int platform_serror(struct pt_regs *regs, unsigned int esr) ++{ ++ if (pciep_access_in_progress()) ++ return 1; ++ ++ if ((esr >> 26) == 0x2f && (esr & 0x3) == 0x0) { /* Decode Error */ ++ if (user_mode(regs)) { ++ struct task_struct *tsk = current; ++ ++ pr_info("%s[%d]: serror converted to bus error\n", ++ tsk->comm, task_pid_nr(tsk)); ++ force_signal_inject(SIGBUS, BUS_ADRERR, regs->pc, esr); ++ } else { ++ /* ignore */ ++ pr_info("ignoring serror decode-error in kernel mode\n"); ++ } ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static void pciep_access_begin(const u64 pciepa) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ ++ pi->pciep_access_address = pciepa; ++ pi->pciep_access_error = 0; ++} ++ ++static int pciep_access_end(void) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ ++ if (pi->pciep_access_error) { ++ pr_info_ratelimited("pcie access serror: address 0x%llx\n", ++ pi->pciep_access_address); ++ } ++ pi->pciep_access_address = 0; ++ return pi->pciep_access_error; ++} ++ ++static int pciep_valid_pa(const uint64_t pciepa, const uint32_t size) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ ++ if (!pi->pcieva) ++ return -ENXIO; ++ if (pciepa < pi->pcie_base || ++ pciepa > pi->pcie_base + pi->pcie_size || ++ size > pi->pcie_size || ++ pciepa + size > pi->pcie_base + pi->pcie_size) ++ return -ERANGE; ++ return 0; ++} ++ ++/* ++ * Protect reads to pcie registers in the pcie clock domain. ++ * The pcie refclock can be removed by the system without warning, ++ * and outstanding read requests to these registers will generate ++ * an AXI read error response. ARM will treat this as an asynchronous ++ * System Error (SError) event. The default handling of SError is to ++ * send SIGILL if SError arrives while in user space, or panic if the ++ * SError arrives when not in process context. Neither of these responses ++ * is desireable for our case where a pcie register might be accessed ++ * just as the pcie refclock gets removed. Here we detect the SError ++ * event during our pcie register access and return failure to the ++ * caller, but the system continues. ++ */ ++int pciep_regrd32(const uint64_t pciepa, uint32_t *val) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ int r; ++ unsigned long flags; ++ ++ r = pciep_valid_pa(pciepa, sizeof(u32)); ++ if (r) ++ return r; ++ ++ spin_lock_irqsave(&pi->pciep_access_lock, flags); ++ pciep_access_begin(pciepa); ++ ++ *val = pcie_readl(pciepa); ++ asm volatile("msr daifclr, #4" ::: "memory"); /* unmask async SError */ ++ dsb(sy); /* sync in-flight ld/st */ ++ isb(); ++ ++ r = pciep_access_end(); ++ spin_unlock_irqrestore(&pi->pciep_access_lock, flags); ++ if (r) ++ return -EIO; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(pciep_regrd32); ++ ++static long pcie_unlocked_ioctl(struct file *file, ++ unsigned int cmd, unsigned long arg) ++{ ++ void __user *p = (void __user *)arg; ++ struct pcie_rw rw; ++ uint32_t val; ++ int r; ++ ++ switch (cmd) { ++ ++ case PCIE_PCIEP_REGRD: ++ if (copy_from_user(&rw, p, sizeof(rw))) ++ return -EFAULT; ++ if (rw.size != sizeof(u32)) ++ return -EINVAL; ++ r = pciep_regrd32(rw.pciepa, &val); ++ if (r) ++ return r; ++ return copy_to_user(rw.rdvalp, &val, sizeof(val)); ++ ++ default: ++ return -ENOTTY; ++ } ++} ++ ++const struct file_operations pcie_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = pcie_unlocked_ioctl, ++}; ++ ++static struct miscdevice pcie_dev = { ++ MISC_DYNAMIC_MINOR, ++ PENPCIE_NAME, ++ &pcie_fops ++}; ++ ++static int pcieport_get_ltssm_en(const int port) ++{ ++ const u32 val = pcie_readl(PXC_(CFG_C_PORT_MAC, port)); ++ ++ return (val & CFG_MACF_(0_2_LTSSM_EN)) != 0; ++} ++ ++static int pcie_get_ltssm_en(void) ++{ ++ int port; ++ ++ for (port = 0; port < PCIEPORT_NPORTS; port++) ++ if (pcieport_get_ltssm_en(port)) ++ return port; ++ return -1; ++} ++ ++static void pcieport_set_crs(const int port, const int on) ++{ ++ u32 val; ++ ++ val = pcie_readl(PXC_(CFG_C_PORT_MAC, port)); ++ if (on) ++ val |= CFG_MACF_(0_2_CFG_RETRY_EN); ++ else ++ val &= ~CFG_MACF_(0_2_CFG_RETRY_EN); ++ pcie_writel(val, PXC_(CFG_C_PORT_MAC, port)); ++} ++ ++static void pcie_set_crs(const int on) ++{ ++ int port; ++ ++ for (port = 0; port < PCIEPORT_NPORTS; port++) ++ pcieport_set_crs(port, on); ++} ++ ++static int pcieport_poll_for_hostdn(const int port) ++{ ++ const u32 val = pcie_readl(PXC_(INT_C_MAC_INTREG, port)); ++ ++ return (val & MAC_INTREGF_(RST_DN2UP)) != 0; ++} ++ ++/* ++ * Detect if the host is rebooting by watching the pcie mac ++ * for an interrupt indicating the link went into reset. ++ */ ++static int pcie_poll_for_hostdn(void) ++{ ++ int port; ++ ++ for (port = 0; port < PCIEPORT_NPORTS; port++) ++ if (pcieport_poll_for_hostdn(port)) ++ return port; ++ return -1; ++} ++ ++/* ++ * Asic reset using the WDT0 configured to reset immediately. ++ * Note that we do NOT touch the WDT config here until *after* ++ * we are in the panic handling. The WDT might be used by the ++ * watchdog driver while the system is up, but here after a panic ++ * we take ownership of the WDT to reset the system. ++ * ++ * Note also this function never returns. ++ */ ++static void cap_reset(void) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ u32 val; ++ ++ pr_info(PFX "pensando reset!\n"); ++ ++ /* Enable WDT0 to reset the system */ ++ val = ioread32(pi->ms_cfg_wdt); ++ val |= (1 << CFG_WDT_RST_EN); ++ iowrite32(val, pi->ms_cfg_wdt); ++ ++ /* Configure WDT to immediately reset */ ++ iowrite32(0, pi->wdt + WDT_TORR); ++ iowrite32(WDT_KICK_VAL, pi->wdt + WDT_CRR); ++ iowrite32(WDT_CR_PCLK_256, pi->wdt + WDT_CR); ++ iowrite32(WDT_CR_PCLK_256 | WDT_CR_ENABLE, pi->wdt + WDT_CR); ++ for (;;) ++ asm volatile("wfi"); ++ /* NOTREACHED */ ++} ++ ++/* ++ * This function is called by the spin loop at the end of a ++ * system panic. We'll watch for the host to reset and ++ * reset ourselves at the same time. ++ * ++ * If we haven't yet initialized the link (ltssm_en=0) then the ++ * host side hasn't come up yet. In that case just reset immediately. ++ */ ++static long pcie_panic_blink(int state) ++{ ++ int port; ++ ++ /* Check sysfs for immediate reboot */ ++ if (cap_panic_reboot()) ++ cap_reset(); ++ ++ port = pcie_get_ltssm_en(); ++ if (port >= 0) { ++ pr_info(PFX "port %d enabled\n", port); ++ pcie_set_crs(0); ++ while ((port = pcie_poll_for_hostdn()) < 0) ++ continue; ++ pr_info(PFX "port %d hostdn\n", port); ++#ifdef CONFIG_PENSANDO_SOC_RSTCAUSE ++ /* reflect the pcie reset state in the reset cause */ ++ cap_rstcause_set(CAP_RSTCAUSE_EV_PCIE_RESET); ++#endif ++ } ++ cap_reset(); ++ ++ /* NOTREACHED */ ++ return 0; ++} ++ ++static int map_resources(struct platform_device *pd) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ struct device_node *dn = pd->dev.of_node; ++ ++ pi->ms_cfg_wdt = of_iomap(dn, MS_CFG_WDT_IDX); ++ pi->wdt = of_iomap(dn, WDT_IDX); ++ pi->pcieva = of_iomap(dn, PCIE_IDX); ++ ++ if (IS_ERR(pi->ms_cfg_wdt) || ++ IS_ERR(pi->wdt) || ++ IS_ERR(pi->pcieva)) { ++ pr_err(PFX "iomap resources failed\n"); ++ goto errout; ++ } ++ return 0; ++ ++ errout: ++ if (pi->ms_cfg_wdt != NULL) ++ iounmap(pi->ms_cfg_wdt); ++ if (pi->wdt != NULL) ++ iounmap(pi->wdt); ++ if (pi->pcieva != NULL) ++ iounmap(pi->pcieva); ++ return -ENOMEM; ++} ++ ++static void unmap_resources(struct platform_device *pd) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ ++ if (pi->ms_cfg_wdt != NULL) ++ iounmap(pi->ms_cfg_wdt); ++ if (pi->wdt != NULL) ++ iounmap(pi->wdt); ++ if (pi->pcieva != NULL) ++ iounmap(pi->pcieva); ++} ++ ++static int pcie_probe(struct platform_device *pd) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ struct device_node *dn = pd->dev.of_node; ++ struct resource res; ++ int err; ++ ++ spin_lock_init(&pi->pciep_access_lock); ++ ++ err = map_resources(pd); ++ if (err) ++ goto errout; ++ ++ err = of_address_to_resource(dn, PCIE_IDX, &res); ++ if (err) { ++ pr_err(PFX "can't find PCIE_IDX res: %d\n", err); ++ goto errout_unmap; ++ } ++ pi->pcie_base = res.start; ++ pi->pcie_size = resource_size(&res); ++ ++ err = misc_register(&pcie_dev); ++ if (err) { ++ pr_err(PFX "register pcie_dev failed: %d\n", err); ++ goto errout_unmap; ++ } ++ ++ /* ++ * Hook the panic_blink handler so we run after ++ * all the panic notifiers and after all the ++ * console msgs have been flushed. ++ */ ++ pi->saved_panic_blink = panic_blink; ++ panic_blink = pcie_panic_blink; ++ return 0; ++ ++ errout_unmap: ++ unmap_resources(pd); ++ errout: ++ return err; ++} ++ ++static void pcie_remove(struct platform_device *pd) ++{ ++ struct pciedev_info *pi = &pciedev_info; ++ ++ misc_deregister(&pcie_dev); ++ panic_blink = pi->saved_panic_blink; ++ unmap_resources(pd); ++ return; ++} ++ ++static const struct of_device_id pcie_of_match[] = { ++ { .compatible = "pensando,pcie" }, ++ { /* end of table */ } ++}; ++ ++static struct platform_driver pcie_driver = { ++ .probe = pcie_probe, ++ .remove = pcie_remove, ++ .driver = { ++ .name = "pensando-pcie", ++ .owner = THIS_MODULE, ++ .of_match_table = pcie_of_match, ++ }, ++}; ++module_platform_driver(pcie_driver); +diff --git a/drivers/soc/pensando/cap_pcie_elba.h b/drivers/soc/pensando/cap_pcie_elba.h +new file mode 100644 +index 0000000..b79046a +--- /dev/null ++++ b/drivers/soc/pensando/cap_pcie_elba.h +@@ -0,0 +1,54 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#ifndef __CAP_PCIE_ELBA_H__ ++#define __CAP_PCIE_ELBA_H__ ++ ++#define PCIEPORT_NPORTS 8 ++ ++#define ELB_ADDR_BASE_PP_PXC_0_OFFSET 0x20100000 ++#define ELB_ADDR_BASE_PP_PXC_0_SIZE 0x40000 ++#define ELB_ADDR_BASE_PP_PP_0_OFFSET 0x20300000 ++#define ELB_ADDR_BASE_PP_PP_0_SIZE 0x40000 ++ ++#define ELB_PXC_CSR_CFG_C_PORT_MAC_BYTE_ADDRESS 0x20f8 ++#define ELB_PXC_CSR_CFG_C_PORT_MAC_CFG_C_PORT_MAC_0_2_LTSSM_EN_FIELD_MASK 0x00000002 ++#define ELB_PXC_CSR_CFG_C_PORT_MAC_CFG_C_PORT_MAC_0_2_CFG_RETRY_EN_FIELD_MASK 0x00000008 ++ ++#define ELB_PXC_CSR_INT_C_MAC_INTREG_BYTE_ADDRESS 0x2220 ++#define ELB_PXC_CSR_INT_C_MAC_INTREG_RST_DN2UP_INTERRUPT_FIELD_MASK 0x00000010 ++ ++#define _PP_BASE(pn) \ ++ (ELB_ADDR_BASE_PP_PP_0_OFFSET + \ ++ (((pn) >> 2) * ELB_ADDR_BASE_PP_PP_0_SIZE)) ++ ++#define PP_(REG, pn) \ ++ (_PP_BASE(pn) + ELB_PP_CSR_ ##REG## _BYTE_ADDRESS) ++ ++#define _PXC_BASE(pn) \ ++ (ELB_ADDR_BASE_PP_PXC_0_OFFSET + \ ++ ((pn) * ELB_ADDR_BASE_PP_PXC_0_SIZE)) ++ ++#define PXC_(REG, pn) \ ++ (_PXC_BASE(pn) + ELB_PXC_CSR_ ##REG## _BYTE_ADDRESS) ++ ++#define CFG_MACF_(REG) \ ++ (ELB_PXC_CSR_CFG_C_PORT_MAC_CFG_C_PORT_MAC_ ##REG## _FIELD_MASK) ++#define MAC_INTREGF_(REG) \ ++ (ELB_PXC_CSR_INT_C_MAC_INTREG_ ##REG## _INTERRUPT_FIELD_MASK) ++ ++#define ELB_SOC_CSR_CFG_WDT_RST_EN_LSB 0 ++#define CFG_WDT_RST_EN ELB_SOC_CSR_CFG_WDT_RST_EN_LSB ++ ++#define WDT_CR 0x00 ++#define WDT_TORR 0x01 ++#define WDT_CRR 0x03 ++ ++#define WDT_CR_ENABLE 0x1 ++#define WDT_CR_PCLK_256 (0x7 << 2) ++ ++#define WDT_KICK_VAL 0x76 ++ ++#endif /* __CAP_PCIE_ELBA_H__ */ +diff --git a/drivers/soc/pensando/cap_reboot.c b/drivers/soc/pensando/cap_reboot.c +new file mode 100644 +index 0000000..42d87f2 +--- /dev/null ++++ b/drivers/soc/pensando/cap_reboot.c +@@ -0,0 +1,85 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Pensando reboot control via sysfs ++ * ++ * Copyright (c) 2020-2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include "cap_reboot.h" ++ ++/* ++ * This module provides userspace control of reboot behavior ++ * after a panic. Naples25 SWM and OCP cards will use this ++ * to enable immediate reboot after panic handling. ++ */ ++static int panic_reboot; /* default=0, no reboot */ ++ ++bool cap_panic_reboot(void) ++{ ++ if (panic_reboot) ++ return true; ++ return false; ++} ++ ++extern struct kobject *pensando_fw_kobj_get(void); ++struct kobject *reboot_kobj; ++ ++static ssize_t panic_reboot_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "%d\n", panic_reboot); ++} ++ ++static ssize_t panic_reboot_store(struct kobject *kobj, ++ struct kobj_attribute *attr, const char *buf, size_t count) ++{ ++ int ret; ++ ++ ret = kstrtoint(buf, 10, &panic_reboot); ++ if (ret < 0) ++ return ret; ++ return count; ++} ++ ++static struct kobj_attribute panic_reboot_attribute = ++ __ATTR(panic_reboot, 0664, panic_reboot_show, panic_reboot_store); ++ ++static struct attribute *attrs[] = { ++ &panic_reboot_attribute.attr, ++ NULL, ++}; ++ ++/* Put all attributes in the kobject directory */ ++static struct attribute_group attr_group = { ++ .attrs = attrs, ++}; ++ ++static int __init capri_reboot_init(void) ++{ ++ struct kobject *pensando_kobj; ++ int ret; ++ ++ pensando_kobj = pensando_fw_kobj_get(); ++ if (!pensando_kobj) ++ return -ENOMEM; ++ reboot_kobj = kobject_create_and_add("reboot", pensando_kobj); ++ if (!reboot_kobj) ++ return -ENOMEM; ++ ++ ret = sysfs_create_group(reboot_kobj, &attr_group); ++ if (ret) ++ kobject_put(reboot_kobj); ++ return ret; ++} ++ ++static void __exit capri_reboot_exit(void) ++{ ++ kobject_put(reboot_kobj); ++} ++ ++module_init(capri_reboot_init); ++module_exit(capri_reboot_exit); +diff --git a/drivers/soc/pensando/cap_reboot.h b/drivers/soc/pensando/cap_reboot.h +new file mode 100644 +index 0000000..8718fff +--- /dev/null ++++ b/drivers/soc/pensando/cap_reboot.h +@@ -0,0 +1,11 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2020-2021, Pensando Systems Inc. ++ */ ++ ++#ifndef __CAP_REBOOT_H__ ++#define __CAP_REBOOT_H__ ++ ++bool cap_panic_reboot(void); ++ ++#endif +diff --git a/drivers/soc/pensando/penpcie_dev.h b/drivers/soc/pensando/penpcie_dev.h +new file mode 100644 +index 0000000..5426d2e +--- /dev/null ++++ b/drivers/soc/pensando/penpcie_dev.h +@@ -0,0 +1,26 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2020-2022, Pensando Systems Inc. ++ */ ++ ++#ifndef __PENPCIE_DEV_H__ ++#define __PENPCIE_DEV_H__ ++ ++#define PENPCIE_NAME "penpcie" ++#define PENPCIE_DEV "/dev/penpcie" ++ ++struct pcie_rw { ++ uint64_t pciepa; ++ size_t size; ++ union { ++ void *rdvalp; ++ uint64_t wrval; ++ }; ++}; ++ ++#define PCIE_IOCTL_NUM 'P' ++#define PCIE_PCIEP_REGRD _IOWR(PCIE_IOCTL_NUM, 1, struct pcie_rw) ++ ++int pciep_regrd32(const uint64_t pciepa, uint32_t *val); ++ ++#endif +-- +1.8.3.1 + diff --git a/patches-sonic/0017-Extend-driver-to-support-XMC-XM25QH256C-device.patch b/patches-sonic/0017-Extend-driver-to-support-XMC-XM25QH256C-device.patch new file mode 100644 index 000000000..59abd5c46 --- /dev/null +++ b/patches-sonic/0017-Extend-driver-to-support-XMC-XM25QH256C-device.patch @@ -0,0 +1,33 @@ +From b7c35b19dd2e5548534b21404397d53c7d772cfa Mon Sep 17 00:00:00 2001 +From: Owen Wu +Date: Tue, 15 Oct 2024 14:02:06 +0800 +Subject: [PATCH] Extend driver to support XMC XM25QH256C device. + +Add new component for alternative support + +Signed-off-by: Owen Wu +Signed-off-by: Yan Markman +--- + drivers/mtd/spi-nor/xmc.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/drivers/mtd/spi-nor/xmc.c b/drivers/mtd/spi-nor/xmc.c +index d5a06054b..941016cad 100644 +--- a/drivers/mtd/spi-nor/xmc.c ++++ b/drivers/mtd/spi-nor/xmc.c +@@ -19,6 +19,12 @@ static const struct flash_info xmc_nor_parts[] = { + .name = "XM25QH128A", + .size = SZ_16M, + .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, ++ }, { ++ .id = SNOR_ID(0x20, 0x40, 0x19), ++ .name = "XM25QH256C", ++ .size = SZ_32M, ++ .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, ++ .fixup_flags = SPI_NOR_4B_OPCODES, + }, + }; + +-- +2.34.1 + diff --git a/patches-sonic/0017-drivers-soc-pensando-kpcimgr-driver.patch b/patches-sonic/0017-drivers-soc-pensando-kpcimgr-driver.patch new file mode 100644 index 000000000..e898ed5d7 --- /dev/null +++ b/patches-sonic/0017-drivers-soc-pensando-kpcimgr-driver.patch @@ -0,0 +1,1540 @@ +From df67d4e9ee91c3d2d7b2dd868f18f5a4d2743f0b Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:23:49 +0000 +Subject: [PATCH 17/27] drivers/soc/pensando: kpcimgr driver + +kpcimgr driver enables the relocation of the module code to handle +Elba indirect PCIe transactions. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 11 + + drivers/soc/pensando/Makefile | 2 + + drivers/soc/pensando/kpci_constants.h | 41 ++ + drivers/soc/pensando/kpcimgr.c | 956 +++++++++++++++++++++++++++++++++ + drivers/soc/pensando/kpcimgr_api.h | 179 ++++++ + drivers/soc/pensando/kpcimgr_sysfs.c | 277 ++++++++++ + 6 files changed, 1466 insertions(+) + create mode 100644 drivers/soc/pensando/kpci_constants.h + create mode 100644 drivers/soc/pensando/kpcimgr.c + create mode 100644 drivers/soc/pensando/kpcimgr_api.h + create mode 100644 drivers/soc/pensando/kpcimgr_sysfs.c + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index b6bf8fa..f567386 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -277,5 +277,16 @@ + 0x0 0x1400 0x0 0x10 /* WDT0 */ + 0x0 0x20000000 0x0 0x380000>; /* PXB Base */ + }; ++ ++ kpcimgr@20000000 { ++ compatible = "pensando,kpcimgr"; ++ msi-parent = <&its 0xf>; ++ shmem-index = <3>; ++ hwmem-index = <2>; ++ reg = <0x0 0x20000000 0x0 0x00380000 /* PXB Base */ ++ 0x0 0x61800000 0x0 0x00101000 /* Interrupts */ ++ 0x0 0xc4000000 0x0 0x01000000 /* HWMEM */ ++ 0x0 0x0 0x0 0x02000000>; /* SHMEM */ ++ }; + }; + }; +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +index 675e84d..5c89a15 100644 +--- a/drivers/soc/pensando/Makefile ++++ b/drivers/soc/pensando/Makefile +@@ -4,9 +4,11 @@ + obj-y += cap_soc.o + + CFLAGS_cap_mem.o := -I$(src) ++ccflags-$(CONFIG_PENSANDO_SOC_PCIE) += -DPEN_COMPAT_V2 + + obj-$(CONFIG_PENSANDO_SOC_CAPMEM) += cap_mem.o + obj-$(CONFIG_PENSANDO_SOC_PCIE) += cap_pcie.o cap_reboot.o ++obj-$(CONFIG_PENSANDO_SOC_PCIE) += kpcimgr.o kpcimgr_sysfs.o + obj-$(CONFIG_PENSANDO_SOC_RSTCAUSE) += cap_rstcause.o + obj-$(CONFIG_PENSANDO_SOC_CRASH) += cap_crash.o + obj-$(CONFIG_PENSANDO_SOC_BSM) += cap_bsm.o +diff --git a/drivers/soc/pensando/kpci_constants.h b/drivers/soc/pensando/kpci_constants.h +new file mode 100644 +index 0000000..602da16 +--- /dev/null ++++ b/drivers/soc/pensando/kpci_constants.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. ++ */ ++/* ++ * Layout of non-Linux Memory: ++ * (base address provided in device tree and may change) ++ * C500 0000 SHMEM segment (pciehw_shmem_t) [0x942440 bytes ~9.25Mb] ++ * C5F0 0000 kpcimgr state (kstate_t) [3 * 64k] ++ * C5F3 0000 relocated code [Allow 256k] ++ * C5F7 0000 available for stack when in nommu mode (64k) ++ * C5F8 0000 top of stack ++ * C5FF FFFF end of 1M allotted range ++ */ ++#define SHMEM_KSTATE_OFFSET 0xF00000 ++#define SHMEM_KSTATE_SIZE 0x30000 ++#define KSTATE_STACK_OFFSET 0x80000 ++#define KSTATE_CODE_OFFSET (SHMEM_KSTATE_OFFSET + SHMEM_KSTATE_SIZE) ++#define KSTATE_CODE_SIZE (256 * 1024) ++#define KSTATE_MAGIC 0x1743BA1F ++ ++/* size of trace data arrays */ ++#define DATA_SIZE 100 ++#define MSG_BUF_SIZE 32768 ++ ++/* uart and time related constants */ ++#define PEN_UART 0x4800 ++#define UART_THR 0 ++#define UART_LSR 0x14 ++#define DATA_READY 1 ++#define OK_TO_WRITE 0x20 ++#define UART_THRE_BIT 5 ++ ++/* phases */ ++#define NOMMU 0 ++#define NORMAL 1 ++#define NUM_PHASES 2 ++ ++#define MSI_INDIRECT_IDX 0 /* indirect vector */ ++#define MSI_NOTIFY_IDX 1 /* notify vector */ ++#define MSI_NVECTORS 2 +diff --git a/drivers/soc/pensando/kpcimgr.c b/drivers/soc/pensando/kpcimgr.c +new file mode 100644 +index 0000000..e0c3712 +--- /dev/null ++++ b/drivers/soc/pensando/kpcimgr.c +@@ -0,0 +1,956 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Kernel PCIE Manager Infrastructure ++ * ++ * This driver enables the relocation of module code to handle ++ * Pensando/Elba indirect PCIe transactions. The purpose is to allow ++ * code to persist and run during a kexec reboot. The loaded code runs ++ * in physical mode during arm64_relocate_new_kernel and also during ++ * the early boot phase before traditional driver code can run. This ++ * is all to provide extremely low latency response to indirect ++ * transactions, which must be serviced within 200ms. ++ * ++ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. ++ */ ++ ++#include "kpcimgr_api.h" ++#include "penpcie_dev.h" ++ ++MODULE_LICENSE("GPL"); ++ ++kstate_t *kstate; ++DEFINE_SPINLOCK(kpcimgr_lock); ++static DECLARE_WAIT_QUEUE_HEAD(event_queue); ++ ++void wake_up_event_queue(void) ++{ ++ wake_up_interruptible(&event_queue); ++} ++ ++/* ++ * We need our own memset/memcpy to avoid using ++ * any arm instructions that affect the memory cache. ++ * The memory used for kstate/code/etc is uncached. ++ */ ++static void *kpci_memset(void *s, int c, size_t n) ++{ ++ if (((uintptr_t)s & 0x3) == 0 && (n & 0x3) == 0) { ++ u32 *p; ++ int i; ++ ++ c &= 0xff; ++ c = ((c << 0) | ++ (c << 8) | ++ (c << 16) | ++ (c << 24)); ++ for (p = s, i = 0; i < n >> 2; i++, p++) ++ *p = c; ++ } else { ++ u8 *p; ++ int i; ++ ++ for (p = s, i = 0; i < n; i++, p++) ++ *p = c; ++ } ++ ++ return s; ++} ++ ++void *kpci_memcpy(void *dst, const void *src, size_t n) ++{ ++ u8 *d = dst; ++ const u8 *s = src; ++ int i; ++ ++ for (i = 0; i < n; i++) ++ *d++ = *s++; ++ ++ return dst; ++} ++ ++/* ++ * Normal poll ++ */ ++static void kpcimgr_normal_poll(void) ++{ ++ void (*poll_fn)(kstate_t *, int, int); ++ kstate_t *ks = get_kstate(); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&kpcimgr_lock, flags); ++ if (ks->valid == KSTATE_MAGIC) { ++ poll_fn = ks->code_base + ks->code_offsets[K_ENTRY_POLL]; ++ poll_fn(ks, 0, NORMAL); ++ } ++ spin_unlock_irqrestore(&kpcimgr_lock, flags); ++} ++ ++void kpcimgr_start_running(void) ++{ ++ kstate_t *ks = get_kstate(); ++ void (*init_fn)(kstate_t *ks); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&kpcimgr_lock, flags); ++ if (ks->valid == KSTATE_MAGIC) { ++ init_fn = ks->code_base + ks->code_offsets[K_ENTRY_INIT_INTR]; ++ ks->running = 1; ++ init_fn(ks); ++ } ++ spin_unlock_irqrestore(&kpcimgr_lock, flags); ++} ++ ++void kpcimgr_stop_running(void) ++{ ++ kstate_t *ks = get_kstate(); ++ void (*shut_fn)(int n); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&kpcimgr_lock, flags); ++ if (ks->valid == KSTATE_MAGIC) { ++ shut_fn = ks->code_base + ks->code_offsets[K_ENTRY_SHUT]; ++ shut_fn(ks->active_port); ++ } ++ spin_unlock_irqrestore(&kpcimgr_lock, flags); ++ ++ ks->running = 0; ++} ++ ++/* ++ * Read event(s) from the event queue. Used by pciemgrd to find out ++ * about h/w event notifications that arrived during times when ++ * pciemgrd is not running (ie, during a kexec). ++ * ++ * Standard event queue semantics: ++ * evq_head = index of slot used for next insertion ++ * evq_tail = index of slot used for next removal ++ * queue is empty when head == tail ++ * queue is full when (head + 1) % queue_size == tail ++ * queue is nearly full when (head + 2) % queue_size == tail ++ * ++ * Only tail is modified here, and the event handler only ++ * modifies head, so theoretically no race can exist between ++ * queue insertion/removal. The mutex is here only to ++ * cover the case of multiple readers. ++ */ ++static ssize_t ++read_kpcimgr(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) ++{ ++ static DEFINE_MUTEX(evq_lock); ++ kstate_t *ks = get_kstate(); ++ char localmem[EVENT_SIZE]; ++ ssize_t n = 0; ++ int tail; ++ ++ mutex_lock(&evq_lock); ++ tail = ks->evq_tail; ++ ++ while (nbytes >= EVENT_SIZE && ks->evq_head != tail) { ++ /* ++ * intermediate copy since we cannot prevent copy_to_user ++ * from doing cache operations ++ */ ++ kpci_memcpy(localmem, (void *)ks->evq[tail], EVENT_SIZE); ++ ++ if (copy_to_user(buf + n, localmem, EVENT_SIZE)) { ++ mutex_unlock(&evq_lock); ++ return -EFAULT; ++ } ++ ++ tail = (tail + 1) % EVENT_QUEUE_LENGTH; ++ n = n + EVENT_SIZE; ++ nbytes = nbytes - EVENT_SIZE; ++ } ++ ks->evq_tail = tail; ++ mutex_unlock(&evq_lock); ++ ++ return n; ++} ++ ++/* ++ * pciemgrd wants to select() on /dev/kpcimgr to discover ++ * if there are events in the event queue. ++ */ ++static unsigned int ++poll_kpcimgr(struct file *file, poll_table *wait) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ poll_wait(file, &event_queue, wait); ++ if (ks->evq_head != ks->evq_tail) ++ return POLLIN | POLLRDNORM; ++ else ++ return 0; ++} ++ ++static int mmap_kpcimgr(struct file *file, struct vm_area_struct *vma) ++{ ++ phys_addr_t offset = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT; ++ size_t size = vma->vm_end - vma->vm_start; ++ pgprot_t pgprot = vma->vm_page_prot; ++ kstate_t *ks = get_kstate(); ++ unsigned long pfn, start; ++ void *pos; ++ ++ if (offset + size > ks->shmem_size) ++ return -EINVAL; ++ ++ if (ks->shmembase) { ++ pfn = (ks->shmembase + offset) >> PAGE_SHIFT; ++ pgprot = pgprot_device(pgprot); ++ ++ if (!(file->f_flags & O_SYNC)) ++ return -EINVAL; ++ ++ pgprot = pgprot_writecombine(pgprot); ++ vma->vm_page_prot = pgprot; ++ if (remap_pfn_range(vma, vma->vm_start, pfn, ++ size, vma->vm_page_prot)) ++ return -EINVAL; ++ } else { ++ for (start = vma->vm_start, pos = ks->shmemva + offset; ++ size > 0; ++ start += PAGE_SIZE, pos += PAGE_SIZE, size -= PAGE_SIZE) { ++ pfn = vmalloc_to_pfn(pos); ++ if (remap_pfn_range(vma, start, pfn, ++ PAGE_SIZE, vma->vm_page_prot)) ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++/* ++ * Semantics of open(): if no code is loaded then open fails. ++ */ ++static int open_kpcimgr(struct inode *inode, struct file *filp) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ if (ks->valid == KSTATE_MAGIC) ++ return 0; ++ else ++ return -ENODEV; ++} ++ ++static int compute_code_layout(struct kpcimgr_entry_points_t *ep, ++ unsigned long *start_addr, ++ unsigned long *code_size) ++{ ++ unsigned long code_start_addr; ++ ++ /* Use explicit code_start if provided, otherwise compute from entry points */ ++ if (ep->code_start) { ++ code_start_addr = (unsigned long)ep->code_start; ++ } else { ++ /* Fallback: compute from minimum entry point address */ ++ unsigned long minaddr = ~0UL; ++ bool have = false; ++ int i; ++ ++ for (i = 0; i < K_NUM_ENTRIES; i++) { ++ if (!ep->entry_point[i]) ++ continue; ++ have = true; ++ if ((unsigned long)ep->entry_point[i] < minaddr) ++ minaddr = (unsigned long)ep->entry_point[i]; ++ } ++ ++ if (!have) ++ return -EINVAL; ++ ++ code_start_addr = minaddr; ++ } ++ ++ if (!ep->code_end || ++ (unsigned long)ep->code_end <= code_start_addr) ++ return -EINVAL; ++ ++ *start_addr = code_start_addr; ++ *code_size = (unsigned long)ep->code_end - code_start_addr; ++ return 0; ++} ++ ++/* ++ * Examine code and look for calls (BL insn) and data references ++ * (ADRP) to memory addresses outside of the bounds of the module. If ++ * any are found, report them and return an error. ++ */ ++static int contains_external_refs(void *code_start, void *code_end) ++{ ++ unsigned long start = (unsigned long)code_start; ++ unsigned long size = (unsigned long)code_end - start; ++ unsigned long insn_addr, target; ++ int insn_count = 0, call_count = 0, adrp_count = 0; ++ s32 offset; ++ u32 insn; ++ char code_loc[KSYM_SYMBOL_LEN], target_ref[KSYM_SYMBOL_LEN]; ++ ++ for (insn_addr = start; ++ insn_addr < start + size; ++ insn_addr += sizeof(u32)) { ++ ++ if (aarch64_insn_read((void *)insn_addr, &insn)) { ++ pr_err("Failed to read insn @ %lx\n", insn_addr); ++ return 1; ++ } ++ insn_count++; ++ ++ if (aarch64_insn_is_bl(insn)) { ++ offset = aarch64_get_branch_offset(insn); ++ target = insn_addr + offset; ++ ++ if (target >= start && ++ target < (unsigned long)code_end) ++ continue; ++ ++ sprint_symbol(code_loc, insn_addr); ++ sprint_symbol(target_ref, target); ++ pr_err("Found call to %s at %s\n", ++ target_ref, code_loc); ++ call_count++; ++ } ++ ++ if (aarch64_insn_is_adrp(insn)) { ++ offset = aarch64_insn_adrp_get_offset(insn); ++ target = (insn_addr & PAGE_MASK) + offset; ++ ++ if (target >= start && ++ target < (unsigned long)code_end) ++ continue; ++ ++ sprint_symbol(code_loc, insn_addr); ++ sprint_symbol(target_ref, target); ++ pr_err("Found approximate reference to %s at %s\n", ++ target_ref, code_loc); ++ adrp_count++; ++ } ++ } ++ ++ pr_info("processed %d insns, %d extern calls, %d extern adrps\n", ++ insn_count, call_count, adrp_count); ++ ++ return (call_count || adrp_count); ++} ++ ++/* ++ * module_register ++ * ++ * Register module code/data to be used with kpcimgr. If requested, we ++ * relocate module code to "non-linux memory". The struct module ++ * pointer is not quite enough to do this, and we require a pointer ++ * to the end of the module code section. This is because we need to ++ * examine the code for certain instructions, and we don't want to ++ * look beyond the end of the code since that will be data which ++ * might contain values which just look like instructions. ++ * ++ * If the code contains no external references, then we can freely ++ * relocate the code repeatedly without relinking. ++ * ++ * We shut down service and then copy the module in its entirety to ++ * non-linux memory which we have previously mapped executable. ++ * ++ * We can also run with the module code unrelocated, but this is only ++ * for debugging, as it preserves the modules symbols in kallsyms, so ++ * any stack trace will show useful function names instead of raw hex ++ * addresses. ++ * ++ * After the copy, we restart the service if necessary. ++ */ ++int kpcimgr_module_register(struct module *mod, ++ struct kpcimgr_entry_points_t *ep, int relocate) ++{ ++ kstate_t *ks = get_kstate(); ++ unsigned long start_addr, code_size, iflags; ++ void (*init_fn)(kstate_t *); ++ void (*version_fn)(char **); ++ char *mod_buildtime = ""; ++ int i, was_running, nentries; ++ ++ if (compute_code_layout(ep, &start_addr, &code_size)) { ++ pr_err("KPCIMGR: invalid code layout for '%s'\n", mod->name); ++ return -EINVAL; ++ } ++ ++ if (code_size > KSTATE_CODE_SIZE) { ++ pr_err("KPCIMGR: module '%s' too large\n", mod->name); ++ return -EFBIG; ++ } ++ ++ /* Check for external references - the module must be fully relocatable ++ * for kexec support. Unity builds should have no external refs. ++ */ ++ if (contains_external_refs((void *)start_addr, ep->code_end)) { ++ pr_err("KPCIMGR: relocation failed for '%s' - module has external refs\n", mod->name); ++ pr_err("KPCIMGR: hint: use unity build (pciesvc_unity.c) to eliminate cross-file references\n"); ++ return -ENXIO; ++ } ++ ++ was_running = ks->running; ++ if (was_running) { ++ pr_info("%s: kpcimgr has stopped running\n", __func__); ++ kpcimgr_stop_running(); ++ } ++ spin_lock_irqsave(&kpcimgr_lock, iflags); ++ ks->valid = 0; ++ ++ if (ks->mod) { ++ module_put(ks->mod); ++ ks->mod = NULL; ++ ks->code_base = NULL; ++ } ++ ++ if (ks->code_base) ++ execmem_free(ks->code_base); ++ ++ if (relocate) { ++ ks->code_base = execmem_alloc(EXECMEM_MODULE_TEXT, ++ PAGE_ALIGN(code_size)); ++ if (!ks->code_base) ++ return -ENOMEM; ++ ++ kpci_memcpy(ks->code_base, (void *)start_addr, code_size); ++ flush_icache_range((long)ks->code_base, ++ (long)ks->code_base + code_size); ++ set_memory_x((unsigned long)ks->code_base, ++ code_size >> PAGE_SHIFT); ++ } else { ++ try_module_get(mod); ++ ks->mod = mod; ++ ks->code_base = (void *)start_addr; ++ } ++ ++ ks->code_size = code_size; ++ ++#ifdef PEN_COMPAT_V2 ++ nentries = ep->expected_mgr_version == 2 ? 7 : K_NUM_ENTRIES; ++#else ++ nentries = K_NUM_ENTRIES; ++#endif ++ for (i = 0; i < nentries; i++) ++ ks->code_offsets[i] = (unsigned long)ep->entry_point[i] ++ - start_addr; ++ for (; i < K_NUM_ENTRIES; i++) ++ ks->code_offsets[i] = 0; ++ ++ if (ks->code_offsets[K_ENTRY_INIT_FN]) { ++ init_fn = ks->code_base + ks->code_offsets[K_ENTRY_INIT_FN]; ++ init_fn(ks); ++ } ++ ++ mod_buildtime = ""; ++ if (ks->code_offsets[K_ENTRY_GET_VERSION]) { ++ version_fn = ks->code_base + ks->code_offsets[K_ENTRY_GET_VERSION]; ++ version_fn(&mod_buildtime); ++ } ++ ++ pr_info("KPCIMGR: module '%s: %s', start=%lx, end=%lx, size=%lu\n", ++ mod->name, mod_buildtime, ++ start_addr, start_addr + code_size, code_size); ++ ++ set_init_state(ks); ++ ks->valid = KSTATE_MAGIC; ++ ks->lib_version_major = ep->lib_version_major; ++ ks->lib_version_minor = ep->lib_version_minor; ++ ++ spin_unlock_irqrestore(&kpcimgr_lock, iflags); ++ if (was_running) { ++ kpcimgr_start_running(); ++ pr_info("%s: kpcimgr will begin running\n", __func__); ++ } else { ++ reset_stats(ks); ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL(kpcimgr_module_register); ++ ++static void unmap_resources(void) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ int i; ++ ++ for (i = 0; i < ks->nranges; i++) { ++ if (ks->mem_ranges[i].vaddr) ++ iounmap(ks->mem_ranges[i].vaddr); ++ } ++ ++ if (ks->uart_addr) ++ iounmap(ks->uart_addr); ++ ++ if (ks->have_persistent_mem) { ++ if (ks->persistent_base) ++ iounmap(ks->persistent_base); ++ if (ks->shmemva) ++ iounmap(ks->shmemva); ++ iounmap(ks); ++ } else { ++ vfree(ks->shmemva); ++ vfree((void *)ks); ++ } ++} ++ ++static int map_resources(struct platform_device *pfdev) ++{ ++ struct device_node *dn = pfdev->dev.of_node; ++ u32 shmem_idx, hwmem_idx; ++ struct resource res; ++ kstate_t *ks; ++ void *shmem; ++ int i, err; ++ ++ err = of_property_read_u32(dn, "hwmem-index", &hwmem_idx); ++ if (err) { ++ pr_err("KPCIMGR: no hwmem-index value found\n"); ++ return -ENOMEM; ++ } ++ ++ err = of_property_read_u32(dn, "shmem-index", &shmem_idx); ++ if (err) { ++ pr_err("KPCIMGR: no shmem-index value found\n"); ++ return -ENOMEM; ++ } ++ ++ err = of_address_to_resource(dn, shmem_idx, &res); ++ if (err) { ++ pr_err("KPCIMGR: no resource found for shmem-index=%d\n", ++ shmem_idx); ++ return -ENOMEM; ++ } ++ ++ if (res.start == 0) { ++ /* indicates no persistent memory */ ++ pr_info("KPCIMGR: no persistent memory\n"); ++ ks = vmalloc(sizeof(kstate_t)); ++ if (ks == NULL) ++ return -ENOMEM; ++ memset((void *)ks, 0, sizeof(kstate_t)); ++ ks->active_port = -1; ++ ks->have_persistent_mem = 0; ++ shmem = vmalloc(resource_size(&res)); ++ if (shmem == NULL) { ++ vfree((void *)ks); ++ return -ENOMEM; ++ } ++ ks->shmembase = 0; ++ ks->shmem_size = resource_size(&res); ++ } else { ++ if (resource_size(&res) > SHMEM_KSTATE_OFFSET) { ++ pr_err("KPCIMGR: shmem size overlaps kstate\n"); ++ return -ENODEV; ++ } ++ shmem = ioremap(res.start, resource_size(&res)); ++ if (shmem == NULL) { ++ pr_err("KPCIMGR: failed to map shmem\n"); ++ return -ENODEV; ++ } ++ ++ ks = ioremap(res.start + SHMEM_KSTATE_OFFSET, sizeof(kstate_t)); ++ if (ks == NULL) { ++ pr_err("KPCIMGR: failed to map kstate\n"); ++ iounmap(shmem); ++ return -ENOMEM; ++ } ++ if (ks->valid != KSTATE_MAGIC) { ++ kpci_memset((void *)ks, 0, sizeof(kstate_t)); ++ ks->active_port = -1; ++ } ++ ++ ks->have_persistent_mem = 1; ++ ks->shmembase = res.start; ++ ks->shmem_size = resource_size(&res); ++ pr_info("KPCIMGR: kstate mapped %llx at %lx\n", ++ res.start + SHMEM_KSTATE_OFFSET, (long)ks); ++ ++ ks->persistent_base = ioremap(res.start + KSTATE_CODE_OFFSET, ++ KSTATE_CODE_SIZE); ++ if (ks->persistent_base == NULL) { ++ pr_err("KPCIMGR: failed to map shmem code space\n"); ++ goto errout; ++ } ++ ++ if (ks->valid == KSTATE_MAGIC) { ++ ks->code_base = execmem_alloc(EXECMEM_MODULE_TEXT, ++ PAGE_ALIGN(ks->code_size)); ++ if (ks->code_base == NULL) { ++ pr_err("KPCIMGR: execmem_alloc(%lx) failed\n", ++ ks->code_size); ++ goto errout; ++ } ++ kpci_memcpy(ks->code_base, ks->persistent_base, ++ ks->code_size); ++ flush_icache_range((long)ks->code_base, (long)ks->code_base + KSTATE_CODE_SIZE); ++ set_memory_x((unsigned long)ks->code_base, ++ ks->code_size >> PAGE_SHIFT); ++ } ++ } ++ ++ kstate = ks; ++ ks->shmemva = shmem; ++ ++ ks->uart_addr = ioremap(PEN_UART, 0x1000); ++ if (ks->uart_addr == NULL) { ++ pr_err("KPCIMGR: failed to map elba uart\n"); ++ goto errout; ++ } ++ ks->driver_start_time = read_sysreg(cntvct_el0); ++ ++ ks->nranges = 0; ++ for (i = 0; i < NUM_MEMRANGES; i++) { ++ struct mem_range_t *mr = &ks->mem_ranges[ks->nranges]; ++ ++ if (i == shmem_idx) ++ continue; ++ ++ err = of_address_to_resource(dn, i, &res); ++ if (err) ++ break; ++ ++ mr->base = res.start; ++ mr->end = res.start + resource_size(&res); ++ mr->vaddr = ioremap(res.start, resource_size(&res)); ++ if (IS_ERR(mr->vaddr)) { ++ pr_err(PFX "iomap resource %d failed\n", i); ++ goto errout; ++ } ++ if (i == hwmem_idx) ++ ks->hwmem_idx = ks->nranges; ++ ks->nranges++; ++ } ++ return 0; ++ ++ errout: ++ unmap_resources(); ++ return -ENOMEM; ++} ++ ++/* ++ * ISR for indirect transaction ++ */ ++static irqreturn_t kpcimgr_indirect_intr(int irq, void *arg) ++{ ++ int (*intr_fn)(kstate_t *, int); ++ kstate_t *ks = (kstate_t *)arg; ++ int port, r = 0; ++ ++ spin_lock(&kpcimgr_lock); ++ if (ks->valid == KSTATE_MAGIC) { ++ ks->ind_intr++; ++ intr_fn = ks->code_base + ++ ks->code_offsets[K_ENTRY_INDIRECT_INTR]; ++ ++ port = ks->active_port; ++ if (port >= 0) ++ r = intr_fn(ks, port); ++ } ++ spin_unlock(&kpcimgr_lock); ++ ++ return r ? IRQ_HANDLED : IRQ_NONE; ++} ++ ++/* ++ * ISR for notify transaction ++ */ ++static irqreturn_t kpcimgr_notify_intr(int irq, void *arg) ++{ ++ int (*intr_fn)(kstate_t *, int); ++ kstate_t *ks = (kstate_t *)arg; ++ int port, r = 0; ++ ++ spin_lock(&kpcimgr_lock); ++ if (ks->valid == KSTATE_MAGIC) { ++ ks->not_intr++; ++ intr_fn = ks->code_base + ks->code_offsets[K_ENTRY_NOTIFY_INTR]; ++ ++ port = ks->active_port; ++ if (port >= 0) ++ r = intr_fn(ks, port); ++ } ++ spin_unlock(&kpcimgr_lock); ++ ++ return r ? IRQ_HANDLED : IRQ_NONE; ++} ++ ++static u64 kpcimgr_preg_read(u64 pa) ++{ ++ u32 val; ++ ++ pciep_regrd32((uint64_t)pa, &val); ++ return (u64)val; ++} ++ ++static u64 kpcimgr_upcall(int req, u64 arg1, u64 arg2, u64 arg3) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ if (ks->valid != KSTATE_MAGIC) /* no code loaded */ ++ return 1; ++ ++ switch (req) { ++ case WAKE_UP_EVENT_QUEUE: ++ ks->event_intr++; ++ wake_up_event_queue(); ++ break; ++ case PRINT_LOG_MSG: ++ printk((char *)arg1); /* KERN_LEVEL provided by arg1 */ ++ break; ++ case PREG_READ: ++ return kpcimgr_preg_read(arg1); ++ default: ++ return 1; ++ } ++ return 0; ++} ++ ++static void free_intrs(struct platform_device *pfdev) ++{ ++ kstate_t *ks = get_kstate(); ++ struct device *dev = &pfdev->dev; ++ ++ /* 1. Release the handlers first */ ++ free_irq(msi_get_virq(dev, MSI_INDIRECT_IDX), (void *)ks); ++ free_irq(msi_get_virq(dev, MSI_NOTIFY_IDX), (void *)ks); ++ ++ /* 2. Tear down the MSI descriptors and data structures */ ++ platform_device_msi_free_irqs_all(dev); ++} ++ ++struct { ++ irqreturn_t (*isr)(int irq, void *arg); ++ char *name; ++} kpcimgr_irq_table[] = { ++ { kpcimgr_indirect_intr, "kpcimgr-indirect"}, ++ { kpcimgr_notify_intr, "kpcimgr-notify" }, ++}; ++ ++static int alloc_intrs(struct platform_device *pfdev) ++{ ++ struct device *dev = &pfdev->dev; ++ kstate_t *ks = get_kstate(); ++ int r; ++ ++ /* * REMOVED: msi_domain_alloc_irqs(dev, 0, MSI_NVECTORS); ++ * This is now handled in probe via platform_device_msi_init_and_alloc_irqs. ++ */ ++ ++ r = devm_request_irq(dev, msi_get_virq(dev, MSI_INDIRECT_IDX), ++ kpcimgr_irq_table[MSI_INDIRECT_IDX].isr, 0, ++ kpcimgr_irq_table[MSI_INDIRECT_IDX].name, (void *)ks); ++ if (r) ++ goto err_out; ++ ++ r = devm_request_irq(dev, msi_get_virq(dev, MSI_NOTIFY_IDX), ++ kpcimgr_irq_table[MSI_NOTIFY_IDX].isr, 0, ++ kpcimgr_irq_table[MSI_NOTIFY_IDX].name, (void *)ks); ++ if (r) ++ goto err_out; ++ ++ return 0; ++ ++ err_out: ++ free_intrs(pfdev); ++ return r; ++} ++ ++/* ++ * Called when a kexec is about to happen ++ */ ++static int kpcimgr_notify_reboot(struct notifier_block *this, ++ unsigned long code, ++ void *unused) ++{ ++ kstate_t *ks = get_kstate(); ++ int was_running = ks->running; ++ ++ /* stop running regardless of why a reboot is happening */ ++ free_intrs(ks->pfdev); ++ if (was_running) ++ kpcimgr_stop_running(); ++ ++ if (!ks->code_base) { ++ pr_err("KPCIMGR: halting since no code is loaded\n"); ++ ks->valid = 0; ++ return NOTIFY_DONE; ++ } ++ ++ if (!ks->have_persistent_mem) { ++ pr_err("KPCIMGR: halting since code is not persistent\n"); ++ ks->valid = 0; ++ return NOTIFY_DONE; ++ } ++ ++ /* relocate code to "persistent" memory */ ++ kpci_memcpy(ks->persistent_base, ks->code_base, ks->code_size); ++ ++ if (code == SYS_DOWN) { ++ pr_err("KPCIMGR: going down at tick %lld\n", ++ read_sysreg(cntvct_el0)); ++ ++ if (was_running) ++ ks->running = 1; ++ ++ reset_stats(ks); ++ ks->ncalls = 0; ++ ks->kexec_time = read_sysreg(cntvct_el0); ++ } ++ return NOTIFY_DONE; ++} ++ ++/* ++ * Driver Initialization ++ */ ++static const struct file_operations __maybe_unused kpcimgr_fops = { ++ .owner = THIS_MODULE, ++ .read = read_kpcimgr, ++ .poll = poll_kpcimgr, ++ .open = open_kpcimgr, ++ .mmap = mmap_kpcimgr, ++}; ++ ++static struct miscdevice kpcimgr_dev = { ++ MISC_DYNAMIC_MINOR, ++ KPCIMGR_NAME, ++ &kpcimgr_fops ++}; ++ ++/* Restore the callback from the original patch */ ++static void set_msi_msg(struct msi_desc *desc, struct msi_msg *msg) ++{ ++ kstate_t *ks = get_kstate(); ++ /* Ensure we don't out-of-bounds the msi array */ ++ if (desc->msi_index < MSI_NVECTORS) { ++ struct msi_info *msi = &ks->msi[desc->msi_index]; ++ msi->msgaddr = ((u64)msg->address_hi << 32) | msg->address_lo; ++ msi->msgdata = msg->data; ++ } ++} ++ ++static int kpcimgr_probe(struct platform_device *pfdev) ++{ ++ struct device *dev = &pfdev->dev; ++ kstate_t *ks; ++ int err; ++ ++ #if IS_ENABLED(CONFIG_OF) ++ of_msi_configure(dev, dev->of_node); ++ #endif ++ ++ /* * IMPORTANT: In 6.12, we must provide the callback to ++ * handle the specific Elba MSI message format. ++ */ ++ err = platform_device_msi_init_and_alloc_irqs(dev, MSI_NVECTORS, set_msi_msg); ++ if (err) { ++ /* If this still returns -22, we need to check the DTS DeviceID 0xf */ ++ dev_err(dev, "Failed to initialize MSI interrupts: %d\n", err); ++ return err; ++ } ++ ++ err = map_resources(pfdev); ++ if (err) ++ goto errout_free_msi; ++ ++ ks = get_kstate(); ++ ks->pfdev = pfdev; ++ ++ /* * Note: Since platform_device_msi_init_and_alloc_irqs already allocated ++ * the IRQs, we just need to request them (attach ISRs). ++ */ ++ err = alloc_intrs(ks->pfdev); ++ if (err) { ++ pr_err(PFX "alloc intrs: %d\n", err); ++ goto errout_unmap; ++ } ++ ++ ++ err = misc_register(&kpcimgr_dev); ++ if (err) { ++ pr_err(PFX "register pciemgr_dev failed: %d\n", err); ++ goto errout_free_intrs; ++ } ++ ++ ks->upcall = (void *)kpcimgr_upcall; ++ ks->mod = NULL; ++ if (ks->valid == KSTATE_MAGIC && ks->running) { ++ kpcimgr_start_running(); ++ kpcimgr_normal_poll(); ++ pr_err("KPCIMGR: initialized and running.\n"); ++ } ++ if (ks->have_persistent_mem) { ++ static struct notifier_block kpcimgr_nb = { ++ .notifier_call = kpcimgr_notify_reboot, ++ .next = NULL, ++ .priority = 0, ++ }; ++ register_reboot_notifier(&kpcimgr_nb); ++ } ++ ++ pr_info("KPCIMGR: kstate mapped at %lx, code at %lx\n", ++ (long)ks, (long)ks->code_base); ++ ++ kpcimgr_sysfs_setup(pfdev); ++ return 0; ++ ++errout_free_intrs: ++ /* This now calls the updated free_intrs which cleans up ++ both IRQ requests and the MSI descriptors */ ++ free_intrs(pfdev); ++ ++errout_unmap: ++ unmap_resources(); ++ ++errout_free_msi: ++ /* If map_resources failed before alloc_intrs was called, ++ we still need to free the MSI structures allocated at the start */ ++ if (dev->msi.data) ++ platform_device_msi_free_irqs_all(dev); ++ ++ return err; ++} ++ ++static const struct of_device_id kpcimgr_of_match[] = { ++ { .compatible = "pensando,kpcimgr" }, ++ { /* end of table */ } ++}; ++ ++static struct platform_driver kpcimgr_driver = { ++ .probe = kpcimgr_probe, ++ .driver = { ++ .name = "pensando-kpcimgr", ++ .owner = THIS_MODULE, ++ .of_match_table = kpcimgr_of_match, ++ }, ++}; ++// Remove builtin_platform_driver(kpcimgr_driver); ++static int __init kpcimgr_late_init(void) { ++ return platform_driver_register(&kpcimgr_driver); ++} ++late_initcall(kpcimgr_late_init); ++ ++/* ++ * Get entry point for pciesvc specific secondary cpu holding pen. ++ * Called from arch/arm64/kernel/smp_spin_table.c ++ * We choose the first cpu to arrive here. They will all try ++ * concurrently, but only one will be hijacked and the rest ++ * will go to their default holding pens. ++ */ ++unsigned long kpcimgr_get_entry(unsigned long old_entry, unsigned int cpu) ++{ ++ unsigned long (*entry_fn)(unsigned long entry, unsigned int cpu); ++ static DEFINE_SPINLOCK(choose_cpu_lock); ++ kstate_t *ks = get_kstate(); ++ unsigned long entry; ++ ++ if (ks == NULL || ks->valid != KSTATE_MAGIC || ++ !ks->running || !ks->have_persistent_mem) ++ return old_entry; ++ ++ entry_fn = ks->code_base + ks->code_offsets[K_ENTRY_HOLDING_PEN]; ++ ++ spin_lock(&choose_cpu_lock); ++ entry = entry_fn(old_entry, cpu); ++ spin_unlock(&choose_cpu_lock); ++ ++ return entry; ++} +diff --git a/drivers/soc/pensando/kpcimgr_api.h b/drivers/soc/pensando/kpcimgr_api.h +new file mode 100644 +index 0000000..7bc7b9e +--- /dev/null ++++ b/drivers/soc/pensando/kpcimgr_api.h +@@ -0,0 +1,179 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. ++ */ ++#ifndef __KPCIMGR_API_H__ ++#define __KPCIMGR_API_H__ ++ ++#ifdef __KERNEL__ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#endif ++ ++#include "kpci_constants.h" ++ ++#define K_ENTRY_INIT_INTR 0 ++#define K_ENTRY_INIT_POLL 1 ++#define K_ENTRY_SHUT 2 ++#define K_ENTRY_POLL 3 ++#define K_ENTRY_HOLDING_PEN 4 ++#define K_ENTRY_INDIRECT_INTR 5 ++#define K_ENTRY_NOTIFY_INTR 6 ++#define K_ENTRY_INIT_FN 7 ++#define K_ENTRY_CMD_READ 8 ++#define K_ENTRY_CMD_WRITE 9 ++#define K_ENTRY_GET_VERSION 10 ++#define K_NUM_ENTRIES 16 ++ ++struct kpcimgr_entry_points_t { ++ int expected_mgr_version; ++ int lib_version_major; ++ int lib_version_minor; ++ void *code_start; ++ void *code_end; ++ void *entry_point[K_NUM_ENTRIES]; ++}; ++ ++/* upcalls */ ++#define WAKE_UP_EVENT_QUEUE 1 ++#define PRINT_LOG_MSG 2 ++#define PREG_READ 3 ++ ++/* event queue sizing */ ++#define EVENT_QUEUE_LENGTH 1024 ++#define EVENT_SIZE 128 ++ ++/* max command size for sysfs cmd node */ ++#define CMD_SIZE 4096 ++ ++/* max number of memory ranges from device tree */ ++#define NUM_MEMRANGES 32 ++ ++struct kpcimgr_state_t { ++ /* essential state */ ++ int valid; ++ int debug; ++ int running; ++ int active_port; ++ int have_persistent_mem; ++ int lib_version_major; ++ int lib_version_minor; ++ ++ /* timestamps and general trace data */ ++ long kexec_time; ++ long driver_start_time; ++ unsigned long trace_data[NUM_PHASES][DATA_SIZE]; ++ ++ /* virtual addresses */ ++ void *uart_addr; ++ void *code_base; ++ void *persistent_base; ++ void *upcall; ++ void *pfdev; ++ void *shmemva; ++ ++ unsigned long shmembase, shmem_size, code_size; ++ struct mem_range_t { ++ unsigned long base, end; ++ void *vaddr; ++ } mem_ranges[NUM_MEMRANGES]; ++ int nranges; ++ int hwmem_idx; ++ ++ /* interrupt vectors */ ++ struct msi_info { ++ unsigned long msgaddr; ++ unsigned int msgdata; ++ } msi[MSI_NVECTORS]; ++ ++ /* stats for work done */ ++ int ind_cfgrd, ind_cfgwr; ++ int ind_memrd, ind_memwr; ++ int ncalls; ++ int ind_intr, not_intr, event_intr; ++ ++ int unused1[7]; /* was version=2 code_offsets[], keep evq* compat */ ++ ++ /* Event queue handling */ ++ int evq_head, evq_tail; ++ char evq[EVENT_QUEUE_LENGTH][EVENT_SIZE]; ++ ++ /* debugging */ ++ void *mod; ++ int msg_idx; ++ int cfgval; ++ ++ /* offsets into relocated library code */ ++ int code_offsets[K_NUM_ENTRIES]; ++}; ++ ++typedef struct kpcimgr_state_t kstate_t; ++_Static_assert(sizeof(kstate_t) < SHMEM_KSTATE_SIZE, ++ "kstate size insufficient"); ++ ++/* trace_data[] elements */ ++#define FIRST_CALL_TIME 0 ++#define FIRST_SEQNUM 1 ++#define LAST_SEQNUM 2 ++#define TAG 3 ++#define PA_BAD_CNT 4 ++#define NUM_CHECKS 5 ++#define NUM_CALLS 6 ++#define NUM_PENDINGS 7 ++#define LAST_CALL_TIME 8 ++#define EARLY_POLL 9 ++#define MAX_DATA 10 ++ ++#define KPCIMGR_DEV "/dev/kpcimgr" ++#define KPCIMGR_NAME "kpcimgr" ++#define PFX KPCIMGR_NAME ": " ++#define KPCIMGR_KERNEL_VERSION 3 ++ ++#ifdef __KERNEL__ ++int kpcimgr_module_register(struct module *mod, ++ struct kpcimgr_entry_points_t *ep, int relocate); ++void kpcimgr_start_running(void); ++void kpcimgr_stop_running(void); ++void kpcimgr_sysfs_setup(struct platform_device *pfdev); ++void *kpci_memcpy(void *dst, const void *src, size_t n); ++void wake_up_event_queue(void); ++int aarch64_insn_read(void *addr, u32 *insnp); ++extern spinlock_t kpcimgr_lock; ++ ++#define reset_stats(k) \ ++ kpci_memset((void *)&(k)->trace_data[0][0], 0, sizeof((k)->trace_data)) ++ ++static inline void set_init_state(kstate_t *k) ++{ ++ k->trace_data[NORMAL][FIRST_CALL_TIME] = 0; ++ k->ncalls = 0; ++} ++ ++static inline kstate_t *get_kstate(void) ++{ ++ extern kstate_t *kstate; ++ return kstate; ++} ++#endif ++ ++#endif +diff --git a/drivers/soc/pensando/kpcimgr_sysfs.c b/drivers/soc/pensando/kpcimgr_sysfs.c +new file mode 100644 +index 0000000..fcee544 +--- /dev/null ++++ b/drivers/soc/pensando/kpcimgr_sysfs.c +@@ -0,0 +1,277 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Kernel PCIE Manager SYSFS functions ++ * ++ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. ++ */ ++ ++#include "kpcimgr_api.h" ++ ++int kpcimgr_active_port; ++ ++/* 'valid' read returns value of valid field */ ++static ssize_t valid_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ return sprintf(buf, "%x\n", ks->valid); ++} ++ ++/* 'valid' write causes invalidation, regardless of value written */ ++static ssize_t valid_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ if (ks->running) { ++ kpcimgr_stop_running(); ++ pr_info("%s: kpcimgr has stopped running\n", __func__); ++ } ++ ks->valid = 0; ++ ks->debug = 0; ++ if (ks->mod) { ++ module_put(ks->mod); ++ ks->mod = NULL; ++ ks->code_base = NULL; ++ } ++ ++ pr_info("%s: code unloaded\n", __func__); ++ return count; ++} ++ ++static ssize_t running_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ return sprintf(buf, "%x\n", ks->running | ks->debug); ++} ++ ++static ssize_t running_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ kstate_t *ks = get_kstate(); ++ ssize_t rc; ++ long val; ++ ++ rc = kstrtol(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (!ks->valid) ++ return -EINVAL; ++ ++ if (val == 0) { ++ if (ks->running) { ++ kpcimgr_stop_running(); ++ pr_info("%s: kpcimgr has stopped polling\n", __func__); ++ } ++ } else { ++ if (ks->running) { ++ pr_info("%s: kpcimgr is already running\n", __func__); ++ } else { ++ ks->active_port = ffs(kpcimgr_active_port) - 1; ++ pr_info("%s: kpcimgr will begin running on port %d\n", ++ __func__, ks->active_port); ++ kpcimgr_start_running(); ++ } ++ ks->debug = val & 0xfff0; ++ } ++ ++ return count; ++} ++ ++static ssize_t cfgval_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ return sprintf(buf, "%x\n", ks->cfgval); ++} ++ ++static ssize_t cfgval_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ kstate_t *ks = get_kstate(); ++ ssize_t rc; ++ long val; ++ ++ rc = kstrtol(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (!ks->valid) ++ return -EINVAL; ++ ++ ks->cfgval = val; ++ return count; ++} ++ ++static ssize_t lib_version_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ if (!ks->valid) ++ return -ENODEV; ++ ++ return sprintf(buf, "%d.%d\n", ks->lib_version_major, ++ ks->lib_version_minor); ++} ++ ++static ssize_t mgr_version_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return sprintf(buf, "%d\n", KPCIMGR_KERNEL_VERSION); ++} ++ ++static ssize_t command_read(struct file *file, struct kobject *kobj, ++ struct bin_attribute *attr, char *out, ++ loff_t off, size_t count) ++{ ++ int (*cmd_read)(kstate_t *, char *, loff_t, size_t, int *); ++ kstate_t *ks = get_kstate(); ++ int ret, success = 0; ++ unsigned long flags; ++ ++ if (!ks->valid) ++ return -ENODEV; ++ if (ks->code_offsets[K_ENTRY_CMD_READ]) { ++ cmd_read = ks->code_base + ks->code_offsets[K_ENTRY_CMD_READ]; ++ spin_lock_irqsave(&kpcimgr_lock, flags); ++ ret = cmd_read(ks, out, off, count, &success); ++ spin_unlock_irqrestore(&kpcimgr_lock, flags); ++ } ++ if (success) ++ return ret; ++ else ++ return 0; ++} ++ ++static ssize_t command_write(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *bin_attr, char *buf, ++ loff_t off, size_t count) ++{ ++ int (*cmd_write)(kstate_t *, const char *, loff_t, size_t, int *); ++ kstate_t *ks = get_kstate(); ++ int ret, success = 0; ++ unsigned long flags; ++ ++ if (!ks->valid) ++ return -ENODEV; ++ if (ks->code_offsets[K_ENTRY_CMD_WRITE]) { ++ cmd_write = ks->code_base + ks->code_offsets[K_ENTRY_CMD_WRITE]; ++ spin_lock_irqsave(&kpcimgr_lock, flags); ++ ret = cmd_write(ks, buf, off, count, &success); ++ spin_unlock_irqrestore(&kpcimgr_lock, flags); ++ } ++ if (success) ++ return ret; ++ else ++ return count; ++} ++ ++/* event queue peek */ ++static ssize_t event_queue_read(struct file *file, struct kobject *kobj, ++ struct bin_attribute *attr, char *out, ++ loff_t off, size_t count) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ /* is queue empty? */ ++ if (ks->evq_head == ks->evq_tail) ++ return 0; ++ ++ kpci_memcpy(out, (void *)ks->evq[ks->evq_tail], EVENT_SIZE); ++ return EVENT_SIZE; ++} ++ ++/* ++ * This function is for testing. It injects an event onto the ++ * event queue, simulating an event notification from h/w. ++ */ ++static ssize_t event_queue_write(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *bin_attr, char *buf, ++ loff_t off, size_t count) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ if (count != EVENT_SIZE) ++ return -EINVAL; ++ ++ if ((ks->evq_head + 1) % EVENT_QUEUE_LENGTH == ks->evq_tail) ++ return -ENOSPC; ++ ++ kpci_memcpy((void *)ks->evq[ks->evq_head], buf, EVENT_SIZE); ++ ks->evq_head = (ks->evq_head + 1) % EVENT_QUEUE_LENGTH; ++ wake_up_event_queue(); ++ ++ return EVENT_SIZE; ++} ++ ++static ssize_t kstate_read(struct file *file, struct kobject *kobj, ++ struct bin_attribute *attr, char *out, ++ loff_t off, size_t count) ++{ ++ kstate_t *ks = get_kstate(); ++ ++ kpci_memcpy(out, (void *)ks + off, count); ++ return count; ++} ++ ++static DEVICE_ATTR_RW(valid); ++static DEVICE_ATTR_RW(running); ++static DEVICE_ATTR_RW(cfgval); ++static DEVICE_ATTR_RO(lib_version); ++static DEVICE_ATTR_RO(mgr_version); ++static DEVICE_INT_ATTR(active_port, 0644, kpcimgr_active_port); ++static BIN_ATTR_RO(kstate, sizeof(kstate_t)); ++static BIN_ATTR_RW(event_queue, EVENT_SIZE); ++static BIN_ATTR_RW(command, CMD_SIZE); ++ ++static struct attribute *dev_attrs[] = { ++ &dev_attr_valid.attr, ++ &dev_attr_running.attr, ++ &dev_attr_cfgval.attr, ++ &dev_attr_active_port.attr.attr, ++ &dev_attr_lib_version.attr, ++ &dev_attr_mgr_version.attr, ++ NULL, ++}; ++ ++static struct bin_attribute *dev_bin_attrs[] = { ++ &bin_attr_kstate, ++ &bin_attr_event_queue, ++ &bin_attr_command, ++ NULL, ++}; ++ ++const struct attribute_group kpci_attr_group = { ++ .attrs = dev_attrs, ++ .bin_attrs = dev_bin_attrs, ++}; ++ ++void kpcimgr_sysfs_setup(struct platform_device *pfdev) ++{ ++ if (sysfs_create_group(&pfdev->dev.kobj, &kpci_attr_group)) { ++ pr_err("KPCIMGR:sysfs_create_group failed\n"); ++ return; ++ } ++ ++ if (sysfs_create_link(kernel_kobj, &pfdev->dev.kobj, "kpcimgr")) { ++ pr_err("KPCIMGR: failed top create sysfs link\n"); ++ return; ++ } ++} +-- +1.8.3.1 + diff --git a/patches-sonic/0017-dt-bindings-trivial-devices-Add-Delta-Q54SN120A1-and-Q54SW120A7.patch b/patches-sonic/0017-dt-bindings-trivial-devices-Add-Delta-Q54SN120A1-and-Q54SW120A7.patch new file mode 100644 index 000000000..5feb2cd29 --- /dev/null +++ b/patches-sonic/0017-dt-bindings-trivial-devices-Add-Delta-Q54SN120A1-and-Q54SW120A7.patch @@ -0,0 +1,32 @@ +From 32a7bdbd201be809c690c64a3a233c7bb640c48c Mon Sep 17 00:00:00 2001 +From: Colin Huang +Date: Mon, 16 Mar 2026 16:39:33 +0800 +Subject: [PATCH] dt-bindings: trivial-devices: Add Delta Q54SN120A1 and + Q54SW120A7 + +Add two additional Delta 1/4-brick DC/DC power modules, +Q54SN120A1 and Q54SW120A7, to the trivial-devices list. + +Acked-by: Conor Dooley +Signed-off-by: Colin Huang +Link: https://lore.kernel.org/r/20260316-add-q54sn120a1-q54q54sw120a7-v2-1-60e6182cc4a7@gmail.com +Signed-off-by: Guenter Roeck +--- + Documentation/devicetree/bindings/trivial-devices.yaml | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml +index ccac768f5380a5..03a27494322364 100644 +--- a/Documentation/devicetree/bindings/trivial-devices.yaml ++++ b/Documentation/devicetree/bindings/trivial-devices.yaml +@@ -101,6 +101,10 @@ properties: + - delta,dps920ab + # 1/4 Brick DC/DC Regulated Power Module + - delta,q54sj108a2 ++ # 1300W 1/4 Brick DC/DC Regulated Power Module ++ - delta,q54sn120a1 ++ # 2000W 1/4 Brick DC/DC Regulated Power Module ++ - delta,q54sw120a7 + # Devantech SRF02 ultrasonic ranger in I2C mode + - devantech,srf02 + # Devantech SRF08 ultrasonic ranger diff --git a/patches-sonic/0017-leds-mlxreg-Add-support-for-new-flavour-of-capabilit.patch b/patches-sonic/0017-leds-mlxreg-Add-support-for-new-flavour-of-capabilit.patch new file mode 100644 index 000000000..d61644802 --- /dev/null +++ b/patches-sonic/0017-leds-mlxreg-Add-support-for-new-flavour-of-capabilit.patch @@ -0,0 +1,62 @@ +From 5dc4e5b4c1244e5d974dc87b2b8caf898d7d721c Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 20 Jul 2023 11:01:56 +0000 +Subject: [PATCH 17/78] leds: mlxreg: Add support for new flavour of capability + register + +LED platform data is common across the various systems, while LED +driver should be able to apply only the LED instances relevant +to specific system. + +For example, platform data might contain descriptions for fan1, +fan2, ..., fan{n} LEDs, while some systems equipped with all 'n' fan +LEDs, others with less. + +For detection of the real number of equipped LEDs special capability +register is used. +This register used to indicate presence of LED through the bitmap. + +For some new big modular systems this register will provide presence +data by counter. + +Use slot parameter to distinct whether capability register contains +bitmask or counter. + +Signed-off-by: Vadim Pasternak +--- + drivers/leds/leds-mlxreg.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +index 1b70de7..5b73cc4 100644 +--- a/drivers/leds/leds-mlxreg.c ++++ b/drivers/leds/leds-mlxreg.c +@@ -204,16 +204,22 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) + dev_err(&priv->pdev->dev, "Failed to query capability register\n"); + return err; + } +- if (!(regval & data->bit)) ++ /* ++ * If slot is specified - validate if slot is equipped on system. ++ * In case slot is specified in platform data, capability register ++ * comtains the counter of untits. ++ */ ++ if (data->slot && data->slot > regval) ++ continue; ++ else if (!(regval & data->bit) && !data->slot) + continue; + /* + * Field "bit" can contain one capability bit in 0 byte + * and offset bit in 1-3 bytes. Clear capability bit and +- * keep only offset bit. ++ * contains the counter of units. + */ + data->bit &= MLXREG_LED_CAPABILITY_CLEAR; + } +- + led_cdev = &led_data->led_cdev; + led_data->data_parent = priv; + if (strstr(data->label, "red") || +-- +2.8.4 + diff --git a/patches-sonic/0018-drivers-i2c-fix-after-kdump-crash.patch b/patches-sonic/0018-drivers-i2c-fix-after-kdump-crash.patch new file mode 100644 index 000000000..07c84ab02 --- /dev/null +++ b/patches-sonic/0018-drivers-i2c-fix-after-kdump-crash.patch @@ -0,0 +1,46 @@ +From da0d305f60d9686048033d0765c20c763ad5a986 Mon Sep 17 00:00:00 2001 +From: Elad Nachman +Date: Sun, 18 May 2025 14:02:58 +0000 +Subject: [PATCH 1/2] drivers: i2c: fix after kdump crash + +Spurious interrupts left while i2c controller still +at RX or TX state would try to access the RX or TX +buffer pointer, which is NULL. +Add check to verify buffer pointer is not NULL before +reading or writing the buffer for additional TX or RX +operations. + +Signed-off-by: Elad Nachman +--- + drivers/i2c/busses/i2c-mv64xxx.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c +index 13e9bdeb4..36fdd9b19 100644 +--- a/drivers/i2c/busses/i2c-mv64xxx.c ++++ b/drivers/i2c/busses/i2c-mv64xxx.c +@@ -413,14 +413,18 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data) + break; + + case MV64XXX_I2C_ACTION_SEND_DATA: +- writel(drv_data->msg->buf[drv_data->byte_posn++], +- drv_data->reg_base + drv_data->reg_offsets.data); ++ if (drv_data->msg && drv_data->msg->buf) ++ writel(drv_data->msg->buf[drv_data->byte_posn++], ++ drv_data->reg_base + drv_data->reg_offsets.data); + writel(drv_data->cntl_bits, + drv_data->reg_base + drv_data->reg_offsets.control); + break; + + case MV64XXX_I2C_ACTION_RCV_DATA: +- drv_data->msg->buf[drv_data->byte_posn++] = ++ if (drv_data->msg && drv_data->msg->buf) ++ drv_data->msg->buf[drv_data->byte_posn++] = ++ readl(drv_data->reg_base + drv_data->reg_offsets.data); ++ else + readl(drv_data->reg_base + drv_data->reg_offsets.data); + writel(drv_data->cntl_bits, + drv_data->reg_base + drv_data->reg_offsets.control); +-- +2.34.1 + diff --git a/patches-sonic/0018-dts-pensando-add-mnet-and-mcrypt-devices-with-reserv.patch b/patches-sonic/0018-dts-pensando-add-mnet-and-mcrypt-devices-with-reserv.patch new file mode 100644 index 000000000..b460888c6 --- /dev/null +++ b/patches-sonic/0018-dts-pensando-add-mnet-and-mcrypt-devices-with-reserv.patch @@ -0,0 +1,133 @@ +From 02eb3baa3e66053efa1bf763d1e067a2cdf06b4b Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:27:02 +0000 +Subject: [PATCH 18/27] dts/pensando: add mnet and mcrypt devices, with + reserved dma memory + +This patch adds the mnet and mcrypt devices to the device-tree, along +with a reserved memory region for use by the mnet instances. This +avoids +memory allocation failure due to wider system memory fragmentation. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba-asic-memory.dtsi | 16 +++++ + arch/arm64/boot/dts/pensando/elba-asic.dts | 2 + + arch/arm64/boot/dts/pensando/elba-mdev.dtsi | 71 ++++++++++++++++++++++ + 3 files changed, 89 insertions(+) + create mode 100644 arch/arm64/boot/dts/pensando/elba-asic-memory.dtsi + create mode 100644 arch/arm64/boot/dts/pensando/elba-mdev.dtsi + +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-memory.dtsi b/arch/arm64/boot/dts/pensando/elba-asic-memory.dtsi +new file mode 100644 +index 0000000..6652b42 +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-asic-memory.dtsi +@@ -0,0 +1,16 @@ ++ ++/ { ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ ranges; ++ ++ mnet_reserved: mnet_dma_region { ++ compatible = "shared-dma-pool"; ++ no-map; ++ size = <0x0 0x6000000>; ++ alignment = <0x0 0x1000>; ++ }; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/pensando/elba-asic.dts b/arch/arm64/boot/dts/pensando/elba-asic.dts +index 8c482bd..70d0dbd 100644 +--- a/arch/arm64/boot/dts/pensando/elba-asic.dts ++++ b/arch/arm64/boot/dts/pensando/elba-asic.dts +@@ -22,3 +22,5 @@ + #include "elba-16core-spin-table.dtsi" + #include "elba-asic-common.dtsi" + #include "elba-flash-parts.dtsi" ++#include "elba-asic-memory.dtsi" ++#include "elba-mdev.dtsi" +diff --git a/arch/arm64/boot/dts/pensando/elba-mdev.dtsi b/arch/arm64/boot/dts/pensando/elba-mdev.dtsi +new file mode 100644 +index 0000000..a728a76 +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-mdev.dtsi +@@ -0,0 +1,71 @@ ++/ { ++ dma-coherent; ++ ++ mnet0: mnet0 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x0>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet1: mnet1 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x1>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet2: mnet2 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x2>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet3: mnet3 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x3>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet4: mnet4 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x4>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet5: mnet5 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x5>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet6: mnet6 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x6>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet7: mnet7 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x7>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet8: mnet8 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x8>; ++ memory-region = <&mnet_reserved>; ++ }; ++ mnet9: mnet9 { ++ compatible = "pensando,mnet"; ++ msi-parent = <&its 0x9>; ++ memory-region = <&mnet_reserved>; ++ }; ++ ++ mcrypt0: mcrypt0 { ++ compatible = "pensando,mcrypt"; ++ msi-parent = <&its 0xb>; ++ }; ++ mcrypt1: mcrypt1 { ++ compatible = "pensando,mcrypt"; ++ msi-parent = <&its 0xc>; ++ }; ++ mcrypt2: mcrypt2 { ++ compatible = "pensando,mcrypt"; ++ msi-parent = <&its 0xd>; ++ }; ++ mcrypt3: mcrypt3 { ++ compatible = "pensando,mcrypt"; ++ msi-parent = <&its 0xe>; ++ }; ++}; +-- +1.8.3.1 + diff --git a/patches-sonic/0018-hwmon-pmbus-Add-Delta-Q54SN120A1-Q54SW120A7-chip.patch b/patches-sonic/0018-hwmon-pmbus-Add-Delta-Q54SN120A1-Q54SW120A7-chip.patch new file mode 100644 index 000000000..f5cb16be1 --- /dev/null +++ b/patches-sonic/0018-hwmon-pmbus-Add-Delta-Q54SN120A1-Q54SW120A7-chip.patch @@ -0,0 +1,72 @@ +From ffa2ad0aa64697c29d501f76cb7b7187cbbb147a Mon Sep 17 00:00:00 2001 +From: Colin Huang +Date: Mon, 16 Mar 2026 16:39:34 +0800 +Subject: [PATCH] hwmon: (pmbus) Add Delta Q54SN120A1 Q54SW120A7 chip + +Add the DELTA chips Q54SN120A1, Q54SW120A7 in q54sj108a2, +1/4 Brick DC/DC Regulated Power Module with PMBus support + +Signed-off-by: Colin Huang +Link: https://lore.kernel.org/r/20260316-add-q54sn120a1-q54q54sw120a7-v2-2-60e6182cc4a7@gmail.com +Signed-off-by: Guenter Roeck +--- + drivers/hwmon/pmbus/q54sj108a2.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c +index 7e799f36f923ed..a368cfa9d45ac2 100644 +--- a/drivers/hwmon/pmbus/q54sj108a2.c ++++ b/drivers/hwmon/pmbus/q54sj108a2.c +@@ -271,6 +271,8 @@ static const struct file_operations q54sj108a2_fops = { + + static const struct i2c_device_id q54sj108a2_id[] = { + { "q54sj108a2", q54sj108a2 }, ++ { "q54sn120a1", q54sj108a2 }, ++ { "q54sw120a7", q54sj108a2 }, + { }, + }; + +@@ -280,6 +282,7 @@ static int q54sj108a2_probe(struct i2c_client *client) + { + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; ++ const struct i2c_device_id *mid; + enum chips chip_id; + int ret, i; + struct dentry *debugfs; +@@ -313,8 +316,12 @@ static int q54sj108a2_probe(struct i2c_client *client) + dev_err(dev, "Failed to read Manufacturer Model\n"); + return ret; + } +- if (ret != 14 || strncmp(buf, "Q54SJ108A2", 10)) { +- buf[ret] = '\0'; ++ buf[ret] = '\0'; ++ for (mid = q54sj108a2_id; mid->name[0]; mid++) { ++ if (!strncasecmp(mid->name, buf, strlen(mid->name))) ++ break; ++ } ++ if (!mid->name[0]) { + dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } +@@ -324,7 +331,10 @@ static int q54sj108a2_probe(struct i2c_client *client) + dev_err(dev, "Failed to read Manufacturer Revision\n"); + return ret; + } +- if (ret != 4 || buf[0] != 'S') { ++ /* ++ * accept manufacturer revision with optional NUL byte ++ */ ++ if (!(ret == 4 || ret == 5) || buf[0] != 'S') { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf); + return -ENODEV; +@@ -401,6 +411,8 @@ static int q54sj108a2_probe(struct i2c_client *client) + + static const struct of_device_id q54sj108a2_of_match[] = { + { .compatible = "delta,q54sj108a2", .data = (void *)q54sj108a2 }, ++ { .compatible = "delta,q54sn120a1", .data = (void *)q54sj108a2 }, ++ { .compatible = "delta,q54sw120a7", .data = (void *)q54sj108a2 }, + { }, + }; + diff --git a/patches-sonic/0018-i2c-mv64xxx-kdump-sonic.patch b/patches-sonic/0018-i2c-mv64xxx-kdump-sonic.patch new file mode 100644 index 000000000..0f08411e3 --- /dev/null +++ b/patches-sonic/0018-i2c-mv64xxx-kdump-sonic.patch @@ -0,0 +1,44 @@ +From 3c04dd543d85e6e087e7ad3147b47b3d6d702bdd Mon Sep 17 00:00:00 2001 +From: Elad Nachman +Date: Tue, 29 Jul 2025 11:09:46 +0300 +Subject: [PATCH v2 1/1] i2c: mv64xxx: prevent illegal pointer access + +Spurious interrupts left while i2c controller still at RX or TX state +would try to access the RX or TX buffer pointer, which is NULL. +Add check to verify buffer pointer is not NULL before reading or writing +the buffer for additional TX or RX operations. + +Signed-off-by: Elad Nachman +--- + drivers/i2c/busses/i2c-mv64xxx.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c +index 8fc26a511320..e6baa9c520b4 100644 +--- a/drivers/i2c/busses/i2c-mv64xxx.c ++++ b/drivers/i2c/busses/i2c-mv64xxx.c +@@ -390,14 +390,18 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data) + break; + + case MV64XXX_I2C_ACTION_SEND_DATA: +- writel(drv_data->msg->buf[drv_data->byte_posn++], +- drv_data->reg_base + drv_data->reg_offsets.data); ++ if (drv_data->msg && drv_data->msg->buf) ++ writel(drv_data->msg->buf[drv_data->byte_posn++], ++ drv_data->reg_base + drv_data->reg_offsets.data); + writel(drv_data->cntl_bits, + drv_data->reg_base + drv_data->reg_offsets.control); + break; + + case MV64XXX_I2C_ACTION_RCV_DATA: +- drv_data->msg->buf[drv_data->byte_posn++] = ++ if (drv_data->msg && drv_data->msg->buf) ++ drv_data->msg->buf[drv_data->byte_posn++] = ++ readl(drv_data->reg_base + drv_data->reg_offsets.data); ++ else + readl(drv_data->reg_base + drv_data->reg_offsets.data); + writel(drv_data->cntl_bits, + drv_data->reg_base + drv_data->reg_offsets.control); +-- +2.25.1 + diff --git a/patches-sonic/0018-leds-mlxreg-Remove-code-for-amber-LED-colour.patch b/patches-sonic/0018-leds-mlxreg-Remove-code-for-amber-LED-colour.patch new file mode 100644 index 000000000..d7e616952 --- /dev/null +++ b/patches-sonic/0018-leds-mlxreg-Remove-code-for-amber-LED-colour.patch @@ -0,0 +1,46 @@ +From 5feddc9d395ceb20792619e506d93bdbcc9b5cbf Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 20 Jul 2023 11:17:31 +0000 +Subject: [PATCH 18/78] leds: mlxreg: Remove code for amber LED colour + +Remove unused code for amber LED colour. + +In case system LED color is "green", "orange" or "amber" same code is +to be used for colour setting. + +Signed-off-by: Vadim Pasternak +--- + drivers/leds/leds-mlxreg.c | 8 ++------ + 1 file changed, 2 insertions(+), 6 deletions(-) + +diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +index 5b73cc4..a7057b0 100644 +--- a/drivers/leds/leds-mlxreg.c ++++ b/drivers/leds/leds-mlxreg.c +@@ -18,7 +18,6 @@ + #define MLXREG_LED_IS_OFF 0x00 /* Off */ + #define MLXREG_LED_RED_SOLID 0x05 /* Solid red */ + #define MLXREG_LED_GREEN_SOLID 0x0D /* Solid green */ +-#define MLXREG_LED_AMBER_SOLID 0x09 /* Solid amber */ + #define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */ + #define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */ + #define MLXREG_LED_CAPABILITY_CLEAR GENMASK(31, 8) /* Clear mask */ +@@ -222,13 +221,10 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) + } + led_cdev = &led_data->led_cdev; + led_data->data_parent = priv; +- if (strstr(data->label, "red") || +- strstr(data->label, "orange")) { ++ if (strstr(data->label, "red") || strstr(data->label, "orange") || ++ strstr(data->label, "amber")) { + brightness = LED_OFF; + led_data->base_color = MLXREG_LED_RED_SOLID; +- } else if (strstr(data->label, "amber")) { +- brightness = LED_OFF; +- led_data->base_color = MLXREG_LED_AMBER_SOLID; + } else { + brightness = LED_OFF; + led_data->base_color = MLXREG_LED_GREEN_SOLID; +-- +2.8.4 + diff --git a/patches-sonic/0019-Extend-driver-to-support-Infineon-Digital-Multi-phas.patch b/patches-sonic/0019-Extend-driver-to-support-Infineon-Digital-Multi-phas.patch new file mode 100644 index 000000000..0e83590f0 --- /dev/null +++ b/patches-sonic/0019-Extend-driver-to-support-Infineon-Digital-Multi-phas.patch @@ -0,0 +1,37 @@ +From 92de6069e75d9981c2f9ab0f7cbd2970236d51e3 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 17 Jul 2023 15:40:08 +0000 +Subject: [PATCH 19/78] Extend driver to support Infineon Digital Multi-phase + XDPE1A2G7 device. + +From telemetry perspective device is almost the same as XDPE15284, but +does not support READ_EIN (86h) and READ_EOUT (87h) commands. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/pmbus/xdpe152c4.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/hwmon/pmbus/xdpe152c4.c b/drivers/hwmon/pmbus/xdpe152c4.c +index 7f3b31d..089f268 100644 +--- a/drivers/hwmon/pmbus/xdpe152c4.c ++++ b/drivers/hwmon/pmbus/xdpe152c4.c +@@ -44,6 +44,7 @@ static int xdpe152_probe(struct i2c_client *client) + } + + static const struct i2c_device_id xdpe152_id[] = { ++ {"xdpe1a2g7"}, + {"xdpe152c4"}, + {"xdpe15284"}, + {} +@@ -52,6 +53,7 @@ static const struct i2c_device_id xdpe152_id[] = { + MODULE_DEVICE_TABLE(i2c, xdpe152_id); + + static const struct of_device_id __maybe_unused xdpe152_of_match[] = { ++ {.compatible = "infineon,xdpe1a2g7"}, + {.compatible = "infineon,xdpe152c4"}, + {.compatible = "infineon,xdpe15284"}, + {} +-- +2.8.4 + diff --git a/patches-sonic/0019-drivers-soc-pensando-sbus-driver.patch b/patches-sonic/0019-drivers-soc-pensando-sbus-driver.patch new file mode 100644 index 000000000..82b35fa93 --- /dev/null +++ b/patches-sonic/0019-drivers-soc-pensando-sbus-driver.patch @@ -0,0 +1,393 @@ +From ed1d277e2603b83955043941f01ff850bb44de75 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:29:44 +0000 +Subject: [PATCH 19/27] drivers/soc/pensando sbus driver + +sbus driver provides read/write capability to the devices +on the sbus rings using ioctl interface to /dev/sbus device. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba.dtsi | 24 +++ + drivers/soc/pensando/Kconfig | 7 + + drivers/soc/pensando/Makefile | 1 + + drivers/soc/pensando/sbus.c | 305 +++++++++++++++++++++++++++++++++ + 4 files changed, 337 insertions(+) + create mode 100644 drivers/soc/pensando/sbus.c + +diff --git a/arch/arm64/boot/dts/pensando/elba.dtsi b/arch/arm64/boot/dts/pensando/elba.dtsi +index f567386..e9451c2 100644 +--- a/arch/arm64/boot/dts/pensando/elba.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba.dtsi +@@ -288,5 +288,29 @@ + 0x0 0xc4000000 0x0 0x01000000 /* HWMEM */ + 0x0 0x0 0x0 0x02000000>; /* SHMEM */ + }; ++ ++ sbus@307c24a8 { ++ compatible = "pensando,sbus"; ++ sbus-ring-num = <0>; ++ reg = <0x0 0x307c24a8 0x0 0x8>; /* SBUS0 REG */ ++ }; ++ ++ sbus@307c24b8 { ++ compatible = "pensando,sbus"; ++ sbus-ring-num = <1>; ++ reg = <0x0 0x307c24b8 0x0 0x8>; /* SBUS1 REG */ ++ }; ++ ++ sbus@307c24c8 { ++ compatible = "pensando,sbus"; ++ sbus-ring-num = <2>; ++ reg = <0x0 0x307c24c8 0x0 0x8>; /* SBUS2 REG */ ++ }; ++ ++ sbus@6f242134 { ++ compatible = "pensando,sbus"; ++ sbus-ring-num = <3>; ++ reg = <0x0 0x6f242134 0x0 0x8>; /* SBUS3 REG */ ++ }; + }; + }; +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +index be2d3db..fd290aa 100644 +--- a/drivers/soc/pensando/Kconfig ++++ b/drivers/soc/pensando/Kconfig +@@ -49,6 +49,13 @@ config PENSANDO_SOC_BSM_ENABLE + help + Re-enable the BSM on kernel startup + ++config PENSANDO_SOC_SBUS ++ tristate "Pensando sbus read/write driver" ++ depends on OF ++ default y ++ help ++ Support for the Pensando SoC sbus read/write driver ++ + endmenu + + endif +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +index 5c89a15..555438a 100644 +--- a/drivers/soc/pensando/Makefile ++++ b/drivers/soc/pensando/Makefile +@@ -12,3 +12,4 @@ obj-$(CONFIG_PENSANDO_SOC_PCIE) += kpcimgr.o kpcimgr_sysfs.o + obj-$(CONFIG_PENSANDO_SOC_RSTCAUSE) += cap_rstcause.o + obj-$(CONFIG_PENSANDO_SOC_CRASH) += cap_crash.o + obj-$(CONFIG_PENSANDO_SOC_BSM) += cap_bsm.o ++obj-$(CONFIG_PENSANDO_SOC_SBUS) += sbus.o +diff --git a/drivers/soc/pensando/sbus.c b/drivers/soc/pensando/sbus.c +new file mode 100644 +index 0000000..b796a60 +--- /dev/null ++++ b/drivers/soc/pensando/sbus.c +@@ -0,0 +1,305 @@ ++/* ++ * Copyright (c) 2022, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MAX_DEVICES 4 ++#define SBUS_SBUS_RST 0x20 ++#define SBUS_SBUS_WR 0x21 ++#define SBUS_SBUS_RD 0x22 ++#define SBUS_INDIR_DATA_ADDR_LSB 8 ++#define SBUS_INDIR_DATA_COMMAND_LSB 16 ++ ++static dev_t sbus_dev = 0; ++static struct class *dev_class; ++static int dev_inst = 0; ++ ++struct sbus_ioctl_args { ++ u32 sbus_rcvr_addr; ++ u32 sbus_data_addr; ++ u32 sbus_data; ++}; ++ ++struct sbusdev_info { ++ struct platform_device *pdev; ++ void __iomem *sbus_indir; ++ void __iomem *sbus_dhs; ++ spinlock_t sbus_lock; ++ struct cdev cdev; ++}; ++ ++#define SBUS_WRITE _IOW('a', 'a', struct sbus_ioctl_args) ++#define SBUS_READ _IOWR('a', 'b', struct sbus_ioctl_args) ++#define SBUS_RESET _IOW('a', 'c', struct sbus_ioctl_args) ++ ++static int sbus_drv_open(struct inode *inode, struct file *file) ++{ ++ struct sbusdev_info *sbus_ring; /* device information */ ++ ++ sbus_ring = container_of(inode->i_cdev, struct sbusdev_info, cdev); ++ file->private_data = sbus_ring; ++ pr_debug("Device File Opened...!!\n"); ++ return 0; ++} ++ ++static void sbus_write(struct sbus_ioctl_args param, ++ struct sbusdev_info *sbus_ring) ++{ ++ uint32_t sbus_val; ++ ++ sbus_val = param.sbus_rcvr_addr | ++ (param.sbus_data_addr << SBUS_INDIR_DATA_ADDR_LSB) | ++ (SBUS_SBUS_WR << SBUS_INDIR_DATA_COMMAND_LSB); ++ ++ pr_debug("sbus_rcvr_addr %d sbus_data_addr %d sbus_data %d\n", ++ param.sbus_rcvr_addr, param.sbus_data_addr, param.sbus_data); ++ ++ spin_lock(&sbus_ring->sbus_lock); ++ ++ iowrite32(sbus_val, sbus_ring->sbus_indir); ++ iowrite32(param.sbus_data, sbus_ring->sbus_dhs); ++ ++ spin_unlock(&sbus_ring->sbus_lock); ++} ++ ++static uint32_t sbus_read(struct sbus_ioctl_args param, ++ struct sbusdev_info * sbus_ring) ++{ ++ uint32_t sbus_val, val; ++ ++ sbus_val = param.sbus_rcvr_addr | ++ (param.sbus_data_addr << SBUS_INDIR_DATA_ADDR_LSB) | ++ (SBUS_SBUS_RD << SBUS_INDIR_DATA_COMMAND_LSB); ++ ++ pr_debug("sbus_rcvr_addr %d sbus_data_addr %d\n", ++ param.sbus_rcvr_addr, param.sbus_data_addr); ++ ++ spin_lock(&sbus_ring->sbus_lock); ++ ++ iowrite32(sbus_val, sbus_ring->sbus_indir); ++ val = ioread32(sbus_ring->sbus_dhs); ++ ++ spin_unlock(&sbus_ring->sbus_lock); ++ ++ return val; ++} ++ ++static void sbus_reset(struct sbus_ioctl_args param, ++ struct sbusdev_info *sbus_ring) ++{ ++ uint32_t sbus_val; ++ ++ sbus_val = param.sbus_rcvr_addr | ++ (param.sbus_data_addr << SBUS_INDIR_DATA_ADDR_LSB) | ++ (SBUS_SBUS_RST << SBUS_INDIR_DATA_COMMAND_LSB); ++ ++ pr_debug("sbus_rcvr_addr %d sbus_data_addr %d\n", ++ param.sbus_rcvr_addr, param.sbus_data_addr); ++ ++ spin_lock(&sbus_ring->sbus_lock); ++ ++ iowrite32(sbus_val, sbus_ring->sbus_indir); ++ iowrite32(0, sbus_ring->sbus_dhs); ++ ++ spin_unlock(&sbus_ring->sbus_lock); ++} ++ ++/* ++ * This function will be called when we write IOCTL on the Device file ++ */ ++static long sbus_drv_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct sbusdev_info *sbus_ring = file->private_data; ++ struct sbus_ioctl_args param_ioctl; ++ ++ switch (cmd) { ++ case SBUS_WRITE: ++ if (copy_from_user ++ (¶m_ioctl, (struct sbus_ioctl_args *)arg, ++ sizeof(struct sbus_ioctl_args))) { ++ return -EFAULT; ++ } ++ sbus_write(param_ioctl, sbus_ring); ++ break; ++ case SBUS_READ: ++ if (copy_from_user ++ (¶m_ioctl, (struct sbus_ioctl_args *)arg, ++ sizeof(struct sbus_ioctl_args))) { ++ return -EFAULT; ++ } ++ param_ioctl.sbus_data = sbus_read(param_ioctl, sbus_ring); ++ if (copy_to_user ++ ((struct sbus_ioctl_args *)arg, ¶m_ioctl, ++ sizeof(struct sbus_ioctl_args))) { ++ return -EFAULT; ++ } ++ break; ++ case SBUS_RESET: ++ if (copy_from_user ++ (¶m_ioctl, (struct sbus_ioctl_args *)arg, ++ sizeof(struct sbus_ioctl_args))) { ++ return -EFAULT; ++ } ++ sbus_reset(param_ioctl, sbus_ring); ++ break; ++ default: ++ return -ENOTTY; ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * File operation structure ++ */ ++static struct file_operations fops = { ++ .open = sbus_drv_open, ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = sbus_drv_ioctl, ++}; ++ ++/* ++ * Module Init function ++ */ ++static int sbus_probe(struct platform_device *pdev) ++{ ++ int major, sbus_ring_num; ++ dev_t device; ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ struct sbusdev_info *sbus_ring; ++ ++ if (dev_inst > MAX_DEVICES-1) ++ return -ENODEV; ++ ++ if (of_property_read_u32(pdev->dev.of_node, "sbus-ring-num", ++ &sbus_ring_num)) { ++ pr_debug("missing sbus-ring-num in device tree\n"); ++ return -EINVAL; ++ } ++ ++ sbus_ring = devm_kzalloc(dev, sizeof(*sbus_ring), GFP_KERNEL); ++ if (!sbus_ring) ++ return -ENOMEM; ++ ++ sbus_ring->pdev = pdev; ++ platform_set_drvdata(pdev, sbus_ring); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ sbus_ring->sbus_indir = devm_ioremap_resource(dev, res); ++ if (IS_ERR(sbus_ring->sbus_indir)) { ++ dev_err(dev, "Cannot remap sbus reg addresses.\n"); ++ return PTR_ERR(sbus_ring->sbus_indir); ++ } ++ sbus_ring->sbus_dhs = sbus_ring->sbus_indir + 0x4; ++ ++ spin_lock_init(&sbus_ring->sbus_lock); ++ ++ pr_debug("sbus_indir %p sbus_dhs %p\n", ++ sbus_ring->sbus_indir, sbus_ring->sbus_dhs); ++ ++ if (dev_inst == 0) { ++ /* Allocating Major number & reserving minor numbers */ ++ if ((alloc_chrdev_region ++ (&sbus_dev, 0, MAX_DEVICES, "pensbus_dev")) < 0) { ++ pr_err("Cannot allocate major number\n"); ++ return -1; ++ } ++ pr_debug("Major = %d Minor = %d \n", MAJOR(sbus_dev), ++ MINOR(sbus_dev)); ++ ++ /* Creating struct class */ ++ if ((dev_class = ++ class_create("sbus_class")) == NULL) { ++ pr_err("Cannot create the struct class\n"); ++ goto r_class; ++ } ++ } ++ ++ major = MAJOR(sbus_dev); ++ ++ /* Creating cdev structure */ ++ cdev_init(&sbus_ring->cdev, &fops); ++ sbus_ring->cdev.owner = THIS_MODULE; ++ sbus_ring->cdev.ops = &fops; ++ ++ /* Adding character device to the system */ ++ device = MKDEV(major, dev_inst); ++ if ((cdev_add(&sbus_ring->cdev, device, 1)) < 0) { ++ pr_err("Cannot add the device to the system\n"); ++ goto r_device; ++ } ++ ++ /* Creating device */ ++ if ((device_create(dev_class, NULL, device, NULL, "sbus%d", ++ sbus_ring_num)) == NULL) { ++ pr_err("Cannot create the Device 1\n"); ++ goto r_device; ++ } ++ dev_inst++; ++ ++ return 0; ++ ++r_device: ++ if (dev_inst == 0) ++ class_destroy(dev_class); ++r_class: ++ if (dev_inst == 0) ++ unregister_chrdev_region(sbus_dev, MAX_DEVICES); ++ ++ return -1; ++} ++ ++/* ++ * Module exit function ++ */ ++static void sbus_remove(struct platform_device *pdev) ++{ ++ dev_inst--; ++ ++ if (dev_inst == 0) { ++ device_destroy(dev_class, sbus_dev); ++ class_destroy(dev_class); ++ unregister_chrdev_region(sbus_dev, MAX_DEVICES); ++ pr_debug("Device Driver Remove...Done!!!\n"); ++ } ++ ++ return; ++} ++ ++static const struct of_device_id sbus_of_match[] = { ++ { .compatible = "pensando,sbus" }, ++ { /* end of table */ } ++}; ++ ++static struct platform_driver sbus_driver = { ++ .probe = sbus_probe, ++ .remove = sbus_remove, ++ .driver = { ++ .name = "pensando-sbus", ++ .owner = THIS_MODULE, ++ .of_match_table = sbus_of_match, ++ }, ++}; ++ ++module_platform_driver(sbus_driver); +-- +1.8.3.1 + diff --git a/patches-sonic/0019-irqchip-mvebu-gicp-Clear-pending-interrupts-on-init.patch b/patches-sonic/0019-irqchip-mvebu-gicp-Clear-pending-interrupts-on-init.patch new file mode 100644 index 000000000..7da1254d2 --- /dev/null +++ b/patches-sonic/0019-irqchip-mvebu-gicp-Clear-pending-interrupts-on-init.patch @@ -0,0 +1,51 @@ +From e7ca85e98fd395437c2633d8ccbf93ee20db93ca Mon Sep 17 00:00:00 2001 +From: Elad Nachman +Date: Sun, 3 Aug 2025 13:25:48 +0300 +Subject: [PATCH 2/2] irqchip/mvebu-gicp: Clear pending interrupts on init + +When a kexec'ed kernel boots up, there might be stale unhandled interrupts +pending in the interrupt controller. These are delivered as spurious +interrupts once the boot CPU enables interrupts. + +Clear all pending interrupts when the driver is initialized to prevent +these spurious interrupts from locking the CPU in an endless loop. + +Signed-off-by: Elad Nachman +Signed-off-by: Thomas Gleixner +Link: https://lore.kernel.org/all/20250803102548.669682-2-enachman@marvell.com +Signed-off-by: Yan Markman +--- + drivers/irqchip/irq-mvebu-gicp.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/drivers/irqchip/irq-mvebu-gicp.c b/drivers/irqchip/irq-mvebu-gicp.c +index 2b6183919..a582b6a2f 100644 +--- a/drivers/irqchip/irq-mvebu-gicp.c ++++ b/drivers/irqchip/irq-mvebu-gicp.c +@@ -173,6 +173,7 @@ static int mvebu_gicp_probe(struct platform_device *pdev) + struct device_node *node = pdev->dev.of_node; + struct device_node *irq_parent_dn; + struct mvebu_gicp *gicp; ++ void __iomem *base; + int ret, i; + + gicp = devm_kzalloc(&pdev->dev, sizeof(*gicp), GFP_KERNEL); +@@ -229,6 +230,15 @@ static int mvebu_gicp_probe(struct platform_device *pdev) + return -ENODEV; + } + ++ base = ioremap(gicp->res->start, gicp->res->end - gicp->res->start); ++ if (IS_ERR(base)) { ++ dev_err(&pdev->dev, "ioremap() failed. Unable to clear pending interrupts.\n"); ++ } else { ++ for (i = 0; i < 64; i++) ++ writel(i, base + GICP_CLRSPI_NSR_OFFSET); ++ iounmap(base); ++ } ++ + inner_domain = irq_domain_create_hierarchy(parent_domain, 0, + gicp->spi_cnt, + of_node_to_fwnode(node), +-- +2.34.1 + diff --git a/patches-sonic/0019-mrvl-gicp-clr-irqs-on-probe.patch b/patches-sonic/0019-mrvl-gicp-clr-irqs-on-probe.patch new file mode 100644 index 000000000..fe8467b6f --- /dev/null +++ b/patches-sonic/0019-mrvl-gicp-clr-irqs-on-probe.patch @@ -0,0 +1,48 @@ +From 21c4ec02660984feece49f1863245d48fc9774a7 Mon Sep 17 00:00:00 2001 +From: Elad Nachman +Date: Sun, 10 Aug 2025 12:48:23 +0300 +Subject: [PATCH v2 1/1] irqchip/mvebu-gicp: clear pending irqs on init + +When a kexec'ed kernel boots up, there might be stale unhandled interrupts +pending in the interrupt controller. These are delivered as spurious +interrupts once the boot CPU enables interrupts. +Clear all pending interrupts when the driver is initialized to prevent +these spurious interrupts from locking the CPU in an endless loop. + +Signed-off-by: Elad Nachman +--- + drivers/irqchip/irq-mvebu-gicp.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/drivers/irqchip/irq-mvebu-gicp.c b/drivers/irqchip/irq-mvebu-gicp.c +index d3232d6d8dce..4b2cad09bc71 100644 +--- a/drivers/irqchip/irq-mvebu-gicp.c ++++ b/drivers/irqchip/irq-mvebu-gicp.c +@@ -172,6 +172,7 @@ + struct device_node *irq_parent_dn; + struct mvebu_gicp *gicp; + int ret, i; ++ void __iomem *base; + + gicp = devm_kzalloc(&pdev->dev, sizeof(*gicp), GFP_KERNEL); + if (!gicp) +@@ -245,6 +246,16 @@ + irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_GENERIC_MSI); + inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + inner_domain->msi_parent_ops = &gicp_msi_parent_ops; ++ ++ base = ioremap(gicp->res->start, resource_size(gicp->res)); ++ if (!base) { ++ dev_err(&pdev->dev, "ioremap() failed. Unable to clear pending interrupts.\n"); ++ } else { ++ for (i = 0; i < 64; i++) ++ writel(i, base + GICP_CLRSPI_NSR_OFFSET); ++ iounmap(base); ++ } ++ + return 0; + } + +-- +2.25.1 + diff --git a/patches-sonic/0020-drivers-soc-pensando-boot_count-to-sysfs-for-kdump-l.patch b/patches-sonic/0020-drivers-soc-pensando-boot_count-to-sysfs-for-kdump-l.patch new file mode 100644 index 000000000..74cbdde6e --- /dev/null +++ b/patches-sonic/0020-drivers-soc-pensando-boot_count-to-sysfs-for-kdump-l.patch @@ -0,0 +1,149 @@ +From c478c83c6a7cf3bdd3e7bd6f713e877e53a79d0f Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:31:58 +0000 +Subject: [PATCH 20/27] drivers/soc/pensando:boot_count to sysfs for kdump log + +Add boot_count to sysfs. Augment the kdump timestamp with +the boot_count number. Also, adjust the kdump timestamp. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + drivers/soc/pensando/cap_crash.c | 10 +++++++--- + drivers/soc/pensando/cap_reboot.c | 35 +++++++++++++++++++++++++++++++++-- + drivers/soc/pensando/cap_reboot.h | 3 ++- + 3 files changed, 42 insertions(+), 6 deletions(-) + +diff --git a/drivers/soc/pensando/cap_crash.c b/drivers/soc/pensando/cap_crash.c +index 385b02d..ebd9464 100644 +--- a/drivers/soc/pensando/cap_crash.c ++++ b/drivers/soc/pensando/cap_crash.c +@@ -12,6 +12,8 @@ + #include + #include + #include ++#include ++#include "cap_reboot.h" + + #define PCRASH_NAME "pensando-crash" + +@@ -73,7 +75,7 @@ static void pcrash_prepare_controller(void) + } + + static void pcrash_do_dump(struct kmsg_dumper *dumper, +- enum kmsg_dump_reason reason) ++ struct kmsg_dump_detail *detail) + { + int idx; + u32 *src; +@@ -91,7 +93,8 @@ static void pcrash_do_dump(struct kmsg_dumper *dumper, + * read first 32bits, if all ff then the new panic data + * can be written to the panic buf. + */ +- if (hdr->magic == 0xffffffff) { ++ if (detail->reason == KMSG_DUMP_PANIC && ++ hdr->magic == 0xffffffff) { + kmsg_dump_rewind(&iter); + kmsg_dump_get_buffer(&iter, false, pcrash->panic_buf, + pcrash->size - sizeof(struct panicbuf_header), &kmsg_dump_len); +@@ -114,7 +117,8 @@ static int cap_panic_callback(struct notifier_block *nb, + + ktime_get_real_ts64(&ts); + time64_to_tm(ts.tv_sec, 0, &tm); +- pr_info("Panic on %04ld-%02d-%02d %02d:%02d:%02d.%06ld\n", ++ pr_info("Panic at Boot #%lu %04ld-%02d-%02d %02d:%02d:%02d.%06ld\n", ++ cap_boot_count(), + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + ts.tv_nsec / 1000); +diff --git a/drivers/soc/pensando/cap_reboot.c b/drivers/soc/pensando/cap_reboot.c +index 42d87f2..d2e1ebf 100644 +--- a/drivers/soc/pensando/cap_reboot.c ++++ b/drivers/soc/pensando/cap_reboot.c +@@ -2,7 +2,7 @@ + /* + * Pensando reboot control via sysfs + * +- * Copyright (c) 2020-2021, Pensando Systems Inc. ++ * Copyright (c) 2020-2022, Pensando Systems Inc. + */ + + #include +@@ -18,12 +18,22 @@ + */ + static int panic_reboot; /* default=0, no reboot */ + ++/* value of system "boot_count" for panic logging */ ++static unsigned long boot_count; ++ + bool cap_panic_reboot(void) + { + if (panic_reboot) + return true; + return false; + } ++EXPORT_SYMBOL_GPL(cap_panic_reboot); ++ ++unsigned long cap_boot_count(void) ++{ ++ return boot_count; ++} ++EXPORT_SYMBOL_GPL(cap_boot_count); + + extern struct kobject *pensando_fw_kobj_get(void); + struct kobject *reboot_kobj; +@@ -45,11 +55,32 @@ static ssize_t panic_reboot_store(struct kobject *kobj, + return count; + } + ++static ssize_t boot_count_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "%lu\n", boot_count); ++} ++ ++static ssize_t boot_count_store(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int ret; ++ ++ ret = kstrtoul(buf, 0, &boot_count); ++ if (ret) ++ return ret; ++ return count; ++} ++ + static struct kobj_attribute panic_reboot_attribute = +- __ATTR(panic_reboot, 0664, panic_reboot_show, panic_reboot_store); ++ __ATTR(panic_reboot, 0644, panic_reboot_show, panic_reboot_store); ++static struct kobj_attribute boot_count_attribute = ++ __ATTR(boot_count, 0644, boot_count_show, boot_count_store); + + static struct attribute *attrs[] = { + &panic_reboot_attribute.attr, ++ &boot_count_attribute.attr, + NULL, + }; + +diff --git a/drivers/soc/pensando/cap_reboot.h b/drivers/soc/pensando/cap_reboot.h +index 8718fff..e4bd89a 100644 +--- a/drivers/soc/pensando/cap_reboot.h ++++ b/drivers/soc/pensando/cap_reboot.h +@@ -1,11 +1,12 @@ + /* SPDX-License-Identifier: GPL-2.0 */ + /* +- * Copyright (c) 2020-2021, Pensando Systems Inc. ++ * Copyright (c) 2020-2022, Pensando Systems Inc. + */ + + #ifndef __CAP_REBOOT_H__ + #define __CAP_REBOOT_H__ + + bool cap_panic_reboot(void); ++unsigned long cap_boot_count(void); + + #endif +-- +1.8.3.1 + diff --git a/patches-sonic/0020-dt-bindings-trivial-devices-Add-infineon-xdpe1a2g7.patch b/patches-sonic/0020-dt-bindings-trivial-devices-Add-infineon-xdpe1a2g7.patch new file mode 100644 index 000000000..73a7e6ab2 --- /dev/null +++ b/patches-sonic/0020-dt-bindings-trivial-devices-Add-infineon-xdpe1a2g7.patch @@ -0,0 +1,28 @@ +From a2f7a59cc93759e1e7cde05831afa049e7413c43 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 17 Jul 2023 16:24:58 +0000 +Subject: [PATCH 20/78] dt-bindings: trivial-devices: Add infineon,xdpe1a2g7 + +Add new Infineon Multi-phase Digital VR Controller xdpe1a2g7 + +Signed-off-by: Vadim Pasternak +--- + Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml +index 9bf0fb1..a190c56 100644 +--- a/Documentation/devicetree/bindings/trivial-devices.yaml ++++ b/Documentation/devicetree/bindings/trivial-devices.yaml +@@ -131,6 +131,8 @@ properties: + - infineon,xdp710 + # Infineon Multi-phase Digital VR Controller xdpe11280 + - infineon,xdpe11280 ++ # Infineon Multi-phase Digital VR Controller xdpe1a2g7 ++ - infineon,xdpe1a2g7 + # Infineon Multi-phase Digital VR Controller xdpe12254 + - infineon,xdpe12254 + # Infineon Multi-phase Digital VR Controller xdpe12284 +-- +2.8.4 + diff --git a/patches-sonic/0021-arch-arm64-boot-dts-psci-support.patch b/patches-sonic/0021-arch-arm64-boot-dts-psci-support.patch new file mode 100644 index 000000000..4206c5e30 --- /dev/null +++ b/patches-sonic/0021-arch-arm64-boot-dts-psci-support.patch @@ -0,0 +1,94 @@ +From bccef5fb697b92538e75fd80cd5ba4cc08882aca Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:34:55 +0000 +Subject: [PATCH 21/27] arch/arm64/boot/dts: psci support + +Change CPU enable-method from 'spin-table' to 'psci'. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba-16core-psci.dtsi | 57 ++++++++++++++++++++++ + arch/arm64/boot/dts/pensando/elba-asic.dts | 2 +- + 2 files changed, 58 insertions(+), 1 deletion(-) + create mode 100644 arch/arm64/boot/dts/pensando/elba-16core-psci.dtsi + +diff --git a/arch/arm64/boot/dts/pensando/elba-16core-psci.dtsi b/arch/arm64/boot/dts/pensando/elba-16core-psci.dtsi +new file mode 100644 +index 0000000..1bae597 +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-16core-psci.dtsi +@@ -0,0 +1,57 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++&cpu0 { ++ enable-method = "psci"; ++}; ++&cpu1 { ++ enable-method = "psci"; ++}; ++&cpu2 { ++ enable-method = "psci"; ++}; ++&cpu3 { ++ enable-method = "psci"; ++}; ++&cpu4 { ++ enable-method = "psci"; ++}; ++&cpu5 { ++ enable-method = "psci"; ++}; ++&cpu6 { ++ enable-method = "psci"; ++}; ++&cpu7 { ++ enable-method = "psci"; ++}; ++&cpu8 { ++ enable-method = "psci"; ++}; ++&cpu9 { ++ enable-method = "psci"; ++}; ++&cpu10 { ++ enable-method = "psci"; ++}; ++&cpu11 { ++ enable-method = "psci"; ++}; ++&cpu12 { ++ enable-method = "psci"; ++}; ++&cpu13 { ++ enable-method = "psci"; ++}; ++&cpu14 { ++ enable-method = "psci"; ++}; ++&cpu15 { ++ enable-method = "psci"; ++}; ++ ++/ { ++ psci { ++ compatible = "arm,psci-1.0"; ++ method = "smc"; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/pensando/elba-asic.dts b/arch/arm64/boot/dts/pensando/elba-asic.dts +index 70d0dbd..dfa6dea 100644 +--- a/arch/arm64/boot/dts/pensando/elba-asic.dts ++++ b/arch/arm64/boot/dts/pensando/elba-asic.dts +@@ -19,7 +19,7 @@ + + #include "elba.dtsi" + #include "elba-16core.dtsi" +-#include "elba-16core-spin-table.dtsi" ++#include "elba-16core-psci.dtsi" + #include "elba-asic-common.dtsi" + #include "elba-flash-parts.dtsi" + #include "elba-asic-memory.dtsi" +-- +1.8.3.1 + diff --git a/patches-sonic/0021-leds-mlxreg-Skip-setting-LED-color-during-initializa.patch b/patches-sonic/0021-leds-mlxreg-Skip-setting-LED-color-during-initializa.patch new file mode 100644 index 000000000..57c60ba4b --- /dev/null +++ b/patches-sonic/0021-leds-mlxreg-Skip-setting-LED-color-during-initializa.patch @@ -0,0 +1,39 @@ +From cbf289ab3a923985e9939ee35814644a3def9f1c Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Wed, 7 Jul 2021 10:18:14 +0000 +Subject: [PATCH 21/78] leds: mlxreg: Skip setting LED color during + initialization + +Hardware controls LED through CPLD device and LED control ownership +passes to the software after it performs the first write operation for +any LED on a system. +For example, hardware sets "system" LED "green blink" during boot and +might change it to "red", in case something is went wrong from hardware +point of view. +The motivation for not setting LED during kernel initialization is for +keeping hardware settings visible for user, until user will not decide +to set LEDs according to user OS specific requirements. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Michael Shych +--- + drivers/leds/leds-mlxreg.c | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +index a7057b0..26add3e 100644 +--- a/drivers/leds/leds-mlxreg.c ++++ b/drivers/leds/leds-mlxreg.c +@@ -244,9 +244,6 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) + if (err) + return err; + +- if (led_cdev->brightness) +- mlxreg_led_brightness_set(led_cdev, +- led_cdev->brightness); + dev_info(led_cdev->dev, "label: %s, mask: 0x%02x, offset:0x%02x\n", + data->label, data->mask, data->reg); + } +-- +2.8.4 + diff --git a/patches-sonic/0022-arm64-dts-marvell-Add-DTS-for-cn9131-db-comexpress-trixie.patch b/patches-sonic/0022-arm64-dts-marvell-Add-DTS-for-cn9131-db-comexpress-trixie.patch new file mode 100644 index 000000000..2e046b7ca --- /dev/null +++ b/patches-sonic/0022-arm64-dts-marvell-Add-DTS-for-cn9131-db-comexpress-trixie.patch @@ -0,0 +1,570 @@ +From 75b9a7225192327f5d0fd3962227b4e1b68e095c Mon Sep 17 00:00:00 2001 +From: Yan Markman +Date: Mon, 3 Nov 2025 14:46:29 +0200 +Subject: [PATCH 1/1] arm64: dts: marvell: Add DTS for cn9131-db-comexpress + trixie + +Signed-off-by: Yan Markman +--- + arch/arm64/boot/dts/marvell/Makefile | 1 + + .../boot/dts/marvell/cn9130-db-comexpress.dts | 316 ++++++++++++++++++ + .../boot/dts/marvell/cn9131-db-comexpress.dts | 212 ++++++++++++ + 3 files changed, 529 insertions(+) + create mode 100644 arch/arm64/boot/dts/marvell/cn9130-db-comexpress.dts + create mode 100644 arch/arm64/boot/dts/marvell/cn9131-db-comexpress.dts + +diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile +index 02cbba99b..78fe5e532 100644 +--- a/arch/arm64/boot/dts/marvell/Makefile ++++ b/arch/arm64/boot/dts/marvell/Makefile +@@ -33,6 +33,7 @@ dtb-$(CONFIG_ARCH_MVEBU) += cn9130-cf-pro.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9131-cf-solidwan.dtb + dtb-$(CONFIG_ARCH_MVEBU) += cn9132-clearfog.dtb + dtb-$(CONFIG_ARCH_MVEBU) += armada-7020-comexpress.dtb ++dtb-$(CONFIG_ARCH_MVEBU) += cn9131-db-comexpress.dtb + dtb-$(CONFIG_ARCH_MVEBU) += 7215-ixs-a1.dtb + dtb-$(CONFIG_ARCH_MVEBU) += smc_sse-g3748.dtb + dtb-$(CONFIG_ARCH_MVEBU) += es1227-54ts.dtb +diff --git a/arch/arm64/boot/dts/marvell/cn9130-db-comexpress.dts b/arch/arm64/boot/dts/marvell/cn9130-db-comexpress.dts +new file mode 100644 +index 000000000..d738a214d +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/cn9130-db-comexpress.dts +@@ -0,0 +1,316 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2025 Marvell International Ltd. ++ */ ++ ++/* This DB board is an extension for "ac5x-rd-carrier-cn9131" */ ++ ++#include "cn9130.dtsi" ++ ++#include ++ ++/ { ++ model = "Marvell Armada CN9130-DB-Comexpress type 7 CPU module (TRIXIE)"; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ aliases { ++ gpio1 = &cp0_gpio1; ++ gpio2 = &cp0_gpio2; ++ i2c0 = &cp0_i2c0; ++ i2c1 = &cp0_i2c1; ++ ethernet0 = &cp0_eth0; ++ ethernet1 = &cp0_eth1; ++ ethernet2 = &cp0_eth2; ++ spi1 = &cp0_spi0; ++ spi2 = &cp0_spi1; ++ }; ++ ++ memory@0 { ++ device_type = "memory"; ++ reg = <0x0 0x0 0x2 0x00000000>; ++ }; ++ ++ ap0_reg_sd_vccq: regulator-1 { /*OLD: ap0_sd_vccq@0 */ ++ /* Don't set ++ * gpios = <&expander0 8 GPIO_ACTIVE_HIGH>; ++ * expander0: pca9555@21 { compatible = "nxp,pca9555"; ++ */ ++ compatible = "regulator-fixed"; /* regulator-gpio not on board */ ++ regulator-name = "ap0_sd_vccq"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ states = <1800000 0x1 1800000 0x0>; ++ gpios-states = <0>; ++ }; ++ ++ cp0_reg_usb3_vbus0: regulator-2 { /*OLD: cp0_usb3_vbus@0 */ ++ compatible = "regulator-fixed"; ++ regulator-name = "cp0-xhci0-vbus"; ++ regulator-min-microvolt = <5000000>; ++ regulator-max-microvolt = <5000000>; ++ enable-active-high; ++ gpios-states = <0>; ++ }; ++ ++ cp0_usb3_0_phy0: usb-phy-1 { /*OLD: cp0_usb3_phy@0 */ ++ compatible = "usb-nop-xceiv"; ++ vcc-supply = <&cp0_reg_usb3_vbus0>; ++ }; ++ ++ cp0_reg_usb3_vbus1: regulator-3 { /*OLD: cp0_usb3_vbus@1 */ ++ compatible = "regulator-fixed"; ++ regulator-name = "cp0-xhci1-vbus"; ++ regulator-min-microvolt = <5000000>; ++ regulator-max-microvolt = <5000000>; ++ enable-active-high; ++ }; ++ ++ cp0_usb3_0_phy1: usb-phy-2 { /*OLD: cp0_usb3_phy@1 */ ++ compatible = "usb-nop-xceiv"; ++ vcc-supply = <&cp0_reg_usb3_vbus1>; ++ }; ++ ++ cp0_reg_sd_vccq: regulator-4 { /*OLD: cp0_sd_vccq@0 */ ++ compatible = "regulator-gpio"; ++ regulator-name = "cp0_sd_vccq"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ states = <1800000 0x1 ++ 3300000 0x0>; ++ }; ++ ++ cp0_reg_sd_vcc: regulator-5 { /*OLD: cp0_sd_vcc@0 */ ++ compatible = "regulator-fixed"; ++ regulator-name = "cp0_sd_vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ enable-active-high; ++ regulator-always-on; ++ }; ++ ++ cp0_sfp_eth0: sfp-eth-1 { /*OLD: sfp-eth@0 */ ++ compatible = "sff,sfp"; ++ /* ++ * SFP cages are unconnected on early PCBs because of an the I2C ++ * lanes not being connected. Prevent the port for being ++ * unusable by disabling the SFP node. ++ */ ++ status = "disabled"; ++ }; ++}; ++ ++&uart0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_uart2_pins>; ++ status = "okay"; ++}; ++ ++/* on-board eMMC - U9 */ ++&ap_sdhci0 { ++ pinctrl-names = "default"; ++ bus-width = <8>; ++ vqmmc-supply = <&ap0_reg_sd_vccq>; ++ status = "okay"; ++ non-removable; ++ mmc-ddr-1_8v; ++ mmc-hs200-1_8v; ++ mmc-hs400-1_8v; ++}; ++ ++&cp0_crypto { ++ status = "disabled"; ++}; ++ ++&cp0_ethernet { ++ status = "okay"; ++}; ++ ++/* SLM-1521-V2, CON9 */ ++&cp0_eth0 { ++ status = "disabled"; ++ phy-mode = "10gbase-kr"; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp0_comphy4 0>; ++ managed = "in-band-status"; ++ sfp = <&cp0_sfp_eth0>; ++}; ++ ++/* CON56 */ ++&cp0_eth1 { ++ status = "okay"; ++ phy = <&phy0>; ++ phy-mode = "rgmii-id"; ++}; ++ ++/* CON57 */ ++&cp0_eth2 { ++ status = "disabled"; ++ phy-mode = "rgmii-id"; ++}; ++ ++&cp0_gpio1 { ++ status = "okay"; ++}; ++ ++&cp0_gpio2 { ++ status = "okay"; ++}; ++ ++&cp0_i2c0 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c0_pins>; ++ clock-frequency = <100000>; ++ ++ /* U42 */ ++ eeprom0: eeprom@50 { ++ compatible = "atmel,24c64"; ++ reg = <0x50>; ++ pagesize = <0x20>; ++ }; ++}; ++ ++&cp0_i2c1 { ++ status = "okay"; ++ clock-frequency = <100000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_i2c1_pins>; ++}; ++ ++&cp0_mdio { ++ status = "okay"; ++ pinctrl-0 = <&cp0_ge_mdio_pins>; ++ phy0: ethernet-phy@0 { ++ marvell,reg-init = <3 16 0 0x1a4a>; ++ reg = <0>; ++ }; ++}; ++ ++/* PCIE X4 Slot */ ++&cp0_pcie0 { ++ status = "okay"; ++ num-lanes = <4>; ++ num-viewport = <8>; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp0_comphy0 0 ++ &cp0_comphy1 0 ++ &cp0_comphy2 0 ++ &cp0_comphy3 0>; ++}; ++ ++/* PCIE X1 Slot */ ++&cp0_pcie2 { ++ status = "okay"; ++ num-lanes = <1>; ++ num-viewport = <8>; ++ phys = <&cp0_comphy5 2>; ++}; ++ ++/* ++ * &cp0_sdhci0 { ++ * status = "disabled"; ++ * pinctrl-names = "default"; ++ * pinctrl-0 = <&cp0_sdhci_pins ++ * &cp0_sdhci_cd_pins>; ++ * bus-width = <4>; ++ * cd-gpios = <&cp0_gpio2 11 GPIO_ACTIVE_LOW>; ++ * no-1-8-v; ++ * vqmmc-supply = <&cp0_reg_sd_vccq>; ++ * vmmc-supply = <&cp0_reg_sd_vcc>; ++ * }; ++ */ ++ ++/* U55 */ ++&cp0_spi1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp0_spi0_pins>; ++ reg = <0x700680 0x50>; ++ ++ spi-flash@0 { ++ #address-cells = <0x1>; ++ #size-cells = <0x1>; ++ compatible = "jedec,spi-nor"; ++ reg = <0x0>; ++ /* On-board MUX does not allow higher frequencies */ ++ spi-max-frequency = <40000000>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "U-Boot-0"; ++ reg = <0x0 0x200000>; ++ }; ++ ++ partition@400000 { ++ label = "Filesystem-0"; ++ reg = <0x200000 0xe00000>; ++ }; ++ }; ++ }; ++}; ++ ++&cp0_syscon0 { ++ cp0_pinctrl: pinctrl { ++ compatible = "marvell,cp115-standalone-pinctrl"; ++ ++ cp0_ge_mdio_pins: ge-mdio-pins { ++ marvell,pins = "mpp40", "mpp41"; ++ marvell,function = "ge"; ++ }; ++ cp0_i2c0_pins: cp0-i2c-pins-0 { ++ marvell,pins = "mpp37", "mpp38"; ++ marvell,function = "i2c0"; ++ }; ++ cp0_i2c1_pins: cp0-i2c-pins-1 { ++ marvell,pins = "mpp35", "mpp36"; ++ marvell,function = "i2c1"; ++ }; ++ cp0_ge1_rgmii_pins: cp0-ge-rgmii-pins-0 { ++ marvell,pins = "mpp0", "mpp1", "mpp2", ++ "mpp3", "mpp4", "mpp5", ++ "mpp6", "mpp7", "mpp8", ++ "mpp9", "mpp10", "mpp11"; ++ marvell,function = "ge0"; ++ }; ++ cp0_sdhci_cd_pins: cp0-sdhci-cd-pins-0 { ++ marvell,pins = "mpp55"; ++ marvell,function = "sdio"; ++ }; ++ cp0_sdhci_pins: cp0-sdhi-pins-0 { ++ marvell,pins = "mpp56", "mpp57", "mpp58", ++ "mpp59", "mpp60", "mpp61"; ++ marvell,function = "sdio"; ++ }; ++ cp0_spi0_pins: cp0-spi-pins-0 { ++ marvell,pins = "mpp13", "mpp14", "mpp15", "mpp16"; ++ marvell,function = "spi1"; ++ }; ++ cp0_uart2_pins: uart22-pins { ++ marvell,pins = "mpp50", "mpp51"; ++ marvell,function = "uart2"; ++ }; ++ cp0_jtag_upgrade_pins: cp0-jtag-pins-0 { ++ marvell,pins = "mpp54", "mpp56", "mpp57", "mpp61"; ++ marvell,function = "gpio"; ++ }; ++ }; ++}; ++ ++&cp0_usb3_0 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy0>; ++ phy-names = "usb"; ++}; ++ ++&cp0_usb3_1 { ++ status = "okay"; ++ usb-phy = <&cp0_usb3_0_phy1>; ++ phy-names = "usb"; ++}; +diff --git a/arch/arm64/boot/dts/marvell/cn9131-db-comexpress.dts b/arch/arm64/boot/dts/marvell/cn9131-db-comexpress.dts +new file mode 100644 +index 000000000..46adda40a +--- /dev/null ++++ b/arch/arm64/boot/dts/marvell/cn9131-db-comexpress.dts +@@ -0,0 +1,212 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2025 Marvell International Ltd. ++ */ ++ ++/* This DB board is an extension for "ac5x-rd-carrier-cn9131" */ ++ ++/dts-v1/; ++ ++#include "cn9130-db-comexpress.dts" ++ ++/ { ++ model = "Marvell Armada CN9131-DB-Comexpress type 7 CPU module (TRIXIE)"; ++ compatible = "marvell,cn9131", "marvell,cn9130", ++ "marvell,armada-ap807-quad", "marvell,armada-ap807"; ++ ++ aliases { ++ /** From "cn9130-db-comexpress.dts": ++ gpio1 = &cp0_gpio1; ++ gpio2 = &cp0_gpio2; ++ i2c0 = &cp0_i2c0; ++ i2c1 = &cp0_i2c1; ++ ethernet0 = &cp0_eth0; ++ ethernet1 = &cp0_eth1; ++ ethernet2 = &cp0_eth2; ++ spi1 = &cp0_spi0; ++ spi2 = &cp0_spi1; ++ **/ ++ i2c2 = &cp1_i2c0; ++ i2c3 = &cp1_i2c1; ++ gpio3 = &cp1_gpio1; ++ gpio4 = &cp1_gpio2; ++ ethernet3 = &cp1_eth0; ++ ethernet4 = &cp1_eth1; ++ }; ++ ++ cp1_reg_usb3_vbus0: regulator-6 { /*OLD: cp1_usb3_vbus@0 */ ++ compatible = "regulator-fixed"; ++ regulator-name = "cp1-xhci0-vbus"; ++ regulator-min-microvolt = <5000000>; ++ regulator-max-microvolt = <5000000>; ++ enable-active-high; ++ }; ++ ++ cp1_usb3_0_phy0: usb-phy-3 { /*OLD: cp1_usb3_phy0 */ ++ compatible = "usb-nop-xceiv"; ++ vcc-supply = <&cp1_reg_usb3_vbus0>; ++ }; ++ ++ cp1_reg_usb3_vbus1: cp1_usb3_vbus@1 { ++ compatible = "regulator-fixed"; ++ regulator-name = "cp1-xhci1-vbus"; ++ regulator-min-microvolt = <5000000>; ++ regulator-max-microvolt = <5000000>; ++ enable-active-high; ++ }; ++ ++ cp1_usb3_0_phy1: cp1_usb3_phy@1 { ++ compatible = "usb-nop-xceiv"; ++ vcc-supply = <&cp1_reg_usb3_vbus1>; ++ }; ++ ++ cp1_sfp_eth0: sfp-eth0 { ++ compatible = "sff,sfp"; ++ /*i2c-bus = <&cp1_i2c1>;*/ ++ mod-def0-gpio = <&cp1_gpio2 10 GPIO_ACTIVE_LOW>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp1_sfp_present_pins>; ++ }; ++}; ++ ++/* ++ * Instantiate the first slave CP115 ++ */ ++ ++#define CP11X_NAME cp1 ++#define CP11X_BASE f4000000 ++#define CP11X_PCIEx_MEM_BASE(iface) (0xe2000000 + (iface * 0x1000000)) ++#define CP11X_PCIEx_MEM_SIZE(iface) 0xf00000 ++#define CP11X_PCIE0_BASE f4600000 ++#define CP11X_PCIE1_BASE f4620000 ++#define CP11X_PCIE2_BASE f4640000 ++ ++#include "armada-cp115.dtsi" ++ ++#undef CP11X_NAME ++#undef CP11X_BASE ++#undef CP11X_PCIEx_MEM_BASE ++#undef CP11X_PCIEx_MEM_SIZE ++#undef CP11X_PCIE0_BASE ++#undef CP11X_PCIE1_BASE ++#undef CP11X_PCIE2_BASE ++ ++&cp1_crypto { ++ status = "disabled"; ++}; ++ ++&cp1_ethernet { ++ status = "disabled"; ++}; ++ ++/* CON50 */ ++&cp1_eth0 { ++ status = "okay"; ++ phy-mode = "10gbase-kr"; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp1_comphy2 0>; ++ managed = "in-band-status"; ++ sfp = <&cp1_sfp_eth0>; ++}; ++ ++&cp1_eth1 { ++ status = "disabled"; ++}; ++ ++&cp1_eth2 { ++ status = "disabled"; ++}; ++ ++&cp1_gpio1 { ++ status = "okay"; ++}; ++ ++&cp1_gpio2 { ++ status = "okay"; ++}; ++ ++/* ++ * CP1_I2C1 MPP[02:03] ++ * or ++ * CP1_I2C1 MPP[35:36] ++ * ++ * CP1_MSSI2C? MPP[00:01] ++ * or ++ * CP1_MSSI2C? MPP[50:51] ++ */ ++&cp1_i2c0 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp1_mss_i2c0_pins>; ++ clock-frequency = <100000>; ++}; ++ ++&cp1_i2c1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&cp1_i2c1_pins>; ++ clock-frequency = <100000>; ++}; ++ ++/* ++ * Comphy chip #1: ++ * Comphy-0: PEX0 ++ * Comphy-1: PEX0 ++ * Comphy-2: SFI0 10.3125 Gbps ++ * Comphy-3: SATA1 ++ * Comphy-4: PEX1 ++ * Comphy-5: PEX2 ++ */ ++ ++/* PCIE X2 NVME */ ++&cp1_pcie0 { ++ num-lanes = <2>; ++ num-viewport = <8>; ++ marvell,reset-gpio = <&cp1_gpio1 0 GPIO_ACTIVE_HIGH>; ++ status = "okay"; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp1_comphy0 0 ++ &cp1_comphy1 0>; ++}; ++ ++&cp1_sata0 { ++ status = "okay"; ++ ++ /* CON32 */ ++ sata-port@1 { ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp1_comphy3 1>; ++ }; ++}; ++ ++&cp1_syscon0 { ++ cp1_pinctrl: pinctrl { ++ compatible = "marvell,cp115-standalone-pinctrl"; ++ ++ cp1_i2c1_pins: cp1-i2c-pins-1 { ++ marvell,pins = "mpp3", "mpp2"; ++ marvell,function = "i2c1"; ++ }; ++ cp1_mss_i2c0_pins: cp1-mss-i2c-pins-1 { ++ marvell,pins = "mpp0", "mpp1"; ++ marvell,function = "mss_i2c"; ++ }; ++ cp1_xmdio_pins: cp1_xmdio_pins-0 { ++ marvell,pins = "mpp37", "mpp38"; ++ marvell,function = "xg"; ++ }; ++ cp1_sfp_present_pins: cp1_sfp_present_pins-0 { ++ marvell,pins = "mpp50"; ++ marvell,function = "gpio"; ++ }; ++ }; ++}; ++ ++/* CON58 */ ++&cp1_usb3_1 { ++ status = "okay"; ++ usb-phy = <&cp1_usb3_0_phy0>; ++ /* Generic PHY, providing serdes lanes */ ++ phys = <&cp1_comphy3 1>; ++ phy-names = "usb"; ++}; +-- +2.25.1 + diff --git a/patches-sonic/0022-drivers-soc-pensando-penfw-driver.patch b/patches-sonic/0022-drivers-soc-pensando-penfw-driver.patch new file mode 100644 index 000000000..788478102 --- /dev/null +++ b/patches-sonic/0022-drivers-soc-pensando-penfw-driver.patch @@ -0,0 +1,798 @@ +From 46977f7bd42c255a643ab0ace10021f26de28ceb Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Tue, 24 Oct 2023 10:37:48 +0000 +Subject: [PATCH 22/27] drivers/soc/pensando: penfw driver + +penfw driver provides sysfs and ioctl interface for the userspace +to communicate to the bl31 secure monitor + +This driver is only used with Secure Boot. + +Signed-off-by: David Clear +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba-asic.dts | 8 + + drivers/soc/pensando/Kconfig | 6 + + drivers/soc/pensando/Makefile | 1 + + drivers/soc/pensando/penfw.h | 36 +++ + drivers/soc/pensando/penfw_drv.c | 146 ++++++++++ + drivers/soc/pensando/penfw_smc.c | 63 +++++ + drivers/soc/pensando/penfw_sysfs.c | 438 +++++++++++++++++++++++++++++ + drivers/soc/pensando/penfw_sysfs.h | 11 + + 8 files changed, 709 insertions(+) + create mode 100644 drivers/soc/pensando/penfw.h + create mode 100644 drivers/soc/pensando/penfw_drv.c + create mode 100644 drivers/soc/pensando/penfw_smc.c + create mode 100644 drivers/soc/pensando/penfw_sysfs.c + create mode 100644 drivers/soc/pensando/penfw_sysfs.h + +diff --git a/arch/arm64/boot/dts/pensando/elba-asic.dts b/arch/arm64/boot/dts/pensando/elba-asic.dts +index dfa6dea..27a1057 100644 +--- a/arch/arm64/boot/dts/pensando/elba-asic.dts ++++ b/arch/arm64/boot/dts/pensando/elba-asic.dts +@@ -24,3 +24,11 @@ + #include "elba-flash-parts.dtsi" + #include "elba-asic-memory.dtsi" + #include "elba-mdev.dtsi" ++ ++/ { ++ firmware { ++ penfw { ++ compatible = "pensando,penfw"; ++ }; ++ }; ++}; +diff --git a/drivers/soc/pensando/Kconfig b/drivers/soc/pensando/Kconfig +index fd290aa..766924c 100644 +--- a/drivers/soc/pensando/Kconfig ++++ b/drivers/soc/pensando/Kconfig +@@ -49,6 +49,12 @@ config PENSANDO_SOC_BSM_ENABLE + help + Re-enable the BSM on kernel startup + ++config PENSANDO_SOC_PENFW ++ bool "Pensando penfw driver" ++ default n ++ help ++ Driver module to interface secure boot config ++ + config PENSANDO_SOC_SBUS + tristate "Pensando sbus read/write driver" + depends on OF +diff --git a/drivers/soc/pensando/Makefile b/drivers/soc/pensando/Makefile +index 555438a..95689d3 100644 +--- a/drivers/soc/pensando/Makefile ++++ b/drivers/soc/pensando/Makefile +@@ -12,4 +12,5 @@ obj-$(CONFIG_PENSANDO_SOC_PCIE) += kpcimgr.o kpcimgr_sysfs.o + obj-$(CONFIG_PENSANDO_SOC_RSTCAUSE) += cap_rstcause.o + obj-$(CONFIG_PENSANDO_SOC_CRASH) += cap_crash.o + obj-$(CONFIG_PENSANDO_SOC_BSM) += cap_bsm.o ++obj-$(CONFIG_PENSANDO_SOC_PENFW) += penfw_smc.o penfw_sysfs.o penfw_drv.o + obj-$(CONFIG_PENSANDO_SOC_SBUS) += sbus.o +diff --git a/drivers/soc/pensando/penfw.h b/drivers/soc/pensando/penfw.h +new file mode 100644 +index 0000000..37ee4c0 +--- /dev/null ++++ b/drivers/soc/pensando/penfw.h +@@ -0,0 +1,36 @@ ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#ifndef __PENFW_H__ ++#define __PENFW_H__ ++ ++enum penfw_opcodes { ++ PENFW_OP_GET_API_VER = 0, ++ PENFW_OP_GET_PENTRUST_STA, ++ PENFW_OP_SET_PENTRUST_UPG, ++ PENFW_OP_GET_BL1_STA, ++ PENFW_OP_SET_BL1_UPG, ++ PENFW_OP_GET_BL1_AR_NVCNTR, ++ PENFW_OP_COMMIT_BL1_AR_NVCNTR, ++ PENFW_OP_GET_BL31_SW_VER, ++ PENFW_OP_GET_BOOT_LCS, ++ PENFW_OP_GET_NEXT_LCS, ++ PENFW_OP_COMMIT_LCS_PROD, ++ PENFW_OP_GET_PENTRUST_VERSION, ++ PENFW_OPCODE_MAX, ++}; ++ ++struct penfw_call_args { ++ int64_t a0; ++ uint64_t a1; ++ uint64_t a2; ++ uint64_t a3; ++}; ++ ++#define PENFW_IOCTL_NUM 0xcd ++#define PENFW_FWCALL _IOWR(PENFW_IOCTL_NUM, 1, struct penfw_call_args) ++ ++void penfw_smc(struct penfw_call_args *args); ++ ++#endif /* __PENFW_H__ */ +diff --git a/drivers/soc/pensando/penfw_drv.c b/drivers/soc/pensando/penfw_drv.c +new file mode 100644 +index 0000000..dbb6787 +--- /dev/null ++++ b/drivers/soc/pensando/penfw_drv.c +@@ -0,0 +1,146 @@ ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "penfw.h" ++#include "penfw_sysfs.h" ++ ++#define DEVICE_NAME "penfw" ++#define CLASS_NAME "penfw" ++ ++static int majorNumber; ++static struct class *penfw_class; ++static struct device *penfw_dev; ++static DEFINE_MUTEX(penfw_mutex); ++ ++static int penfw_open(struct inode *inodep, struct file *filep) ++{ ++ return 0; ++} ++ ++static long penfw_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ long ret = 0; ++ void __user *argp = (void __user *)arg; ++ struct penfw_call_args penfw_args_ob; ++ ++ mutex_lock(&penfw_mutex); ++ ++ if (copy_from_user(&penfw_args_ob, argp, sizeof(penfw_args_ob))) { ++ dev_err(penfw_dev, "copy from user failed\n"); ++ ret = -EFAULT; ++ goto err; ++ } ++ ++ if (cmd != PENFW_FWCALL) { ++ dev_err(penfw_dev, "received unsupported ioctl %u\n", cmd); ++ ret = -EOPNOTSUPP; ++ goto err; ++ } ++ ++ penfw_smc(&penfw_args_ob); ++ ++ // copy back data to user space struct ++ if (copy_to_user(argp, &penfw_args_ob, sizeof(penfw_args_ob))) { ++ dev_err(penfw_dev, "copy to user failed\n"); ++ ret = -EFAULT; ++ goto err; ++ } ++ ++err: ++ mutex_unlock(&penfw_mutex); ++ ++ return ret; ++} ++ ++static const struct file_operations fops = { ++ .open = penfw_open, ++ .unlocked_ioctl = penfw_ioctl ++}; ++ ++static int penfw_probe(struct platform_device *pdev) ++{ ++ int ret; ++ ++ pr_info("penfw: initializing the device\n"); ++ mutex_init(&penfw_mutex); ++ majorNumber = register_chrdev(0, DEVICE_NAME, &fops); ++ if (majorNumber < 0) { ++ pr_err("penfw: failed to register a major number\n"); ++ return majorNumber; ++ } ++ pr_info("penfw: registered correctly with major number %d\n", ++ majorNumber); ++ ++ // register the device class ++ penfw_class = class_create(CLASS_NAME); ++ if (IS_ERR(penfw_class)) { ++ unregister_chrdev(majorNumber, DEVICE_NAME); ++ pr_err("penfw: failed to register device class\n"); ++ return PTR_ERR(penfw_class); ++ } ++ pr_info("penfw: device class registered correctly\n"); ++ ++ // register the device driver ++ penfw_dev = device_create(penfw_class, NULL, MKDEV(majorNumber, 0), ++ NULL, DEVICE_NAME); ++ if (IS_ERR(penfw_dev)) { ++ class_destroy(penfw_class); ++ unregister_chrdev(majorNumber, DEVICE_NAME); ++ dev_err(penfw_dev, "failed to create the device\n"); ++ return PTR_ERR(penfw_dev); ++ } ++ dev_info(penfw_dev, "device class created correctly\n"); ++ ++ ret = penfw_sysfs_init(penfw_dev); ++ if (ret != 0) { ++ dev_err(penfw_dev, "penfw sys initialization failed\n"); ++ return -1; ++ } ++ dev_info(penfw_dev, "penfw sys initialization success\n"); ++ ++ return 0; ++} ++ ++static void penfw_remove(struct platform_device *pd) ++{ ++ mutex_destroy(&penfw_mutex); ++ device_destroy(penfw_class, MKDEV(majorNumber, 0)); ++ class_unregister(penfw_class); ++ class_destroy(penfw_class); ++ unregister_chrdev(majorNumber, DEVICE_NAME); ++ penfw_sysfs_deinit(); ++ pr_info("penfw: Exiting!\n"); ++ ++ return; ++} ++ ++static const struct of_device_id penfw_of_match[] = { ++ { .compatible = "pensando,penfw" }, ++ { /* end of table */ } ++}; ++ ++static struct platform_driver penfw_driver = { ++ .probe = penfw_probe, ++ .remove = penfw_remove, ++ .driver = { ++ .name = "penfw", ++ .owner = THIS_MODULE, ++ .of_match_table = penfw_of_match, ++ }, ++}; ++ ++module_platform_driver(penfw_driver); +diff --git a/drivers/soc/pensando/penfw_smc.c b/drivers/soc/pensando/penfw_smc.c +new file mode 100644 +index 0000000..94f10ea +--- /dev/null ++++ b/drivers/soc/pensando/penfw_smc.c +@@ -0,0 +1,63 @@ ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "penfw.h" ++ ++#define PENFW_CALL_FID 0xC2000002 ++ ++static const char *_opcode_to_str(uint8_t opcode) ++{ ++ switch (opcode) { ++ case PENFW_OP_GET_API_VER: ++ return "PENFW_OP_GET_API_VER"; ++ case PENFW_OP_GET_PENTRUST_STA: ++ return "PENFW_OP_GET_PENTRUST_STA"; ++ case PENFW_OP_SET_PENTRUST_UPG: ++ return "PENFW_OP_SET_PENTRUST_UPG"; ++ case PENFW_OP_GET_BL1_STA: ++ return "PENFW_OP_GET_BL1_STA"; ++ case PENFW_OP_SET_BL1_UPG: ++ return "PENFW_OP_SET_BL1_UPG"; ++ case PENFW_OP_GET_BL1_AR_NVCNTR: ++ return "PENFW_OP_GET_BL1_AR_NVCNTR"; ++ case PENFW_OP_COMMIT_BL1_AR_NVCNTR: ++ return "PENFW_OP_COMMIT_BL1_AR_NVCNTR"; ++ case PENFW_OP_GET_BL31_SW_VER: ++ return "PENFW_OP_GET_BL31_SW_VER"; ++ case PENFW_OP_GET_BOOT_LCS: ++ return "PENFW_OP_GET_BOOT_LCS"; ++ case PENFW_OP_GET_NEXT_LCS: ++ return "PENFW_OP_GET_NEXT_LCS"; ++ case PENFW_OP_COMMIT_LCS_PROD: ++ return "PENFW_OP_COMMIT_LCS_PROD"; ++ default: ++ return "PENFW_OP_UNKNOWN"; ++ } ++} ++ ++void penfw_smc(struct penfw_call_args *args) ++{ ++ struct arm_smccc_res res = {0}; ++ ++ pr_debug("penfw: smc call for fn: %s\n", ++ _opcode_to_str(args->a1)); ++ ++ arm_smccc_smc(PENFW_CALL_FID, args->a1, args->a2, args->a3, 0, 0, ++ 0, 0, &res); ++ ++ // copy return vals ++ args->a0 = res.a0; ++ args->a1 = res.a1; ++ args->a2 = res.a2; ++ args->a3 = res.a3; ++ ++ pr_debug("penfw: smc return a0: 0x%llx a1: 0x%llx " ++ "a2: 0x%llx a3: 0x%llx\n", args->a0, args->a1, ++ args->a2, args->a3); ++} +diff --git a/drivers/soc/pensando/penfw_sysfs.c b/drivers/soc/pensando/penfw_sysfs.c +new file mode 100644 +index 0000000..1745c90 +--- /dev/null ++++ b/drivers/soc/pensando/penfw_sysfs.c +@@ -0,0 +1,438 @@ ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "penfw_sysfs.h" ++#include "penfw.h" ++ ++extern struct kobject *pensando_fw_kobj_get(void); ++ ++static struct kobject *pensando_kobj; ++static struct kobject *penfw_kobject; ++static struct kobject *pentrust_kobject; ++static struct kobject *bl1_kobject; ++static struct kobject *bl31_kobject; ++static struct kobject *lifecycle_kobject; ++ ++static ssize_t pentrust_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct penfw_call_args args = {0}; ++ ++ args.a1 = PENFW_OP_GET_PENTRUST_STA; ++ penfw_smc(&args); ++ ++ if (args.a0 < 0) ++ return sprintf(buf, "Error\n"); ++ ++ if (!strcmp(attr->attr.name, "version")) ++ return sprintf(buf, "%llu\n", (args.a0 & 0xffffffff)); ++ else if (!strcmp(attr->attr.name, "upgrade")) ++ return sprintf(buf, "%llu\n", (args.a1 >> 1) & 1); ++ else if (!strcmp(attr->attr.name, "image_slot")) ++ return sprintf(buf, "%llu\n", (args.a1 & 1)); ++ else ++ return sprintf(buf, "\n"); ++} ++ ++static ssize_t pentrust_show_version(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct penfw_call_args args = {0}; ++ int major, minor1, minor2; ++ ++ args.a1 = PENFW_OP_GET_PENTRUST_VERSION; ++ penfw_smc(&args); ++ ++ if (args.a0 < 0) ++ return sprintf(buf, "Error\n"); ++ ++ if (!strcmp(attr->attr.name, "sw_version")) { ++ major = args.a1 >> 32; ++ minor1 = (args.a1 & 0xffff0000) >> 16; ++ minor2 = args.a1 & 0xffff; ++ return sprintf(buf, "%d.%d.%d\n", major, minor1, minor2); ++ } else if (!strcmp(attr->attr.name, "crypto_version")) { ++ major = args.a2 >> 32; ++ minor1 = (args.a2 & 0xffff0000) >> 16; ++ minor2 = args.a2 & 0xffff; ++ return sprintf(buf, "%d.%d.%d\n", major, minor1, minor2); ++ } else ++ return sprintf(buf, "\n"); ++} ++ ++static ssize_t pentrust_store(struct kobject *kobj, struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct penfw_call_args args = {0}; ++ ++ if (strcmp(attr->attr.name, "upgrade") != 0) ++ return -1; ++ args.a1 = PENFW_OP_SET_PENTRUST_UPG; ++ penfw_smc(&args); ++ if (args.a0 < 0) ++ return -EIO; ++ ++ return count; ++} ++ ++static ssize_t bl1_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct penfw_call_args args = {0}; ++ ++ args.a1 = PENFW_OP_GET_BL1_STA; ++ penfw_smc(&args); ++ ++ if (args.a0 < 0) ++ return sprintf(buf, "Error\n"); ++ ++ if (!strcmp(attr->attr.name, "version")) ++ return sprintf(buf, "%llu\n", (args.a0 & 0xffffffff)); ++ else if (!strcmp(attr->attr.name, "ar_version")) ++ return sprintf(buf, "%llu\n", ((args.a0 >> 32) & 0xff)); ++ else if (!strcmp(attr->attr.name, "upgrade")) ++ return sprintf(buf, "%llu\n", (args.a1 >> 1) & 1); ++ else if (!strcmp(attr->attr.name, "image_slot")) ++ return sprintf(buf, "%llu\n", (args.a1 & 1)); ++ else ++ return sprintf(buf, "\n"); ++} ++ ++static ssize_t bl1_ar_nvcntr_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ struct penfw_call_args args = {0}; ++ ++ args.a1 = PENFW_OP_GET_BL1_AR_NVCNTR; ++ penfw_smc(&args); ++ ++ if (args.a0 < 0) ++ return sprintf(buf, "Error\n"); ++ ++ if (!strcmp(attr->attr.name, "ar_nv_cntr")) ++ return sprintf(buf, "%llu\n", (args.a0 & 0xffffffff)); ++ else ++ return sprintf(buf, "\n"); ++} ++ ++static ssize_t bl1_store(struct kobject *kobj, struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct penfw_call_args args = {0}; ++ ++ if (strcmp(attr->attr.name, "upgrade") != 0) ++ return -1; ++ args.a1 = PENFW_OP_SET_BL1_UPG; ++ penfw_smc(&args); ++ if (args.a0 < 0) ++ return -EIO; ++ ++ return count; ++} ++ ++static ssize_t bl31_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct penfw_call_args args = {0}; ++ char vers[256]; ++ uint64_t val; ++ int byte, shift; ++ ++ args.a1 = PENFW_OP_GET_BL31_SW_VER; ++ penfw_smc(&args); ++ ++ if (args.a0 < 0) ++ return -EIO; ++ ++ for (byte = 0; byte < 256 - 1; byte++) { ++ if ((byte / 8) == 0) ++ val = args.a0; ++ else if ((byte / 8) == 1) ++ val = args.a1; ++ else if ((byte / 8) == 2) ++ val = args.a2; ++ else ++ val = args.a3; ++ ++ shift = (byte * 8) % 64; ++ vers[byte] = (val >> (shift)) & 0x7f; ++ if (vers[byte] == 0) ++ break; ++ } ++ vers[255] = 0; ++ ++ return sprintf(buf, "%s\n", vers); ++} ++ ++static const char *_lcs_to_str(int lcs_state) ++{ ++ switch (lcs_state) { ++ case 0: ++ return "disabled"; ++ case 1: ++ return "open"; ++ case 2: ++ return "soft_prod"; ++ case 3: ++ return "prod"; ++ case 4: ++ return "rip"; ++ default: ++ return "unknown"; ++ } ++} ++ ++static ssize_t boot_lcs_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct penfw_call_args args = {0}; ++ ++ args.a1 = PENFW_OP_GET_BOOT_LCS; ++ penfw_smc(&args); ++ ++ if (args.a0 < 0) ++ return sprintf(buf, "Error\n"); ++ ++ if (!strcmp(attr->attr.name, "boot_state")) ++ return sprintf(buf, "%s\n", _lcs_to_str(args.a0)); ++ else ++ return sprintf(buf, "\n"); ++} ++ ++static ssize_t next_lcs_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct penfw_call_args args = {0}; ++ ++ args.a1 = PENFW_OP_GET_NEXT_LCS; ++ penfw_smc(&args); ++ ++ if (args.a0 < 0) ++ return sprintf(buf, "Error\n"); ++ ++ if (!strcmp(attr->attr.name, "next_state")) ++ return sprintf(buf, "%s\n", _lcs_to_str(args.a0)); ++ else ++ return sprintf(buf, "\n"); ++} ++ ++// pentrust attributes ++static struct kobj_attribute pentrust_attr_ver = __ATTR(version, 0400, ++ pentrust_show, NULL); ++static struct kobj_attribute pentrust_attr_img_slot = __ATTR(image_slot, 0400, ++ pentrust_show, ++ NULL); ++static struct kobj_attribute pentrust_attr_upg = __ATTR(upgrade, 0600, ++ pentrust_show, ++ pentrust_store); ++static struct kobj_attribute pentrust_attr_sw_ver = __ATTR(sw_version, 0400, ++ pentrust_show_version, NULL); ++ ++static struct kobj_attribute pentrust_attr_crypto_ver = __ATTR(crypto_version, 0400, ++ pentrust_show_version, NULL); ++ ++static struct attribute *pentrust_attrs[] = { ++ &pentrust_attr_ver.attr, ++ &pentrust_attr_img_slot.attr, ++ &pentrust_attr_upg.attr, ++ &pentrust_attr_sw_ver.attr, ++ &pentrust_attr_crypto_ver.attr, ++ NULL ++}; ++ ++static struct attribute_group pentrust_attr_group = { ++ .attrs = pentrust_attrs, ++}; ++ ++// bl1 attributes ++static struct kobj_attribute bl1_attr_ver = __ATTR(version, 0400, bl1_show, ++ NULL); ++static struct kobj_attribute bl1_attr_ar_version = __ATTR(ar_version, 0400, ++ bl1_show, NULL); ++static struct kobj_attribute bl1_attr_img_slot = __ATTR(image_slot, 0400, ++ bl1_show, NULL); ++static struct kobj_attribute bl1_attr_upg = __ATTR(upgrade, 0600, bl1_show, ++ bl1_store); ++static struct kobj_attribute bl1_attr_ar_nv_cntr = __ATTR(ar_nv_cntr, 0400, ++ bl1_ar_nvcntr_show, ++ NULL); ++ ++static struct attribute *bl1_attrs[] = { ++ &bl1_attr_ver.attr, ++ &bl1_attr_ar_version.attr, ++ &bl1_attr_img_slot.attr, ++ &bl1_attr_upg.attr, ++ &bl1_attr_ar_nv_cntr.attr, ++ NULL ++}; ++ ++static struct attribute_group bl1_attr_group = { ++ .attrs = bl1_attrs, ++}; ++ ++// bl31 attributes ++static struct kobj_attribute bl31_attr_sw_ver = __ATTR(sw_version, 0400, ++ bl31_show, NULL); ++ ++static struct attribute *bl31_attrs[] = { ++ &bl31_attr_sw_ver.attr, ++ NULL ++}; ++ ++static struct attribute_group bl31_attr_group = { ++ .attrs = bl31_attrs, ++}; ++ ++// lifecycle attributes ++static struct kobj_attribute lifecycle_attr_boot_state = __ATTR(boot_state, ++ 0400, ++ boot_lcs_show, ++ NULL); ++static struct kobj_attribute lifecycle_attr_next_state = __ATTR(next_state, ++ 0400, ++ next_lcs_show, ++ NULL); ++ ++static struct attribute *lifecycle_attrs[] = { ++ &lifecycle_attr_boot_state.attr, ++ &lifecycle_attr_next_state.attr, ++ NULL ++}; ++ ++static struct attribute_group lifecycle_attr_group = { ++ .attrs = lifecycle_attrs, ++}; ++ ++ ++int penfw_sysfs_init(struct device *penfwDevice) ++{ ++ int ret = 0; ++ ++ // /sys/firmware/pensando ++ pensando_kobj = pensando_fw_kobj_get(); ++ if (!pensando_kobj) { ++ dev_err(penfwDevice, "Unable to create /sys/firmware/pensando" ++ " node\n"); ++ return -ENOMEM; ++ } ++ ++ // /sys/firmware/pensando/penfw ++ penfw_kobject = kobject_create_and_add("penfw", pensando_kobj); ++ if (!penfw_kobject) { ++ dev_err(penfwDevice, "Unable to create " ++ "/sys/firmware/pensando/penfw node\n"); ++ ret = -ENOMEM; ++ goto penfw_err; ++ } ++ ++ // /sys/firmware/pensando/penfw/pentrust ++ pentrust_kobject = kobject_create_and_add("pentrust", penfw_kobject); ++ if (!pentrust_kobject) { ++ dev_err(penfwDevice, "Unable to create " ++ "/sys/firmware/pensando/penfw/pentrust " ++ "node\n"); ++ ret = -ENOMEM; ++ goto pentrust_err; ++ } ++ ++ if (sysfs_create_group(pentrust_kobject, &pentrust_attr_group)) { ++ dev_err(penfwDevice, "Unable to create pentrust attributes " ++ "group\n"); ++ ret = -1; ++ goto bl1_err; ++ } ++ ++ // /sys/firmware/pensando/penfw/bl1 ++ bl1_kobject = kobject_create_and_add("bl1", penfw_kobject); ++ if (!bl1_kobject) { ++ dev_err(penfwDevice, "Unable to create " ++ "/sys/firmware/pensando/penfw/bl1 node\n"); ++ ret = -ENOMEM; ++ goto bl1_err; ++ } ++ ++ if (sysfs_create_group(bl1_kobject, &bl1_attr_group)) { ++ dev_err(penfwDevice, "Unable to create bl1 attributes group\n"); ++ ret = -1; ++ goto bl31_err; ++ } ++ ++ // /sys/firmware/pensando/penfw/bl31 ++ bl31_kobject = kobject_create_and_add("bl31", penfw_kobject); ++ if (!bl31_kobject) { ++ dev_err(penfwDevice, "Unable to create " ++ "/sys/firmware/pensando/penfw/bl31 " ++ "node\n"); ++ ret = -ENOMEM; ++ goto bl31_err; ++ } ++ ++ if (sysfs_create_group(bl31_kobject, &bl31_attr_group)) { ++ dev_err(penfwDevice, "Unable to create bl31 attributes " ++ "group\n"); ++ ret = -1; ++ goto lifecycle_err; ++ } ++ ++ // /sys/firmware/pensando/penfw/lifecycle ++ lifecycle_kobject = kobject_create_and_add("lifecycle", penfw_kobject); ++ if (!lifecycle_kobject) { ++ dev_err(penfwDevice, "Unable to create " ++ "/sys/firmware/pensando/penfw/lifecycle " ++ "node\n"); ++ ret = -ENOMEM; ++ goto lifecycle_err; ++ } ++ ++ if (sysfs_create_group(lifecycle_kobject, &lifecycle_attr_group)) { ++ dev_err(penfwDevice, "Unable to create lifecycle attributes " ++ "group\n"); ++ ret = -1; ++ goto lifecycle_attr_err; ++ } ++ ++return ret; ++ ++lifecycle_attr_err: ++ kobject_put(lifecycle_kobject); ++lifecycle_err: ++ kobject_put(bl31_kobject); ++bl31_err: ++ kobject_put(bl1_kobject); ++bl1_err: ++ kobject_put(pentrust_kobject); ++pentrust_err: ++ kobject_put(penfw_kobject); ++penfw_err: ++ kobject_put(pensando_kobj); ++ ++ return ret; ++} ++ ++int penfw_sysfs_deinit(void) ++{ ++ if (lifecycle_kobject) ++ kobject_put(lifecycle_kobject); ++ if (bl31_kobject) ++ kobject_put(bl31_kobject); ++ if (bl1_kobject) ++ kobject_put(bl1_kobject); ++ if (pentrust_kobject) ++ kobject_put(penfw_kobject); ++ if (penfw_kobject) ++ kobject_put(penfw_kobject); ++ if (pensando_kobj) ++ kobject_put(pensando_kobj); ++ ++ return 0; ++} +diff --git a/drivers/soc/pensando/penfw_sysfs.h b/drivers/soc/pensando/penfw_sysfs.h +new file mode 100644 +index 0000000..46eb644 +--- /dev/null ++++ b/drivers/soc/pensando/penfw_sysfs.h +@@ -0,0 +1,11 @@ ++/* ++ * Copyright (c) 2021, Pensando Systems Inc. ++ */ ++ ++#ifndef __PENFW_SYSFS_H__ ++#define __PENFW_SYSFS_H__ ++ ++int penfw_sysfs_init(struct device *penfwDevice); ++int penfw_sysfs_deinit(void); ++ ++#endif /* __PENFW_SYSFS_H__ */ +-- +1.8.3.1 + diff --git a/patches-sonic/0022-i2c-mux-Add-register-map-based-mux-driver.patch b/patches-sonic/0022-i2c-mux-Add-register-map-based-mux-driver.patch new file mode 100644 index 000000000..e08a1d32b --- /dev/null +++ b/patches-sonic/0022-i2c-mux-Add-register-map-based-mux-driver.patch @@ -0,0 +1,244 @@ +From a377f235d8b753c65239ba6865354cd465f75010 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Wed, 9 Nov 2022 12:22:12 +0200 +Subject: [PATCH 22/78] i2c: mux: Add register map based mux driver + +Add 'regmap' mux driver to allow virtual bus switching by setting a +single selector register. +The 'regmap' is supposed to be passed to driver within a platform data +by parent platform driver. + +The register can be on type of bus or mapped memory, supported by +'regmap' infrastructure. + +Motivation is to support indirect access to register space. +For example, Lattice FPGA LFD2NX-40 device, being connected through +PCIe bus provides SPI or LPC bus logic through PCIe-to-SPI or +PCIe-to-LPC bridging. Thus, FPGA operates as host controller and some +slave devices can be connected to it. For example: +CPU (PCIe) -> FPGA (PCIe-to-SPI bridge) -> CPLD or another FPGA +CPU (PCIe) -> FPGA (PCIe-to-LPC bridge) -> CPLD or another FPGA +where 1-st FPGA connected to PCIe is located on carrier board, while +2-nd programming logic device is located on some switch board and +cannot be connected to CPU PCIe root complex. + +In case mux selector register is located within the 2-nd device, SPI or +LPC transaction is prepared sent by indirect access, through some +pre-defined protocol. +To support such protocol reg_read()/reg_write() callbacks are provided +to 'regmap' object and these callback implements required indirect +access. + +Signed-off-by: Vadim Pasternak +--- + drivers/i2c/muxes/Kconfig | 12 +++ + drivers/i2c/muxes/Makefile | 1 + + drivers/i2c/muxes/i2c-mux-regmap.c | 120 +++++++++++++++++++++++++++ + include/linux/platform_data/i2c-mux-regmap.h | 34 ++++++++ + 4 files changed, 167 insertions(+) + create mode 100644 drivers/i2c/muxes/i2c-mux-regmap.c + create mode 100644 include/linux/platform_data/i2c-mux-regmap.h + +diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig +index 6d2f668..9b5e525 100644 +--- a/drivers/i2c/muxes/Kconfig ++++ b/drivers/i2c/muxes/Kconfig +@@ -99,6 +99,18 @@ config I2C_MUX_REG + This driver can also be built as a module. If so, the module + will be called i2c-mux-reg. + ++config I2C_MUX_REGMAP ++ tristate "Register map based I2C multiplexer" ++ depends on REGMAP ++ help ++ If you say yes to this option, support will be included for a ++ register map based I2C multiplexer. This driver provides access to ++ I2C busses connected through a MUX, which is controlled ++ by a single register through the regmap. ++ ++ This driver can also be built as a module. If so, the module ++ will be called i2c-mux-regmap. ++ + config I2C_DEMUX_PINCTRL + tristate "pinctrl-based I2C demultiplexer" + depends on PINCTRL && OF +diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile +index 4b24f49..edecfc5 100644 +--- a/drivers/i2c/muxes/Makefile ++++ b/drivers/i2c/muxes/Makefile +@@ -15,5 +15,6 @@ obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o + obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o + obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o + obj-$(CONFIG_I2C_MUX_REG) += i2c-mux-reg.o ++obj-$(CONFIG_I2C_MUX_REGMAP) += i2c-mux-regmap.o + + ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG +diff --git a/drivers/i2c/muxes/i2c-mux-regmap.c b/drivers/i2c/muxes/i2c-mux-regmap.c +new file mode 100644 +index 00000000..fb5e89c +--- /dev/null ++++ b/drivers/i2c/muxes/i2c-mux-regmap.c +@@ -0,0 +1,120 @@ ++// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 ++/* ++ * Regmap i2c mux driver ++ * ++ * Copyright (C) 2023 Nvidia Technologies Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* i2c_mux_regmap - mux control structure: ++ * @last_val - last selected register value or -1 if mux deselected; ++ * @pdata: platform data; ++ */ ++struct i2c_mux_regmap { ++ int last_val; ++ struct i2c_mux_regmap_platform_data pdata; ++}; ++ ++static int i2c_mux_regmap_select_chan(struct i2c_mux_core *muxc, u32 chan) ++{ ++ struct i2c_mux_regmap *mux = i2c_mux_priv(muxc); ++ int err = 0; ++ ++ /* Only select the channel if its different from the last channel */ ++ if (mux->last_val != chan) { ++ err = regmap_write(mux->pdata.regmap, mux->pdata.sel_reg_addr, chan); ++ mux->last_val = err < 0 ? -1 : chan; ++ } ++ ++ return err; ++} ++ ++static int i2c_mux_regmap_deselect(struct i2c_mux_core *muxc, u32 chan) ++{ ++ struct i2c_mux_regmap *mux = i2c_mux_priv(muxc); ++ ++ /* Deselect active channel */ ++ mux->last_val = -1; ++ ++ return regmap_write(mux->pdata.regmap, mux->pdata.sel_reg_addr, 0); ++} ++ ++/* Probe/reomove functions */ ++static int i2c_mux_regmap_probe(struct platform_device *pdev) ++{ ++ struct i2c_mux_regmap_platform_data *pdata = dev_get_platdata(&pdev->dev); ++ struct i2c_mux_regmap *mux; ++ struct i2c_adapter *parent; ++ struct i2c_mux_core *muxc; ++ int num, err; ++ ++ if (!pdata) ++ return -EINVAL; ++ ++ mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); ++ if (!mux) ++ return -ENOMEM; ++ ++ memcpy(&mux->pdata, pdata, sizeof(*pdata)); ++ parent = i2c_get_adapter(mux->pdata.parent); ++ if (!parent) ++ return -EPROBE_DEFER; ++ ++ muxc = i2c_mux_alloc(parent, &pdev->dev, pdata->num_adaps, sizeof(*mux), 0, ++ i2c_mux_regmap_select_chan, i2c_mux_regmap_deselect); ++ if (!muxc) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, muxc); ++ muxc->priv = mux; ++ mux->last_val = -1; /* force the first selection */ ++ ++ /* Create an adapter for each channel. */ ++ for (num = 0; num < pdata->num_adaps; num++) { ++ err = i2c_mux_add_adapter(muxc, 0, pdata->chan_ids[num]); ++ if (err) ++ goto err_i2c_mux_add_adapter; ++ } ++ ++ /* Notify caller when all channels' adapters are created. */ ++ if (pdata->completion_notify) ++ pdata->completion_notify(pdata->handle, muxc->parent, muxc->adapter); ++ ++ return 0; ++ ++err_i2c_mux_add_adapter: ++ i2c_mux_del_adapters(muxc); ++ return err; ++} ++ ++static void i2c_mux_regmap_remove(struct platform_device *pdev) ++{ ++ struct i2c_mux_core *muxc = platform_get_drvdata(pdev); ++ ++ i2c_mux_del_adapters(muxc); ++} ++ ++static struct platform_driver i2c_mux_regmap_driver = { ++ .driver = { ++ .name = "i2c-mux-regmap", ++ }, ++ .probe = i2c_mux_regmap_probe, ++ .remove = i2c_mux_regmap_remove, ++}; ++ ++module_platform_driver(i2c_mux_regmap_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak (vadimp@nvidia.com)"); ++MODULE_DESCRIPTION("Regmap I2C multiplexer driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("platform:i2c-mux-regmap"); +diff --git a/include/linux/platform_data/i2c-mux-regmap.h b/include/linux/platform_data/i2c-mux-regmap.h +new file mode 100644 +index 00000000..a06614e +--- /dev/null ++++ b/include/linux/platform_data/i2c-mux-regmap.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */ ++/* ++ * Regmap i2c mux driver ++ * ++ * Copyright (C) 2023 Nvidia Technologies Ltd. ++ */ ++ ++#ifndef __LINUX_PLATFORM_DATA_I2C_MUX_REGMAP_H ++#define __LINUX_PLATFORM_DATA_I2C_MUX_REGMAP_H ++ ++/** ++ * struct i2c_mux_regmap_platform_data - Platform-dependent data for i2c-mux-regmap ++ * @regmap: register map of parent device; ++ * @parent: Parent I2C bus adapter number ++ * @chan_ids - channels array ++ * @num_adaps - number of adapters ++ * @sel_reg_addr - mux select register offset in CPLD space ++ * @reg_size: register size in bytes ++ * @handle: handle to be passed by callback ++ * @completion_notify: callback to notify when all the adapters are created ++ */ ++struct i2c_mux_regmap_platform_data { ++ void *regmap; ++ int parent; ++ const unsigned int *chan_ids; ++ int num_adaps; ++ int sel_reg_addr; ++ u8 reg_size; ++ void *handle; ++ int (*completion_notify)(void *handle, struct i2c_adapter *parent, ++ struct i2c_adapter *adapters[]); ++}; ++ ++#endif /* __LINUX_PLATFORM_DATA_I2C_MUX_REGMAP_H */ +-- +2.8.4 + diff --git a/patches-sonic/0023-platform-mellanox-Add-support-for-dynamic-I2C-channe.patch b/patches-sonic/0023-platform-mellanox-Add-support-for-dynamic-I2C-channe.patch new file mode 100644 index 000000000..4e1be9282 --- /dev/null +++ b/patches-sonic/0023-platform-mellanox-Add-support-for-dynamic-I2C-channe.patch @@ -0,0 +1,160 @@ +From cf91635805c4971219492c93ca299d472067c68b Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 30 Jul 2023 21:31:54 +0000 +Subject: [PATCH 23/78] platform/mellanox: Add support for dynamic I2C channels + infrastructure + +Allow to support platform configuration for dynamically allocated I2C +channels. +Motivation is to support I2C channels allocated in a non-continuous +way. + +Currently hotplug platform driver data structure contains static mux +channels for I2C hotplug devices. These channels numbers can be updated +dynamically and returned by mux driver's callback through the adapters +array. +Thus, hotplug mux channels will be aligned according to the dynamic +adapters data. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Michael Shych +--- + drivers/platform/x86/mlx-platform.c | 70 +++++++++++++++++++++++++++++++------ + 1 file changed, 60 insertions(+), 10 deletions(-) + +diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c +index 1a09f2d..cbd5634 100644 +--- a/drivers/platform/x86/mlx-platform.c ++++ b/drivers/platform/x86/mlx-platform.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -350,6 +351,7 @@ + * @hotplug_resources: system hotplug resources + * @hotplug_resources_size: size of system hotplug resources + * @hi2c_main_init_status: init status of I2C main bus ++ * @mux_added: number of added mux segments + * @irq_fpga: FPGA IRQ number + */ + struct mlxplat_priv { +@@ -364,6 +366,7 @@ struct mlxplat_priv { + struct resource *hotplug_resources; + unsigned int hotplug_resources_size; + u8 i2c_main_init_status; ++ int mux_added; + int irq_fpga; + }; + +@@ -453,7 +456,9 @@ static struct i2c_mux_reg_platform_data mlxplat_default_mux_data[] = { + /* Platform mux configuration variables */ + static int mlxplat_max_adap_num; + static int mlxplat_mux_num; ++static int mlxplat_mux_hotplug_num; + static struct i2c_mux_reg_platform_data *mlxplat_mux_data; ++static struct i2c_mux_regmap_platform_data *mlxplat_mux_regmap_data; + static struct notifier_block *mlxplat_reboot_nb; + + /* Platform extended mux data */ +@@ -6126,12 +6131,17 @@ static int mlxplat_mlxcpld_verify_bus_topology(int *nr) + /* Shift adapter ids, since expected parent adapter is not free. */ + *nr = i; + for (i = 0; i < mlxplat_mux_num; i++) { +- shift = *nr - mlxplat_mux_data[i].parent; +- mlxplat_mux_data[i].parent = *nr; +- mlxplat_mux_data[i].base_nr += shift; ++ if (mlxplat_mux_data) { ++ shift = *nr - mlxplat_mux_data[i].parent; ++ mlxplat_mux_data[i].parent = *nr; ++ mlxplat_mux_data[i].base_nr += shift; ++ } else if (mlxplat_mux_regmap_data) { ++ mlxplat_mux_regmap_data[i].parent = *nr; ++ } + } + +- if (shift > 0) ++ /* Shift bus only if mux provided by 'mlxplat_mux_data'. */ ++ if (shift > 0 && mlxplat_mux_data) + mlxplat_hotplug->shift_nr = shift; + + return 0; +@@ -6430,8 +6440,32 @@ mlxplat_i2c_mux_complition_notify(void *handle, struct i2c_adapter *parent, + struct i2c_adapter *adapters[]) + { + struct mlxplat_priv *priv = handle; ++ struct mlxreg_core_item *item; ++ int i, j; ++ ++ /* ++ * Hotplug platform driver data structure contains static mux channels for I2C hotplug ++ * devices. These channels numbers can be updated dynamically and returned by mux callback ++ * through the adapters array. Update mux channels according to the dynamic adapters data. ++ */ ++ if (priv->mux_added == mlxplat_mux_hotplug_num) { ++ item = mlxplat_hotplug->items; ++ for (i = 0; i < mlxplat_hotplug->counter; i++, item++) { ++ struct mlxreg_core_data *data = item->data; ++ ++ for (j = 0; j < item->count; j++, data++) { ++ if (data->hpdev.nr != MLXPLAT_CPLD_NR_NONE) ++ data->hpdev.nr = adapters[data->hpdev.nr - 2]->nr; ++ } ++ } ++ } + +- return mlxplat_platdevs_init(priv); ++ ++ /* Start post initialization only after last nux segment is added */ ++ if (++priv->mux_added == mlxplat_mux_num) ++ return mlxplat_platdevs_init(priv); ++ ++ return 0; + } + + static int mlxplat_i2c_mux_topology_init(struct mlxplat_priv *priv) +@@ -6445,17 +6479,33 @@ static int mlxplat_i2c_mux_topology_init(struct mlxplat_priv *priv) + + priv->i2c_main_init_status = MLXPLAT_I2C_MAIN_BUS_HANDLE_CREATED; + for (i = 0; i < mlxplat_mux_num; i++) { +- priv->pdev_mux[i] = platform_device_register_resndata(&priv->pdev_i2c->dev, +- "i2c-mux-reg", i, NULL, 0, +- &mlxplat_mux_data[i], +- sizeof(mlxplat_mux_data[i])); ++ if (mlxplat_mux_data) { ++ priv->pdev_mux[i] = ++ platform_device_register_resndata(&priv->pdev_i2c->dev, ++ "i2c-mux-reg", i, NULL, 0, ++ &mlxplat_mux_data[i], ++ sizeof(mlxplat_mux_data[i])); ++ } else { ++ mlxplat_mux_regmap_data[i].handle = priv; ++ mlxplat_mux_regmap_data[i].regmap = priv->regmap; ++ mlxplat_mux_regmap_data[i].completion_notify = ++ mlxplat_i2c_mux_complition_notify; ++ priv->pdev_mux[i] = ++ platform_device_register_resndata(&priv->pdev_i2c->dev, ++ "i2c-mux-regmap", i, NULL, 0, ++ &mlxplat_mux_regmap_data[i], ++ sizeof(mlxplat_mux_regmap_data[i])); ++ } + if (IS_ERR(priv->pdev_mux[i])) { + err = PTR_ERR(priv->pdev_mux[i]); + goto fail_platform_mux_register; + } + } + +- return mlxplat_i2c_mux_complition_notify(priv, NULL, NULL); ++ if (mlxplat_mux_regmap_data && mlxplat_mux_regmap_data->completion_notify) ++ return 0; ++ ++ return mlxplat_platdevs_init(priv); + + fail_platform_mux_register: + while (--i >= 0) +-- +2.8.4 + diff --git a/patches-sonic/0023-rtc-elba-cpld-support.patch b/patches-sonic/0023-rtc-elba-cpld-support.patch new file mode 100644 index 000000000..6bbe63c97 --- /dev/null +++ b/patches-sonic/0023-rtc-elba-cpld-support.patch @@ -0,0 +1,239 @@ +From eb9a1bfb178e087de1455d3bc37ee49d6d0a3249 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Wed, 24 Apr 2024 06:34:10 +0000 +Subject: [PATCH 23/27] rtc elba cpld support + +Adding rtc spi driver for cards with rtc on fpga and +propagated to cpld + +Signed-off-by: Mushtaq Khan +Signed-off-by: Sahil Chaudhari +Signed-off-by: Shantanu Shrivastava +--- + drivers/mfd/pensando-elbasr.c | 4 ++ + drivers/rtc/Kconfig | 9 +++ + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-elbacpld.c | 164 ++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 178 insertions(+) + create mode 100644 drivers/rtc/rtc-elbacpld.c + +diff --git a/drivers/mfd/pensando-elbasr.c b/drivers/mfd/pensando-elbasr.c +index 868cb98..8f07c3a 100644 +--- a/drivers/mfd/pensando-elbasr.c ++++ b/drivers/mfd/pensando-elbasr.c +@@ -57,6 +57,10 @@ + .name = "pensando_elbasr_reset", + .of_compatible = "amd,pensando-elbasr-reset", + }, ++ { ++ .name = "pensando_elbasr_rtc", ++ .of_compatible = "amd,pensando-elbasr-rtc", ++ }, + }; + + /* Bit masks for spi_device.mode management. Note that incorrect +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 66eb112..909322d 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -2059,6 +2059,15 @@ config RTC_DRV_POLARFIRE_SOC + This driver can also be built as a module, if so, the module + will be called "rtc-mpfs". + ++config RTC_DRV_ELBA_CPLD ++ tristate "Pensando Elba System Resource RTC controller" ++ depends on MFD_PENSANDO_ELBASR || COMPILE_TEST ++ help ++ This option enables support for RTC functions on the ++ Pensando Elba System Resource Chip. RTC is accessed ++ over SPI to the system resource chip device registers ++ using CS0. ++ + config RTC_DRV_SSD202D + tristate "SigmaStar SSD202D RTC" + depends on ARCH_MSTARV7 || COMPILE_TEST +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index f62340e..e4b2fde 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -63,6 +63,7 @@ obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o + obj-$(CONFIG_RTC_DRV_DS2404) += rtc-ds2404.o + obj-$(CONFIG_RTC_DRV_DS3232) += rtc-ds3232.o + obj-$(CONFIG_RTC_DRV_EFI) += rtc-efi.o ++obj-$(CONFIG_RTC_DRV_ELBA_CPLD) += rtc-elbacpld.o + obj-$(CONFIG_RTC_DRV_EM3027) += rtc-em3027.o + obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o + obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o +diff --git a/drivers/rtc/rtc-elbacpld.c b/drivers/rtc/rtc-elbacpld.c +new file mode 100644 +index 0000000..df66fce +--- /dev/null ++++ b/drivers/rtc/rtc-elbacpld.c +@@ -0,0 +1,164 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* drivers/rtc/rtc-elbacpld.c ++ * ++ * Driver for AMD ELBA CPLD real-time clock. ++ * ++ * Copyright (C) 2023 AMD Corporation ++ * Author: Mushtaq Khan ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ * Driver reads Clock and Date registers from ELBA CPLD over SPI. ++ * Uses SPI regmap created by driver mfd/pensando-elbasr.c. ++ * ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define RTC_BASE_OFFSET 0xA2 ++/* ++ * Date/Time registers ++ */ ++#define DT_100THS 0x00 ++#define DT_YEARS 0x01 ++#define DT_MONTHS 0x02 ++#define DT_DATE 0x03 ++#define DT_HOURS 0x04 ++#define DT_MINUTES 0x05 ++#define DT_SECS 0x06 ++#define DT_SUBSECS 0x07 ++#define DT_NUMREGS (DT_SUBSECS + 1) ++ ++struct elba_rtc { ++ struct rtc_device *rtc; ++ struct regmap *regmap; ++}; ++ ++static ssize_t date_reg_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct elba_rtc *rtc = dev_get_drvdata(dev); ++ unsigned int reg_val, count = 0, reg_offset; ++ int i, ret; ++ ++ for (i = 0; i < DT_NUMREGS; i++) { ++ reg_offset = RTC_BASE_OFFSET + i; ++ ret = regmap_read(rtc->regmap, reg_offset, ®_val); ++ if (ret) { ++ return sprintf(buf, "error %d reading offset %02X\n", ++ ret, reg_offset); ++ } ++ count += sprintf(buf+count, "offset:%02X BCD:%02X\n", ++ reg_offset, reg_val); ++ } ++ ++ return count; ++} ++ ++static DEVICE_ATTR(date_reg, S_IRUGO, date_reg_show,NULL); ++ ++static struct attribute *date_attr[] = { ++ &dev_attr_date_reg.attr, ++ NULL, ++}; ++ ++static struct attribute_group date_group = { ++ .attrs = date_attr, ++}; ++ ++static int elba_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct elba_rtc *rtc = dev_get_drvdata(dev); ++ unsigned char buf[DT_NUMREGS]; ++ int ret, len = sizeof(buf); ++ ++ /* read the RTC date and time registers all at once */ ++ ret = regmap_bulk_read(rtc->regmap, (RTC_BASE_OFFSET + DT_100THS), ++ buf, len); ++ if (ret) { ++ dev_err(dev, "%s: error %d\n", __func__, ret); ++ return ret; ++ } ++ ++ tm->tm_year = bcd2bin(buf[DT_YEARS]); ++ /* adjust for 1900 base of rtc_time */ ++ tm->tm_year += 100; ++ ++ tm->tm_wday = 0; ++ tm->tm_sec = bcd2bin(buf[DT_SECS]); ++ tm->tm_min = bcd2bin(buf[DT_MINUTES]); ++ tm->tm_hour = bcd2bin(buf[DT_HOURS]); ++ tm->tm_mday = bcd2bin(buf[DT_DATE]); ++ tm->tm_mon = bcd2bin(buf[DT_MONTHS]) - 1; ++ ++ return 0; ++} ++ ++static const struct rtc_class_ops rtc_ops = { ++ .read_time = elba_rtc_read_time, ++ .set_time = NULL, ++}; ++ ++static int elbasr_rtc_probe(struct platform_device *pdev) ++{ ++ struct elba_rtc *e_rtc; ++ struct elbasr_data *elbasr = dev_get_drvdata(pdev->dev.parent); ++ int ret; ++ ++ e_rtc = devm_kzalloc(&pdev->dev, sizeof(struct elba_rtc), ++ GFP_KERNEL); ++ if (!e_rtc) ++ return -ENOMEM; ++ ++ e_rtc->regmap = elbasr->elbasr_regs; ++ e_rtc->rtc = devm_rtc_allocate_device(&pdev->dev); ++ if (IS_ERR(e_rtc->rtc)) ++ return PTR_ERR(e_rtc->rtc); ++ ++ e_rtc->rtc->ops = &rtc_ops; ++ ++ platform_set_drvdata(pdev, e_rtc); ++ ++ ret = devm_rtc_register_device(e_rtc->rtc); ++ ++ ret = sysfs_create_group(&pdev->dev.kobj, &date_group); ++ if (ret) { ++ sysfs_remove_group(&pdev->dev.kobj, &date_group); ++ dev_err(&pdev->dev, "Failed to create attr group\n"); ++ } ++ ++ return ret; ++} ++ ++static const struct of_device_id elba_rtc_dt_match[] = { ++ { .compatible = "amd,pensando-elbasr-rtc", }, ++ { /* sentinel */ }, ++}; ++ ++static struct platform_driver elbasr_rtc_driver = { ++ .driver = { ++ .name = "pensando_elbasr_rtc", ++ .of_match_table = elba_rtc_dt_match, ++ }, ++ .probe = elbasr_rtc_probe, ++}; ++ ++module_platform_driver(elbasr_rtc_driver); ++ ++MODULE_AUTHOR("Mushtaq Khan "); ++MODULE_DESCRIPTION("elba cpld spi RTC driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_VERSION("1.0"); +-- +1.8.3.1 + diff --git a/patches-sonic/0024-dts-changes-for-mtfuji-and-lipari.patch b/patches-sonic/0024-dts-changes-for-mtfuji-and-lipari.patch new file mode 100644 index 000000000..fb12f4ec8 --- /dev/null +++ b/patches-sonic/0024-dts-changes-for-mtfuji-and-lipari.patch @@ -0,0 +1,242 @@ +From 85f6c655fdbf2a6117f7704cca24d32d325ae608 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Wed, 27 Mar 2024 16:03:00 +0000 +Subject: [PATCH 24/27] dts changes for mtfuji and lipari + +Bringup changes for mtfuji and lipari. Both are distributed +smart switches. + +Signed-off-by: Hiren Mehta +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/Makefile | 5 +- + .../boot/dts/pensando/elba-asic-psci-lipari.dts | 26 ++++++ + .../boot/dts/pensando/elba-asic-psci-mtfuji.dts | 26 ++++++ + arch/arm64/boot/dts/pensando/elba-asic-psci.dts | 26 ++++++ + .../boot/dts/pensando/elba-flash128-parts.dtsi | 101 +++++++++++++++++++++ + 5 files changed, 182 insertions(+), 2 deletions(-) + create mode 100644 arch/arm64/boot/dts/pensando/elba-asic-psci-lipari.dts + create mode 100644 arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts + create mode 100644 arch/arm64/boot/dts/pensando/elba-asic-psci.dts + create mode 100644 arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi + +diff --git a/arch/arm64/boot/dts/pensando/Makefile b/arch/arm64/boot/dts/pensando/Makefile +index 8971be4..b13afe8 100644 +--- a/arch/arm64/boot/dts/pensando/Makefile ++++ b/arch/arm64/boot/dts/pensando/Makefile +@@ -1,6 +1,7 @@ + # SPDX-License-Identifier: GPL-2.0 +-dtb-$(CONFIG_ARCH_PENSANDO_ELBA_SOC) += \ +- elba-asic.dtb ++dtb-$(CONFIG_ARCH_PENSANDO_ELBA_SOC) += elba-asic-psci.dtb \ ++ elba-asic-psci-mtfuji.dtb elba-asic-psci-lipari.dtb \ ++ elba-asic.dtb + + always-y := $(dtb-y) + subdir-y := $(dts-dirs) +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-psci-lipari.dts b/arch/arm64/boot/dts/pensando/elba-asic-psci-lipari.dts +new file mode 100644 +index 0000000..dfa6dea +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-asic-psci-lipari.dts +@@ -0,0 +1,26 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++/dts-v1/; ++ ++/ { ++ model = "Elba ASIC Board"; ++ compatible = "pensando,elba"; ++ ++ aliases { ++ serial0 = &uart0; ++ spi0 = &spi0; ++ spi1 = &qspi; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++}; ++ ++#include "elba.dtsi" ++#include "elba-16core.dtsi" ++#include "elba-16core-psci.dtsi" ++#include "elba-asic-common.dtsi" ++#include "elba-flash-parts.dtsi" ++#include "elba-asic-memory.dtsi" ++#include "elba-mdev.dtsi" +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts b/arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts +new file mode 100644 +index 0000000..714094b +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts +@@ -0,0 +1,26 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++/dts-v1/; ++ ++/ { ++ model = "Elba ASIC Board"; ++ compatible = "pensando,elba"; ++ ++ aliases { ++ serial0 = &uart0; ++ spi0 = &spi0; ++ spi1 = &qspi; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++}; ++ ++#include "elba.dtsi" ++#include "elba-16core.dtsi" ++#include "elba-16core-psci.dtsi" ++#include "elba-asic-common.dtsi" ++#include "elba-flash128-parts.dtsi" ++#include "elba-asic-memory.dtsi" ++#include "elba-mdev.dtsi" +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-psci.dts b/arch/arm64/boot/dts/pensando/elba-asic-psci.dts +new file mode 100644 +index 0000000..dfa6dea +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-asic-psci.dts +@@ -0,0 +1,26 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++/dts-v1/; ++ ++/ { ++ model = "Elba ASIC Board"; ++ compatible = "pensando,elba"; ++ ++ aliases { ++ serial0 = &uart0; ++ spi0 = &spi0; ++ spi1 = &qspi; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++}; ++ ++#include "elba.dtsi" ++#include "elba-16core.dtsi" ++#include "elba-16core-psci.dtsi" ++#include "elba-asic-common.dtsi" ++#include "elba-flash-parts.dtsi" ++#include "elba-asic-memory.dtsi" ++#include "elba-mdev.dtsi" +diff --git a/arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi b/arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi +new file mode 100644 +index 0000000..ddb961b +--- /dev/null ++++ b/arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi +@@ -0,0 +1,101 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++&flash0 { ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ partition@0 { ++ label = "flash"; ++ reg = <0x10000 0x7ff0000>; ++ }; ++ ++ partition@f0000 { ++ label = "golduenv"; ++ reg = <0xf0000 0x10000>; ++ }; ++ ++ partition@100000 { ++ label = "boot0"; ++ reg = <0x100000 0x80000>; ++ }; ++ ++ partition@180000 { ++ label = "golduboot"; ++ reg = <0x180000 0x200000>; ++ }; ++ ++ partition@380000 { ++ label = "brdcfg0"; ++ reg = <0x380000 0x10000>; ++ }; ++ ++ partition@390000 { ++ label = "brdcfg1"; ++ reg = <0x390000 0x10000>; ++ }; ++ ++ partition@400000 { ++ label = "goldfw"; ++ reg = <0x400000 0x3c00000>; ++ }; ++ ++ partition@4010000 { ++ label = "fwmap"; ++ reg = <0x4010000 0x20000>; ++ }; ++ ++ partition@4030000 { ++ label = "fwsel"; ++ reg = <0x4030000 0x20000>; ++ }; ++ ++ partition@4090000 { ++ label = "bootlog"; ++ reg = <0x4090000 0x20000>; ++ }; ++ ++ partition@40b0000 { ++ label = "panicbuf"; ++ reg = <0x40b0000 0x20000>; ++ }; ++ ++ partition@40d0000 { ++ label = "uservars"; ++ reg = <0x40d0000 0x20000>; ++ }; ++ ++ partition@4200000 { ++ label = "uboota"; ++ reg = <0x4200000 0x400000>; ++ }; ++ ++ partition@4600000 { ++ label = "ubootb"; ++ reg = <0x4600000 0x400000>; ++ }; ++ ++ partition@4a00000 { ++ label = "mainfwa"; ++ reg = <0x4a00000 0x1000000>; ++ }; ++ ++ partition@5a00000 { ++ label = "mainfwb"; ++ reg = <0x5a00000 0x1000000>; ++ }; ++ ++ partition@7fe0000 { ++ label = "ubootenv"; ++ reg = <0x7fe0000 0x10000>; ++ }; ++ }; ++}; ++ ++&soc { ++ panicdump@740b0000 { ++ compatible = "pensando,capri-crash"; ++ pensando,crash-ctrl = <&qspi>; ++ reg = <0x0 0x740b0000 0x0 0x20000>; ++ }; ++}; +-- +1.8.3.1 + diff --git a/patches-sonic/0024-mellanox-Relocate-mlx-platform-driver.patch b/patches-sonic/0024-mellanox-Relocate-mlx-platform-driver.patch new file mode 100644 index 000000000..44f42f022 --- /dev/null +++ b/patches-sonic/0024-mellanox-Relocate-mlx-platform-driver.patch @@ -0,0 +1,128 @@ +From 001d29f7f17201ff584f160814408fef22cad10d Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Fri, 24 Jan 2025 19:26:19 +0200 +Subject: [PATCH 26/80] mellanox: Relocate mlx-platform driver +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Move 'mlx-platform' driver 'x86' to 'mellanox' folder. + +Motivation to allow running it on systems with ARM architecture. + +Since drivers/platform/x86/mlx-platform.c is rellocated to folder +drivers/platform/x86/, remove "MELLANOX PLATFORM DRIVER" item. +This driver will be located under "MELLANOX HARDWARE PLATFORM SUPPORT" +item. + +Reviewed-by: Michael Shych +Signed-off-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250124172632.22437-2-vadimp@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + MAINTAINERS | 6 ------ + drivers/platform/mellanox/Kconfig | 13 +++++++++++++ + drivers/platform/mellanox/Makefile | 1 + + drivers/platform/{x86 => mellanox}/mlx-platform.c | 0 + drivers/platform/x86/Kconfig | 13 ------------- + drivers/platform/x86/Makefile | 1 - + 6 files changed, 14 insertions(+), 20 deletions(-) + rename drivers/platform/{x86 => mellanox}/mlx-platform.c (100%) + +diff --git a/MAINTAINERS b/MAINTAINERS +index d0f18fdba068..81730c72c960 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -14775,12 +14775,6 @@ F: Documentation/leds/leds-mlxcpld.rst + F: drivers/leds/leds-mlxcpld.c + F: drivers/leds/leds-mlxreg.c + +-MELLANOX PLATFORM DRIVER +-M: Vadim Pasternak +-L: platform-driver-x86@vger.kernel.org +-S: Supported +-F: drivers/platform/x86/mlx-platform.c +- + MEMBARRIER SUPPORT + M: Mathieu Desnoyers + M: "Paul E. McKenney" +diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig +index f7dfa0e785fd..aa760f064a17 100644 +--- a/drivers/platform/mellanox/Kconfig ++++ b/drivers/platform/mellanox/Kconfig +@@ -14,6 +14,19 @@ menuconfig MELLANOX_PLATFORM + + if MELLANOX_PLATFORM + ++config MLX_PLATFORM ++ tristate "Mellanox Technologies platform support" ++ depends on ACPI && I2C && PCI ++ select REGMAP ++ help ++ This option enables system support for the Mellanox Technologies ++ platform. The Mellanox systems provide data center networking ++ solutions based on Virtual Protocol Interconnect (VPI) technology ++ enable seamless connectivity to 56/100Gb/s InfiniBand or 10/40/56GbE ++ connection. ++ ++ If you have a Mellanox system, say Y or M here. ++ + config MLXREG_HOTPLUG + tristate "Mellanox platform hotplug driver support" + depends on HWMON +diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile +index 04703c0416b1..ba56485cbe8c 100644 +--- a/drivers/platform/mellanox/Makefile ++++ b/drivers/platform/mellanox/Makefile +@@ -3,6 +3,7 @@ + # Makefile for linux/drivers/platform/mellanox + # Mellanox Platform-Specific Drivers + # ++obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o + obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o + obj-$(CONFIG_MLXBF_PMC) += mlxbf-pmc.o + obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o +diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +similarity index 100% +rename from drivers/platform/x86/mlx-platform.c +rename to drivers/platform/mellanox/mlx-platform.c +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 3875abba5a79..92a3d5be0e36 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -1010,19 +1010,6 @@ config SERIAL_MULTI_INSTANTIATE + To compile this driver as a module, choose M here: the module + will be called serial-multi-instantiate. + +-config MLX_PLATFORM +- tristate "Mellanox Technologies platform support" +- depends on ACPI && I2C && PCI +- select REGMAP +- help +- This option enables system support for the Mellanox Technologies +- platform. The Mellanox systems provide data center networking +- solutions based on Virtual Protocol Interconnect (VPI) technology +- enable seamless connectivity to 56/100Gb/s InfiniBand or 10/40/56GbE +- connection. +- +- If you have a Mellanox system, say Y or M here. +- + config TOUCHSCREEN_DMI + bool "DMI based touchscreen configuration info" + depends on ACPI && DMI && I2C=y && TOUCHSCREEN_SILEAD +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index 4631c7bb22cd..776401fb9f04 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -123,7 +123,6 @@ obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o + + # Platform drivers + obj-$(CONFIG_SERIAL_MULTI_INSTANTIATE) += serial-multi-instantiate.o +-obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o + obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o + obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o + obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets/ +-- +2.47.2 + diff --git a/patches-sonic/0025-Add-support-for-new-qspi-macronix-flash-mx66u1g45g.patch b/patches-sonic/0025-Add-support-for-new-qspi-macronix-flash-mx66u1g45g.patch new file mode 100644 index 000000000..29379513c --- /dev/null +++ b/patches-sonic/0025-Add-support-for-new-qspi-macronix-flash-mx66u1g45g.patch @@ -0,0 +1,32 @@ +From 56e188c50e989da68d6695b20c3d2f9bca1d649b Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Mon, 5 Feb 2024 15:25:58 +0000 +Subject: [PATCH 25/27] Add support for new qspi macronix flash - mx66u1g45g + +Changes for supporting new qspi macronix flash mx66u1g45g + +Signed-off-by: Shantanu Shrivastava +--- + drivers/mtd/spi-nor/macronix.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c +index ea6be95..610b48e 100644 +--- a/drivers/mtd/spi-nor/macronix.c ++++ b/drivers/mtd/spi-nor/macronix.c +@@ -150,6 +150,12 @@ + .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .fixup_flags = SPI_NOR_4B_OPCODES, + }, { ++ .id = SNOR_ID(0xc2, 0x25, 0x3b), ++ .name = "mx66u1g45g", ++ .size = SZ_128M, ++ .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, ++ .fixup_flags = SPI_NOR_4B_OPCODES, ++ }, { + .id = SNOR_ID(0xc2, 0x26, 0x18), + .name = "mx25l12855e", + .size = SZ_16M, +-- +1.8.3.1 + diff --git a/patches-sonic/0025-platform-mellanox-mlx-platform-Cosmetic-changes.patch b/patches-sonic/0025-platform-mellanox-mlx-platform-Cosmetic-changes.patch new file mode 100644 index 000000000..4b691ada5 --- /dev/null +++ b/patches-sonic/0025-platform-mellanox-mlx-platform-Cosmetic-changes.patch @@ -0,0 +1,71 @@ +From 21cb84ee58d84c50143bb6abcf8648777f6134c7 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Fri, 24 Jan 2025 19:26:20 +0200 +Subject: [PATCH 25/78] platform: mellanox: mlx-platform: Cosmetic changes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Remove redundant spaces. + +Reviewed-by: Felix Radensky +Signed-off-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250124172632.22437-3-vadimp@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlx-platform.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index cbd5634..cc61f8e 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -2252,7 +2252,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_modular_data = { + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, + }; + +-/* Platform hotplug for NVLink blade systems family data */ ++/* Platform hotplug for NVLink blade systems family data */ + static struct mlxreg_core_data mlxplat_mlxcpld_global_wp_items_data[] = { + { + .label = "global_wp_grant", +@@ -2284,7 +2284,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_chassis_blade_data = { + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, + }; + +-/* Platform hotplug for switch systems family data */ ++/* Platform hotplug for switch systems family data */ + static struct mlxreg_core_data mlxplat_mlxcpld_erot_ap_items_data[] = { + { + .label = "erot1_ap", +@@ -2392,7 +2392,7 @@ static struct mlxreg_core_hotplug_notifier mlxplat_mlxcpld_l1_switch_pwr_events_ + .user_handler = mlxplat_mlxcpld_l1_switch_pwr_events_handler, + }; + +-/* Platform hotplug for l1 switch systems family data */ ++/* Platform hotplug for l1 switch systems family data */ + static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_pwr_events_items_data[] = { + { + .label = "power_button", +@@ -4406,7 +4406,7 @@ static struct mlxreg_core_platform_data mlxplat_modular_regs_io_data = { + .counter = ARRAY_SIZE(mlxplat_mlxcpld_modular_regs_io_data), + }; + +-/* Platform register access for chassis blade systems family data */ ++/* Platform register access for chassis blade systems family data */ + static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = { + { + .label = "cpld1_version", +@@ -6623,7 +6623,7 @@ static int mlxplat_probe(struct platform_device *pdev) + } + + /* Set default registers. */ +- for (i = 0; i < mlxplat_regmap_config->num_reg_defaults; i++) { ++ for (i = 0; i < mlxplat_regmap_config->num_reg_defaults; i++) { + err = regmap_write(priv->regmap, + mlxplat_regmap_config->reg_defaults[i].reg, + mlxplat_regmap_config->reg_defaults[i].def); +-- +2.8.4 + diff --git a/patches-sonic/0026-Create-miniaturised-diafgw-partition-for-128MB-flash.patch b/patches-sonic/0026-Create-miniaturised-diafgw-partition-for-128MB-flash.patch new file mode 100644 index 000000000..fb7a7b5f6 --- /dev/null +++ b/patches-sonic/0026-Create-miniaturised-diafgw-partition-for-128MB-flash.patch @@ -0,0 +1,32 @@ +From 6cc3594c513a34eea7c5b8f9ad9146d0aba6ebc5 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Mon, 11 Mar 2024 14:06:05 +0000 +Subject: [PATCH 26/27] Create miniaturised diafgw partition for 128MB flash + +Changes for creating a miniaturised diagfw for installing +minigold + +Signed-off-by: Shantanu Shrivastava +--- + arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi b/arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi +index ddb961b..7a86eb7 100644 +--- a/arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi ++++ b/arch/arm64/boot/dts/pensando/elba-flash128-parts.dtsi +@@ -85,6 +85,11 @@ + reg = <0x5a00000 0x1000000>; + }; + ++ partition@6a00000 { ++ label = "diagfw"; ++ reg = <0x6a00000 0x15e0000>; ++ }; ++ + partition@7fe0000 { + label = "ubootenv"; + reg = <0x7fe0000 0x10000>; +-- +1.8.3.1 + diff --git a/patches-sonic/0026-platform-mellanox-Rename-field-to-improve-code-reada.patch b/patches-sonic/0026-platform-mellanox-Rename-field-to-improve-code-reada.patch new file mode 100644 index 000000000..814a0142a --- /dev/null +++ b/patches-sonic/0026-platform-mellanox-Rename-field-to-improve-code-reada.patch @@ -0,0 +1,239 @@ +From 66134d289e9ca0d1c9a3225e9e82f4ef41788290 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sat, 12 Apr 2025 12:18:37 +0300 +Subject: [PATCH 26/78] platform/mellanox: Rename field to improve code + readability +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Rename field 'counter' in 'mlxreg_core_hotplug_platform_data' to count. + +Signed-off-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250412091843.33943-2-vadimp@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlx-platform.c | 28 ++++++++++++++-------------- + drivers/platform/mellanox/mlxreg-hotplug.c | 8 ++++---- + drivers/platform/mellanox/nvsw-sn2201.c | 4 ++-- + include/linux/platform_data/mlxreg.h | 4 ++-- + 4 files changed, 22 insertions(+), 22 deletions(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index cc61f8e..a7a4c7f 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -857,7 +857,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_data = { + .items = mlxplat_mlxcpld_default_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -897,7 +897,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_wc_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_wc_data = { + .items = mlxplat_mlxcpld_default_wc_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_wc_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_wc_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -907,7 +907,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_wc_data = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_comex_data = { + .items = mlxplat_mlxcpld_comex_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_comex_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_comex_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_CARR_DEF, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET, +@@ -954,7 +954,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_msn21xx_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn21xx_data = { + .items = mlxplat_mlxcpld_msn21xx_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -1063,7 +1063,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_msn274x_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn274x_data = { + .items = mlxplat_mlxcpld_msn274x_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn274x_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_msn274x_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -1110,7 +1110,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_msn201x_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn201x_data = { + .items = mlxplat_mlxcpld_msn201x_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn201x_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_msn201x_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -1234,7 +1234,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_ng_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_ng_data = { + .items = mlxplat_mlxcpld_default_ng_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -1394,7 +1394,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { + .items = mlxplat_mlxcpld_ext_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_ext_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -1404,7 +1404,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = { + .items = mlxplat_mlxcpld_ng800_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_ng800_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_ng800_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -2245,7 +2245,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_modular_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_modular_data = { + .items = mlxplat_mlxcpld_modular_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_modular_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_modular_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_MODULAR, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -2277,7 +2277,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_chassis_blade_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_chassis_blade_data = { + .items = mlxplat_mlxcpld_chassis_blade_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_chassis_blade_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_chassis_blade_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -2368,7 +2368,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_rack_switch_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_rack_switch_data = { + .items = mlxplat_mlxcpld_rack_switch_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_rack_switch_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_rack_switch_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -2523,7 +2523,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_l1_switch_events_items[] = { + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_l1_switch_data = { + .items = mlxplat_mlxcpld_l1_switch_events_items, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_events_items), ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_events_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +@@ -6450,7 +6450,7 @@ mlxplat_i2c_mux_complition_notify(void *handle, struct i2c_adapter *parent, + */ + if (priv->mux_added == mlxplat_mux_hotplug_num) { + item = mlxplat_hotplug->items; +- for (i = 0; i < mlxplat_hotplug->counter; i++, item++) { ++ for (i = 0; i < mlxplat_hotplug->count; i++, item++) { + struct mlxreg_core_data *data = item->data; + + for (j = 0; j < item->count; j++, data++) { +diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c +index 40d5b99..23fdbcb 100644 +--- a/drivers/platform/mellanox/mlxreg-hotplug.c ++++ b/drivers/platform/mellanox/mlxreg-hotplug.c +@@ -262,7 +262,7 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) + item = pdata->items; + + /* Go over all kinds of items - psu, pwr, fan. */ +- for (i = 0; i < pdata->counter; i++, item++) { ++ for (i = 0; i < pdata->count; i++, item++) { + if (item->capability) { + /* + * Read group capability register to get actual number +@@ -541,7 +541,7 @@ static void mlxreg_hotplug_work_handler(struct work_struct *work) + goto unmask_event; + + /* Handle topology and health configuration changes. */ +- for (i = 0; i < pdata->counter; i++, item++) { ++ for (i = 0; i < pdata->count; i++, item++) { + if (aggr_asserted & item->aggr_mask) { + if (item->health) + mlxreg_hotplug_health_work_helper(priv, item); +@@ -590,7 +590,7 @@ static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) + pdata = dev_get_platdata(&priv->pdev->dev); + item = pdata->items; + +- for (i = 0; i < pdata->counter; i++, item++) { ++ for (i = 0; i < pdata->count; i++, item++) { + /* Clear group presense event. */ + ret = regmap_write(priv->regmap, item->reg + + MLXREG_HOTPLUG_EVENT_OFF, 0); +@@ -674,7 +674,7 @@ static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) + 0); + + /* Clear topology configurations. */ +- for (i = 0; i < pdata->counter; i++, item++) { ++ for (i = 0; i < pdata->count; i++, item++) { + data = item->data; + /* Mask group presense event. */ + regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF, +diff --git a/drivers/platform/mellanox/nvsw-sn2201.c b/drivers/platform/mellanox/nvsw-sn2201.c +index beb3f82..db31c8b 100644 +--- a/drivers/platform/mellanox/nvsw-sn2201.c ++++ b/drivers/platform/mellanox/nvsw-sn2201.c +@@ -523,7 +523,7 @@ static struct mlxreg_core_item nvsw_sn2201_items[] = { + static + struct mlxreg_core_hotplug_platform_data nvsw_sn2201_hotplug = { + .items = nvsw_sn2201_items, +- .counter = ARRAY_SIZE(nvsw_sn2201_items), ++ .count = ARRAY_SIZE(nvsw_sn2201_items), + .cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET, + .mask = NVSW_SN2201_CPLD_AGGR_MASK_DEF, + }; +@@ -552,7 +552,7 @@ static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = { + static + struct mlxreg_core_hotplug_platform_data nvsw_sn2201_busbar_hotplug = { + .items = nvsw_sn2201_busbar_items, +- .counter = ARRAY_SIZE(nvsw_sn2201_busbar_items), ++ .count = ARRAY_SIZE(nvsw_sn2201_busbar_items), + .cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET, + .mask = NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF, + }; +diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h +index 0b9f81a..f6cca7a 100644 +--- a/include/linux/platform_data/mlxreg.h ++++ b/include/linux/platform_data/mlxreg.h +@@ -209,7 +209,7 @@ struct mlxreg_core_platform_data { + * @items: same type components with the hotplug capability; + * @irq: platform interrupt number; + * @regmap: register map of parent device; +- * @counter: number of the components with the hotplug capability; ++ * @count: number of the components with the hotplug capability; + * @cell: location of top aggregation interrupt register; + * @mask: top aggregation interrupt common mask; + * @cell_low: location of low aggregation interrupt register; +@@ -224,7 +224,7 @@ struct mlxreg_core_hotplug_platform_data { + struct mlxreg_core_item *items; + int irq; + void *regmap; +- int counter; ++ int count; + u32 cell; + u32 mask; + u32 cell_low; +-- +2.8.4 + diff --git a/patches-sonic/0027-pcie-if-hotplug-enabled-do-immediate-reset-on-panic.patch b/patches-sonic/0027-pcie-if-hotplug-enabled-do-immediate-reset-on-panic.patch new file mode 100644 index 000000000..220074068 --- /dev/null +++ b/patches-sonic/0027-pcie-if-hotplug-enabled-do-immediate-reset-on-panic.patch @@ -0,0 +1,53 @@ +From fc10b0d68bebee92730732306b4450d489355e5e Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Mon, 11 Mar 2024 14:12:44 +0000 +Subject: [PATCH 27/27] pcie: if hotplug enabled do immediate reset on panic + +Signed-off-by: Ganesan Ramalingam +Signed-off-by: Shantanu Shrivastava +--- + drivers/soc/pensando/cap_pcie.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/drivers/soc/pensando/cap_pcie.c b/drivers/soc/pensando/cap_pcie.c +index bbfd71c..5588986 100644 +--- a/drivers/soc/pensando/cap_pcie.c ++++ b/drivers/soc/pensando/cap_pcie.c +@@ -41,6 +41,7 @@ struct pciedev_info { + int pciep_access_error; + spinlock_t pciep_access_lock; + long (*saved_panic_blink)(int state); ++ u32 hotplug_delaylnkup; + }; + + static struct pciedev_info pciedev_info; +@@ -302,6 +303,8 @@ static void cap_reset(void) + * + * If we haven't yet initialized the link (ltssm_en=0) then the + * host side hasn't come up yet. In that case just reset immediately. ++ * ++ * If hotplug enabled then reset immediately + */ + static long pcie_panic_blink(int state) + { +@@ -312,7 +315,7 @@ static long pcie_panic_blink(int state) + cap_reset(); + + port = pcie_get_ltssm_en(); +- if (port >= 0) { ++ if (!pciedev_info.hotplug_delaylnkup && port >= 0) { + pr_info(PFX "port %d enabled\n", port); + pcie_set_crs(0); + while ((port = pcie_poll_for_hostdn()) < 0) +@@ -389,6 +392,8 @@ static int pcie_probe(struct platform_device *pd) + pi->pcie_base = res.start; + pi->pcie_size = resource_size(&res); + ++ of_property_read_u32(dn, "hotplug_delaylnkup", &pi->hotplug_delaylnkup); ++ + err = misc_register(&pcie_dev); + if (err) { + pr_err(PFX "register pcie_dev failed: %d\n", err); +-- +1.8.3.1 + diff --git a/patches-sonic/0027-platform-mellanox-mlx-platform-Change-register-name.patch b/patches-sonic/0027-platform-mellanox-mlx-platform-Change-register-name.patch new file mode 100644 index 000000000..205ce004f --- /dev/null +++ b/patches-sonic/0027-platform-mellanox-mlx-platform-Change-register-name.patch @@ -0,0 +1,62 @@ +From f6d1e88b0f6809f755fae05b74bafb187db36f64 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Fri, 24 Jan 2025 19:26:21 +0200 +Subject: [PATCH 27/78] platform: mellanox: mlx-platform: Change register name +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Register 0xd9 was repurposed on new systems. Change its name +to correctly reflect the new functionality. + +Reviewed-by: Felix Radensky +Signed-off-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250124172632.22437-4-vadimp@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlx-platform.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index a7a4c7f..6f24de1 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -146,7 +146,7 @@ + #define MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET 0xd1 + #define MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET 0xd2 + #define MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET 0xd3 +-#define MLXPLAT_CPLD_LPC_REG_DBG_CTRL_OFFSET 0xd9 ++#define MLXPLAT_CPLD_LPC_REG_CPLD6_MVER_OFFSET 0xd9 + #define MLXPLAT_CPLD_LPC_REG_I2C_CH1_OFFSET 0xdb + #define MLXPLAT_CPLD_LPC_REG_I2C_CH2_OFFSET 0xda + #define MLXPLAT_CPLD_LPC_REG_I2C_CH3_OFFSET 0xdc +@@ -5055,7 +5055,6 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: +- case MLXPLAT_CPLD_LPC_REG_DBG_CTRL_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH3_OFFSET: +@@ -5191,7 +5190,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: +- case MLXPLAT_CPLD_LPC_REG_DBG_CTRL_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_MVER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH3_OFFSET: +@@ -5348,7 +5347,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: +- case MLXPLAT_CPLD_LPC_REG_DBG_CTRL_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_MVER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_I2C_CH3_OFFSET: +-- +2.8.4 + diff --git a/patches-sonic/0028-Added-current-sensors-in-dtsi.patch b/patches-sonic/0028-Added-current-sensors-in-dtsi.patch new file mode 100644 index 000000000..e34bfff9f --- /dev/null +++ b/patches-sonic/0028-Added-current-sensors-in-dtsi.patch @@ -0,0 +1,38 @@ +From 6953e74c7cf345116ddd148c279ce5e562b78db9 Mon Sep 17 00:00:00 2001 +From: Shantanu Shrivastava +Date: Mon, 11 Mar 2024 08:13:31 +0000 +Subject: [PATCH] Added current sensors in dtsi + +--- + .../boot/dts/pensando/elba-asic-psci-mtfuji.dts | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts b/arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts +index 1d2e99d8c..10917168c 100644 +--- a/arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts ++++ b/arch/arm64/boot/dts/pensando/elba-asic-psci-mtfuji.dts +@@ -27,6 +27,21 @@ chosen { + + &i2c0 { + /delete-node/ rtc@51; ++ ++ ltc3882@44 { ++ compatible = "lltc,ltc3882"; ++ reg = <0x44>; ++ }; ++ ++ ltc3882@55 { ++ compatible = "lltc,ltc3882"; ++ reg = <0x55>; ++ }; ++ ++ ltc3882@66 { ++ compatible = "lltc,ltc3882"; ++ reg = <0x66>; ++ }; + }; + + &spi0_cs0 { +-- +2.25.1 + diff --git a/patches-sonic/0028-platform-mellanox-mlxreg-dpu-Add-initial-support-for.patch b/patches-sonic/0028-platform-mellanox-mlxreg-dpu-Add-initial-support-for.patch new file mode 100644 index 000000000..a8c1dc775 --- /dev/null +++ b/patches-sonic/0028-platform-mellanox-mlxreg-dpu-Add-initial-support-for.patch @@ -0,0 +1,694 @@ +From 10505bcbd54c4e7fc5194384f5bb0aea5230b296 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 21 Apr 2025 12:20:46 +0300 +Subject: [PATCH 28/78] platform/mellanox: mlxreg-dpu: Add initial support for + Nvidia DPU +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Provide platform support for Nvidia (DPU) Data Processor Unit for the +Smart Switch SN4280. + +The Smart Switch equipped with: +- Nvidia COME module based on AMD EPYC™ Embedded 3451 CPU. +- Nvidia Spectrum-3 ASIC. +- Four DPUs, each equipped with Nvidia BF3 ARM based processor and + with Lattice LFD2NX-40 FPGA device. +- 28xQSFP-DD external ports. +- Two power supplies. +- Four cooling drawers. + +Driver provides support for the platform management and monitoring +of DPU components. It includes support for: health events, resets and +boot progress indications logic, implemented by FPGA device. + +Reviewed-by: Ciju Rajan K +Signed-off-by: Vadim Pasternak +[ij: added depends on I2C] +Reviewed-by: Ilpo Järvinen +Link: https://lore.kernel.org/r/20250421092051.7687-2-vadimp@nvidia.com +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/Kconfig | 13 + + drivers/platform/mellanox/Makefile | 1 + + drivers/platform/mellanox/mlxreg-dpu.c | 611 +++++++++++++++++++++++++++++++++ + 3 files changed, 625 insertions(+) + create mode 100644 drivers/platform/mellanox/mlxreg-dpu.c + +diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig +index aa760f0..e3afbe6 100644 +--- a/drivers/platform/mellanox/Kconfig ++++ b/drivers/platform/mellanox/Kconfig +@@ -27,6 +27,19 @@ config MLX_PLATFORM + + If you have a Mellanox system, say Y or M here. + ++config MLXREG_DPU ++ tristate "Nvidia Data Processor Unit platform driver support" ++ depends on I2C ++ select REGMAP_I2C ++ help ++ This driver provides support for the Nvidia BF3 Data Processor Units, ++ which are the part of SN4280 Ethernet smart switch systems ++ providing a high performance switching solution for Enterprise Data ++ Centers (EDC) for building Ethernet based clusters, High-Performance ++ Computing (HPC) and embedded environments. ++ ++ If you have a Nvidia smart switch system, say Y or M here. ++ + config MLXREG_HOTPLUG + tristate "Mellanox platform hotplug driver support" + depends on HWMON +diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile +index ba56485..e86723b 100644 +--- a/drivers/platform/mellanox/Makefile ++++ b/drivers/platform/mellanox/Makefile +@@ -7,6 +7,7 @@ obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o + obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o + obj-$(CONFIG_MLXBF_PMC) += mlxbf-pmc.o + obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o ++obj-$(CONFIG_MLXREG_DPU) += mlxreg-dpu.o + obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o + obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o + obj-$(CONFIG_MLXREG_LC) += mlxreg-lc.o +diff --git a/drivers/platform/mellanox/mlxreg-dpu.c b/drivers/platform/mellanox/mlxreg-dpu.c +new file mode 100644 +index 00000000..277e4b8 +--- /dev/null ++++ b/drivers/platform/mellanox/mlxreg-dpu.c +@@ -0,0 +1,611 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Nvidia Data Processor Unit platform driver ++ * ++ * Copyright (C) 2025 Nvidia Technologies Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* I2C bus IO offsets */ ++#define MLXREG_DPU_REG_FPGA1_VER_OFFSET 0x2400 ++#define MLXREG_DPU_REG_FPGA1_PN_OFFSET 0x2404 ++#define MLXREG_DPU_REG_FPGA1_PN1_OFFSET 0x2405 ++#define MLXREG_DPU_REG_PG_OFFSET 0x2414 ++#define MLXREG_DPU_REG_PG_EVENT_OFFSET 0x2415 ++#define MLXREG_DPU_REG_PG_MASK_OFFSET 0x2416 ++#define MLXREG_DPU_REG_RESET_GP1_OFFSET 0x2417 ++#define MLXREG_DPU_REG_RST_CAUSE1_OFFSET 0x241e ++#define MLXREG_DPU_REG_GP0_RO_OFFSET 0x242b ++#define MLXREG_DPU_REG_GP0_OFFSET 0x242e ++#define MLXREG_DPU_REG_GP1_OFFSET 0x242c ++#define MLXREG_DPU_REG_GP4_OFFSET 0x2438 ++#define MLXREG_DPU_REG_AGGRCO_OFFSET 0x2442 ++#define MLXREG_DPU_REG_AGGRCO_MASK_OFFSET 0x2443 ++#define MLXREG_DPU_REG_HEALTH_OFFSET 0x244d ++#define MLXREG_DPU_REG_HEALTH_EVENT_OFFSET 0x244e ++#define MLXREG_DPU_REG_HEALTH_MASK_OFFSET 0x244f ++#define MLXREG_DPU_REG_FPGA1_MVER_OFFSET 0x24de ++#define MLXREG_DPU_REG_CONFIG3_OFFSET 0x24fd ++#define MLXREG_DPU_REG_MAX 0x3fff ++ ++/* Power Good event masks. */ ++#define MLXREG_DPU_PG_VDDIO_MASK BIT(0) ++#define MLXREG_DPU_PG_VDD_CPU_MASK BIT(1) ++#define MLXREG_DPU_PG_VDD_MASK BIT(2) ++#define MLXREG_DPU_PG_1V8_MASK BIT(3) ++#define MLXREG_DPU_PG_COMPARATOR_MASK BIT(4) ++#define MLXREG_DPU_PG_VDDQ_MASK BIT(5) ++#define MLXREG_DPU_PG_HVDD_MASK BIT(6) ++#define MLXREG_DPU_PG_DVDD_MASK BIT(7) ++#define MLXREG_DPU_PG_MASK (MLXREG_DPU_PG_DVDD_MASK | \ ++ MLXREG_DPU_PG_HVDD_MASK | \ ++ MLXREG_DPU_PG_VDDQ_MASK | \ ++ MLXREG_DPU_PG_COMPARATOR_MASK | \ ++ MLXREG_DPU_PG_1V8_MASK | \ ++ MLXREG_DPU_PG_VDD_CPU_MASK | \ ++ MLXREG_DPU_PG_VDD_MASK | \ ++ MLXREG_DPU_PG_VDDIO_MASK) ++ ++/* Health event masks. */ ++#define MLXREG_DPU_HLTH_THERMAL_TRIP_MASK BIT(0) ++#define MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK BIT(1) ++#define MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK BIT(2) ++#define MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK BIT(3) ++#define MLXREG_DPU_HLTH_VDDQ_ALERT_MASK BIT(4) ++#define MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK BIT(5) ++#define MLXREG_DPU_HEALTH_MASK (MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK | \ ++ MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK | \ ++ MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK | \ ++ MLXREG_DPU_HLTH_VDDQ_ALERT_MASK | \ ++ MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK | \ ++ MLXREG_DPU_HLTH_THERMAL_TRIP_MASK) ++ ++/* Hotplug aggregation masks. */ ++#define MLXREG_DPU_HEALTH_AGGR_MASK BIT(0) ++#define MLXREG_DPU_PG_AGGR_MASK BIT(1) ++#define MLXREG_DPU_AGGR_MASK (MLXREG_DPU_HEALTH_AGGR_MASK | \ ++ MLXREG_DPU_PG_AGGR_MASK) ++ ++/* Voltage regulator firmware update status mask. */ ++#define MLXREG_DPU_VOLTREG_UPD_MASK GENMASK(5, 4) ++ ++#define MLXREG_DPU_NR_NONE (-1) ++ ++/* ++ * enum mlxreg_dpu_type - Data Processor Unit types ++ * ++ * @MLXREG_DPU_BF3: DPU equipped with BF3 SoC; ++ */ ++enum mlxreg_dpu_type { ++ MLXREG_DPU_BF3 = 0x0050, ++}; ++ ++/* Default register access data. */ ++static struct mlxreg_core_data mlxreg_dpu_io_data[] = { ++ { ++ .label = "fpga1_version", ++ .reg = MLXREG_DPU_REG_FPGA1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "fpga1_pn", ++ .reg = MLXREG_DPU_REG_FPGA1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "fpga1_version_min", ++ .reg = MLXREG_DPU_REG_FPGA1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "perst_rst", ++ .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "usbphy_rst", ++ .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "phy_rst", ++ .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "tpm_rst", ++ .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_from_main_board", ++ .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_reload", ++ .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_pwr_fail", ++ .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_dpu_thermal", ++ .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_off", ++ .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "dpu_id", ++ .reg = MLXREG_DPU_REG_GP0_RO_OFFSET, ++ .bit = GENMASK(3, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "voltreg_update_status", ++ .reg = MLXREG_DPU_REG_GP0_RO_OFFSET, ++ .mask = MLXREG_DPU_VOLTREG_UPD_MASK, ++ .bit = 5, ++ .mode = 0444, ++ }, ++ { ++ .label = "boot_progress", ++ .reg = MLXREG_DPU_REG_GP1_OFFSET, ++ .bit = GENMASK(3, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "ufm_upgrade", ++ .reg = MLXREG_DPU_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxreg_dpu_default_regs_io_data = { ++ .data = mlxreg_dpu_io_data, ++ .counter = ARRAY_SIZE(mlxreg_dpu_io_data), ++}; ++ ++/* Default hotplug data. */ ++static struct mlxreg_core_data mlxreg_dpu_power_events_items_data[] = { ++ { ++ .label = "pg_vddio", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_VDDIO_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "pg_vdd_cpu", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_VDD_CPU_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "pg_vdd", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_VDD_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "pg_1v8", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_1V8_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "pg_comparator", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_COMPARATOR_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "pg_vddq", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_VDDQ_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "pg_hvdd", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_HVDD_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "pg_dvdd", ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_DVDD_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data mlxreg_dpu_health_events_items_data[] = { ++ { ++ .label = "thermal_trip", ++ .reg = MLXREG_DPU_REG_HEALTH_OFFSET, ++ .mask = MLXREG_DPU_HLTH_THERMAL_TRIP_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "ufm_upgrade_done", ++ .reg = MLXREG_DPU_REG_HEALTH_OFFSET, ++ .mask = MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "vddq_hot_alert", ++ .reg = MLXREG_DPU_REG_HEALTH_OFFSET, ++ .mask = MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "vdd_cpu_hot_alert", ++ .reg = MLXREG_DPU_REG_HEALTH_OFFSET, ++ .mask = MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "vddq_alert", ++ .reg = MLXREG_DPU_REG_HEALTH_OFFSET, ++ .mask = MLXREG_DPU_HLTH_VDDQ_ALERT_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++ { ++ .label = "vdd_cpu_alert", ++ .reg = MLXREG_DPU_REG_HEALTH_OFFSET, ++ .mask = MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK, ++ .hpdev.nr = MLXREG_DPU_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_item mlxreg_dpu_hotplug_items[] = { ++ { ++ .data = mlxreg_dpu_power_events_items_data, ++ .aggr_mask = MLXREG_DPU_PG_AGGR_MASK, ++ .reg = MLXREG_DPU_REG_PG_OFFSET, ++ .mask = MLXREG_DPU_PG_MASK, ++ .count = ARRAY_SIZE(mlxreg_dpu_power_events_items_data), ++ .health = false, ++ .inversed = 0, ++ }, ++ { ++ .data = mlxreg_dpu_health_events_items_data, ++ .aggr_mask = MLXREG_DPU_HEALTH_AGGR_MASK, ++ .reg = MLXREG_DPU_REG_HEALTH_OFFSET, ++ .mask = MLXREG_DPU_HEALTH_MASK, ++ .count = ARRAY_SIZE(mlxreg_dpu_health_events_items_data), ++ .health = false, ++ .inversed = 0, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxreg_dpu_default_hotplug_data = { ++ .items = mlxreg_dpu_hotplug_items, ++ .count = ARRAY_SIZE(mlxreg_dpu_hotplug_items), ++ .cell = MLXREG_DPU_REG_AGGRCO_OFFSET, ++ .mask = MLXREG_DPU_AGGR_MASK, ++}; ++ ++/** ++ * struct mlxreg_dpu - device private data ++ * @dev: platform device ++ * @data: platform core data ++ * @io_data: register access platform data ++ * @io_regs: register access device ++ * @hotplug_data: hotplug platform data ++ * @hotplug: hotplug device ++ */ ++struct mlxreg_dpu { ++ struct device *dev; ++ struct mlxreg_core_data *data; ++ struct mlxreg_core_platform_data *io_data; ++ struct platform_device *io_regs; ++ struct mlxreg_core_hotplug_platform_data *hotplug_data; ++ struct platform_device *hotplug; ++}; ++ ++static bool mlxreg_dpu_writeable_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case MLXREG_DPU_REG_PG_EVENT_OFFSET: ++ case MLXREG_DPU_REG_PG_MASK_OFFSET: ++ case MLXREG_DPU_REG_RESET_GP1_OFFSET: ++ case MLXREG_DPU_REG_GP0_OFFSET: ++ case MLXREG_DPU_REG_GP1_OFFSET: ++ case MLXREG_DPU_REG_GP4_OFFSET: ++ case MLXREG_DPU_REG_AGGRCO_OFFSET: ++ case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_MASK_OFFSET: ++ return true; ++ } ++ return false; ++} ++ ++static bool mlxreg_dpu_readable_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case MLXREG_DPU_REG_FPGA1_VER_OFFSET: ++ case MLXREG_DPU_REG_FPGA1_PN_OFFSET: ++ case MLXREG_DPU_REG_FPGA1_PN1_OFFSET: ++ case MLXREG_DPU_REG_PG_OFFSET: ++ case MLXREG_DPU_REG_PG_EVENT_OFFSET: ++ case MLXREG_DPU_REG_PG_MASK_OFFSET: ++ case MLXREG_DPU_REG_RESET_GP1_OFFSET: ++ case MLXREG_DPU_REG_RST_CAUSE1_OFFSET: ++ case MLXREG_DPU_REG_GP0_RO_OFFSET: ++ case MLXREG_DPU_REG_GP0_OFFSET: ++ case MLXREG_DPU_REG_GP1_OFFSET: ++ case MLXREG_DPU_REG_GP4_OFFSET: ++ case MLXREG_DPU_REG_AGGRCO_OFFSET: ++ case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_MASK_OFFSET: ++ case MLXREG_DPU_REG_FPGA1_MVER_OFFSET: ++ case MLXREG_DPU_REG_CONFIG3_OFFSET: ++ return true; ++ } ++ return false; ++} ++ ++static bool mlxreg_dpu_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case MLXREG_DPU_REG_FPGA1_VER_OFFSET: ++ case MLXREG_DPU_REG_FPGA1_PN_OFFSET: ++ case MLXREG_DPU_REG_FPGA1_PN1_OFFSET: ++ case MLXREG_DPU_REG_PG_OFFSET: ++ case MLXREG_DPU_REG_PG_EVENT_OFFSET: ++ case MLXREG_DPU_REG_PG_MASK_OFFSET: ++ case MLXREG_DPU_REG_RESET_GP1_OFFSET: ++ case MLXREG_DPU_REG_RST_CAUSE1_OFFSET: ++ case MLXREG_DPU_REG_GP0_RO_OFFSET: ++ case MLXREG_DPU_REG_GP0_OFFSET: ++ case MLXREG_DPU_REG_GP1_OFFSET: ++ case MLXREG_DPU_REG_GP4_OFFSET: ++ case MLXREG_DPU_REG_AGGRCO_OFFSET: ++ case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET: ++ case MLXREG_DPU_REG_HEALTH_MASK_OFFSET: ++ case MLXREG_DPU_REG_FPGA1_MVER_OFFSET: ++ case MLXREG_DPU_REG_CONFIG3_OFFSET: ++ return true; ++ } ++ return false; ++} ++ ++/* Configuration for the register map of a device with 2 bytes address space. */ ++static const struct regmap_config mlxreg_dpu_regmap_conf = { ++ .reg_bits = 16, ++ .val_bits = 8, ++ .max_register = MLXREG_DPU_REG_MAX, ++ .cache_type = REGCACHE_FLAT, ++ .writeable_reg = mlxreg_dpu_writeable_reg, ++ .readable_reg = mlxreg_dpu_readable_reg, ++ .volatile_reg = mlxreg_dpu_volatile_reg, ++}; ++ ++static int ++mlxreg_dpu_copy_hotplug_data(struct device *dev, struct mlxreg_dpu *mlxreg_dpu, ++ const struct mlxreg_core_hotplug_platform_data *hotplug_data) ++{ ++ struct mlxreg_core_item *item; ++ int i; ++ ++ mlxreg_dpu->hotplug_data = devm_kmemdup(dev, hotplug_data, ++ sizeof(*mlxreg_dpu->hotplug_data), GFP_KERNEL); ++ if (!mlxreg_dpu->hotplug_data) ++ return -ENOMEM; ++ ++ mlxreg_dpu->hotplug_data->items = devm_kmemdup(dev, hotplug_data->items, ++ mlxreg_dpu->hotplug_data->count * ++ sizeof(*mlxreg_dpu->hotplug_data->items), ++ GFP_KERNEL); ++ if (!mlxreg_dpu->hotplug_data->items) ++ return -ENOMEM; ++ ++ item = mlxreg_dpu->hotplug_data->items; ++ for (i = 0; i < hotplug_data->count; i++, item++) { ++ item->data = devm_kmemdup(dev, hotplug_data->items[i].data, ++ hotplug_data->items[i].count * sizeof(*item->data), ++ GFP_KERNEL); ++ if (!item->data) ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static int mlxreg_dpu_config_init(struct mlxreg_dpu *mlxreg_dpu, void *regmap, ++ struct mlxreg_core_data *data, int irq) ++{ ++ struct device *dev = &data->hpdev.client->dev; ++ u32 regval; ++ int err; ++ ++ /* Validate DPU type. */ ++ err = regmap_read(regmap, MLXREG_DPU_REG_CONFIG3_OFFSET, ®val); ++ if (err) ++ return err; ++ ++ switch (regval) { ++ case MLXREG_DPU_BF3: ++ /* Copy platform specific hotplug data. */ ++ err = mlxreg_dpu_copy_hotplug_data(dev, mlxreg_dpu, ++ &mlxreg_dpu_default_hotplug_data); ++ if (err) ++ return err; ++ ++ mlxreg_dpu->io_data = &mlxreg_dpu_default_regs_io_data; ++ ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ /* Register IO access driver. */ ++ if (mlxreg_dpu->io_data) { ++ mlxreg_dpu->io_data->regmap = regmap; ++ mlxreg_dpu->io_regs = ++ platform_device_register_resndata(dev, "mlxreg-io", ++ data->slot, NULL, 0, ++ mlxreg_dpu->io_data, ++ sizeof(*mlxreg_dpu->io_data)); ++ if (IS_ERR(mlxreg_dpu->io_regs)) { ++ dev_err(dev, "Failed to create regio for client %s at bus %d at addr 0x%02x\n", ++ data->hpdev.brdinfo->type, data->hpdev.nr, ++ data->hpdev.brdinfo->addr); ++ return PTR_ERR(mlxreg_dpu->io_regs); ++ } ++ } ++ ++ /* Register hotplug driver. */ ++ if (mlxreg_dpu->hotplug_data && irq) { ++ mlxreg_dpu->hotplug_data->regmap = regmap; ++ mlxreg_dpu->hotplug_data->irq = irq; ++ mlxreg_dpu->hotplug = ++ platform_device_register_resndata(dev, "mlxreg-hotplug", ++ data->slot, NULL, 0, ++ mlxreg_dpu->hotplug_data, ++ sizeof(*mlxreg_dpu->hotplug_data)); ++ if (IS_ERR(mlxreg_dpu->hotplug)) { ++ err = PTR_ERR(mlxreg_dpu->hotplug); ++ goto fail_register_hotplug; ++ } ++ } ++ ++ return 0; ++ ++fail_register_hotplug: ++ platform_device_unregister(mlxreg_dpu->io_regs); ++ ++ return err; ++} ++ ++static void mlxreg_dpu_config_exit(struct mlxreg_dpu *mlxreg_dpu) ++{ ++ platform_device_unregister(mlxreg_dpu->hotplug); ++ platform_device_unregister(mlxreg_dpu->io_regs); ++} ++ ++static int mlxreg_dpu_probe(struct platform_device *pdev) ++{ ++ struct mlxreg_core_data *data; ++ struct mlxreg_dpu *mlxreg_dpu; ++ void *regmap; ++ int err; ++ ++ data = dev_get_platdata(&pdev->dev); ++ if (!data || !data->hpdev.brdinfo) ++ return -EINVAL; ++ ++ data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr); ++ if (!data->hpdev.adapter) ++ return -EPROBE_DEFER; ++ ++ mlxreg_dpu = devm_kzalloc(&pdev->dev, sizeof(*mlxreg_dpu), GFP_KERNEL); ++ if (!mlxreg_dpu) ++ return -ENOMEM; ++ ++ /* Create device at the top of DPU I2C tree. */ ++ data->hpdev.client = i2c_new_client_device(data->hpdev.adapter, ++ data->hpdev.brdinfo); ++ if (IS_ERR(data->hpdev.client)) { ++ dev_err(&pdev->dev, "Failed to create client %s at bus %d at addr 0x%02x\n", ++ data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); ++ err = PTR_ERR(data->hpdev.client); ++ goto i2c_new_device_fail; ++ } ++ ++ regmap = devm_regmap_init_i2c(data->hpdev.client, &mlxreg_dpu_regmap_conf); ++ if (IS_ERR(regmap)) { ++ dev_err(&pdev->dev, "Failed to create regmap for client %s at bus %d at addr 0x%02x\n", ++ data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); ++ err = PTR_ERR(regmap); ++ goto devm_regmap_init_i2c_fail; ++ } ++ ++ /* Sync registers with hardware. */ ++ regcache_mark_dirty(regmap); ++ err = regcache_sync(regmap); ++ if (err) { ++ dev_err(&pdev->dev, "Failed to sync regmap for client %s at bus %d at addr 0x%02x\n", ++ data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); ++ err = PTR_ERR(regmap); ++ goto regcache_sync_fail; ++ } ++ ++ mlxreg_dpu->data = data; ++ mlxreg_dpu->dev = &pdev->dev; ++ platform_set_drvdata(pdev, mlxreg_dpu); ++ ++ err = mlxreg_dpu_config_init(mlxreg_dpu, regmap, data, data->hpdev.brdinfo->irq); ++ if (err) ++ goto mlxreg_dpu_config_init_fail; ++ ++ return err; ++ ++mlxreg_dpu_config_init_fail: ++regcache_sync_fail: ++devm_regmap_init_i2c_fail: ++ i2c_unregister_device(data->hpdev.client); ++i2c_new_device_fail: ++ i2c_put_adapter(data->hpdev.adapter); ++ return err; ++} ++ ++static void mlxreg_dpu_remove(struct platform_device *pdev) ++{ ++ struct mlxreg_core_data *data = dev_get_platdata(&pdev->dev); ++ struct mlxreg_dpu *mlxreg_dpu = platform_get_drvdata(pdev); ++ ++ mlxreg_dpu_config_exit(mlxreg_dpu); ++ i2c_unregister_device(data->hpdev.client); ++ i2c_put_adapter(data->hpdev.adapter); ++} ++ ++static struct platform_driver mlxreg_dpu_driver = { ++ .probe = mlxreg_dpu_probe, ++ .remove = mlxreg_dpu_remove, ++ .driver = { ++ .name = "mlxreg-dpu", ++ }, ++}; ++ ++module_platform_driver(mlxreg_dpu_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Nvidia Data Processor Unit platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("platform:mlxreg-dpu"); +-- +2.8.4 + diff --git a/patches-sonic/0029-platform-mellanox-Introduce-support-of-Nvidia-smart-.patch b/patches-sonic/0029-platform-mellanox-Introduce-support-of-Nvidia-smart-.patch new file mode 100644 index 000000000..d4120dd54 --- /dev/null +++ b/patches-sonic/0029-platform-mellanox-Introduce-support-of-Nvidia-smart-.patch @@ -0,0 +1,4665 @@ +From 1cc8a8a867f1a15602e1188c3a03be10b68b198e Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 21 Apr 2025 12:20:47 +0300 +Subject: [PATCH 21/70] platform: mellanox: Introduce support of Nvidia smart + switch +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Provide platform support for Nvidia Smart Switch SN4280. + +The Smart Switch equipped with: +- Nvidia COME module based on AMD EPYC™ Embedded 3451 CPU. +- Nvidia Spectrum-3 ASIC. +- Four DPUs, each equipped with Nvidia BF3 ARM based processor and + with Lattice LFD2NX-40 FPGA device. +- 28xQSFP-DD external ports. +- Two power supplies. +- Four cooling drawers. + +Reviewed-by: Ciju Rajan K +Signed-off-by: Vadim Pasternak +Reviewed-by: Ilpo Järvinen +Link: https://lore.kernel.org/r/20250421092051.7687-3-vadimp@nvidia.com +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlx-platform.c | 3554 +++++++++++++++------- + 1 file changed, 2485 insertions(+), 1069 deletions(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index 6f24de172b90..f563a4456ce6 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -39,6 +39,7 @@ + #define MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET 0x0b + #define MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET 0x17 + #define MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET 0x19 ++#define MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET 0x1b + #define MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET 0x1c + #define MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET 0x1d + #define MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET 0x1e +@@ -50,9 +51,11 @@ + #define MLXPLAT_CPLD_LPC_REG_LED5_OFFSET 0x24 + #define MLXPLAT_CPLD_LPC_REG_LED6_OFFSET 0x25 + #define MLXPLAT_CPLD_LPC_REG_LED7_OFFSET 0x26 ++#define MLXPLAT_CPLD_LPC_REG_LED8_OFFSET 0x27 + #define MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION 0x2a + #define MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET 0x2b + #define MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET 0x2d ++#define MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET 0x2c + #define MLXPLAT_CPLD_LPC_REG_GP0_OFFSET 0x2e + #define MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET 0x2f + #define MLXPLAT_CPLD_LPC_REG_GP1_OFFSET 0x30 +@@ -72,12 +75,14 @@ + #define MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET 0x43 + #define MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET 0x44 + #define MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET 0x45 ++#define MLXPLAT_CPLD_LPC_REG_GP3_OFFSET 0x46 + #define MLXPLAT_CPLD_LPC_REG_BRD_OFFSET 0x47 + #define MLXPLAT_CPLD_LPC_REG_BRD_EVENT_OFFSET 0x48 + #define MLXPLAT_CPLD_LPC_REG_BRD_MASK_OFFSET 0x49 + #define MLXPLAT_CPLD_LPC_REG_GWP_OFFSET 0x4a + #define MLXPLAT_CPLD_LPC_REG_GWP_EVENT_OFFSET 0x4b + #define MLXPLAT_CPLD_LPC_REG_GWP_MASK_OFFSET 0x4c ++#define MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET 0x4e + #define MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET 0x50 + #define MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET 0x51 + #define MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET 0x52 +@@ -89,15 +94,20 @@ + #define MLXPLAT_CPLD_LPC_REG_PSU_OFFSET 0x58 + #define MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET 0x59 + #define MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET 0x5a ++#define MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET 0x5e + #define MLXPLAT_CPLD_LPC_REG_PWR_OFFSET 0x64 + #define MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET 0x65 + #define MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET 0x66 ++#define MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET 0x6a + #define MLXPLAT_CPLD_LPC_REG_LC_IN_OFFSET 0x70 + #define MLXPLAT_CPLD_LPC_REG_LC_IN_EVENT_OFFSET 0x71 + #define MLXPLAT_CPLD_LPC_REG_LC_IN_MASK_OFFSET 0x72 + #define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88 + #define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89 + #define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a ++#define MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET 0x8b ++#define MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET 0x8c ++#define MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET 0x8d + #define MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET 0x8e + #define MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET 0x8f + #define MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET 0x90 +@@ -129,10 +139,15 @@ + #define MLXPLAT_CPLD_LPC_REG_LC_SD_EVENT_OFFSET 0xaa + #define MLXPLAT_CPLD_LPC_REG_LC_SD_MASK_OFFSET 0xab + #define MLXPLAT_CPLD_LPC_REG_LC_PWR_ON 0xb2 ++#define MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET 0xb4 ++#define MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET 0xb5 + #define MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET 0xb6 + #define MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET 0xb7 + #define MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET 0xb8 + #define MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET 0xb9 ++#define MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET 0xba ++#define MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET 0xbb ++#define MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET 0xc1 + #define MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET 0xc2 + #define MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT 0xc3 + #define MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET 0xc4 +@@ -183,6 +198,9 @@ + #define MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET 0xfb + #define MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET 0xfc + #define MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET 0xfd ++#define MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET 0xfe ++#define MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET 0xff ++ + #define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 + + #define MLXPLAT_CPLD_LPC_PIO_OFFSET 0x10000UL +@@ -211,9 +229,15 @@ + #define MLXPLAT_CPLD_AGGR_MASK_NG_DEF 0x04 + #define MLXPLAT_CPLD_AGGR_MASK_COMEX BIT(0) + #define MLXPLAT_CPLD_AGGR_MASK_LC BIT(3) ++#define MLXPLAT_CPLD_AGGR_MASK_DPU_BRD BIT(4) ++#define MLXPLAT_CPLD_AGGR_MASK_DPU_CORE BIT(5) + #define MLXPLAT_CPLD_AGGR_MASK_MODULAR (MLXPLAT_CPLD_AGGR_MASK_NG_DEF | \ + MLXPLAT_CPLD_AGGR_MASK_COMEX | \ + MLXPLAT_CPLD_AGGR_MASK_LC) ++#define MLXPLAT_CPLD_AGGR_MASK_SMART_SW (MLXPLAT_CPLD_AGGR_MASK_COMEX | \ ++ MLXPLAT_CPLD_AGGR_MASK_NG_DEF | \ ++ MLXPLAT_CPLD_AGGR_MASK_DPU_BRD | \ ++ MLXPLAT_CPLD_AGGR_MASK_DPU_CORE) + #define MLXPLAT_CPLD_AGGR_MASK_LC_PRSNT BIT(0) + #define MLXPLAT_CPLD_AGGR_MASK_LC_RDY BIT(1) + #define MLXPLAT_CPLD_AGGR_MASK_LC_PG BIT(2) +@@ -236,15 +260,21 @@ + #define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0) + #define MLXPLAT_CPLD_PSU_EXT_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_PWR_EXT_MASK GENMASK(3, 0) ++#define MLXPLAT_CPLD_PSU_XDR_MASK GENMASK(7, 0) ++#define MLXPLAT_CPLD_PWR_XDR_MASK GENMASK(7, 0) + #define MLXPLAT_CPLD_FAN_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_ASIC_MASK GENMASK(1, 0) ++#define MLXPLAT_CPLD_ASIC_XDR_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_FAN_NG_MASK GENMASK(6, 0) ++#define MLXPLAT_CPLD_FAN_XDR_MASK GENMASK(7, 0) + #define MLXPLAT_CPLD_LED_LO_NIBBLE_MASK GENMASK(7, 4) + #define MLXPLAT_CPLD_LED_HI_NIBBLE_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_VOLTREG_UPD_MASK GENMASK(5, 4) + #define MLXPLAT_CPLD_GWP_MASK GENMASK(0, 0) + #define MLXPLAT_CPLD_EROT_MASK GENMASK(1, 0) + #define MLXPLAT_CPLD_FU_CAP_MASK GENMASK(1, 0) ++#define MLXPLAT_CPLD_BIOS_STATUS_MASK GENMASK(3, 1) ++#define MLXPLAT_CPLD_DPU_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_PWR_BUTTON_MASK BIT(0) + #define MLXPLAT_CPLD_LATCH_RST_MASK BIT(6) + #define MLXPLAT_CPLD_THERMAL1_PDB_MASK BIT(3) +@@ -268,6 +298,9 @@ + /* Masks for aggregation for modular systems */ + #define MLXPLAT_CPLD_LPC_LC_MASK GENMASK(7, 0) + ++/* Masks for aggregation for smart switch systems */ ++#define MLXPLAT_CPLD_LPC_SM_SW_MASK GENMASK(7, 0) ++ + #define MLXPLAT_CPLD_HALT_MASK BIT(3) + #define MLXPLAT_CPLD_RESET_MASK GENMASK(7, 1) + +@@ -298,15 +331,18 @@ + #define MLXPLAT_CPLD_NR_NONE -1 + #define MLXPLAT_CPLD_PSU_DEFAULT_NR 10 + #define MLXPLAT_CPLD_PSU_MSNXXXX_NR 4 ++#define MLXPLAT_CPLD_PSU_XDR_NR 3 + #define MLXPLAT_CPLD_FAN1_DEFAULT_NR 11 + #define MLXPLAT_CPLD_FAN2_DEFAULT_NR 12 + #define MLXPLAT_CPLD_FAN3_DEFAULT_NR 13 + #define MLXPLAT_CPLD_FAN4_DEFAULT_NR 14 + #define MLXPLAT_CPLD_NR_ASIC 3 + #define MLXPLAT_CPLD_NR_LC_BASE 34 ++#define MLXPLAT_CPLD_NR_DPU_BASE 18 + + #define MLXPLAT_CPLD_NR_LC_SET(nr) (MLXPLAT_CPLD_NR_LC_BASE + (nr)) + #define MLXPLAT_CPLD_LC_ADDR 0x32 ++#define MLXPLAT_CPLD_DPU_ADDR 0x68 + + /* Masks and default values for watchdogs */ + #define MLXPLAT_CPLD_WD1_CLEAR_MASK GENMASK(7, 1) +@@ -321,6 +357,7 @@ + #define MLXPLAT_CPLD_WD_DFLT_TIMEOUT 30 + #define MLXPLAT_CPLD_WD3_DFLT_TIMEOUT 600 + #define MLXPLAT_CPLD_WD_MAX_DEVS 2 ++#define MLXPLAT_CPLD_DPU_MAX_DEVS 4 + + #define MLXPLAT_CPLD_LPC_SYSIRQ 17 + +@@ -347,6 +384,7 @@ + * @pdev_io_regs - register access platform devices + * @pdev_fan - FAN platform devices + * @pdev_wd - array of watchdog platform devices ++ * pdev_dpu - array of Data Processor Unit platform devices + * @regmap: device register map + * @hotplug_resources: system hotplug resources + * @hotplug_resources_size: size of system hotplug resources +@@ -362,6 +400,7 @@ struct mlxplat_priv { + struct platform_device *pdev_io_regs; + struct platform_device *pdev_fan; + struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS]; ++ struct platform_device *pdev_dpu[MLXPLAT_CPLD_DPU_MAX_DEVS]; + void *regmap; + struct resource *hotplug_resources; + unsigned int hotplug_resources_size; +@@ -631,6 +670,21 @@ static struct i2c_board_info mlxplat_mlxcpld_pwr_ng800[] = { + }, + }; + ++static struct i2c_board_info mlxplat_mlxcpld_xdr_pwr[] = { ++ { ++ I2C_BOARD_INFO("dps460", 0x5d), ++ }, ++ { ++ I2C_BOARD_INFO("dps460", 0x5c), ++ }, ++ { ++ I2C_BOARD_INFO("dps460", 0x5e), ++ }, ++ { ++ I2C_BOARD_INFO("dps460", 0x5f), ++ }, ++}; ++ + static struct i2c_board_info mlxplat_mlxcpld_fan[] = { + { + I2C_BOARD_INFO("24c32", 0x50), +@@ -2375,466 +2429,584 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_rack_switch_data = { + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, + }; + +-/* Callback performs graceful shutdown after notification about power button event */ +-static int +-mlxplat_mlxcpld_l1_switch_pwr_events_handler(void *handle, enum mlxreg_hotplug_kind kind, +- u8 action) +-{ +- if (action) { +- dev_info(&mlxplat_dev->dev, "System shutdown due to short press of power button"); +- kernel_power_off(); +- } +- +- return 0; +-} +- +-static struct mlxreg_core_hotplug_notifier mlxplat_mlxcpld_l1_switch_pwr_events_notifier = { +- .user_handler = mlxplat_mlxcpld_l1_switch_pwr_events_handler, +-}; +- +-/* Platform hotplug for l1 switch systems family data */ +-static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_pwr_events_items_data[] = { ++/* Platform hotplug XDR and smart switch system family data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + { +- .label = "power_button", +- .reg = MLXPLAT_CPLD_LPC_REG_PWRB_OFFSET, +- .mask = MLXPLAT_CPLD_PWR_BUTTON_MASK, ++ .label = "psu1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(0), ++ .slot = 1, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, +- .hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION, +- .hpdev.notifier = &mlxplat_mlxcpld_l1_switch_pwr_events_notifier, + }, +-}; +- +-/* Callback activates latch reset flow after notification about intrusion event */ +-static int +-mlxplat_mlxcpld_l1_switch_intrusion_events_handler(void *handle, enum mlxreg_hotplug_kind kind, +- u8 action) +-{ +- struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); +- u32 regval; +- int err; +- +- err = regmap_read(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ®val); +- if (err) +- goto fail_regmap_read; +- +- if (action) { +- dev_info(&mlxplat_dev->dev, "Detected intrusion - system latch is opened"); +- err = regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- regval | MLXPLAT_CPLD_LATCH_RST_MASK); +- } else { +- dev_info(&mlxplat_dev->dev, "System latch is properly closed"); +- err = regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- regval & ~MLXPLAT_CPLD_LATCH_RST_MASK); +- } +- +- if (err) +- goto fail_regmap_write; +- +- return 0; +- +-fail_regmap_read: +-fail_regmap_write: +- dev_err(&mlxplat_dev->dev, "Register access failed"); +- return err; +-} +- +-static struct mlxreg_core_hotplug_notifier mlxplat_mlxcpld_l1_switch_intrusion_events_notifier = { +- .user_handler = mlxplat_mlxcpld_l1_switch_intrusion_events_handler, +-}; +- +-static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_health_events_items_data[] = { + { +- .label = "thermal1_pdb", +- .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, +- .mask = MLXPLAT_CPLD_THERMAL1_PDB_MASK, ++ .label = "psu2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(1), ++ .slot = 2, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "thermal2_pdb", +- .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, +- .mask = MLXPLAT_CPLD_THERMAL2_PDB_MASK, ++ .label = "psu3", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(2), ++ .slot = 3, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "intrusion", +- .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, +- .mask = MLXPLAT_CPLD_INTRUSION_MASK, ++ .label = "psu4", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(3), ++ .slot = 4, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, +- .hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION, +- .hpdev.notifier = &mlxplat_mlxcpld_l1_switch_intrusion_events_notifier, + }, + { +- .label = "pwm_pg", +- .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, +- .mask = MLXPLAT_CPLD_PWM_PG_MASK, ++ .label = "psu5", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(4), ++ .slot = 5, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +-}; +- +-static struct mlxreg_core_item mlxplat_mlxcpld_l1_switch_events_items[] = { +- { +- .data = mlxplat_mlxcpld_default_ng_fan_items_data, +- .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, +- .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, +- .mask = MLXPLAT_CPLD_FAN_NG_MASK, +- .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data), +- .inversed = 1, +- .health = false, +- }, +- { +- .data = mlxplat_mlxcpld_erot_ap_items_data, +- .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, +- .reg = MLXPLAT_CPLD_LPC_REG_EROT_OFFSET, +- .mask = MLXPLAT_CPLD_EROT_MASK, +- .count = ARRAY_SIZE(mlxplat_mlxcpld_erot_ap_items_data), +- .inversed = 1, +- .health = false, +- }, + { +- .data = mlxplat_mlxcpld_erot_error_items_data, +- .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, +- .reg = MLXPLAT_CPLD_LPC_REG_EROTE_OFFSET, +- .mask = MLXPLAT_CPLD_EROT_MASK, +- .count = ARRAY_SIZE(mlxplat_mlxcpld_erot_error_items_data), +- .inversed = 1, +- .health = false, ++ .label = "psu6", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(5), ++ .slot = 6, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .data = mlxplat_mlxcpld_l1_switch_pwr_events_items_data, +- .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, +- .reg = MLXPLAT_CPLD_LPC_REG_PWRB_OFFSET, +- .mask = MLXPLAT_CPLD_PWR_BUTTON_MASK, +- .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_pwr_events_items_data), +- .inversed = 1, +- .health = false, ++ .label = "psu7", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(6), ++ .slot = 7, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .data = mlxplat_mlxcpld_l1_switch_health_events_items_data, +- .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, +- .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, +- .mask = MLXPLAT_CPLD_L1_CHA_HEALTH_MASK, +- .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_health_events_items_data), +- .inversed = 1, +- .health = false, +- .ind = 8, ++ .label = "psu8", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(7), ++ .slot = 8, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + }; + +-static +-struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_l1_switch_data = { +- .items = mlxplat_mlxcpld_l1_switch_events_items, +- .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_events_items), +- .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, +- .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, +- .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, +- .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_PWR_BUT, +-}; +- +-/* Platform led default data */ +-static struct mlxreg_core_data mlxplat_mlxcpld_default_led_data[] = { ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + { +- .label = "status:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "pwr1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(0), ++ .slot = 1, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { +- .label = "status:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ .label = "pwr2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(1), ++ .slot = 2, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { +- .label = "psu:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "pwr3", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(2), ++ .slot = 3, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ext_pwr[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { +- .label = "psu:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "pwr4", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(3), ++ .slot = 4, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ext_pwr[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { +- .label = "fan1:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "pwr5", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(4), ++ .slot = 5, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, + { +- .label = "fan1:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "pwr6", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(5), ++ .slot = 6, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, + { +- .label = "fan2:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "pwr7", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(6), ++ .slot = 7, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[2], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, + { +- .label = "fan2:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "pwr8", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(7), ++ .slot = 8, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[3], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + { +- .label = "fan3:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "fan1", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(0), ++ .slot = 1, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(0), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "fan3:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "fan2", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(1), ++ .slot = 2, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(1), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "fan4:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "fan3", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(2), ++ .slot = 3, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(2), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "fan4:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "fan4", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(3), ++ .slot = 4, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(3), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +-}; +- +-static struct mlxreg_core_platform_data mlxplat_default_led_data = { +- .data = mlxplat_mlxcpld_default_led_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_data), +-}; +- +-/* Platform led default data for water cooling */ +-static struct mlxreg_core_data mlxplat_mlxcpld_default_led_wc_data[] = { + { +- .label = "status:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "fan5", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(4), ++ .slot = 5, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(4), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "status:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ .label = "fan6", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(5), ++ .slot = 6, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(5), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "psu:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "fan7", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(6), ++ .slot = 7, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(6), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "psu:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .label = "fan8", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = BIT(7), ++ .slot = 8, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(7), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + }; + +-static struct mlxreg_core_platform_data mlxplat_default_led_wc_data = { +- .data = mlxplat_mlxcpld_default_led_wc_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_wc_data), ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_asic1_items_data[] = { ++ { ++ .label = "asic1", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .slot = 1, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ } + }; + +-/* Platform led default data for water cooling Ethernet switch blade */ +-static struct mlxreg_core_data mlxplat_mlxcpld_default_led_eth_wc_blade_data[] = { ++/* Platform hotplug for smart switch systems families data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_ready_data[] = { + { +- .label = "status:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "dpu1_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, ++ .mask = BIT(0), ++ .slot = 1, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "status:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ .label = "dpu2_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, ++ .mask = BIT(1), ++ .slot = 2, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ }, ++ { ++ .label = "dpu3_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, ++ .mask = BIT(2), ++ .slot = 3, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ }, ++ { ++ .label = "dpu4_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, ++ .mask = BIT(3), ++ .slot = 4, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + }; + +-static struct mlxreg_core_platform_data mlxplat_default_led_eth_wc_blade_data = { +- .data = mlxplat_mlxcpld_default_led_eth_wc_blade_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_eth_wc_blade_data), +-}; +- +-/* Platform led MSN21xx system family data */ +-static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_led_data[] = { ++static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_shtdn_ready_data[] = { + { +- .label = "status:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "dpu1_shtdn_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, ++ .mask = BIT(0), ++ .slot = 1, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "status:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ .label = "dpu2_shtdn_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, ++ .mask = BIT(1), ++ .slot = 2, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "fan:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "dpu3_shtdn_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, ++ .mask = BIT(2), ++ .slot = 3, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "fan:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "dpu4_shtdn_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, ++ .mask = BIT(3), ++ .slot = 4, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_smart_switch_items[] = { + { +- .label = "psu1:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .data = mlxplat_mlxcpld_xdr_psu_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = MLXPLAT_CPLD_PSU_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_psu_items_data), ++ .inversed = 1, ++ .health = false, + }, + { +- .label = "psu1:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .data = mlxplat_mlxcpld_xdr_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = MLXPLAT_CPLD_PWR_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_pwr_items_data), ++ .inversed = 0, ++ .health = false, + }, + { +- .label = "psu2:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .data = mlxplat_mlxcpld_xdr_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = MLXPLAT_CPLD_FAN_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_fan_items_data), ++ .inversed = 1, ++ .health = false, + }, + { +- .label = "psu2:red", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .data = mlxplat_mlxcpld_xdr_asic1_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_asic1_items_data), ++ .inversed = 0, ++ .health = true, + }, + { +- .label = "uid:blue", +- .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .data = mlxplat_mlxcpld_smart_switch_dpu_ready_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_DPU_CORE, ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, ++ .mask = MLXPLAT_CPLD_DPU_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_dpu_ready_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_smart_switch_dpu_shtdn_ready_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_DPU_CORE, ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, ++ .mask = MLXPLAT_CPLD_DPU_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_dpu_shtdn_ready_data), ++ .inversed = 1, ++ .health = false, + }, + }; + +-static struct mlxreg_core_platform_data mlxplat_msn21xx_led_data = { +- .data = mlxplat_mlxcpld_msn21xx_led_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_led_data), ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_smart_switch_data = { ++ .items = mlxplat_mlxcpld_smart_switch_items, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX | ++ MLXPLAT_CPLD_AGGR_MASK_DPU_BRD | MLXPLAT_CPLD_AGGR_MASK_DPU_CORE, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, + }; + +-/* Platform led for default data for 200GbE systems */ +-static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_led_data[] = { ++/* Smart switch data processor units data */ ++static struct i2c_board_info mlxplat_mlxcpld_smart_switch_dpu_devs[] = { + { +- .label = "status:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), ++ .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, + { +- .label = "status:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), ++ .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, + { +- .label = "psu:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), ++ .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, + { +- .label = "psu:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), ++ .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_data[] = { + { +- .label = "fan1:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(0), ++ .label = "dpu1", ++ .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[0], ++ .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE, ++ .slot = 1, + }, + { +- .label = "fan1:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(0), ++ .label = "dpu2", ++ .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[1], ++ .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 1, ++ .slot = 2, + }, + { +- .label = "fan2:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(1), ++ .label = "dpu3", ++ .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[2], ++ .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 2, ++ .slot = 3, + }, + { +- .label = "fan2:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(1), +- }, +- { +- .label = "fan3:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(2), ++ .label = "dpu4", ++ .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[3], ++ .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 3, ++ .slot = 4, + }, ++}; ++ ++/* Callback performs graceful shutdown after notification about power button event */ ++static int ++mlxplat_mlxcpld_l1_switch_pwr_events_handler(void *handle, enum mlxreg_hotplug_kind kind, ++ u8 action) ++{ ++ if (action) { ++ dev_info(&mlxplat_dev->dev, "System shutdown due to short press of power button"); ++ kernel_power_off(); ++ } ++ ++ return 0; ++} ++ ++static struct mlxreg_core_hotplug_notifier mlxplat_mlxcpld_l1_switch_pwr_events_notifier = { ++ .user_handler = mlxplat_mlxcpld_l1_switch_pwr_events_handler, ++}; ++ ++/* Platform hotplug for l1 switch systems family data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_pwr_events_items_data[] = { + { +- .label = "fan3:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(2), ++ .label = "power_button", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWRB_OFFSET, ++ .mask = MLXPLAT_CPLD_PWR_BUTTON_MASK, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ .hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION, ++ .hpdev.notifier = &mlxplat_mlxcpld_l1_switch_pwr_events_notifier, + }, ++}; ++ ++/* Callback activates latch reset flow after notification about intrusion event */ ++static int ++mlxplat_mlxcpld_l1_switch_intrusion_events_handler(void *handle, enum mlxreg_hotplug_kind kind, ++ u8 action) ++{ ++ struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); ++ u32 regval; ++ int err; ++ ++ err = regmap_read(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ®val); ++ if (err) ++ goto fail_regmap_read; ++ ++ if (action) { ++ dev_info(&mlxplat_dev->dev, "Detected intrusion - system latch is opened"); ++ err = regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ regval | MLXPLAT_CPLD_LATCH_RST_MASK); ++ } else { ++ dev_info(&mlxplat_dev->dev, "System latch is properly closed"); ++ err = regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ regval & ~MLXPLAT_CPLD_LATCH_RST_MASK); ++ } ++ ++ if (err) ++ goto fail_regmap_write; ++ ++ return 0; ++ ++fail_regmap_read: ++fail_regmap_write: ++ dev_err(&mlxplat_dev->dev, "Register access failed"); ++ return err; ++} ++ ++static struct mlxreg_core_hotplug_notifier mlxplat_mlxcpld_l1_switch_intrusion_events_notifier = { ++ .user_handler = mlxplat_mlxcpld_l1_switch_intrusion_events_handler, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_health_events_items_data[] = { + { +- .label = "fan4:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(3), ++ .label = "thermal1_pdb", ++ .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, ++ .mask = MLXPLAT_CPLD_THERMAL1_PDB_MASK, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "fan4:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(3), ++ .label = "thermal2_pdb", ++ .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, ++ .mask = MLXPLAT_CPLD_THERMAL2_PDB_MASK, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +- .label = "fan5:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(4), ++ .label = "intrusion", ++ .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, ++ .mask = MLXPLAT_CPLD_INTRUSION_MASK, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ .hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION, ++ .hpdev.notifier = &mlxplat_mlxcpld_l1_switch_intrusion_events_notifier, + }, + { +- .label = "fan5:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(4), ++ .label = "pwm_pg", ++ .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, ++ .mask = MLXPLAT_CPLD_PWM_PG_MASK, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_l1_switch_events_items[] = { + { +- .label = "fan6:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(5), ++ .data = mlxplat_mlxcpld_default_ng_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = MLXPLAT_CPLD_FAN_NG_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data), ++ .inversed = 1, ++ .health = false, + }, + { +- .label = "fan6:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(5), ++ .data = mlxplat_mlxcpld_erot_ap_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_EROT_OFFSET, ++ .mask = MLXPLAT_CPLD_EROT_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_erot_ap_items_data), ++ .inversed = 1, ++ .health = false, + }, + { +- .label = "fan7:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(6), ++ .data = mlxplat_mlxcpld_erot_error_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_EROTE_OFFSET, ++ .mask = MLXPLAT_CPLD_EROT_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_erot_error_items_data), ++ .inversed = 1, ++ .health = false, + }, + { +- .label = "fan7:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(6), ++ .data = mlxplat_mlxcpld_l1_switch_pwr_events_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWRB_OFFSET, ++ .mask = MLXPLAT_CPLD_PWR_BUTTON_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_pwr_events_items_data), ++ .inversed = 1, ++ .health = false, + }, + { +- .label = "uid:blue", +- .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .data = mlxplat_mlxcpld_l1_switch_health_events_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET, ++ .mask = MLXPLAT_CPLD_L1_CHA_HEALTH_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_health_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .ind = 8, + }, + }; + +-static struct mlxreg_core_platform_data mlxplat_default_ng_led_data = { +- .data = mlxplat_mlxcpld_default_ng_led_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_led_data), ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_l1_switch_data = { ++ .items = mlxplat_mlxcpld_l1_switch_events_items, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_events_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_PWR_BUT, + }; + +-/* Platform led for Comex based 100GbE systems */ +-static struct mlxreg_core_data mlxplat_mlxcpld_comex_100G_led_data[] = { ++/* Platform led default data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_led_data[] = { + { + .label = "status:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +@@ -2895,27 +3067,22 @@ static struct mlxreg_core_data mlxplat_mlxcpld_comex_100G_led_data[] = { + .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + }, +- { +- .label = "uid:blue", +- .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- }, + }; + +-static struct mlxreg_core_platform_data mlxplat_comex_100G_led_data = { +- .data = mlxplat_mlxcpld_comex_100G_led_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_comex_100G_led_data), ++static struct mlxreg_core_platform_data mlxplat_default_led_data = { ++ .data = mlxplat_mlxcpld_default_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_data), + }; + +-/* Platform led for data for modular systems */ +-static struct mlxreg_core_data mlxplat_mlxcpld_modular_led_data[] = { ++/* Platform led default data for water cooling */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_led_wc_data[] = { + { + .label = "status:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + { +- .label = "status:orange", ++ .label = "status:red", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK + }, +@@ -2925,36 +3092,138 @@ static struct mlxreg_core_data mlxplat_mlxcpld_modular_led_data[] = { + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + }, + { +- .label = "psu:orange", ++ .label = "psu:red", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_default_led_wc_data = { ++ .data = mlxplat_mlxcpld_default_led_wc_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_wc_data), ++}; ++ ++/* Platform led default data for water cooling Ethernet switch blade */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_led_eth_wc_blade_data[] = { + { +- .label = "fan1:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(0), + }, + { +- .label = "fan1:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .label = "status:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_default_led_eth_wc_blade_data = { ++ .data = mlxplat_mlxcpld_default_led_eth_wc_blade_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_eth_wc_blade_data), ++}; ++ ++/* Platform led MSN21xx system family data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(0), + }, + { +- .label = "fan2:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +- .bit = BIT(1), ++ .label = "status:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK + }, + { +- .label = "fan2:orange", ++ .label = "fan:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +- .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, +- .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu1:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu2:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_msn21xx_led_data = { ++ .data = mlxplat_mlxcpld_msn21xx_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_led_data), ++}; ++ ++/* Platform led for default data for 200GbE systems */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "psu:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(0), ++ }, ++ { ++ .label = "fan1:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(0), ++ }, ++ { ++ .label = "fan2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(1), ++ }, ++ { ++ .label = "fan2:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(1), + }, + { +@@ -3016,14 +3285,14 @@ static struct mlxreg_core_data mlxplat_mlxcpld_modular_led_data[] = { + { + .label = "fan7:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(6), + }, + { + .label = "fan7:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(6), + }, +@@ -3032,35 +3301,89 @@ static struct mlxreg_core_data mlxplat_mlxcpld_modular_led_data[] = { + .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_default_ng_led_data = { ++ .data = mlxplat_mlxcpld_default_ng_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_led_data), ++}; ++ ++/* Platform led for Comex based 100GbE systems */ ++static struct mlxreg_core_data mlxplat_mlxcpld_comex_100G_led_data[] = { + { +- .label = "fan_front:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + { +- .label = "fan_front:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .label = "status:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "psu:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + { +- .label = "mgmt:green", +- .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .label = "fan1:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + { +- .label = "mgmt:orange", +- .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .label = "fan2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan2:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan3:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan3:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan4:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan4:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + }; + +-static struct mlxreg_core_platform_data mlxplat_modular_led_data = { +- .data = mlxplat_mlxcpld_modular_led_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_modular_led_data), ++static struct mlxreg_core_platform_data mlxplat_comex_100G_led_data = { ++ .data = mlxplat_mlxcpld_comex_100G_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_comex_100G_led_data), + }; + +-/* Platform led data for chassis system */ +-static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_led_data[] = { ++/* Platform led for data for modular systems */ ++static struct mlxreg_core_data mlxplat_mlxcpld_modular_led_data[] = { + { + .label = "status:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +@@ -3071,6 +3394,16 @@ static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_led_data[] = { + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK + }, ++ { ++ .label = "psu:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, + { + .label = "fan1:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, +@@ -3156,102 +3489,862 @@ static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_led_data[] = { + .bit = BIT(5), + }, + { +- .label = "uid:blue", +- .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .label = "fan7:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(6), ++ }, ++ { ++ .label = "fan7:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(6), ++ }, ++ { ++ .label = "uid:blue", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan_front:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan_front:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "mgmt:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "mgmt:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_modular_led_data = { ++ .data = mlxplat_mlxcpld_modular_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_modular_led_data), ++}; ++ ++/* Platform led data for chassis system */ ++static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "fan1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(0), ++ }, ++ { ++ .label = "fan1:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(0), ++ }, ++ { ++ .label = "fan2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(1), ++ }, ++ { ++ .label = "fan2:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(1), ++ }, ++ { ++ .label = "fan3:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(2), ++ }, ++ { ++ .label = "fan3:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(2), ++ }, ++ { ++ .label = "fan4:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(3), ++ }, ++ { ++ .label = "fan4:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(3), ++ }, ++ { ++ .label = "fan5:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(4), ++ }, ++ { ++ .label = "fan5:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(4), ++ }, ++ { ++ .label = "fan6:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(5), ++ }, ++ { ++ .label = "fan6:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .bit = BIT(5), ++ }, ++ { ++ .label = "uid:blue", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_l1_switch_led_data = { ++ .data = mlxplat_mlxcpld_l1_switch_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_led_data), ++}; ++ ++/* Platform led data for XDR and smart switch systems */ ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 1, ++ }, ++ { ++ .label = "fan1:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 1, ++ }, ++ { ++ .label = "fan2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 2, ++ }, ++ { ++ .label = "fan2:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 2, ++ }, ++ { ++ .label = "fan3:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 3, ++ }, ++ { ++ .label = "fan3:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 3, ++ }, ++ { ++ .label = "fan4:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 4, ++ }, ++ { ++ .label = "fan4:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 4, ++ }, ++ { ++ .label = "fan5:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 5, ++ }, ++ { ++ .label = "fan5:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 5, ++ }, ++ { ++ .label = "fan6:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 6, ++ }, ++ { ++ .label = "fan6:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 6, ++ }, ++ { ++ .label = "fan7:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 7, ++ }, ++ { ++ .label = "fan7:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 7, ++ }, ++ { ++ .label = "fan8:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 8, ++ }, ++ { ++ .label = "fan8:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 8, ++ }, ++ { ++ .label = "fan9:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 9, ++ }, ++ { ++ .label = "fan9:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 9, ++ }, ++ { ++ .label = "fan10:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED8_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 10, ++ }, ++ { ++ .label = "fan10:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED8_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .slot = 10, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_xdr_led_data = { ++ .data = mlxplat_mlxcpld_xdr_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_xdr_led_data), ++}; ++ ++/* Platform register access default */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_ref", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_fw_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_hotswap_or_wd", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "psu1_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu2_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "select_iio", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_default_regs_io_data = { ++ .data = mlxplat_mlxcpld_default_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_regs_io_data), ++}; ++ ++/* Platform register access MSN21xx, MSN201x, MSN274x systems families data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_ref", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_hotswap_or_halt", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sff_wd", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "psu1_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu2_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "select_iio", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_msn21xx_regs_io_data = { ++ .data = mlxplat_mlxcpld_msn21xx_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_regs_io_data), ++}; ++ ++/* Platform register access for next generation systems families data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld5_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "asic2_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot1_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot2_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "clk_brd_prog_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "erot1_recovery", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot2_recovery", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot1_wp", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "erot2_wp", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_ref", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_asic", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, + }, +-}; +- +-static struct mlxreg_core_platform_data mlxplat_l1_switch_led_data = { +- .data = mlxplat_mlxcpld_l1_switch_led_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_led_data), +-}; +- +-/* Platform register access default */ +-static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { + { +- .label = "cpld1_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_asic_thermal", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { +- .label = "cpld2_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_sw_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "cpld1_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, +- .bit = GENMASK(15, 0), ++ .label = "reset_comex_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, +- .regnum = 2, + }, + { +- .label = "cpld2_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, +- .bit = GENMASK(15, 0), ++ .label = "reset_platform", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, +- .regnum = 2, + }, + { +- .label = "cpld1_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_soc", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { +- .label = "cpld2_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_comex_wd", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "reset_long_pb", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_pwr_converter_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "reset_short_pb", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_system", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { +- .label = "reset_aux_pwr_or_ref", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_sw_pwr_off", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +- .label = "reset_main_pwr_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_comex_thermal", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { +- .label = "reset_sw_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0444, +- }, +- { +- .label = "reset_fw_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_reload_bios", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { +- .label = "reset_hotswap_or_wd", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_ac_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "reset_asic_thermal", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_ac_ok_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, +@@ -3280,9 +4373,52 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { + .mode = 0200, + }, + { +- .label = "select_iio", +- .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .label = "deep_pwr_cycle", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "latch_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET, ++ .mask = MLXPLAT_CPLD_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg1", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg2", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg3", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg4", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0644, + }, + { +@@ -3292,147 +4428,151 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { + .bit = 1, + .mode = 0444, + }, +-}; +- +-static struct mlxreg_core_platform_data mlxplat_default_regs_io_data = { +- .data = mlxplat_mlxcpld_default_regs_io_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_regs_io_data), +-}; +- +-/* Platform register access MSN21xx, MSN201x, MSN274x systems families data */ +-static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = { + { +- .label = "cpld1_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "asic2_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC2_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, + .mode = 0444, + }, + { +- .label = "cpld2_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, ++ .label = "fan_dir", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { +- .label = "cpld1_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, +- .bit = GENMASK(15, 0), ++ .label = "bios_safe_mode", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, +- .regnum = 2, + }, + { +- .label = "cpld2_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, +- .bit = GENMASK(15, 0), ++ .label = "bios_active_image", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, +- .regnum = 2, + }, + { +- .label = "cpld1_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "bios_auth_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "cpld2_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "bios_upgrade_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { +- .label = "reset_long_pb", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "voltreg_update_status", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET, ++ .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK, ++ .bit = 5, + .mode = 0444, + }, + { +- .label = "reset_short_pb", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), ++ .label = "pwr_converter_prog_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot1_ap_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "reset_aux_pwr_or_ref", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(2), ++ .label = "erot2_ap_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { +- .label = "reset_sw_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), ++ .label = "lid_open", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +- .label = "reset_main_pwr_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "clk_brd1_boot_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, + }, + { +- .label = "reset_asic_thermal", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "clk_brd2_boot_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { +- .label = "reset_hotswap_or_halt", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "clk_brd_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "reset_sff_wd", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), ++ .label = "asic_pg_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { +- .label = "psu1_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), +- .mode = 0200, +- }, +- { +- .label = "psu2_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), +- .mode = 0200, ++ .label = "spi_chnl_select", ++ .reg = MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, + }, + { +- .label = "pwr_cycle", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(2), +- .mode = 0200, ++ .label = "config1", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "pwr_down", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), +- .mode = 0200, ++ .label = "config2", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "select_iio", +- .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0644, ++ .label = "config3", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "asic_health", +- .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, +- .mask = MLXPLAT_CPLD_ASIC_MASK, +- .bit = 1, ++ .label = "ufm_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + }; + +-static struct mlxreg_core_platform_data mlxplat_msn21xx_regs_io_data = { +- .data = mlxplat_mlxcpld_msn21xx_regs_io_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_regs_io_data), ++static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = { ++ .data = mlxplat_mlxcpld_default_ng_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data), + }; + +-/* Platform register access for next generation systems families data */ +-static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { ++/* Platform register access for modular systems families data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = { + { + .label = "cpld1_version", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, +@@ -3457,12 +4597,6 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .bit = GENMASK(7, 0), + .mode = 0444, + }, +- { +- .label = "cpld5_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, +- }, + { + .label = "cpld1_pn", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, +@@ -3491,13 +4625,6 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0444, + .regnum = 2, + }, +- { +- .label = "cpld5_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET, +- .bit = GENMASK(15, 0), +- .mode = 0444, +- .regnum = 2, +- }, + { + .label = "cpld1_version_min", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, +@@ -3523,68 +4650,53 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0444, + }, + { +- .label = "cpld5_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "lc1_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, + }, + { +- .label = "asic_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), +- .mode = 0200, ++ .label = "lc2_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, + }, + { +- .label = "asic2_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .label = "lc3_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), +- .mode = 0200, ++ .mode = 0644, + }, + { +- .label = "erot1_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), ++ .label = "lc4_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { +- .label = "erot2_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), ++ .label = "lc5_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0644, + }, + { +- .label = "clk_brd_prog_en", +- .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), ++ .label = "lc6_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0644, +- .secured = 1, + }, + { +- .label = "erot1_recovery", +- .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, ++ .label = "lc7_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0644, + }, + { +- .label = "erot2_recovery", +- .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, ++ .label = "lc8_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0644, + }, +- { +- .label = "erot1_wp", +- .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0644, +- .secured = 1, +- }, +- { +- .label = "erot2_wp", +- .reg = MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0644, +- .secured = 1, +- }, + { + .label = "reset_long_pb", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +@@ -3598,39 +4710,33 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0444, + }, + { +- .label = "reset_aux_pwr_or_ref", ++ .label = "reset_aux_pwr_or_fu", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +- .label = "reset_swb_dc_dc_pwr_fail", ++ .label = "reset_mgmt_dc_dc_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { +- .label = "reset_from_asic", ++ .label = "reset_sys_comex_bios", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { +- .label = "reset_swb_wd", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0444, +- }, +- { +- .label = "reset_asic_thermal", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), ++ .label = "reset_sw_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "reset_sw_reset", ++ .label = "reset_aux_pwr_or_reload", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +@@ -3652,144 +4758,45 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0444, + }, + { +- .label = "reset_comex_wd", ++ .label = "reset_pwr_off_from_carrier", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { +- .label = "reset_pwr_converter_fail", ++ .label = "reset_swb_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "reset_system", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), +- .mode = 0444, +- }, +- { +- .label = "reset_sw_pwr_off", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .label = "reset_swb_aux_pwr_or_fu", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +- .label = "reset_comex_thermal", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), +- .mode = 0444, +- }, +- { +- .label = "reset_reload_bios", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0444, +- }, +- { +- .label = "reset_ac_pwr_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0444, +- }, +- { +- .label = "reset_ac_ok_fail", ++ .label = "reset_swb_dc_dc_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), +- .mode = 0444, +- }, +- { +- .label = "psu1_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), +- .mode = 0200, +- }, +- { +- .label = "psu2_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), +- .mode = 0200, +- }, +- { +- .label = "pwr_cycle", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(2), +- .mode = 0200, +- }, +- { +- .label = "pwr_down", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), +- .mode = 0200, +- }, +- { +- .label = "deep_pwr_cycle", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0200, +- }, +- { +- .label = "latch_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0200, +- }, +- { +- .label = "jtag_cap", +- .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET, +- .mask = MLXPLAT_CPLD_FU_CAP_MASK, +- .bit = 1, + .mode = 0444, + }, + { +- .label = "jtag_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0644, +- }, +- { +- .label = "dbg1", +- .reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0644, +- }, +- { +- .label = "dbg2", +- .reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0644, +- }, +- { +- .label = "dbg3", +- .reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0644, +- }, +- { +- .label = "dbg4", +- .reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0644, +- }, +- { +- .label = "asic_health", +- .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, +- .mask = MLXPLAT_CPLD_ASIC_MASK, +- .bit = 1, ++ .label = "reset_swb_12v_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, + }, + { +- .label = "asic2_health", +- .reg = MLXPLAT_CPLD_LPC_REG_ASIC2_HEALTH_OFFSET, +- .mask = MLXPLAT_CPLD_ASIC_MASK, +- .bit = 1, ++ .label = "reset_system", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { +- .label = "fan_dir", +- .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, +- .bit = GENMASK(7, 0), ++ .label = "reset_thermal_spc_or_pciesw", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { +@@ -3823,13 +4830,6 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .bit = 5, + .mode = 0444, + }, +- { +- .label = "pwr_converter_prog_en", +- .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), +- .mode = 0644, +- .secured = 1, +- }, + { + .label = "vpd_wp", + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, +@@ -3843,251 +4843,249 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0644, + }, + { +- .label = "erot1_ap_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .label = "shutdown_unlock", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "lc1_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), +- .mode = 0444, ++ .mode = 0200, + }, + { +- .label = "erot2_ap_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .label = "lc2_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), +- .mode = 0444, ++ .mode = 0200, + }, + { +- .label = "lid_open", +- .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .label = "lc3_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), +- .mode = 0444, ++ .mode = 0200, + }, + { +- .label = "clk_brd1_boot_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .label = "lc4_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "lc5_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0444, ++ .mode = 0200, + }, + { +- .label = "clk_brd2_boot_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .label = "lc6_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0444, ++ .mode = 0200, + }, + { +- .label = "clk_brd_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .label = "lc7_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0444, ++ .mode = 0200, + }, + { +- .label = "asic_pg_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .label = "lc8_rst_mask", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), +- .mode = 0444, +- }, +- { +- .label = "spi_chnl_select", +- .reg = MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT, +- .mask = GENMASK(7, 0), +- .bit = 1, +- .mode = 0644, +- }, +- { +- .label = "config1", +- .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, +- }, +- { +- .label = "config2", +- .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, +- }, +- { +- .label = "config3", +- .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .mode = 0200, + }, + { +- .label = "ufm_version", +- .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "psu1_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, + }, +-}; +- +-static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = { +- .data = mlxplat_mlxcpld_default_ng_regs_io_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data), +-}; +- +-/* Platform register access for modular systems families data */ +-static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = { + { +- .label = "cpld1_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "psu2_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0200, + }, + { +- .label = "cpld2_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "pwr_cycle", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, + }, + { +- .label = "cpld3_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "pwr_down", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, + }, + { +- .label = "cpld4_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "psu3_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, + }, + { +- .label = "cpld1_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, +- .bit = GENMASK(15, 0), +- .mode = 0444, +- .regnum = 2, ++ .label = "psu4_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, + }, + { +- .label = "cpld2_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, +- .bit = GENMASK(15, 0), +- .mode = 0444, +- .regnum = 2, ++ .label = "auto_power_mode", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, + }, + { +- .label = "cpld3_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET, +- .bit = GENMASK(15, 0), +- .mode = 0444, +- .regnum = 2, ++ .label = "pm_mgmt_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, + }, + { +- .label = "cpld4_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET, +- .bit = GENMASK(15, 0), +- .mode = 0444, +- .regnum = 2, ++ .label = "jtag_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE, ++ .mask = GENMASK(3, 0), ++ .bit = 1, ++ .mode = 0644, + }, + { +- .label = "cpld1_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "safe_bios_dis", ++ .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, + }, + { +- .label = "cpld2_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "safe_bios_dis_wp", ++ .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, + }, + { +- .label = "cpld3_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "asic_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, + .mode = 0444, + }, + { +- .label = "cpld4_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET, ++ .label = "fan_dir", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { +- .label = "lc1_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc1_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { +- .label = "lc2_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc2_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { +- .label = "lc3_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc3_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0644, + }, + { +- .label = "lc4_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc4_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { +- .label = "lc5_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc5_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0644, + }, + { +- .label = "lc6_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc6_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0644, + }, + { +- .label = "lc7_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc7_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0644, + }, + { +- .label = "lc8_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .label = "lc8_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0644, + }, + { +- .label = "reset_long_pb", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "config1", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { +- .label = "reset_short_pb", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), ++ .label = "ufm_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_modular_regs_io_data = { ++ .data = mlxplat_mlxcpld_modular_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_modular_regs_io_data), ++}; ++ ++/* Platform register access for chassis blade systems family data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = { + { +- .label = "reset_aux_pwr_or_fu", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(2), ++ .label = "cpld1_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { +- .label = "reset_mgmt_dc_dc_pwr_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), ++ .label = "cpld1_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), + .mode = 0444, ++ .regnum = 2, + }, + { +- .label = "reset_sys_comex_bios", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), ++ .label = "cpld1_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { +- .label = "reset_sw_reset", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "reset_aux_pwr_or_ref", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +- .label = "reset_aux_pwr_or_reload", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(2), ++ .label = "reset_from_comex", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, + }, + { +@@ -4109,47 +5107,83 @@ static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = { + .mode = 0444, + }, + { +- .label = "reset_pwr_off_from_carrier", ++ .label = "reset_comex_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "reset_swb_wd", ++ .label = "reset_voltmon_upgrade_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "reset_swb_aux_pwr_or_fu", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_system", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +- .label = "reset_swb_dc_dc_pwr_fail", ++ .label = "reset_comex_thermal", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { +- .label = "reset_swb_12v_fail", ++ .label = "reset_reload_bios", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { +- .label = "reset_system", ++ .label = "reset_ac_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "reset_thermal_spc_or_pciesw", ++ .label = "reset_long_pwr_pb", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, ++ { ++ .label = "pwr_cycle", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "global_wp_request", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "comm_chnl_ready", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, + { + .label = "bios_safe_mode", + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, +@@ -4194,251 +5228,215 @@ static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = { + .mode = 0644, + }, + { +- .label = "shutdown_unlock", +- .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0644, +- }, +- { +- .label = "lc1_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, ++ .label = "global_wp_response", ++ .reg = MLXPLAT_CPLD_LPC_REG_GWP_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), +- .mode = 0200, +- }, +- { +- .label = "lc2_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), +- .mode = 0200, +- }, +- { +- .label = "lc3_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(2), +- .mode = 0200, +- }, +- { +- .label = "lc4_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), +- .mode = 0200, +- }, +- { +- .label = "lc5_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0200, ++ .mode = 0444, + }, + { +- .label = "lc6_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0200, ++ .label = "config1", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "lc7_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0200, ++ .label = "config2", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "lc8_rst_mask", +- .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), +- .mode = 0200, ++ .label = "config3", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "psu1_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), +- .mode = 0200, ++ .label = "ufm_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_chassis_blade_regs_io_data = { ++ .data = mlxplat_mlxcpld_chassis_blade_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_chassis_blade_regs_io_data), ++}; ++ ++/* Platform register access for smart switch systems families data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_regs_io_data[] = { + { +- .label = "psu2_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(1), +- .mode = 0200, ++ .label = "cpld1_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "pwr_cycle", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(2), +- .mode = 0200, ++ .label = "cpld2_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "pwr_down", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), +- .mode = 0200, ++ .label = "cpld3_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "psu3_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0200, ++ .label = "cpld1_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, + }, + { +- .label = "psu4_on", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0200, ++ .label = "cpld2_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, + }, + { +- .label = "auto_power_mode", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0644, ++ .label = "cpld3_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, + }, + { +- .label = "pm_mgmt_en", +- .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), +- .mode = 0644, ++ .label = "cpld1_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "jtag_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE, +- .mask = GENMASK(3, 0), +- .bit = 1, +- .mode = 0644, ++ .label = "cpld2_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "safe_bios_dis", +- .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0644, ++ .label = "cpld3_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { +- .label = "safe_bios_dis_wp", +- .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), ++ .label = "kexec_activated", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { +- .label = "asic_health", +- .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, +- .mask = MLXPLAT_CPLD_ASIC_MASK, +- .bit = 1, +- .mode = 0444, ++ .label = "asic_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, + }, + { +- .label = "fan_dir", +- .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, +- .bit = GENMASK(7, 0), +- .mode = 0444, ++ .label = "eth_switch_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, + }, + { +- .label = "lc1_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, ++ .label = "dpu1_rst", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), +- .mode = 0644, ++ .mode = 0200, + }, + { +- .label = "lc2_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, ++ .label = "dpu2_rst", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), +- .mode = 0644, ++ .mode = 0200, + }, + { +- .label = "lc3_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, ++ .label = "dpu3_rst", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), +- .mode = 0644, ++ .mode = 0200, + }, + { +- .label = "lc4_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, ++ .label = "dpu4_rst", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), +- .mode = 0644, ++ .mode = 0200, + }, + { +- .label = "lc5_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0644, ++ .label = "dpu1_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, + }, + { +- .label = "lc6_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, +- .mask = GENMASK(7, 0) & ~BIT(5), +- .mode = 0644, ++ .label = "dpu2_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0200, + }, + { +- .label = "lc7_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0644, ++ .label = "dpu3_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, + }, + { +- .label = "lc8_pwr", +- .reg = MLXPLAT_CPLD_LPC_REG_LC_PWR_ON, +- .mask = GENMASK(7, 0) & ~BIT(7), +- .mode = 0644, ++ .label = "dpu4_pwr", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, + }, + { +- .label = "config1", +- .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_long_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "config2", +- .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_short_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { +- .label = "config3", +- .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_aux_pwr_or_ref", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { +- .label = "ufm_version", +- .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, +-}; +- +-static struct mlxreg_core_platform_data mlxplat_modular_regs_io_data = { +- .data = mlxplat_mlxcpld_modular_regs_io_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_modular_regs_io_data), +-}; +- +-/* Platform register access for chassis blade systems family data */ +-static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = { + { +- .label = "cpld1_version", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_swb_wd", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "cpld1_pn", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, +- .bit = GENMASK(15, 0), ++ .label = "reset_asic_thermal", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, +- .regnum = 2, + }, + { +- .label = "cpld1_version_min", +- .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, +- .bit = GENMASK(7, 0), ++ .label = "reset_sw_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { +- .label = "reset_aux_pwr_or_ref", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_aux_pwr_or_reload", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, +- { +- .label = "reset_from_comex", +- .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0444, +- }, + { + .label = "reset_comex_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +@@ -4458,13 +5456,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = { + .mode = 0444, + }, + { +- .label = "reset_comex_wd", ++ .label = "reset_pwr", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { +- .label = "reset_voltmon_upgrade_fail", ++ .label = "reset_pwr_converter_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, +@@ -4488,23 +5486,103 @@ static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = { + .mode = 0444, + }, + { +- .label = "reset_reload_bios", ++ .label = "reset_ac_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { +- .label = "reset_ac_pwr_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), ++ .label = "voltreg_update_status", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET, ++ .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK, ++ .bit = 5, + .mode = 0444, + }, + { +- .label = "reset_long_pwr_pb", +- .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), ++ .label = "port80", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = MLXPLAT_CPLD_BIOS_STATUS_MASK, ++ .bit = 2, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "fan_dir", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, ++ { ++ .label = "dpu1_rst_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ { ++ .label = "dpu2_rst_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0200, ++ }, ++ { ++ .label = "dpu3_rst_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "dpu4_rst_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu1_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu2_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0200, ++ }, + { + .label = "pwr_cycle", + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, +@@ -4518,72 +5596,117 @@ static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = { + .mode = 0200, + }, + { +- .label = "global_wp_request", +- .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .label = "jtag_cap", ++ .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET, ++ .mask = MLXPLAT_CPLD_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "me_reboot", ++ .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "dpu1_pwr_force", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { +- .label = "jtag_enable", +- .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "dpu2_pwr_force", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { +- .label = "comm_chnl_ready", +- .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), +- .mode = 0200, ++ .label = "dpu3_pwr_force", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, + }, + { +- .label = "bios_safe_mode", +- .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), +- .mode = 0444, ++ .label = "dpu4_pwr_force", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, + }, + { +- .label = "bios_active_image", +- .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(5), ++ .label = "ufm_done", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { +- .label = "bios_auth_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(6), ++ .label = "asic_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, + .mode = 0444, + }, + { +- .label = "bios_upgrade_fail", +- .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(7), +- .mode = 0444, ++ .label = "psu1_ac_ok", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, + }, + { +- .label = "voltreg_update_status", +- .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET, +- .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK, +- .bit = 5, +- .mode = 0444, ++ .label = "psu2_ac_ok", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, + }, + { +- .label = "vpd_wp", +- .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(3), ++ .label = "psu1_no_alert", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { +- .label = "pcie_asic_reset_dis", +- .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "psu2_no_alert", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { +- .label = "global_wp_response", +- .reg = MLXPLAT_CPLD_LPC_REG_GWP_OFFSET, +- .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "asic_pg_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, ++ { ++ .label = "spi_chnl_select", ++ .reg = MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, + { + .label = "config1", + .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, +@@ -4610,9 +5733,9 @@ static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = { + }, + }; + +-static struct mlxreg_core_platform_data mlxplat_chassis_blade_regs_io_data = { +- .data = mlxplat_mlxcpld_chassis_blade_regs_io_data, +- .counter = ARRAY_SIZE(mlxplat_mlxcpld_chassis_blade_regs_io_data), ++static struct mlxreg_core_platform_data mlxplat_smart_switch_regs_io_data = { ++ .data = mlxplat_mlxcpld_smart_switch_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_regs_io_data), + }; + + /* Platform FAN default */ +@@ -4756,6 +5879,185 @@ static struct mlxreg_core_platform_data mlxplat_default_fan_data = { + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + }; + ++/* XDR and smart switch platform fan data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_data[] = { ++ { ++ .label = "pwm1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET, ++ }, ++ { ++ .label = "tacho1", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 1, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho2", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 2, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho3", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO3_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 3, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho4", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 4, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho5", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO5_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 5, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho6", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO6_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 6, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho7", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO7_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 7, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho8", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO8_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 8, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho9", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO9_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 9, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho10", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO10_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 10, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho11", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO11_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 11, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho12", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 12, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho13", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO13_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 13, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho14", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO14_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 14, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho15", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 15, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho16", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 16, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho17", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 17, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ }, ++ { ++ .label = "tacho18", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 18, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ }, ++ { ++ .label = "tacho19", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 19, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ }, ++ { ++ .label = "tacho20", ++ .reg = MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, ++ .slot = 20, ++ .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ }, ++ { ++ .label = "conf", ++ .capability = MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_xdr_fan_data = { ++ .data = mlxplat_mlxcpld_xdr_fan_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_xdr_fan_data), ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .version = 1, ++}; ++ + /* Watchdog type1: hardware implementation version1 + * (MSN2700, MSN2410, MSN2740, MSN2100 and MSN2140 systems). + */ +@@ -4980,6 +6282,8 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) + { + switch (reg) { + case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED2_OFFSET: +@@ -4988,12 +6292,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED6_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED7_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_LED8_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_GP3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE: + case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET: + case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET: +@@ -5017,10 +6323,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_ASIC2_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_EROT_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_EROT_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_EROTE_EVENT_OFFSET: +@@ -5088,6 +6398,8 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: +@@ -5099,15 +6411,18 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED6_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED7_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_LED8_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION: + case MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_GP3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE: + case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET: + case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET: +@@ -5127,6 +6442,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_GWP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GWP_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GWP_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_BRD_OFFSET: + case MLXPLAT_CPLD_LPC_REG_BRD_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_BRD_MASK_OFFSET: +@@ -5139,12 +6455,17 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_PSU_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_EROT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_EROT_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_EROT_MASK_OFFSET: +@@ -5218,6 +6539,13 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO13_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO14_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: +@@ -5253,6 +6581,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: +@@ -5264,13 +6594,16 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED6_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED7_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_LED8_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION: + case MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_GP3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE: + case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET: + case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET: +@@ -5290,6 +6623,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_GWP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GWP_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GWP_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_BRD_OFFSET: + case MLXPLAT_CPLD_LPC_REG_BRD_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_BRD_MASK_OFFSET: +@@ -5302,9 +6636,11 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_PSU_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: +@@ -5375,6 +6711,13 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO13_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO14_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: +@@ -5436,6 +6779,14 @@ static const struct reg_default mlxplat_mlxcpld_regmap_eth_modular[] = { + MLXPLAT_CPLD_AGGR_MASK_LC_LOW }, + }; + ++static const struct reg_default mlxplat_mlxcpld_regmap_smart_switch[] = { ++ { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET, MLXPLAT_CPLD_LPC_SM_SW_MASK }, ++}; ++ + struct mlxplat_mlxcpld_regmap_context { + void __iomem *base; + }; +@@ -5544,6 +6895,20 @@ static const struct regmap_config mlxplat_mlxcpld_regmap_config_eth_modular = { + .reg_write = mlxplat_mlxcpld_reg_write, + }; + ++static const struct regmap_config mlxplat_mlxcpld_regmap_config_smart_switch = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 255, ++ .cache_type = REGCACHE_FLAT, ++ .writeable_reg = mlxplat_mlxcpld_writeable_reg, ++ .readable_reg = mlxplat_mlxcpld_readable_reg, ++ .volatile_reg = mlxplat_mlxcpld_volatile_reg, ++ .reg_defaults = mlxplat_mlxcpld_regmap_smart_switch, ++ .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_smart_switch), ++ .reg_read = mlxplat_mlxcpld_reg_read, ++ .reg_write = mlxplat_mlxcpld_reg_write, ++}; ++ + static struct resource mlxplat_mlxcpld_resources[] = { + [0] = DEFINE_RES_IRQ_NAMED(MLXPLAT_CPLD_LPC_SYSIRQ, "mlxreg-hotplug"), + }; +@@ -5555,6 +6920,7 @@ static struct mlxreg_core_platform_data *mlxplat_regs_io; + static struct mlxreg_core_platform_data *mlxplat_fan; + static struct mlxreg_core_platform_data + *mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS]; ++static struct mlxreg_core_data *mlxplat_dpu_data[MLXPLAT_CPLD_DPU_MAX_DEVS]; + static const struct regmap_config *mlxplat_regmap_config; + static struct pci_dev *lpc_bridge; + static struct pci_dev *i2c_bridge; +@@ -5926,6 +7292,31 @@ static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi) + return mlxplat_register_platform_device(); + } + ++static int __init mlxplat_dmi_smart_switch_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; ++ mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data); ++ mlxplat_mux_data = mlxplat_ng800_mux_data; ++ mlxplat_hotplug = &mlxplat_mlxcpld_smart_switch_data; ++ mlxplat_hotplug->deferred_nr = ++ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; ++ mlxplat_led = &mlxplat_xdr_led_data; ++ mlxplat_regs_io = &mlxplat_smart_switch_regs_io_data; ++ mlxplat_fan = &mlxplat_xdr_fan_data; ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) ++ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_dpu_data); i++) ++ mlxplat_dpu_data[i] = &mlxplat_mlxcpld_smart_switch_dpu_data[i]; ++ ++ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; ++ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_smart_switch; ++ ++ return mlxplat_register_platform_device(); ++} ++ + static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + { + .callback = mlxplat_dmi_default_wc_matched, +@@ -6020,6 +7411,12 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0017"), + }, + }, ++ { ++ .callback = mlxplat_dmi_smart_switch_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0019"), ++ }, ++ }, + { + .callback = mlxplat_dmi_msn274x_matched, + .matches = { +@@ -6400,8 +7797,25 @@ static int mlxplat_platdevs_init(struct mlxplat_priv *priv) + } + } + ++ /* Add DPU drivers. */ ++ for (i = 0; i < MLXPLAT_CPLD_DPU_MAX_DEVS; i++) { ++ if (!mlxplat_dpu_data[i]) ++ continue; ++ priv->pdev_dpu[i] = ++ platform_device_register_resndata(&mlxplat_dev->dev, "mlxreg-dpu", ++ i, NULL, 0, mlxplat_dpu_data[i], ++ sizeof(*mlxplat_dpu_data[i])); ++ if (IS_ERR(priv->pdev_dpu[i])) { ++ err = PTR_ERR(priv->pdev_dpu[i]); ++ goto fail_platform_dpu_register; ++ } ++ } ++ + return 0; + ++fail_platform_dpu_register: ++ while (i--) ++ platform_device_unregister(priv->pdev_dpu[i]); + fail_platform_wd_register: + while (--i >= 0) + platform_device_unregister(priv->pdev_wd[i]); +@@ -6422,6 +7836,8 @@ static void mlxplat_platdevs_exit(struct mlxplat_priv *priv) + { + int i; + ++ for (i = MLXPLAT_CPLD_DPU_MAX_DEVS - 1; i >= 0; i--) ++ platform_device_unregister(priv->pdev_dpu[i]); + for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0 ; i--) + platform_device_unregister(priv->pdev_wd[i]); + if (priv->pdev_fan) +-- +2.47.2 + diff --git a/patches-sonic/0030-hwmon-mlxreg-fan-Separate-methods-of-fan-setting-com.patch b/patches-sonic/0030-hwmon-mlxreg-fan-Separate-methods-of-fan-setting-com.patch new file mode 100644 index 000000000..188054c39 --- /dev/null +++ b/patches-sonic/0030-hwmon-mlxreg-fan-Separate-methods-of-fan-setting-com.patch @@ -0,0 +1,95 @@ +From 453623c4adece1b0d146f88836b4e718956c265d Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Fri, 1 Sep 2023 07:16:20 +0000 +Subject: [PATCH 30/78] hwmon: (mlxreg-fan) Separate methods of fan setting + coming from different subsystems + +Distinct between fan speed setting request coming for hwmon and +thermal subsystems. + +There are fields 'last_hwmon_state' and 'last_thermal_state' in the +structure 'mlxreg_fan_pwm', which respectively store the cooling state +set by the 'hwmon' and 'thermal' subsystem. +The purpose is to make arbitration of fan speed setting. For example, if +fan speed required to be not lower than some limit, such setting is to +be performed through 'hwmon' subsystem, thus 'thermal' subsystem will +not set fan below this limit. + +Currently, the 'last_thermal_state' is also be updated by 'hwmon' causing +cooling state to never be set to a lower value. + +Eliminate update of 'last_thermal_state', when request is coming from +'hwmon' subsystem. + +Fixes: da74944d3a46 ("hwmon: (mlxreg-fan) Use pwm attribute for setting fan speed low limit") +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/mlxreg-fan.c | 24 ++++++++++++++++-------- + 1 file changed, 16 insertions(+), 8 deletions(-) + +diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c +index e54a438..0e9ce70 100644 +--- a/drivers/hwmon/mlxreg-fan.c ++++ b/drivers/hwmon/mlxreg-fan.c +@@ -115,8 +115,8 @@ struct mlxreg_fan { + int divider; + }; + +-static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, +- unsigned long state); ++static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long state, bool thermal); + + static int + mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, +@@ -228,8 +228,9 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + * last thermal state. + */ + if (pwm->last_hwmon_state >= pwm->last_thermal_state) +- return mlxreg_fan_set_cur_state(pwm->cdev, +- pwm->last_hwmon_state); ++ return _mlxreg_fan_set_cur_state(pwm->cdev, ++ pwm->last_hwmon_state, ++ false); + return 0; + } + return regmap_write(fan->regmap, pwm->reg, val); +@@ -361,9 +362,8 @@ static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev, + return 0; + } + +-static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, +- unsigned long state) +- ++static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long state, bool thermal) + { + struct mlxreg_fan_pwm *pwm = cdev->devdata; + struct mlxreg_fan *fan = pwm->fan; +@@ -373,7 +373,8 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + return -EINVAL; + + /* Save thermal state. */ +- pwm->last_thermal_state = state; ++ if (thermal) ++ pwm->last_thermal_state = state; + + state = max_t(unsigned long, state, pwm->last_hwmon_state); + err = regmap_write(fan->regmap, pwm->reg, +@@ -385,6 +386,13 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + return 0; + } + ++static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long state) ++ ++{ ++ return _mlxreg_fan_set_cur_state(cdev, state, true); ++} ++ + static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = { + .get_max_state = mlxreg_fan_get_max_state, + .get_cur_state = mlxreg_fan_get_cur_state, +-- +2.8.4 + diff --git a/patches-sonic/0031-platform-mellanox-indicate-deferred-I2C-bus-creation.patch b/patches-sonic/0031-platform-mellanox-indicate-deferred-I2C-bus-creation.patch new file mode 100644 index 000000000..4562391bb --- /dev/null +++ b/patches-sonic/0031-platform-mellanox-indicate-deferred-I2C-bus-creation.patch @@ -0,0 +1,31 @@ +From 86f67641e8d4052f801b54362abb88c2b9eda242 Mon Sep 17 00:00:00 2001 +From: Michael Shych +Date: Wed, 29 Nov 2023 13:12:38 +0000 +Subject: [PATCH 31/78] platform: mellanox: indicate deferred I2C bus creation + for a hot-plug driver + +It fixes timing issue when during initialization hot-plug driver +attempts to attach a component to I2C bus, which is still not created. +Setting deferred bus parameter will force hot-plug driver to wait +until the bus is available. + +Signed-off-by: Michael Shych +--- + drivers/platform/mellanox/nvsw-sn2201.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/platform/mellanox/nvsw-sn2201.c b/drivers/platform/mellanox/nvsw-sn2201.c +index db31c8b..924ad48 100644 +--- a/drivers/platform/mellanox/nvsw-sn2201.c ++++ b/drivers/platform/mellanox/nvsw-sn2201.c +@@ -526,6 +526,7 @@ struct mlxreg_core_hotplug_platform_data nvsw_sn2201_hotplug = { + .count = ARRAY_SIZE(nvsw_sn2201_items), + .cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET, + .mask = NVSW_SN2201_CPLD_AGGR_MASK_DEF, ++ .deferred_nr = NVSW_SN2201_2ND_MUX_CH3_NR, + }; + + static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = { +-- +2.8.4 + diff --git a/patches-sonic/0032-platform_data-mlxreg-Add-capability-bit-and-mask-fie.patch b/patches-sonic/0032-platform_data-mlxreg-Add-capability-bit-and-mask-fie.patch new file mode 100644 index 000000000..c23d6c3e8 --- /dev/null +++ b/patches-sonic/0032-platform_data-mlxreg-Add-capability-bit-and-mask-fie.patch @@ -0,0 +1,60 @@ +From aa424ad19acfa09047c184ae619963d51e027a82 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 24 Aug 2023 13:20:54 +0000 +Subject: [PATCH 32/78] platform_data/mlxreg: Add capability bit and mask + fields + +Some 'capability' registers can be shared between different resources. +Add new fields 'capability_bit' and 'capability_mask' to structs +'mlxreg_core_data' and and 'mlxreg_core_item' for getting only relevant +capability bits. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Felix Radensky +--- + include/linux/platform_data/mlxreg.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h +index f6cca7a..20f84cf 100644 +--- a/include/linux/platform_data/mlxreg.h ++++ b/include/linux/platform_data/mlxreg.h +@@ -118,6 +118,8 @@ struct mlxreg_hotplug_device { + * @mask: attribute access mask; + * @bit: attribute effective bit; + * @capability: attribute capability register; ++ * @capability_bit: started bit in attribute capability register; ++ * @capability_mask: mask in attribute capability register; + * @reg_prsnt: attribute presence register; + * @reg_sync: attribute synch register; + * @reg_pwr: attribute power register; +@@ -138,6 +140,8 @@ struct mlxreg_core_data { + u32 mask; + u32 bit; + u32 capability; ++ u32 capability_bit; ++ u32 capability_mask; + u32 reg_prsnt; + u32 reg_sync; + u32 reg_pwr; +@@ -162,6 +166,8 @@ struct mlxreg_core_data { + * @reg: group interrupt status register; + * @mask: group interrupt mask; + * @capability: group capability register; ++ * @capability_bit: started bit in attribute capability register; ++ * @capability_mask: mask in attribute capability register; + * @cache: last status value for elements fro the same group; + * @count: number of available elements in the group; + * @ind: element's index inside the group; +@@ -175,6 +181,8 @@ struct mlxreg_core_item { + u32 reg; + u32 mask; + u32 capability; ++ u32 capability_bit; ++ u32 capability_mask; + u32 cache; + u8 count; + u8 ind; +-- +2.8.4 + diff --git a/patches-sonic/0033-platform-mellanox-mlxreg-hotplug-Add-support-for-new.patch b/patches-sonic/0033-platform-mellanox-mlxreg-hotplug-Add-support-for-new.patch new file mode 100644 index 000000000..c928dfd5d --- /dev/null +++ b/patches-sonic/0033-platform-mellanox-mlxreg-hotplug-Add-support-for-new.patch @@ -0,0 +1,87 @@ +From 2b021913a38a7e6821c4ce989742e34d8bf743c8 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 24 Aug 2023 11:47:54 +0000 +Subject: [PATCH 33/78] platform/mellanox: mlxreg-hotplug: Add support for new + flavor of capability registers + +Hotplug platform data is common across the various systems, while +hotplug driver should be able to configure only the instances relevant +to specific system. + +For example, platform hoptplug data might contain descriptions for fan1, +fan2, ..., fan{n}, while some systems equipped with all 'n' fans, +others with less. +Same for power units, power controllers, ASICs and so on. + +For detection of the real number of equipped devices capability +registers are used. +These registers used to indicate presence of hotplug devices through +the bitmap. + +For some new big modular systems, these registers will provide presence +by counters. + +Use slot parameter to determine whether capability register contains +bitmask or counter. + +Some 'capability' registers can be shared between different resources. +Use fields 'capability_bit' and 'capability_mask' for getting only +relevant capability bits. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Felix Radensky +--- + drivers/platform/mellanox/mlxreg-hotplug.c | 23 +++++++++++++++++++++-- + 1 file changed, 21 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c +index 23fdbcb..51dac7e 100644 +--- a/drivers/platform/mellanox/mlxreg-hotplug.c ++++ b/drivers/platform/mellanox/mlxreg-hotplug.c +@@ -274,6 +274,13 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) + if (ret) + return ret; + ++ if (!regval) ++ continue; ++ ++ /* Remove non-relevant bits. */ ++ if (item->capability_mask) ++ regval = rol32(regval & item->capability_mask, ++ item->capability_bit); + item->mask = GENMASK((regval & item->mask) - 1, 0); + } + +@@ -294,7 +301,19 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) + if (ret) + return ret; + +- if (!(regval & data->bit)) { ++ /* ++ * In case slot field is provided, capability ++ * register contains counter, otherwise bitmask. ++ * Skip non-relevant entries if slot set and ++ * exceeds counter. Othewise validate entry by ++ * matching bitmask. ++ */ ++ if (data->capability_mask) ++ regval = rol32(regval & data->capability_mask, ++ data->capability_bit); ++ if (data->slot > regval) { ++ break; ++ } else if (!(regval & data->bit) && !data->slot) { + data++; + continue; + } +@@ -611,7 +630,7 @@ static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) + if (ret) + goto out; + +- if (!(regval & data->bit)) ++ if (!(regval & data->bit) && !data->slot) + item->mask &= ~BIT(j); + } + } +-- +2.8.4 + diff --git a/patches-sonic/0034-platform-mellanox-Cosmetic-changes-to-improve-code-s.patch b/patches-sonic/0034-platform-mellanox-Cosmetic-changes-to-improve-code-s.patch new file mode 100644 index 000000000..d3d0caed1 --- /dev/null +++ b/patches-sonic/0034-platform-mellanox-Cosmetic-changes-to-improve-code-s.patch @@ -0,0 +1,63 @@ +From c15263c31b19f0dd2cea9e09b9951613ec459afa Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 21 Apr 2025 12:20:48 +0300 +Subject: [PATCH 34/78] platform: mellanox: Cosmetic changes to improve code + style +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Replace in 'for' loop - /i >= 0 ; i--/i >= 0 ;i--/. +Replace in 'while' loop - /(--i >= 0)/(--i)/. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Ilpo Järvinen +Link: https://lore.kernel.org/r/20250421092051.7687-4-vadimp@nvidia.com +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlx-platform.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index 053c8a0..fca8883 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -7817,7 +7817,7 @@ static int mlxplat_platdevs_init(struct mlxplat_priv *priv) + while (i--) + platform_device_unregister(priv->pdev_dpu[i]); + fail_platform_wd_register: +- while (--i >= 0) ++ while (i--) + platform_device_unregister(priv->pdev_wd[i]); + fail_platform_fan_register: + if (mlxplat_regs_io) +@@ -7838,7 +7838,7 @@ static void mlxplat_platdevs_exit(struct mlxplat_priv *priv) + + for (i = MLXPLAT_CPLD_DPU_MAX_DEVS - 1; i >= 0; i--) + platform_device_unregister(priv->pdev_dpu[i]); +- for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0 ; i--) ++ for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0; i--) + platform_device_unregister(priv->pdev_wd[i]); + if (priv->pdev_fan) + platform_device_unregister(priv->pdev_fan); +@@ -7923,7 +7923,7 @@ static int mlxplat_i2c_mux_topology_init(struct mlxplat_priv *priv) + return mlxplat_platdevs_init(priv); + + fail_platform_mux_register: +- while (--i >= 0) ++ while (i--) + platform_device_unregister(priv->pdev_mux[i]); + return err; + } +@@ -7932,7 +7932,7 @@ static void mlxplat_i2c_mux_topology_exit(struct mlxplat_priv *priv) + { + int i; + +- for (i = mlxplat_mux_num - 1; i >= 0 ; i--) { ++ for (i = mlxplat_mux_num - 1; i >= 0; i--) { + if (priv->pdev_mux[i]) + platform_device_unregister(priv->pdev_mux[i]); + } +-- +2.8.4 + diff --git a/patches-sonic/0035-platform-mellanox-mlx-platform-Add-support-for-new-N.patch b/patches-sonic/0035-platform-mellanox-mlx-platform-Add-support-for-new-N.patch new file mode 100644 index 000000000..16cf32bdc --- /dev/null +++ b/patches-sonic/0035-platform-mellanox-mlx-platform-Add-support-for-new-N.patch @@ -0,0 +1,160 @@ +From 7f87ccbae3163ac9b1289abb452eeae74df01eda Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 21 Apr 2025 12:20:49 +0300 +Subject: [PATCH 35/78] platform: mellanox: mlx-platform: Add support for new + Nvidia system +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add support for SN5640 and SN5610 Nvidia switches: +- SN5610 is a 51.2Tbps switch based on Nvidia SPC-4 ASIC equipped with 64 + OSFP ports supporting 2.5Gbps - 400Gbps speeds. +- SN5640 is a 51.2Tbps switch based on Nvidia SPC-5 ASIC equipped with 64 + OSFP ports supporting 10Gbps - 800Gbps speeds. + +Both equipped with: +- Air-cooled with 4 + 1 redundant fan units. +- 2 + 2 redundant 2000W PSUs. +- System management board based on AMD CPU with secure-boot support. + +Reviewed-by: Oleksandr Shamray +Signed-off-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250421092051.7687-5-vadimp@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlx-platform.c | 96 ++++++++++++++++++++++++++++++++ + 1 file changed, 96 insertions(+) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index fca8883..a607fd6 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -3005,6 +3005,59 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_l1_switch_data = { + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_PWR_BUT, + }; + ++/* Platform hotplug for 800G systems family data */ ++static struct mlxreg_core_item mlxplat_mlxcpld_ng800_hi171_items[] = { ++ { ++ .data = mlxplat_mlxcpld_ext_psu_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = MLXPLAT_CPLD_PSU_EXT_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_psu_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_modular_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = MLXPLAT_CPLD_PWR_EXT_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_pwr_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = MLXPLAT_CPLD_FAN_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_fan_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_asic_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_hi171_data = { ++ .items = mlxplat_mlxcpld_ng800_hi171_items, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_ng800_hi171_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2, ++}; ++ + /* Platform led default data */ + static struct mlxreg_core_data mlxplat_mlxcpld_default_led_data[] = { + { +@@ -4492,6 +4545,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0644, + }, + { ++ .label = "shutdown_unlock", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { + .label = "erot1_ap_reset", + .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), +@@ -7317,6 +7376,29 @@ static int __init mlxplat_dmi_smart_switch_matched(const struct dmi_system_id *d + return mlxplat_register_platform_device(); + } + ++static int __init mlxplat_dmi_ng400_hi171_matched(const struct dmi_system_id *dmi) ++{ ++ unsigned int i; ++ ++ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; ++ mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data); ++ mlxplat_mux_data = mlxplat_ng800_mux_data; ++ mlxplat_hotplug = &mlxplat_mlxcpld_ng800_hi171_data; ++ mlxplat_hotplug->deferred_nr = ++ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; ++ mlxplat_led = &mlxplat_xdr_led_data; ++ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; ++ mlxplat_fan = &mlxplat_xdr_fan_data; ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type3); i++) ++ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type3[i]; ++ ++ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; ++ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; ++ ++ return mlxplat_register_platform_device(); ++} ++ + static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + { + .callback = mlxplat_dmi_default_wc_matched, +@@ -7418,6 +7500,20 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + }, + }, + { ++ .callback = mlxplat_dmi_ng400_hi171_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0022"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI171"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_ng400_hi171_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0022"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI172"), ++ }, ++ }, ++ { + .callback = mlxplat_dmi_msn274x_matched, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), +-- +2.8.4 + diff --git a/patches-sonic/0036-platform-mellanox-mlxreg-dpu-Fix-smatch-warnings.patch b/patches-sonic/0036-platform-mellanox-mlxreg-dpu-Fix-smatch-warnings.patch new file mode 100644 index 000000000..63274fbe0 --- /dev/null +++ b/patches-sonic/0036-platform-mellanox-mlxreg-dpu-Fix-smatch-warnings.patch @@ -0,0 +1,56 @@ +From e905a420ecf5b9e2fc526ad6c20150c5c0219c14 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 8 May 2025 23:31:39 +0300 +Subject: [PATCH 36/78] platform/mellanox: mlxreg-dpu: Fix smatch warnings +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add missed call to release adapter. +Remove wrong error pointer conversion. + +Fixes: 3e75f2954116 ("platform/mellanox: mlxreg-dpu: Add initial support for Nvidia DPU") +Signed-off-by: Vadim Pasternak +Link: https://lore.kernel.org/r/20250508203139.55171-1-vadimp@nvidia.com +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +--- + drivers/platform/mellanox/mlxreg-dpu.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxreg-dpu.c b/drivers/platform/mellanox/mlxreg-dpu.c +index 277e4b8..5226010 100644 +--- a/drivers/platform/mellanox/mlxreg-dpu.c ++++ b/drivers/platform/mellanox/mlxreg-dpu.c +@@ -535,8 +535,10 @@ static int mlxreg_dpu_probe(struct platform_device *pdev) + return -EPROBE_DEFER; + + mlxreg_dpu = devm_kzalloc(&pdev->dev, sizeof(*mlxreg_dpu), GFP_KERNEL); +- if (!mlxreg_dpu) +- return -ENOMEM; ++ if (!mlxreg_dpu) { ++ err = -ENOMEM; ++ goto alloc_fail; ++ } + + /* Create device at the top of DPU I2C tree. */ + data->hpdev.client = i2c_new_client_device(data->hpdev.adapter, +@@ -562,7 +564,6 @@ static int mlxreg_dpu_probe(struct platform_device *pdev) + if (err) { + dev_err(&pdev->dev, "Failed to sync regmap for client %s at bus %d at addr 0x%02x\n", + data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); +- err = PTR_ERR(regmap); + goto regcache_sync_fail; + } + +@@ -581,6 +582,7 @@ static int mlxreg_dpu_probe(struct platform_device *pdev) + devm_regmap_init_i2c_fail: + i2c_unregister_device(data->hpdev.client); + i2c_new_device_fail: ++alloc_fail: + i2c_put_adapter(data->hpdev.adapter); + return err; + } +-- +2.8.4 + diff --git a/patches-sonic/0037-platform-mellanox-mlxreg-io-Extend-number-of-hwmon-a.patch b/patches-sonic/0037-platform-mellanox-mlxreg-io-Extend-number-of-hwmon-a.patch new file mode 100644 index 000000000..9e271a324 --- /dev/null +++ b/patches-sonic/0037-platform-mellanox-mlxreg-io-Extend-number-of-hwmon-a.patch @@ -0,0 +1,33 @@ +From bcca6b75be5436c0f99dee143b72ec799a7e7ea0 Mon Sep 17 00:00:00 2001 +From: Oleksandr Shamray +Date: Wed, 5 Feb 2025 14:45:21 +0200 +Subject: [PATCH 37/78] platform/mellanox: mlxreg-io: Extend number of hwmon + attributes + +Extend maximum number of the attributes, exposed to 'sysfs' to support +new systems having more attributes. + +Signed-off-by: Oleksandr Shamray +Reviewed-by: Vadim Pasternak + +Signed-off-by: Oleksandr Shamray +--- + drivers/platform/mellanox/mlxreg-io.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/mellanox/mlxreg-io.c b/drivers/platform/mellanox/mlxreg-io.c +index c502f2b..2f28fa7 100644 +--- a/drivers/platform/mellanox/mlxreg-io.c ++++ b/drivers/platform/mellanox/mlxreg-io.c +@@ -17,7 +17,7 @@ + + /* Attribute parameters. */ + #define MLXREG_IO_ATT_SIZE 10 +-#define MLXREG_IO_ATT_NUM 96 ++#define MLXREG_IO_ATT_NUM 128 + + /** + * struct mlxreg_io_priv_data - driver's private data: +-- +2.8.4 + diff --git a/patches-sonic/0038-platform_data-mlxreg-Add-fields-for-interrupt-storm-.patch b/patches-sonic/0038-platform_data-mlxreg-Add-fields-for-interrupt-storm-.patch new file mode 100644 index 000000000..79031302b --- /dev/null +++ b/patches-sonic/0038-platform_data-mlxreg-Add-fields-for-interrupt-storm-.patch @@ -0,0 +1,63 @@ +From 0f24213b7eb71538c48c3df74bc6fb74b08d1842 Mon Sep 17 00:00:00 2001 +From: Roy Cohen +Date: Mon, 13 Jan 2025 10:47:27 +0200 +Subject: [PATCH 38/78] platform_data/mlxreg: Add fields for interrupt storm + handling + +Add fields for interrupt storm handling. +Extend structure mlxreg_core_data with the following fields: + 'wmark_low_cntr' - interrupt storm counter. + 'mark_low_ts' - interrupt storm low bound timestamp. + 'wmark_high_ts' - interrupt storm high bound timestamp. + +Extend structure mlxreg_core_item with the following field: + 'storming_bits' - interrupt storming bits mask. + +Signed-off-by: Roy Cohen +--- + include/linux/platform_data/mlxreg.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h +index 20f84cf..97042f6 100644 +--- a/include/linux/platform_data/mlxreg.h ++++ b/include/linux/platform_data/mlxreg.h +@@ -133,6 +133,9 @@ struct mlxreg_hotplug_device { + * @regnum: number of registers occupied by multi-register attribute; + * @slot: slot number, at which device is located; + * @secured: if set indicates that entry access is secured; ++ * @wmark_low_cntr: interrupt storm counter; ++ * @wmark_low_ts: interrupt storm low bound timestamp; ++ * @wmark_high_ts: interrupt storm high bound timestamp; + */ + struct mlxreg_core_data { + char label[MLXREG_CORE_LABEL_MAX_SIZE]; +@@ -155,6 +158,9 @@ struct mlxreg_core_data { + u8 regnum; + u8 slot; + u8 secured; ++ unsigned int wmark_low_cntr; ++ unsigned long wmark_low_ts; ++ unsigned long wmark_high_ts; + }; + + /** +@@ -173,6 +179,7 @@ struct mlxreg_core_data { + * @ind: element's index inside the group; + * @inversed: if 0: 0 for signal status is OK, if 1 - 1 is OK; + * @health: true if device has health indication, false in other case; ++ * @storming_bits: interrupt storming bits mask; + */ + struct mlxreg_core_item { + struct mlxreg_core_data *data; +@@ -188,6 +195,7 @@ struct mlxreg_core_item { + u8 ind; + u8 inversed; + u8 health; ++ u32 storming_bits; + }; + + /** +-- +2.8.4 + diff --git a/patches-sonic/0039-platform-mellanox-mlxreg-hotplug-Add-support-for-han.patch b/patches-sonic/0039-platform-mellanox-mlxreg-hotplug-Add-support-for-han.patch new file mode 100644 index 000000000..7ffede213 --- /dev/null +++ b/patches-sonic/0039-platform-mellanox-mlxreg-hotplug-Add-support-for-han.patch @@ -0,0 +1,116 @@ +From daa051d1b20b132d7dd8f5b3d1312084f25f392d Mon Sep 17 00:00:00 2001 +From: Roy Cohen +Date: Mon, 13 Jan 2025 10:50:25 +0200 +Subject: [PATCH 39/78] platform/mellanox: mlxreg-hotplug: Add support for + handling interrupt storm + +In case of broken hardware, it is possible that broken device will +flood interrupt handler with false events. +For example, if fan or power supply has damaged presence pin, it will +cause permanent generation of presence in / presence out events. +As a result, interrupt handler will consume a lot of CPU resources and +will keep raising "UDEV" events to the user space. + +At the same device with damaged pin still will be capable to provide +telemetry date. + +Provide mechanism allowing to detect device causing interrupt flooding +and mask interrupt for this specific device, to isolate from interrupt +handling flow. + +Use the following criteria: if the specific interrupt was generated 'N' +time during 'T' seconds, such device is to be considered as broken and +will be closed for getting interrupts. + +User will be notified through the log error and will be instructed to +replace broken device. + +Signed-off-by: Roy Cohen +--- + drivers/platform/mellanox/mlxreg-hotplug.c | 36 +++++++++++++++++++++++++++--- + 1 file changed, 33 insertions(+), 3 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c +index 51dac7e..2b52610 100644 +--- a/drivers/platform/mellanox/mlxreg-hotplug.c ++++ b/drivers/platform/mellanox/mlxreg-hotplug.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +31,11 @@ + #define MLXREG_HOTPLUG_ATTRS_MAX 128 + #define MLXREG_HOTPLUG_NOT_ASSERT 3 + ++/* Interrupt storm definitios */ ++#define MLXREG_HOTPLUG_WM_COUNTER 100 ++/* Time window in milliseconds */ ++#define MLXREG_HOTPLUG_WM_WINDOW 3000 ++ + /** + * struct mlxreg_hotplug_priv_data - platform private data: + * @irq: platform device interrupt number; +@@ -363,7 +369,7 @@ mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, + struct mlxreg_core_item *item) + { + struct mlxreg_core_data *data; +- unsigned long asserted; ++ unsigned long asserted, wmark_low_ts_window; + u32 regval, bit; + int ret; + +@@ -385,11 +391,35 @@ mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, + for_each_set_bit(bit, &asserted, 8) { + int pos; + ++ /* Skip already marked storming bit. */ ++ if (item->storming_bits & BIT(bit)) ++ continue; ++ + pos = mlxreg_hotplug_item_label_index_get(item->mask, bit); + if (pos < 0) + goto out; + + data = item->data + pos; ++ ++ /* Interrupt storm handling logic. */ ++ if (data->wmark_low_cntr == 0) ++ data->wmark_low_ts = jiffies; ++ ++ data->wmark_low_cntr++; ++ if (data->wmark_low_cntr >= MLXREG_HOTPLUG_WM_COUNTER - 1) { ++ data->wmark_high_ts = jiffies; ++ wmark_low_ts_window = data->wmark_low_ts + ++ msecs_to_jiffies(MLXREG_HOTPLUG_WM_WINDOW); ++ if (time_after(wmark_low_ts_window, data->wmark_high_ts)) { ++ dev_err(priv->dev, "Storming bit %d (label: %s) - interrupt masked permanently. Replace broken HW.", ++ bit, data->label); ++ /* Mark bit as storming. */ ++ item->storming_bits |= BIT(bit); ++ } else { ++ data->wmark_low_cntr = 0; ++ } ++ } ++ + if (regval & BIT(bit)) { + if (item->inversed) + mlxreg_hotplug_device_destroy(priv, data, item->kind); +@@ -409,9 +439,9 @@ mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, + if (ret) + goto out; + +- /* Unmask event. */ ++ /* Unmask event, exclude storming bits. */ + ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, +- item->mask); +++ item->mask & ~item->storming_bits); + + out: + if (ret) +-- +2.8.4 + diff --git a/patches-sonic/0040-platform-mellanox-mlxreg-dpu-Introduce-completion-ca.patch b/patches-sonic/0040-platform-mellanox-mlxreg-dpu-Introduce-completion-ca.patch new file mode 100644 index 000000000..f71f3b92a --- /dev/null +++ b/patches-sonic/0040-platform-mellanox-mlxreg-dpu-Introduce-completion-ca.patch @@ -0,0 +1,151 @@ +From e3a655f23fa819710390f2ff27573d47449246c4 Mon Sep 17 00:00:00 2001 +From: Ciju Rajan K +Date: Tue, 25 Mar 2025 11:20:04 +0200 +Subject: [PATCH 40/78] platform/mellanox: mlxreg-dpu: Introduce completion + callback + +DPU auxiliary powering can cause interrupt flooding because +DPU interrupt handlers are not configured yet, while middle +interrupt aggregation register is unmasked by default during +initialization. Thus, interrupts are getting through, while +handlers are still not fully initialized. Do not unmask +aggregation interrupt register at initialization for all +DPUs. Instead do it per DPU, when its initialization is done. + +This patch also adds the change to clear the DPU event +registers. + +Signed-off-by: Vadim Pasternak +Signed-off-by: Ciju Rajan K +--- + drivers/platform/mellanox/mlx-platform.c | 25 ++++++++++++++++++++++++- + drivers/platform/mellanox/mlxreg-dpu.c | 6 ++++++ + include/linux/platform_data/mlxreg.h | 4 ++++ + 3 files changed, 34 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index a607fd6..4242b13 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -1496,6 +1496,8 @@ static struct mlxreg_core_data mlxplat_mlxcpld_modular_pwr_items_data[] = { + }, + }; + ++#define MLXPLAT_SMART_SWITCH_SLOT_TO_MASK(s) (GENMASK((s) * 2 - 1, (s) * 2 - 2)) ++ + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_lc_act = { + .irq = MLXPLAT_CPLD_LPC_SYSIRQ, +@@ -2792,6 +2794,23 @@ static struct mlxreg_core_item mlxplat_mlxcpld_smart_switch_items[] = { + }, + }; + ++static int mlxplat_dpu_completion_notify(void *handle, int id) ++{ ++ u32 regval, mask; ++ int err; ++ ++ if (id <= 0 || id > 4) ++ return -EINVAL; ++ ++ err = regmap_read(handle, MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET, ®val); ++ if (err) ++ return err; ++ ++ mask = MLXPLAT_SMART_SWITCH_SLOT_TO_MASK(id); ++ ++ return regmap_write(handle, MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET, regval | mask); ++} ++ + static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_smart_switch_data = { + .items = mlxplat_mlxcpld_smart_switch_items, +@@ -2829,24 +2848,28 @@ static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_data[] = { + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[0], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE, + .slot = 1, ++ .completion_notify = mlxplat_dpu_completion_notify, + }, + { + .label = "dpu2", + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[1], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 1, + .slot = 2, ++ .completion_notify = mlxplat_dpu_completion_notify, + }, + { + .label = "dpu3", + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[2], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 2, + .slot = 3, ++ .completion_notify = mlxplat_dpu_completion_notify, + }, + { + .label = "dpu4", + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[3], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 3, + .slot = 4, ++ .completion_notify = mlxplat_dpu_completion_notify, + }, + }; + +@@ -6843,7 +6866,6 @@ static const struct reg_default mlxplat_mlxcpld_regmap_smart_switch[] = { + { MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, 0x00 }, +- { MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET, MLXPLAT_CPLD_LPC_SM_SW_MASK }, + }; + + struct mlxplat_mlxcpld_regmap_context { +@@ -7897,6 +7919,7 @@ static int mlxplat_platdevs_init(struct mlxplat_priv *priv) + for (i = 0; i < MLXPLAT_CPLD_DPU_MAX_DEVS; i++) { + if (!mlxplat_dpu_data[i]) + continue; ++ mlxplat_dpu_data[i]->handle = priv->regmap; + priv->pdev_dpu[i] = + platform_device_register_resndata(&mlxplat_dev->dev, "mlxreg-dpu", + i, NULL, 0, mlxplat_dpu_data[i], +diff --git a/drivers/platform/mellanox/mlxreg-dpu.c b/drivers/platform/mellanox/mlxreg-dpu.c +index 5226010..314385c 100644 +--- a/drivers/platform/mellanox/mlxreg-dpu.c ++++ b/drivers/platform/mellanox/mlxreg-dpu.c +@@ -575,8 +575,14 @@ static int mlxreg_dpu_probe(struct platform_device *pdev) + if (err) + goto mlxreg_dpu_config_init_fail; + ++ err = data->completion_notify(data->handle, data->slot); ++ if (err) ++ goto mlxreg_dpu_completion_notify_fail; ++ + return err; + ++mlxreg_dpu_completion_notify_fail: ++ mlxreg_dpu_config_exit(mlxreg_dpu); + mlxreg_dpu_config_init_fail: + regcache_sync_fail: + devm_regmap_init_i2c_fail: +diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h +index 97042f6..84c94b3 100644 +--- a/include/linux/platform_data/mlxreg.h ++++ b/include/linux/platform_data/mlxreg.h +@@ -136,6 +136,8 @@ struct mlxreg_hotplug_device { + * @wmark_low_cntr: interrupt storm counter; + * @wmark_low_ts: interrupt storm low bound timestamp; + * @wmark_high_ts: interrupt storm high bound timestamp; ++ * @handle: parent handle; ++ * @completion_notify: callback to notify when platform driver probing is done; + */ + struct mlxreg_core_data { + char label[MLXREG_CORE_LABEL_MAX_SIZE]; +@@ -161,6 +163,8 @@ struct mlxreg_core_data { + unsigned int wmark_low_cntr; + unsigned long wmark_low_ts; + unsigned long wmark_high_ts; ++ void *handle; ++ int (*completion_notify)(void *handle, int id); + }; + + /** +-- +2.8.4 + diff --git a/patches-sonic/0041-hwmon-add-MP2869-MP29608-MP29612-and-MP29816-series-.patch b/patches-sonic/0041-hwmon-add-MP2869-MP29608-MP29612-and-MP29816-series-.patch new file mode 100644 index 000000000..2db37ff02 --- /dev/null +++ b/patches-sonic/0041-hwmon-add-MP2869-MP29608-MP29612-and-MP29816-series-.patch @@ -0,0 +1,993 @@ +From 293cfacacafbb7e9c088d47676c4ac2eee7fe4a8 Mon Sep 17 00:00:00 2001 +From: Wensheng Wang +Date: Thu, 24 Jul 2025 17:13:04 +0800 +Subject: [PATCH 41/78] hwmon: add MP2869,MP29608,MP29612 and MP29816 series + driver + +Add support for MPS VR mp2869/mp2869a,mp29608/mp29608a,mp29612/mp29612a +and mp29816/mp29816a/mp29816b/mp29816c controller. This driver exposes +telemetry and limit value readings and writtings. + +Signed-off-by: Wensheng Wang +--- + Documentation/hwmon/index.rst | 1 + + Documentation/hwmon/mp2869.rst | 175 ++++++++++ + MAINTAINERS | 7 + + drivers/hwmon/pmbus/Kconfig | 9 + + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/mp2869.c | 719 +++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 912 insertions(+) + create mode 100644 Documentation/hwmon/mp2869.rst + create mode 100644 drivers/hwmon/pmbus/mp2869.c + +diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst +index ea3b5be..1c91bbf 100644 +--- a/Documentation/hwmon/index.rst ++++ b/Documentation/hwmon/index.rst +@@ -163,6 +163,7 @@ Hardware Monitoring Kernel Drivers + menf21bmc + mlxreg-fan + mp2856 ++ mp2869 + mp2888 + mp2891 + mp2975 +diff --git a/Documentation/hwmon/mp2869.rst b/Documentation/hwmon/mp2869.rst +new file mode 100644 +index 00000000..2d9d65fc +--- /dev/null ++++ b/Documentation/hwmon/mp2869.rst +@@ -0,0 +1,175 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++Kernel driver mp2869 ++==================== ++ ++Supported chips: ++ ++ * MPS mp2869 ++ ++ Prefix: 'mp2869' ++ ++ * MPS mp29608 ++ ++ Prefix: 'mp29608' ++ ++ * MPS mp29612 ++ ++ Prefix: 'mp29612' ++ ++ * MPS mp29816 ++ ++ Prefix: 'mp29816' ++ ++Author: ++ ++ Wensheng Wang ++ ++Description ++----------- ++ ++This driver implements support for Monolithic Power Systems, Inc. (MPS) ++MP2869 Dual Loop Digital Multi-phase Controller. ++ ++Device compliant with: ++ ++- PMBus rev 1.3 interface. ++ ++The driver exports the following attributes via the 'sysfs' files ++for input voltage: ++ ++**in1_input** ++ ++**in1_label** ++ ++**in1_crit** ++ ++**in1_crit_alarm** ++ ++**in1_lcrit** ++ ++**in1_lcrit_alarm** ++ ++**in1_min** ++ ++**in1_min_alarm** ++ ++The driver provides the following attributes for output voltage: ++ ++**in2_input** ++ ++**in2_label** ++ ++**in2_crit** ++ ++**in2_crit_alarm** ++ ++**in2_lcrit** ++ ++**in2_lcrit_alarm** ++ ++**in3_input** ++ ++**in3_label** ++ ++**in3_crit** ++ ++**in3_crit_alarm** ++ ++**in3_lcrit** ++ ++**in3_lcrit_alarm** ++ ++The driver provides the following attributes for input current: ++ ++**curr1_input** ++ ++**curr1_label** ++ ++**curr2_input** ++ ++**curr2_label** ++ ++The driver provides the following attributes for output current: ++ ++**curr3_input** ++ ++**curr3_label** ++ ++**curr3_crit** ++ ++**curr3_crit_alarm** ++ ++**curr3_max** ++ ++**curr3_max_alarm** ++ ++**curr4_input** ++ ++**curr4_label** ++ ++**curr4_crit** ++ ++**curr4_crit_alarm** ++ ++**curr4_max** ++ ++**curr4_max_alarm** ++ ++The driver provides the following attributes for input power: ++ ++**power1_input** ++ ++**power1_label** ++ ++**power2_input** ++ ++**power2_label** ++ ++The driver provides the following attributes for output power: ++ ++**power3_input** ++ ++**power3_label** ++ ++**power3_input** ++ ++**power3_label** ++ ++**power3_max** ++ ++**power3_max_alarm** ++ ++**power4_input** ++ ++**power4_label** ++ ++**power4_input** ++ ++**power4_label** ++ ++**power4_max** ++ ++**power4_max_alarm** ++ ++The driver provides the following attributes for temperature: ++ ++**temp1_input** ++ ++**temp1_crit** ++ ++**temp1_crit_alarm** ++ ++**temp1_max** ++ ++**temp1_max_alarm** ++ ++**temp2_input** ++ ++**temp2_crit** ++ ++**temp2_crit_alarm** ++ ++**temp2_max** ++ ++**temp2_max_alarm** +diff --git a/MAINTAINERS b/MAINTAINERS +index 81730c7..c120a8e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -15678,6 +15678,13 @@ S: Maintained + F: Documentation/devicetree/bindings/leds/backlight/mps,mp3309c.yaml + F: drivers/video/backlight/mp3309c.c + ++MPS MP2869 DRIVER ++M: Wensheng Wang ++L: linux-hwmon@vger.kernel.org ++S: Maintained ++F: Documentation/hwmon/mp2869.rst ++F: drivers/hwmon/pmbus/mp2869.c ++ + MPS MP2891 DRIVER + M: Noah Wang + L: linux-hwmon@vger.kernel.org +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +index a4f02cad..e9a4f84 100644 +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -328,6 +328,15 @@ config SENSORS_MP2856 + This driver can also be built as a module. If so, the module will + be called mp2856. + ++config SENSORS_MP2869 ++ tristate "MPS MP2869" ++ help ++ If you say yes here you get hardware monitoring support for MPS ++ MP2869 Dual Loop Digital Multi-Phase Controller. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mp2869. ++ + config SENSORS_MP2888 + tristate "MPS MP2888" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +index d00bcc7..179e2b2 100644 +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -35,6 +35,7 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o + obj-$(CONFIG_SENSORS_MAX34440) += max34440.o + obj-$(CONFIG_SENSORS_MAX8688) += max8688.o + obj-$(CONFIG_SENSORS_MP2856) += mp2856.o ++obj-$(CONFIG_SENSORS_MP2869) += mp2869.o + obj-$(CONFIG_SENSORS_MP2888) += mp2888.o + obj-$(CONFIG_SENSORS_MP2891) += mp2891.o + obj-$(CONFIG_SENSORS_MP2975) += mp2975.o +diff --git a/drivers/hwmon/pmbus/mp2869.c b/drivers/hwmon/pmbus/mp2869.c +new file mode 100644 +index 00000000..04502e5 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mp2869.c +@@ -0,0 +1,719 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2869) ++ */ ++ ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++/* ++ * Vender specific registers, the register MFR_VOUT_SCALE_LOOP(0x29), ++ * MFR_SVI3_IOUT_PRT(0x67), READ_PIN_EST(0x94)and READ_IIN_EST(0x95) ++ * redefine the standard PMBUS register. The MFR_VOUT_LOOP_CTRL(0x29) ++ * is used to identify the vout scale and the MFR_SVI3_IOUT_PRT(0x67) ++ * is used to identify the iout scale. The READ_PIN_EST(0x94) is used ++ * to read input power per rail. The MP2891 does not have standard ++ * READ_IIN register(0x89), the iin telemetry can be obtained through ++ * the vendor redefined register READ_IIN_EST(0x95). ++ */ ++#define MFR_VOUT_SCALE_LOOP 0x29 ++#define MFR_SVI3_IOUT_PRT 0x67 ++#define MFR_READ_PIN_EST 0x94 ++#define MFR_READ_IIN_EST 0x95 ++#define MFR_TSNS_FLT_SET 0xBB ++ ++#define MP2869_VIN_OV_FAULT_GAIN 4 ++#define MP2869_READ_VOUT_DIV 1024 ++#define MP2869_READ_IOUT_DIV 32 ++#define MP2869_OVUV_LIMIT_SCALE 10 ++#define MP2869_OVUV_DELTA_SCALE 50 ++#define MP2869_TEMP_LIMIT_OFFSET 40 ++#define MP2869_IOUT_LIMIT_UINT 8 ++#define MP2869_POUT_OP_GAIN 2 ++ ++#define MP2869_PAGE_NUM 2 ++ ++#define MP2869_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \ ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \ ++ PMBUS_HAVE_IIN | \ ++ PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_INPUT) ++ ++#define MP2869_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \ ++ PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \ ++ PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | \ ++ PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_INPUT) ++ ++enum chips {mp2869, mp29608, mp29612, mp29816}; ++ ++struct mp2869_data { ++ struct pmbus_driver_info info; ++ bool mfr_thwn_flt_en; ++ int vout_scale[MP2869_PAGE_NUM]; ++ int iout_scale[MP2869_PAGE_NUM]; ++}; ++ ++#define to_mp2869_data(x) container_of(x, struct mp2869_data, info) ++ ++static u16 mp2869_reg2data_linear11(u16 word) ++{ ++ s16 exponent; ++ s32 mantissa; ++ s64 val; ++ ++ exponent = ((s16)word) >> 11; ++ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; ++ val = mantissa; ++ ++ if (exponent >= 0) ++ val <<= exponent; ++ else ++ val >>= -exponent; ++ ++ return val; ++} ++ ++static int ++mp2869_identify_thwn_flt(struct i2c_client *client, struct pmbus_driver_info *info, ++ int page) ++{ ++ struct mp2869_data *data = to_mp2869_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET); ++ if (ret < 0) ++ return ret; ++ ++ if (FIELD_GET(GENMASK(13, 13), ret)) ++ data->mfr_thwn_flt_en = true; ++ else ++ data->mfr_thwn_flt_en = false; ++ ++ return 0; ++} ++ ++static int ++mp2869_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info, ++ int page) ++{ ++ struct mp2869_data *data = to_mp2869_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_VOUT_SCALE_LOOP); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The output voltage is equal to the READ_VOUT(0x8B) register value multiply ++ * by vout_scale. ++ * Obtain vout scale from the register MFR_VOUT_SCALE_LOOP, bits 12-10 ++ * MFR_VOUT_SCALE_LOOP[12:10]: ++ * 000b - 6.25mV/LSB, 001b - 5mV/LSB, 010b - 2.5mV/LSB, 011b - 2mV/LSB ++ * 100b - 1mV/Lsb, 101b - (1/256)mV/LSB, 110b - (1/512)mV/LSB, ++ * 111b - (1/1024)mV/LSB ++ */ ++ switch (FIELD_GET(GENMASK(12, 10), ret)) { ++ case 0: ++ data->vout_scale[page] = 6400; ++ break; ++ case 1: ++ data->vout_scale[page] = 5120; ++ break; ++ case 2: ++ data->vout_scale[page] = 2560; ++ break; ++ case 3: ++ data->vout_scale[page] = 2048; ++ break; ++ case 4: ++ data->vout_scale[page] = 1024; ++ break; ++ case 5: ++ data->vout_scale[page] = 4; ++ break; ++ case 6: ++ data->vout_scale[page] = 2; ++ break; ++ case 7: ++ data->vout_scale[page] = 1; ++ break; ++ default: ++ data->vout_scale[page] = 1; ++ break; ++ } ++ ++ return 0; ++} ++ ++static int ++mp2869_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info, ++ int page) ++{ ++ struct mp2869_data *data = to_mp2869_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The output current is equal to the READ_IOUT(0x8C) register value ++ * multiply by iout_scale. ++ * Obtain iout_scale from the register MFR_SVI3_IOUT_PRT[2:0]. ++ * The value is selected as below: ++ * 000b - 1A/LSB, 001b - (1/32)A/LSB, 010b - (1/16)A/LSB, ++ * 011b - (1/8)A/LSB, 100b - (1/4)A/LSB, 101b - (1/2)A/LSB ++ * 110b - 1A/LSB, 111b - 2A/LSB ++ */ ++ switch (ret & GENMASK(2, 0)) { ++ case 0: ++ case 6: ++ data->iout_scale[page] = 32; ++ break; ++ case 1: ++ data->iout_scale[page] = 1; ++ break; ++ case 2: ++ data->iout_scale[page] = 2; ++ break; ++ case 3: ++ data->iout_scale[page] = 4; ++ break; ++ case 4: ++ data->iout_scale[page] = 8; ++ break; ++ case 5: ++ data->iout_scale[page] = 16; ++ break; ++ default: ++ data->iout_scale[page] = 64; ++ break; ++ } ++ ++ return 0; ++} ++ ++static int mp2869_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mp2869_data *data = to_mp2869_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ /* ++ * The calculation of vout in this driver is based on direct format. ++ * As a result, the format of vout is enforced to direct. ++ */ ++ ret = PB_VOUT_MODE_DIRECT; ++ break; ++ case PMBUS_STATUS_BYTE: ++ /* ++ * If the tsns digital fault is enabled, the TEMPERATURE flag ++ * of PMBUS_STATUS_BYTE should come from STATUS_MFR_SPECIFIC ++ * register bit1. ++ */ ++ if (data->mfr_thwn_flt_en) { ++ ret = pmbus_read_byte_data(client, page, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = (ret & ~GENMASK(2, 2)) | ++ FIELD_PREP(GENMASK(2, 2), ++ FIELD_GET(GENMASK(1, 1), ++ pmbus_read_byte_data(client, page, ++ PMBUS_STATUS_MFR_SPECIFIC))); ++ } else { ++ ret = -ENODATA; ++ } ++ break; ++ case PMBUS_STATUS_TEMPERATURE: ++ /* ++ * If the tsns digital fault is enabled, the OT Fault and OT Warning ++ * flag of PMBUS_STATUS_TEMPERATURE should come from STATUS_MFR_SPECIFIC ++ * register bit1. ++ */ ++ if (data->mfr_thwn_flt_en) { ++ ret = pmbus_read_byte_data(client, page, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = (ret & ~GENMASK(7, 6)) | ++ FIELD_PREP(GENMASK(6, 6), ++ FIELD_GET(GENMASK(1, 1), ++ pmbus_read_byte_data(client, page, ++ PMBUS_STATUS_MFR_SPECIFIC))) | ++ FIELD_PREP(GENMASK(7, 7), ++ FIELD_GET(GENMASK(1, 1), ++ pmbus_read_byte_data(client, page, ++ PMBUS_STATUS_MFR_SPECIFIC))); ++ } else { ++ ret = -ENODATA; ++ } ++ break; ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mp2869_read_word_data(struct i2c_client *client, int page, int phase, ++ int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mp2869_data *data = to_mp2869_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_STATUS_WORD: ++ /* ++ * If the tsns digital fault is enabled, the OT Fault flag ++ * of PMBUS_STATUS_WORD should come from STATUS_MFR_SPECIFIC ++ * register bit1. ++ */ ++ if (data->mfr_thwn_flt_en) { ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = (ret & ~GENMASK(2, 2)) | ++ FIELD_PREP(GENMASK(2, 2), ++ FIELD_GET(GENMASK(1, 1), ++ pmbus_read_byte_data(client, page, ++ PMBUS_STATUS_MFR_SPECIFIC))); ++ } else { ++ ret = -ENODATA; ++ } ++ break; ++ case PMBUS_READ_VIN: ++ /* ++ * The MP2869 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is ++ * 31.25mV/LSB. And the vin scale is set to 31.25mV/Lsb(using r/m/b scale) ++ * in MP2869 pmbus_driver_info struct, so the word data bit0-bit10 can be ++ * returned to pmbus core directly. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = FIELD_GET(GENMASK(10, 0), ret); ++ break; ++ case PMBUS_READ_IIN: ++ /* ++ * The MP2869 redefine the standard 0x95 register as iin telemetry ++ * per rail. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST); ++ if (ret < 0) ++ return ret; ++ ++ break; ++ case PMBUS_READ_PIN: ++ /* ++ * The MP2869 redefine the standard 0x94 register as pin telemetry ++ * per rail. The MP2869 MFR_READ_PIN_EST register is linear11 format, ++ * but the pin scale is set to 1W/Lsb(using r/m/b scale). As a result, ++ * the pin read from MP2869 should be converted to W, then return ++ * the result to pmbus core. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST); ++ if (ret < 0) ++ return ret; ++ ++ ret = mp2869_reg2data_linear11(ret); ++ break; ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale[page], ++ MP2869_READ_VOUT_DIV); ++ break; ++ case PMBUS_READ_IOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale[page], ++ MP2869_READ_IOUT_DIV); ++ break; ++ case PMBUS_READ_POUT: ++ /* ++ * The MP2869 PMBUS_READ_POUT register is linear11 format, but the pout ++ * scale is set to 1W/Lsb(using r/m/b scale). As a result, the pout read ++ * from MP2869 should be converted to W, then return the result to pmbus ++ * core. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = mp2869_reg2data_linear11(ret); ++ break; ++ case PMBUS_READ_TEMPERATURE_1: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = FIELD_GET(GENMASK(10, 0), ret); ++ break; ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ if (FIELD_GET(GENMASK(12, 9), ret)) ++ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE + ++ (FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE; ++ else ++ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE; ++ break; ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ if (FIELD_GET(GENMASK(12, 9), ret)) ++ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE - ++ (FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE; ++ else ++ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE; ++ ++ ret = ret < 0 ? 0 : ret; ++ break; ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ /* ++ * The scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT ++ * is 1°C/LSB and they have 40°C offset. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = (ret & GENMASK(7, 0)) - MP2869_TEMP_LIMIT_OFFSET; ++ break; ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = (ret & GENMASK(7, 0)) * MP2869_VIN_OV_FAULT_GAIN; ++ break; ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = FIELD_GET(GENMASK(9, 0), ret); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * data->iout_scale[page] * ++ MP2869_IOUT_LIMIT_UINT, MP2869_READ_IOUT_DIV); ++ break; ++ case PMBUS_POUT_OP_WARN_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = (ret & GENMASK(7, 0)) * MP2869_POUT_OP_GAIN; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mp2869_write_word_data(struct i2c_client *client, int page, int reg, ++ u16 word) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mp2869_data *data = to_mp2869_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ /* ++ * The MP2869 PMBUS_VOUT_UV_FAULT_LIMIT[8:0] is the limit value, ++ * and bit9-bit15 should not be changed. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ if (FIELD_GET(GENMASK(12, 9), ret)) ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(8, 0)) | ++ FIELD_PREP(GENMASK(8, 0), ++ DIV_ROUND_CLOSEST(word + ++ (FIELD_GET(GENMASK(12, 9), ++ ret) + 1) * ++ MP2869_OVUV_DELTA_SCALE, ++ MP2869_OVUV_LIMIT_SCALE))); ++ else ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(8, 0)) | ++ FIELD_PREP(GENMASK(8, 0), ++ DIV_ROUND_CLOSEST(word, ++ MP2869_OVUV_LIMIT_SCALE))); ++ break; ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ /* ++ * The MP2869 PMBUS_VOUT_OV_FAULT_LIMIT[8:0] is the limit value, ++ * and bit9-bit15 should not be changed. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ if (FIELD_GET(GENMASK(12, 9), ret)) ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(8, 0)) | ++ FIELD_PREP(GENMASK(8, 0), ++ DIV_ROUND_CLOSEST(word - ++ (FIELD_GET(GENMASK(12, 9), ++ ret) + 1) * ++ MP2869_OVUV_DELTA_SCALE, ++ MP2869_OVUV_LIMIT_SCALE))); ++ else ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(8, 0)) | ++ FIELD_PREP(GENMASK(8, 0), ++ DIV_ROUND_CLOSEST(word, ++ MP2869_OVUV_LIMIT_SCALE))); ++ break; ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ /* ++ * If the tsns digital fault is enabled, the PMBUS_OT_FAULT_LIMIT and ++ * PMBUS_OT_WARN_LIMIT can not be written. ++ */ ++ if (data->mfr_thwn_flt_en) { ++ ret = -EINVAL; ++ } else { ++ /* ++ * The MP2869 scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT ++ * have 40°C offset. The bit0-bit7 is the limit value, and bit8-bit15 ++ * should not be changed. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(7, 0)) | ++ FIELD_PREP(GENMASK(7, 0), ++ word + MP2869_TEMP_LIMIT_OFFSET)); ++ } ++ break; ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ /* ++ * The MP2869 PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15 ++ * should not be changed. The scale of PMBUS_VIN_OV_FAULT_LIMIT is 125mV/Lsb, ++ * but the vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data ++ * should divide by MP2869_VIN_OV_FAULT_GAIN(4) ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(7, 0)) | ++ FIELD_PREP(GENMASK(7, 0), ++ DIV_ROUND_CLOSEST(word, ++ MP2869_VIN_OV_FAULT_GAIN))); ++ break; ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ /* ++ * The PMBUS_VIN_UV_LIMIT[9:0] is the limit value, and bit10-bit15 should ++ * not be changed. The scale of PMBUS_VIN_UV_LIMIT is 31.25mV/Lsb, and the ++ * vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data can ++ * be written directly. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(9, 0)) | ++ FIELD_PREP(GENMASK(9, 0), ++ word)); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ ret = pmbus_write_word_data(client, page, reg, ++ DIV_ROUND_CLOSEST(word * ++ MP2869_READ_IOUT_DIV, ++ MP2869_IOUT_LIMIT_UINT * ++ data->iout_scale[page])); ++ break; ++ case PMBUS_POUT_OP_WARN_LIMIT: ++ /* ++ * The POUT_OP_WARN_LIMIT[11:0] is the limit value, and bit12-bit15 should ++ * not be changed. The scale of POUT_OP_WARN_LIMIT is 2W/Lsb. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(11, 0)) | ++ FIELD_PREP(GENMASK(11, 0), ++ DIV_ROUND_CLOSEST(word, ++ MP2869_POUT_OP_GAIN))); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mp2869_identify(struct i2c_client *client, struct pmbus_driver_info *info) ++{ ++ int ret; ++ ++ /* Identify whether tsns digital fault is enable */ ++ ret = mp2869_identify_thwn_flt(client, info, 1); ++ if (ret < 0) ++ return 0; ++ ++ /* Identify vout scale for rail1. */ ++ ret = mp2869_identify_vout_scale(client, info, 0); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify vout scale for rail2. */ ++ ret = mp2869_identify_vout_scale(client, info, 1); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify iout scale for rail 1. */ ++ ret = mp2869_identify_iout_scale(client, info, 0); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify iout scale for rail 2. */ ++ return mp2869_identify_iout_scale(client, info, 1); ++} ++ ++static const struct pmbus_driver_info mp2869_info = { ++ .pages = MP2869_PAGE_NUM, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_CURRENT_IN] = linear, ++ .format[PSC_CURRENT_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_POWER] = direct, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ ++ .m[PSC_VOLTAGE_IN] = 32, ++ .R[PSC_VOLTAGE_IN] = 0, ++ .b[PSC_VOLTAGE_IN] = 0, ++ ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ ++ .m[PSC_CURRENT_OUT] = 1, ++ .R[PSC_CURRENT_OUT] = 0, ++ .b[PSC_CURRENT_OUT] = 0, ++ ++ .m[PSC_TEMPERATURE] = 1, ++ .R[PSC_TEMPERATURE] = 0, ++ .b[PSC_TEMPERATURE] = 0, ++ ++ .m[PSC_POWER] = 1, ++ .R[PSC_POWER] = 0, ++ .b[PSC_POWER] = 0, ++ ++ .func[0] = MP2869_RAIL1_FUNC, ++ .func[1] = MP2869_RAIL2_FUNC, ++ .read_word_data = mp2869_read_word_data, ++ .write_word_data = mp2869_write_word_data, ++ .read_byte_data = mp2869_read_byte_data, ++ .identify = mp2869_identify, ++}; ++ ++static int mp2869_probe(struct i2c_client *client) ++{ ++ struct pmbus_driver_info *info; ++ struct mp2869_data *data; ++ ++ data = devm_kzalloc(&client->dev, sizeof(struct mp2869_data), ++ GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mp2869_info, sizeof(*info)); ++ info = &data->info; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id mp2869_id[] = { ++ {"mp2869", mp2869}, ++ {"mp29608", mp29608}, ++ {"mp29612", mp29612}, ++ {"mp29816", mp29816}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mp2869_id); ++ ++static const struct of_device_id __maybe_unused mp2869_of_match[] = { ++ {.compatible = "mps,mp2869", .data = (void *)mp2869}, ++ {.compatible = "mps,mp29608", .data = (void *)mp29608}, ++ {.compatible = "mps,mp29612", .data = (void *)mp29612}, ++ {.compatible = "mps,mp29816", .data = (void *)mp29816}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mp2869_of_match); ++ ++static struct i2c_driver mp2869_driver = { ++ .driver = { ++ .name = "mp2869", ++ .of_match_table = mp2869_of_match, ++ }, ++ .probe = mp2869_probe, ++ .id_table = mp2869_id, ++}; ++ ++module_i2c_driver(mp2869_driver); ++ ++MODULE_AUTHOR("Wensheng Wang "); ++MODULE_DESCRIPTION("PMBus driver for MPS MP2869"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS(PMBUS); +-- +2.8.4 + diff --git a/patches-sonic/0042-hwmon-add-MP29502-driver.patch b/patches-sonic/0042-hwmon-add-MP29502-driver.patch new file mode 100644 index 000000000..8a9392478 --- /dev/null +++ b/patches-sonic/0042-hwmon-add-MP29502-driver.patch @@ -0,0 +1,860 @@ +From 69e303de3d931aecff0e41c9689a1da675766176 Mon Sep 17 00:00:00 2001 +From: Wensheng Wang +Date: Thu, 24 Jul 2025 17:13:06 +0800 +Subject: [PATCH 42/78] hwmon: add MP29502 driver + +Add support for MPS VR controller mp29502. This driver exposes +telemetry and limits value readings and writtings. + +Signed-off-by: Wensheng Wang +--- + Documentation/hwmon/index.rst | 1 + + Documentation/hwmon/mp29502.rst | 93 ++++++ + MAINTAINERS | 7 + + drivers/hwmon/pmbus/Kconfig | 9 + + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/mp29502.c | 670 ++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 781 insertions(+) + create mode 100644 Documentation/hwmon/mp29502.rst + create mode 100644 drivers/hwmon/pmbus/mp29502.c + +diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst +index 1c91bbf..fe0a5ef 100644 +--- a/Documentation/hwmon/index.rst ++++ b/Documentation/hwmon/index.rst +@@ -166,6 +166,7 @@ Hardware Monitoring Kernel Drivers + mp2869 + mp2888 + mp2891 ++ mp29502 + mp2975 + mp2993 + mp5023 +diff --git a/Documentation/hwmon/mp29502.rst b/Documentation/hwmon/mp29502.rst +new file mode 100644 +index 00000000..7743056 +--- /dev/null ++++ b/Documentation/hwmon/mp29502.rst +@@ -0,0 +1,93 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++Kernel driver mp29502 ++==================== ++ ++Supported chips: ++ ++ * MPS mp29502 ++ ++ Prefix: 'mp29502' ++ ++Author: ++ ++ Wensheng Wang ++ ++Description ++----------- ++ ++This driver implements support for Monolithic Power Systems, Inc. (MPS) ++MP29502 Digital Multi-phase Controller. ++ ++Device compliant with: ++ ++- PMBus rev 1.3 interface. ++ ++The driver exports the following attributes via the 'sysfs' files ++for input voltage: ++ ++**in1_input** ++ ++**in1_label** ++ ++**in1_crit** ++ ++**in1_crit_alarm** ++ ++The driver provides the following attributes for output voltage: ++ ++**in2_input** ++ ++**in2_label** ++ ++**in2_crit** ++ ++**in2_crit_alarm** ++ ++**in2_lcrit** ++ ++**in2_lcrit_alarm** ++ ++The driver provides the following attributes for input current: ++ ++**curr1_input** ++ ++**curr1_label** ++ ++The driver provides the following attributes for output current: ++ ++**curr2_input** ++ ++**curr2_label** ++ ++**curr2_crit** ++ ++**curr2_crit_alarm** ++ ++**curr2_max** ++ ++**curr2_max_alarm** ++ ++The driver provides the following attributes for input power: ++ ++**power1_input** ++ ++**power1_label** ++ ++The driver provides the following attributes for output power: ++ ++**power2_input** ++ ++**power2_label** ++ ++The driver provides the following attributes for temperature: ++ ++**temp1_input** ++ ++**temp1_crit** ++ ++**temp1_crit_alarm** ++ ++**temp1_max** ++ ++**temp1_max_alarm** +diff --git a/MAINTAINERS b/MAINTAINERS +index c120a8e..6b06d73 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -15692,6 +15692,13 @@ S: Maintained + F: Documentation/hwmon/mp2891.rst + F: drivers/hwmon/pmbus/mp2891.c + ++MPS MP29502 DRIVER ++M: Wensheng Wang ++L: linux-hwmon@vger.kernel.org ++S: Maintained ++F: Documentation/hwmon/mp29502.rst ++F: drivers/hwmon/pmbus/mp29502.c ++ + MPS MP2993 DRIVER + M: Noah Wang + L: linux-hwmon@vger.kernel.org +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +index e9a4f84..60eabf9 100644 +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -355,6 +355,15 @@ config SENSORS_MP2891 + This driver can also be built as a module. If so, the module will + be called mp2891. + ++config SENSORS_MP29502 ++ tristate "MPS MP29502" ++ help ++ If you say yes here you get hardware monitoring support for MPS ++ MP29502 Dual Loop Digital Multi-Phase Controller. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mp29502. ++ + config SENSORS_MP2975 + tristate "MPS MP2975" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +index 179e2b2..55b0784 100644 +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -38,6 +38,7 @@ obj-$(CONFIG_SENSORS_MP2856) += mp2856.o + obj-$(CONFIG_SENSORS_MP2869) += mp2869.o + obj-$(CONFIG_SENSORS_MP2888) += mp2888.o + obj-$(CONFIG_SENSORS_MP2891) += mp2891.o ++obj-$(CONFIG_SENSORS_MP29502) += mp29502.o + obj-$(CONFIG_SENSORS_MP2975) += mp2975.o + obj-$(CONFIG_SENSORS_MP2993) += mp2993.o + obj-$(CONFIG_SENSORS_MP5023) += mp5023.o +diff --git a/drivers/hwmon/pmbus/mp29502.c b/drivers/hwmon/pmbus/mp29502.c +new file mode 100644 +index 00000000..bee2028 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mp29502.c +@@ -0,0 +1,670 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP29502) ++ */ ++ ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define MFR_VOUT_SCALE_LOOP 0x29 ++#define MFR_SVI3_IOUT_PRT 0x67 ++#define MFR_READ_PIN_EST 0x94 ++#define MFR_READ_IIN_EST 0x95 ++#define MFR_VOUT_PROT1 0x3D ++#define MFR_VOUT_PROT2 0x51 ++#define MFR_SLOPE_CNT_SET 0xA8 ++#define MFR_TSNS_FLT_SET 0xBB ++ ++#define MP29502_VIN_OV_GAIN 4 ++#define MP29502_TEMP_LIMIT_OFFSET 40 ++#define MP29502_READ_VOUT_DIV 1024 ++#define MP29502_READ_IOUT_DIV 32 ++#define MP29502_IOUT_LIMIT_UINT 8 ++#define MP29502_OVUV_LIMIT_SCALE 10 ++#define MP28502_VOUT_OV_GAIN 512 ++#define MP28502_VOUT_OV_SCALE 40 ++#define MP29502_VOUT_UV_OFFSET 36 ++#define MP29502_PIN_GAIN 2 ++#define MP29502_IIN_DIV 2 ++ ++#define MP29502_PAGE_NUM 1 ++ ++#define MP29502_RAIL_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \ ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \ ++ PMBUS_HAVE_IIN | \ ++ PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_INPUT) ++ ++struct mp29502_data { ++ struct pmbus_driver_info info; ++ int vout_scale; ++ int vout_bottom_div; ++ int vout_top_div; ++ int ovp_div; ++ int iout_scale; ++}; ++ ++#define to_mp29502_data(x) container_of(x, struct mp29502_data, info) ++ ++static u16 mp29502_reg2data_linear11(u16 word) ++{ ++ s16 exponent; ++ s32 mantissa; ++ s64 val; ++ ++ exponent = ((s16)word) >> 11; ++ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; ++ val = mantissa; ++ ++ if (exponent >= 0) ++ val <<= exponent; ++ else ++ val >>= -exponent; ++ ++ return val; ++} ++ ++static int ++mp29502_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info, ++ int page) ++{ ++ struct mp29502_data *data = to_mp29502_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_VOUT_SCALE_LOOP); ++ if (ret < 0) ++ return ret; ++ ++ switch (FIELD_GET(GENMASK(12, 10), ret)) { ++ case 0: ++ data->vout_scale = 6400; ++ break; ++ case 1: ++ data->vout_scale = 5120; ++ break; ++ case 2: ++ data->vout_scale = 2560; ++ break; ++ case 3: ++ data->vout_scale = 2048; ++ break; ++ case 4: ++ data->vout_scale = 1024; ++ break; ++ case 5: ++ data->vout_scale = 4; ++ break; ++ case 6: ++ data->vout_scale = 2; ++ break; ++ case 7: ++ data->vout_scale = 1; ++ break; ++ default: ++ data->vout_scale = 1; ++ break; ++ } ++ ++ return 0; ++} ++ ++static int ++mp29502_identify_vout_divider(struct i2c_client *client, struct pmbus_driver_info *info, ++ int page) ++{ ++ struct mp29502_data *data = to_mp29502_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT1); ++ if (ret < 0) ++ return ret; ++ ++ data->vout_bottom_div = FIELD_GET(GENMASK(11, 0), ret); ++ ++ ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT2); ++ if (ret < 0) ++ return ret; ++ ++ data->vout_top_div = FIELD_GET(GENMASK(14, 0), ret); ++ ++ return 0; ++} ++ ++static int ++mp29502_identify_ovp_divider(struct i2c_client *client, struct pmbus_driver_info *info, ++ int page) ++{ ++ struct mp29502_data *data = to_mp29502_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_SLOPE_CNT_SET); ++ if (ret < 0) ++ return ret; ++ ++ data->ovp_div = FIELD_GET(GENMASK(9, 0), ret); ++ ++ return 0; ++} ++ ++static int ++mp29502_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info, ++ int page) ++{ ++ struct mp29502_data *data = to_mp29502_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT); ++ if (ret < 0) ++ return ret; ++ ++ switch (ret & GENMASK(2, 0)) { ++ case 0: ++ case 6: ++ data->iout_scale = 32; ++ break; ++ case 1: ++ data->iout_scale = 1; ++ break; ++ case 2: ++ data->iout_scale = 2; ++ break; ++ case 3: ++ data->iout_scale = 4; ++ break; ++ case 4: ++ data->iout_scale = 8; ++ break; ++ case 5: ++ data->iout_scale = 16; ++ break; ++ default: ++ data->iout_scale = 64; ++ break; ++ } ++ ++ return 0; ++} ++ ++static int mp29502_read_vout_ov_limit(struct i2c_client *client, struct mp29502_data *data) ++{ ++ int ret; ++ int ov_value; ++ ++ /* ++ * This is because the vout ov fault limit value comes from ++ * page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit ++ * value comes from page0 reg. So the page should be set to ++ * 0 after the reading of vout ov limit. ++ */ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET); ++ if (ret < 0) ++ return ret; ++ ++ ov_value = DIV_ROUND_CLOSEST(FIELD_GET(GENMASK(12, 7), ret) * ++ MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE, ++ data->ovp_div); ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ return ov_value; ++} ++ ++static int mp29502_write_vout_ov_limit(struct i2c_client *client, u16 word, ++ struct mp29502_data *data) ++{ ++ int ret; ++ ++ /* ++ * This is because the vout ov fault limit value comes from ++ * page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit ++ * value comes from page0 reg. So the page should be set to ++ * 0 after the writing of vout ov limit. ++ */ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_write_word_data(client, MFR_TSNS_FLT_SET, ++ (ret & ~GENMASK(12, 7)) | ++ FIELD_PREP(GENMASK(12, 7), ++ DIV_ROUND_CLOSEST(word * data->ovp_div, ++ MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE))); ++ ++ return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++} ++ ++static int mp29502_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ ret = PB_VOUT_MODE_DIRECT; ++ break; ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mp29502_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mp29502_data *data = to_mp29502_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_STATUS_WORD: ++ ret = -ENODATA; ++ break; ++ case PMBUS_READ_VIN: ++ /* ++ * The MP29502 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is ++ * 125mV/LSB. And the vin scale is set to 125mV/Lsb(using r/m/b scale) ++ * in MP29502 pmbus_driver_info struct, so the word data bit0-bit10 can ++ * be returned to pmbus core directly. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = FIELD_GET(GENMASK(10, 0), ret); ++ break; ++ case PMBUS_READ_VOUT: ++ /* ++ * The MP29502 PMBUS_READ_VOUT[11:0] is the vout value, and vout ++ * value is calculated based on vout scale and vout divider. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * ++ data->vout_scale * ++ (data->vout_bottom_div + ++ 4 * data->vout_top_div), ++ MP29502_READ_VOUT_DIV * ++ data->vout_bottom_div); ++ break; ++ case PMBUS_READ_IIN: ++ /* ++ * The MP29502 MFR_READ_IIN_EST register is linear11 format, and the ++ * exponent is not a constant value. But the iin scale is set to ++ * 1A/Lsb(using r/m/b scale). As a result, the iin read from MP29502 ++ * should be calculated to A, then return the result to pmbus core. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret), ++ MP29502_IIN_DIV); ++ break; ++ case PMBUS_READ_PIN: ++ /* ++ * The MP29502 MFR_READ_PIN_EST register is linear11 format, and the ++ * exponent is not a constant value. But the pin scale is set to ++ * 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502 ++ * should be calculated to W, then return the result to pmbus core. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST); ++ if (ret < 0) ++ return ret; ++ ++ ret = mp29502_reg2data_linear11(ret) * MP29502_PIN_GAIN; ++ break; ++ case PMBUS_READ_POUT: ++ /* ++ * The MP29502 PMBUS_READ_POUT register is linear11 format, and the ++ * exponent is not a constant value. But the pout scale is set to ++ * 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502 ++ * should be calculated to W, then return the result to pmbus core. ++ * And the pout is calculated based on vout divider. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret) * ++ (data->vout_bottom_div + ++ 4 * data->vout_top_div), ++ data->vout_bottom_div); ++ break; ++ case PMBUS_READ_IOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale, ++ MP29502_READ_IOUT_DIV); ++ break; ++ case PMBUS_READ_TEMPERATURE_1: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = FIELD_GET(GENMASK(10, 0), ret); ++ break; ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ /* ++ * The MP29502 PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but ++ * the vin scale is set to 125mV/Lsb(using r/m/b scale), ++ * so the word data should multiply by 4. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = FIELD_GET(GENMASK(7, 0), ret) * MP29502_VIN_OV_GAIN; ++ break; ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ /* ++ * The MP29502 PMBUS_VIN_UV_WARN_LIMIT and PMBUS_VIN_UV_FAULT_LIMIT ++ * scale is 125mV/Lsb, but the vin scale is set to 125mV/Lsb(using ++ * r/m/b scale), so the word data bit0-bit9 can be returned to pmbus ++ * core directly. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = FIELD_GET(GENMASK(9, 0), ret); ++ break; ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ /* ++ * The MP29502 vout ov fault limit value comes from ++ * page1 MFR_TSNS_FLT_SET[12:7]. ++ */ ++ ret = mp29502_read_vout_ov_limit(client, data); ++ if (ret < 0) ++ return ret; ++ ++ break; ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((FIELD_GET(GENMASK(8, 0), ret) * ++ MP29502_OVUV_LIMIT_SCALE - ++ MP29502_VOUT_UV_OFFSET) * ++ (data->vout_bottom_div + ++ 4 * data->vout_top_div), ++ data->vout_bottom_div); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * ++ data->iout_scale * ++ MP29502_IOUT_LIMIT_UINT, ++ MP29502_READ_IOUT_DIV); ++ break; ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ /* ++ * The scale of MP29502 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT ++ * is 1°C/LSB and they have 40°C offset. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = (ret & GENMASK(7, 0)) - MP29502_TEMP_LIMIT_OFFSET; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mp29502_write_word_data(struct i2c_client *client, int page, int reg, ++ u16 word) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mp29502_data *data = to_mp29502_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ switch (reg) { ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ /* ++ * The PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, ++ * and bit8-bit15 should not be changed. The scale of ++ * PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but the vin ++ * scale is set to 125mV/Lsb(using r/m/b scale), so ++ * the word data should divide by 4. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(7, 0)) | ++ FIELD_PREP(GENMASK(7, 0), ++ DIV_ROUND_CLOSEST(word, ++ MP29502_VIN_OV_GAIN))); ++ break; ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ /* ++ * The PMBUS_VIN_UV_WARN_LIMIT[9:0] and PMBUS_VIN_UV_FAULT_LIMIT[9:0] ++ * are the limit value, and bit10-bit15 should not be changed. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(9, 0)) | ++ FIELD_PREP(GENMASK(9, 0), ++ word)); ++ break; ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ ret = mp29502_write_vout_ov_limit(client, word, data); ++ if (ret < 0) ++ return ret; ++ ++ break; ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(8, 0)) | ++ FIELD_PREP(GENMASK(8, 0), ++ DIV_ROUND_CLOSEST(word * ++ data->vout_bottom_div + ++ MP29502_VOUT_UV_OFFSET * ++ (data->vout_bottom_div + ++ 4 * data->vout_top_div), ++ MP29502_OVUV_LIMIT_SCALE * ++ (data->vout_bottom_div + ++ 4 * data->vout_top_div)))); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ ret = pmbus_write_word_data(client, page, reg, ++ DIV_ROUND_CLOSEST(word * ++ MP29502_READ_IOUT_DIV, ++ MP29502_IOUT_LIMIT_UINT * ++ data->iout_scale)); ++ break; ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ /* ++ * The PMBUS_OT_FAULT_LIMIT[7:0] and PMBUS_OT_WARN_LIMIT[7:0] ++ * are the limit value, and bit8-bit15 should not be changed. ++ */ ++ ret = pmbus_read_word_data(client, page, 0xff, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = pmbus_write_word_data(client, page, reg, ++ (ret & ~GENMASK(7, 0)) | ++ FIELD_PREP(GENMASK(7, 0), ++ word + MP29502_TEMP_LIMIT_OFFSET)); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mp29502_identify(struct i2c_client *client, struct pmbus_driver_info *info) ++{ ++ int ret; ++ ++ /* Identify vout scale */ ++ ret = mp29502_identify_vout_scale(client, info, 0); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify vout divider. */ ++ ret = mp29502_identify_vout_divider(client, info, 1); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify ovp divider. */ ++ ret = mp29502_identify_ovp_divider(client, info, 1); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify iout scale */ ++ return mp29502_identify_iout_scale(client, info, 0); ++} ++ ++static const struct pmbus_driver_info mp29502_info = { ++ .pages = MP29502_PAGE_NUM, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_CURRENT_IN] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_POWER] = direct, ++ ++ .m[PSC_VOLTAGE_IN] = 8, ++ .R[PSC_VOLTAGE_IN] = 0, ++ .b[PSC_VOLTAGE_IN] = 0, ++ ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ ++ .m[PSC_TEMPERATURE] = 1, ++ .R[PSC_TEMPERATURE] = 0, ++ .b[PSC_TEMPERATURE] = 0, ++ ++ .m[PSC_CURRENT_IN] = 1, ++ .R[PSC_CURRENT_IN] = 0, ++ .b[PSC_CURRENT_IN] = 0, ++ ++ .m[PSC_CURRENT_OUT] = 1, ++ .R[PSC_CURRENT_OUT] = 0, ++ .b[PSC_CURRENT_OUT] = 0, ++ ++ .m[PSC_POWER] = 1, ++ .R[PSC_POWER] = 0, ++ .b[PSC_POWER] = 0, ++ ++ .func[0] = MP29502_RAIL_FUNC, ++ .read_word_data = mp29502_read_word_data, ++ .read_byte_data = mp29502_read_byte_data, ++ .write_word_data = mp29502_write_word_data, ++ .identify = mp29502_identify, ++}; ++ ++static int mp29502_probe(struct i2c_client *client) ++{ ++ struct pmbus_driver_info *info; ++ struct mp29502_data *data; ++ ++ data = devm_kzalloc(&client->dev, sizeof(struct mp29502_data), ++ GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mp29502_info, sizeof(*info)); ++ info = &data->info; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id mp29502_id[] = { ++ {"mp29502", 0}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mp29502_id); ++ ++static const struct of_device_id __maybe_unused mp29502_of_match[] = { ++ {.compatible = "mps,mp29502"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mp29502_of_match); ++ ++static struct i2c_driver mp29502_driver = { ++ .driver = { ++ .name = "mp29502", ++ .of_match_table = mp29502_of_match, ++ }, ++ .probe = mp29502_probe, ++ .id_table = mp29502_id, ++}; ++ ++module_i2c_driver(mp29502_driver); ++ ++MODULE_AUTHOR("Wensheng Wang +Date: Thu, 4 Jan 2024 09:12:29 +0000 +Subject: [PATCH 43/78] hwmon: (pmbus) Add support for MPS Multi-phase mp2855 + controller +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add support for MP2855 device, which is a dual-loop, digital, +multi-phase controller that provides power for the core of the +AMD SVI2 2.0 platform. + +Device configurations and fault parameters can be configured or +monitored via the PMBus interface. + +The device: +- Can work with MPS’s Intelli-PhaseTM products +- Can be configured with up to 9-phase operation for rail 1 and up + to 4-phase operation for rail 2. +- Provides an on-chip non-volatile memory (NVM) to store and restore + device configurations. +- Can monitor and report the output current (IOUT) through the CS + output from Intelli-PhaseTM products. + +The device is available in a TQFN-40 (5mmx5mm) package. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/pmbus/Kconfig | 10 ++ + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/mp2855.c | 380 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 391 insertions(+) + create mode 100644 drivers/hwmon/pmbus/mp2855.c + +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +index 60eabf9..369e409 100644 +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -319,6 +319,16 @@ config SENSORS_MAX8688 + This driver can also be built as a module. If so, the module will + be called max8688. + ++config SENSORS_MP2855 ++ tristate "MPS MP2855" ++ help ++ If you say yes here you get hardware monitoring support for MPS ++ MP2855 Dual-Loop, Digital, Multi-Phase Controller with PMBus ++ interface for AMD SVI2 2.0 platform. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mp2855. ++ + config SENSORS_MP2856 + tristate "MPS MP2856" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +index 55b0784..a95fc50 100644 +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -34,6 +34,7 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o + obj-$(CONFIG_SENSORS_MAX31785) += max31785.o + obj-$(CONFIG_SENSORS_MAX34440) += max34440.o + obj-$(CONFIG_SENSORS_MAX8688) += max8688.o ++obj-$(CONFIG_SENSORS_MP2855) += mp2855.o + obj-$(CONFIG_SENSORS_MP2856) += mp2856.o + obj-$(CONFIG_SENSORS_MP2869) += mp2869.o + obj-$(CONFIG_SENSORS_MP2888) += mp2888.o +diff --git a/drivers/hwmon/pmbus/mp2855.c b/drivers/hwmon/pmbus/mp2855.c +new file mode 100644 +index 00000000..3cffd09 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mp2855.c +@@ -0,0 +1,380 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2855) ++ * ++ * Copyright (C) 2023 MPS ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define MP2855_MFR_VR_CONFIG1 0xc1 ++ ++#define MP2855_VOUT_CONFIG_OFF 14 ++#define MP2891_OV_TH_MASK GENMASK(9, 8) ++#define MP2891_OV_TH_POS 8 ++#define MP2891_UV_TH_MASK GENMASK(10, 8) ++#define MP2891_UV_TH_POS 8 ++ ++#define MP2855_PAGE_NUM 2 ++#define MP2855_IOUT_SCALE_MASK GENMASK (31, 27) ++ ++#define MP2855_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | \ ++ PMBUS_HAVE_STATUS_TEMP) ++ ++#define MP2855_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | \ ++ PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP) ++ ++ ++#define MP2891_TMP_TH_GET(ret) DIV_ROUND_CLOSEST(DIV_ROUND_CLOSEST(((ret) & GENMASK(7, 0)) * \ ++ 3125, 1000) - 275, 3); ++ ++struct mp2855_data { ++ struct pmbus_driver_info info; ++ struct i2c_client client; ++ int iout_scale[MP2855_PAGE_NUM]; ++ int vout_scale[MP2855_PAGE_NUM]; ++ int vout_shift[MP2855_PAGE_NUM]; ++ int uv_off[MP2855_PAGE_NUM]; ++ int ov_off[MP2855_PAGE_NUM]; ++}; ++ ++struct mp2855_data data; ++ ++#define to_mp2855_data(x) container_of(x, struct mp2855_data, info) ++ ++static int mp2855_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ /* ++ * Enforce VOUT direct format, since device allows to set the ++ * different formats for the different rails. Conversion from ++ * VID to direct provided by driver internally, in case it is ++ * necessary. ++ */ ++ return PB_VOUT_MODE_DIRECT; ++ default: ++ return -ENODATA; ++ } ++} ++ ++static int mp2855_read_word_data(struct i2c_client *client, int page, int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mp2855_data *data = to_mp2855_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_READ_VIN: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ return (ret < 0) ? ret : (ret & 0x3ff) * 3125 / 100; ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ if (data->vout_shift[page] > 0) ++ return ret & GENMASK(10, 0) >> -data->vout_shift[page]; ++ else if (data->vout_shift[page] < 0) ++ return ret & GENMASK(10, 0) << data->vout_shift[page]; ++ return (ret & GENMASK(10, 0)) * data->vout_scale[page] / 100; ++ case PMBUS_READ_TEMPERATURE_1: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ return (ret < 0) ? ret : ret & GENMASK(7, 0); ++ case PMBUS_READ_IOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ return (ret < 0) ? ret : (ret & GENMASK(7, 0)) * data->iout_scale[page]; ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, PMBUS_READ_VOUT); ++ if (ret <= 0) ++ return ret; ++ ++ return (ret & GENMASK(10, 0)) * data->vout_scale[page] / 100 + data->ov_off[page]; ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, page, phase, PMBUS_READ_VOUT); ++ if (ret <= 0) ++ return ret; ++ ++ return (ret & GENMASK(10, 0)) * data->vout_scale[page] / 100 - data->uv_off[page]; ++ case PMBUS_OT_FAULT_LIMIT: ++ ret = pmbus_read_word_data(client, 1, phase, reg); ++ return (ret <= 0) ? ret : MP2891_TMP_TH_GET(ret); ++ case PMBUS_UT_WARN_LIMIT: ++ case PMBUS_UT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_POUT_OP_WARN_LIMIT: ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ case PMBUS_POUT_MAX: ++ case PMBUS_POUT_OP_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_UC_FAULT_LIMIT: ++ case PMBUS_MFR_VIN_MIN: ++ case PMBUS_MFR_VOUT_MIN: ++ case PMBUS_MFR_VIN_MAX: ++ case PMBUS_MFR_VOUT_MAX: ++ case PMBUS_MFR_IIN_MAX: ++ case PMBUS_MFR_IOUT_MAX: ++ case PMBUS_MFR_PIN_MAX: ++ case PMBUS_MFR_POUT_MAX: ++ case PMBUS_MFR_MAX_TEMP_1: ++ return -ENXIO; ++ default: ++ return -ENODATA; ++ } ++ ++ return ret; ++} ++ ++static struct pmbus_driver_info mp2855_info = { ++ .pages = MP2855_PAGE_NUM, ++ .phases[0] = 1, ++ .phases[1] = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .m[PSC_VOLTAGE_IN] = 1, ++ .R[PSC_VOLTAGE_IN] = 3, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .m[PSC_TEMPERATURE] = 1, ++ .R[PSC_TEMPERATURE] = 0, ++ .b[PSC_TEMPERATURE] = 0, ++ .m[PSC_CURRENT_OUT] = 1, ++ .R[PSC_CURRENT_OUT] = 3, ++ .b[PSC_CURRENT_OUT] = 0, ++ .func[0] = MP2855_RAIL1_FUNC, ++ .func[1] = MP2855_RAIL2_FUNC, ++ .read_byte_data = mp2855_read_byte_data, ++ .read_word_data = mp2855_read_word_data, ++}; ++ ++static int mp2855_vout_scale_get(struct i2c_client *client, struct mp2855_data *data, ++ int vout_mode, int page) ++{ ++ int ret; ++ ++ if (vout_mode & GENMASK(7, 5)) { ++ /* Direct mode. */ ++ ret = i2c_smbus_read_word_data(client, MP2855_MFR_VR_CONFIG1); ++ if (ret < 0) ++ return ret; ++ ++ switch (ret >> MP2855_VOUT_CONFIG_OFF) { ++ case 0 : ++ data->vout_scale[page] = 625; ++ break; ++ case 1: ++ data->vout_scale[page] = 500; ++ break; ++ default: ++ data->vout_scale[page] = 500; ++ break; ++ } ++ return 0; ++ } ++ ++ /* Linear mode. */ ++ vout_mode &= GENMASK(4, 0); ++ vout_mode = vout_mode >= 16 ? vout_mode - 32 : vout_mode; ++ if (vout_mode == -9) ++ data->vout_scale[page] = 200; ++ else if (vout_mode < 0) ++ data->vout_shift[page] = -vout_mode; ++ else ++ data->vout_shift[page] = vout_mode; ++ ++ return 0; ++} ++ ++static int mp2855_rails_vout_scale_get(struct i2c_client *client, struct mp2855_data *data) ++{ ++ int vout_mode, ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ vout_mode = i2c_smbus_read_word_data(client, PMBUS_VOUT_MODE); ++ if (vout_mode < 0) ++ return vout_mode; ++ ++ /* Get iout_scale for rail 1. */ ++ ret = mp2855_vout_scale_get(client, data, vout_mode, 0); ++ /* Get iout_scale for rail 2. */ ++ return ret < 0 ? ret : mp2855_vout_scale_get(client, data, vout_mode, 1); ++} ++ ++static int ++mp2855_iout_scale_get(struct i2c_client *client, struct mp2855_data *data, u32 reg, int page) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, reg); ++ if (ret < 0) ++ return ret; ++ ++ data->iout_scale[page] = (ret & MP2855_IOUT_SCALE_MASK) == MP2855_IOUT_SCALE_MASK ? ++ 500 : 250; ++ ++ return 0; ++} ++ ++static int mp2855_rails_iout_scale_get(struct i2c_client *client, struct mp2855_data *data) ++{ ++ int ret; ++ ++ /* Get iout_scale for rail 1. */ ++ ret = mp2855_iout_scale_get(client, data, PMBUS_READ_IOUT, 0); ++ /* Get iout_scale for rail 2. */ ++ return ret < 0 ? ret : mp2855_iout_scale_get(client, data, PMBUS_READ_IOUT, 1); ++} ++ ++static int ++mp2855_vout_thresholds_get(struct i2c_client *client, struct mp2855_data *data, int page) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_OV_FAULT_LIMIT); ++ if (ret < 0) ++ return ret; ++ ++ switch ((ret & MP2891_OV_TH_MASK) >> MP2891_OV_TH_POS) { ++ case 0: ++ data->ov_off[page] = 200; ++ break; ++ case 1: ++ data->ov_off[page] = 325; ++ break; ++ case 2: ++ data->ov_off[page] = 450; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_UV_FAULT_LIMIT); ++ if (ret < 0) ++ return ret; ++ ++ switch ((ret & MP2891_UV_TH_MASK) >> MP2891_UV_TH_POS) { ++ case 0: ++ data->uv_off[page] = 425; ++ break; ++ case 1: ++ data->uv_off[page] = 375; ++ break; ++ case 2: ++ data->uv_off[page] = 325; ++ break; ++ case 3: ++ data->uv_off[page] = 275; ++ break; ++ case 4: ++ data->uv_off[page] = 225; ++ break; ++ case 5: ++ data->uv_off[page] = 175; ++ break; ++ case 6: ++ data->uv_off[page] = 125; ++ break; ++ case 7: ++ data->uv_off[page] = 75; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int mp2855_rails_vout_thresholds_get(struct i2c_client *client, struct mp2855_data *data) ++{ ++ int ret; ++ ++ /* Get vout thresholds for rail 1. */ ++ ret = mp2855_vout_thresholds_get(client, data, 0); ++ /* Get vout thresholds for rail 2. */ ++ return ret < 0 ? ret : mp2855_vout_thresholds_get(client, data, 1); ++} ++ ++static int mp2855_probe(struct i2c_client *client) ++{ ++ struct pmbus_driver_info *info; ++ struct mp2855_data *data; ++ int ret; ++ ++ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mp2855_info, sizeof(*info)); ++ info = &data->info; ++ ++ ret = mp2855_rails_iout_scale_get(client, data); ++ if (ret < 0) ++ return ret; ++ ret = mp2855_rails_vout_scale_get(client, data); ++ if (ret < 0) ++ return ret; ++ ++ mp2855_rails_vout_thresholds_get(client, data); ++ if (ret < 0) ++ return ret; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id mp2855_id[] = { ++ {"mp2855", 0}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mp2855_id); ++ ++static const struct of_device_id mp2855_of_match[] = { ++ {.compatible = "mps,mp2855"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mp2855_of_match); ++ ++static struct i2c_driver mp2855_driver = { ++ .driver = { ++ .name = "mp2855", ++ .of_match_table = mp2855_of_match, ++ }, ++ .probe = mp2855_probe, ++ .id_table = mp2855_id, ++}; ++ ++module_i2c_driver(mp2855_driver); ++ ++MODULE_DESCRIPTION("PMBus driver for MPS MP2855 device"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS(PMBUS); +-- +2.8.4 + diff --git a/patches-sonic/0044-platform_data-mlxreg-Add-non-sticky-bit-indication-f.patch b/patches-sonic/0044-platform_data-mlxreg-Add-non-sticky-bit-indication-f.patch new file mode 100644 index 000000000..8d1bd86df --- /dev/null +++ b/patches-sonic/0044-platform_data-mlxreg-Add-non-sticky-bit-indication-f.patch @@ -0,0 +1,42 @@ +From da53757b0d6a86d772fd6fe6768c9213d0bb2aa4 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Fri, 6 Sep 2024 13:59:03 +0300 +Subject: [PATCH 44/78] platform_data/mlxreg: Add non-sticky bit indication + field + +Add non-sticky filed indicating that signal bit is changed to its +previous value after acknowledging. + +Detection of non-sticky signals is to be performed through the reading +event register, while sticky signals are detected through the reading +status register. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Felix Radensky +--- + include/linux/platform_data/mlxreg.h | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h +index 84c94b3..fca2d7e43 100644 +--- a/include/linux/platform_data/mlxreg.h ++++ b/include/linux/platform_data/mlxreg.h +@@ -184,6 +184,7 @@ struct mlxreg_core_data { + * @inversed: if 0: 0 for signal status is OK, if 1 - 1 is OK; + * @health: true if device has health indication, false in other case; + * @storming_bits: interrupt storming bits mask; ++ * @non_sticky: 1 - indicates that signal bit is changed to the previous value after acknowledging; + */ + struct mlxreg_core_item { + struct mlxreg_core_data *data; +@@ -200,6 +201,7 @@ struct mlxreg_core_item { + u8 inversed; + u8 health; + u32 storming_bits; ++ u8 non_sticky; + }; + + /** +-- +2.8.4 + diff --git a/patches-sonic/0045-platform-mellanox-mlxreg-hotplug-Add-handling-of-non.patch b/patches-sonic/0045-platform-mellanox-mlxreg-hotplug-Add-handling-of-non.patch new file mode 100644 index 000000000..8f997db8e --- /dev/null +++ b/patches-sonic/0045-platform-mellanox-mlxreg-hotplug-Add-handling-of-non.patch @@ -0,0 +1,63 @@ +From b33a8c29d95f0b998f326de0c651e0262cd603cd Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Fri, 6 Sep 2024 14:10:30 +0300 +Subject: [PATCH 45/78] platform/mellanox: mlxreg-hotplug: Add handling of + non-sticky signals + +Add support for non-sticky signals. For such signal status bit is +non-sticky and changes to the previous value after acknowledging. + +Detection of non-sticky signals is to be performed through the reading +event register, while sticky signals are detected through the reading +status register. + +Signed-off-by: Vadim Pasternak +Reviewed-by: Felix Radensky +--- + drivers/platform/mellanox/mlxreg-hotplug.c | 29 +++++++++++++++++++++-------- + 1 file changed, 21 insertions(+), 8 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c +index 2b52610..a395969e 100644 +--- a/drivers/platform/mellanox/mlxreg-hotplug.c ++++ b/drivers/platform/mellanox/mlxreg-hotplug.c +@@ -379,15 +379,28 @@ mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, + if (ret) + goto out; + +- /* Read status. */ +- ret = regmap_read(priv->regmap, item->reg, ®val); +- if (ret) +- goto out; ++ if (item->non_sticky) { ++ /* Read event. */ ++ ret = regmap_read(priv->regmap, item->reg + ++ MLXREG_HOTPLUG_EVENT_OFF, ®val); ++ if (ret) ++ goto out; ++ ++ /* Set asserted bits. */ ++ regval &= item->mask; ++ asserted = regval; ++ } else { ++ /* Read status. */ ++ ret = regmap_read(priv->regmap, item->reg, ®val); ++ if (ret) ++ goto out; ++ ++ /* Set asserted bits and save last status. */ ++ regval &= item->mask; ++ asserted = item->cache ^ regval; ++ item->cache = regval; ++ } + +- /* Set asserted bits and save last status. */ +- regval &= item->mask; +- asserted = item->cache ^ regval; +- item->cache = regval; + for_each_set_bit(bit, &asserted, 8) { + int pos; + +-- +2.8.4 + diff --git a/patches-sonic/0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch b/patches-sonic/0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch new file mode 100644 index 000000000..aa18500ea --- /dev/null +++ b/patches-sonic/0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch @@ -0,0 +1,11025 @@ +From 79d11ae9b4611ac726289d9a27f4ac065c6a0efc Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 4 Jul 2024 23:50:27 +0300 +Subject: [PATCH] platform/mellanox: nvsw-bmc: Add system control and + monitoring driver for Nvidia BMC + +The driver allows system control and monitoring of Nvidia switches +through CPLD/FPGA devices from Based Management Controller SoC. + +This control includes: +- Handling of hotplug events. +- Various resets handling through sysfs attributes. +- Reset cause reports through sysfs attributes. +- Mux attributes for hw components selection. +- General purpose registers attributes. +- LED control. +- FAN control. + +Upstream-Status: Pending + +Signed-off-by: Vadim Pasternak +--- + .../devicetree/bindings/trivial-devices.yaml | 5 + + drivers/platform/mellanox/Kconfig | 38 + + drivers/platform/mellanox/Makefile | 4 + + drivers/platform/mellanox/nvsw-bmc-hid162.c | 7304 +++++++++++++++++ + drivers/platform/mellanox/nvsw-core.c | 717 ++ + drivers/platform/mellanox/nvsw-host-l1.c | 735 ++ + drivers/platform/mellanox/nvsw-host-spc5.c | 943 +++ + drivers/platform/mellanox/nvsw-host-spc6.c | 852 ++ + drivers/platform/mellanox/nvsw.h | 318 + + 9 files changed, 10916 insertions(+) + create mode 100644 drivers/platform/mellanox/nvsw-bmc-hid162.c + create mode 100644 drivers/platform/mellanox/nvsw-core.c + create mode 100644 drivers/platform/mellanox/nvsw-host-l1.c + create mode 100644 drivers/platform/mellanox/nvsw-host-spc5.c + create mode 100644 drivers/platform/mellanox/nvsw-host-spc6.c + create mode 100644 drivers/platform/mellanox/nvsw.h + +diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml +index 9bf0fb17a..b6c6576f7 100644 +--- a/Documentation/devicetree/bindings/trivial-devices.yaml ++++ b/Documentation/devicetree/bindings/trivial-devices.yaml +@@ -305,6 +305,11 @@ properties: + - national,lm85 + # I2C ±0.33°C Accurate, 12-Bit + Sign Temperature Sensor and Thermal Window Comparator + - national,lm92 ++ # Nvidia BMC platform CPLD control ++ - nvidia,hid162 ++ - nvidia,hid176 ++ - nvidia,hid177 ++ - nvidia,hid180 + # Nuvoton Temperature Sensor + - nuvoton,w83773g + # OKI ML86V7667 video decoder +diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig +index f7dfa0e78..53e5a04aa 100644 +--- a/drivers/platform/mellanox/Kconfig ++++ b/drivers/platform/mellanox/Kconfig +@@ -79,6 +79,44 @@ config MLXBF_PMC + to performance monitoring counters within various blocks in the + Mellanox BlueField SoC via a sysfs interface. + ++config NVSW_BMC_HID162 ++ tristate "Nvidia BMC CPLD/FPGA Hardware Control and Monitoring" ++ depends on REGMAP_I2C && OF && REGMAP && HWMON ++ help ++ Say Y here to include support for the FPGA/CPLD logic by Baseboard ++ Management Controller equipped on Nvidia switches. ++ This driver can also be built as a module. If so the module ++ will be called nvsw-bmc-hid162. ++ ++config NVSW_HOST_L1 ++ tristate "Nvidia L1 host CPLD/FPGA Hardware Control and Monitoring" ++ depends on REGMAP_I2C && OF && REGMAP && HWMON ++ help ++ Say Y here to include support for the FPGA/CPLD logic by host CPU ++ equipped on Nvidia L1 switches. ++ This driver can also be built as a module. If so the module ++ will be called nvsw-host-l1. ++ ++config NVSW_HOST_SPC5 ++ tristate "Nvidia SPC5 host CPLD/FPGA Hardware Control and Monitoring" ++ depends on HWMON ++ select REGMAP_I2C ++ help ++ Say Y here to include support for the FPGA/CPLD logic by host CPU ++ equipped on Nvidia SPC5 ethernet switches. ++ This driver can also be built as a module. If so the module ++ will be called nvsw-host-spc5. ++ ++config NVSW_HOST_SPC6 ++ tristate "Nvidia SPC6 host CPLD/FPGA Hardware Control and Monitoring" ++ depends on HWMON ++ select REGMAP_I2C ++ help ++ Say Y here to include support for the FPGA/CPLD logic by host CPU ++ equipped on Nvidia SPC6 ethernet switches. ++ This driver can also be built as a module. If so the module ++ will be called nvsw-host-spc6. ++ + config NVSW_SN2201 + tristate "Nvidia SN2201 platform driver support" + depends on HWMON && I2C +diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile +index 04703c041..5ad42435e 100644 +--- a/drivers/platform/mellanox/Makefile ++++ b/drivers/platform/mellanox/Makefile +@@ -9,4 +9,8 @@ obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o + obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o + obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o + obj-$(CONFIG_MLXREG_LC) += mlxreg-lc.o ++obj-$(CONFIG_NVSW_BMC_HID162) += nvsw-bmc-hid162.o nvsw-core.o ++obj-$(CONFIG_NVSW_HOST_L1) += nvsw-host-l1.o nvsw-core.o ++obj-$(CONFIG_NVSW_HOST_SPC5) += nvsw-host-spc5.o nvsw-core.o ++obj-$(CONFIG_NVSW_HOST_SPC6) += nvsw-host-spc6.o nvsw-core.o + obj-$(CONFIG_NVSW_SN2201) += nvsw-sn2201.o +diff --git a/drivers/platform/mellanox/nvsw-bmc-hid162.c b/drivers/platform/mellanox/nvsw-bmc-hid162.c +new file mode 100644 +index 000000000..9f4c94f5d +--- /dev/null ++++ b/drivers/platform/mellanox/nvsw-bmc-hid162.c +@@ -0,0 +1,7304 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Nvidia BMC platform driver ++ * ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nvsw.h" ++ ++#define NVSW_HID162_TACHO_SAMPLES 20 ++#define NVSW_HID162_TACHO_DIV 1981 ++ ++/* Configuration for the register map of a device with 2 bytes address space. */ ++static const struct reg_default nvsw_bmc_hid162_reg_def[] = { ++ { NVSW_REG_PWM_CONTROL_OFFSET, 0x00 }, ++}; ++ ++/* Channels vectors. ++ * They contain only the channels, which physically connected to the devices, ++ * empty channels are skipped. ++ */ ++static int nvsw_bmc_hid162_chan1[] = { ++ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x11, 0x14, 0x18, 0x20, 0x21, 0x30, 0x31, ++ 0x32, 0x33, 0x34, 0x35, 0x24, 0x0a, ++}; ++ ++static int nvsw_bmc_hid176_chan1[] = { ++ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x11, 0x14, 0x18, 0x20, 0x21, 0x30, 0x31, ++ 0x32, 0x33, 0x34, 0x35, 0x24, 0x0a, 0x22, ++}; ++ ++static int nvsw_bmc_hid180_chan1[] = { ++ 0x01, 0x11, 0x21, 0x31, 0x09, 0x02, 0x03, 0x04, 0x14, 0x24, 0x34, 0x05, 0x06, 0x40, 0x41, ++ 0x42, 0x43, 0x0a, 0x0b, 0x1b, ++}; ++ ++static int nvsw_bmc_hid180_chan2[] = { ++ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, ++}; ++ ++static int nvsw_bmc_hid181_chan1[] = { ++ 0x01, 0x11, 0x21, 0x31, 0x09, 0x02, 0x03, 0x04, 0x14, 0x24, 0x34, 0x05, 0x06, 0x40, 0x41, ++ 0x42, 0x43, 0x0a, 0x0b, 0x1b, ++}; ++ ++static int nvsw_bmc_hid181_chan2[] = { ++ 0x00, 0x01, 0x02, 0x03, 0x04, ++}; ++ ++static int nvsw_bmc_hid189_chan1[] = { ++ 0x01, 0x02, 0X0a, 0x07, 0x09, 0x03, 0x04, 0x08, 0x0c, 0x0d, 0x015, ++ // 0x03, 0x05, 0x0C, 0x08, 0x02, 0x07, 0x0A, 0x0D, ++}; ++ ++static int nvsw_bmc_hid189_chan2[] = { ++ 0x10, 0x11, ++}; ++ ++static int nvsw_bmc_hid191_chan1[] = { ++ 0x03, 0x05, 0x0C, 0x08, 0x02, 0x07, 0x0A, 0x0D, ++}; ++ ++static int nvsw_bmc_hid191_chan2[] = { ++ 0x10, 0x11, ++}; ++ ++static int nvsw_bmc_hid193_chan1[] = { ++ 0x01, 0x02, 0X0a, 0x07, 0x09, 0x03, 0x04, 0x08, 0x0c, 0x0d, 0x015, ++}; ++ ++static int nvsw_bmc_hid193_chan2[] = { ++ 0x10, 0x11, ++}; ++ ++/* Mux configuration. */ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid162_mux_data[] = { ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid162_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid162_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid176_mux_data[] = { ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid176_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid176_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid180_mux_data[] = { ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid180_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid180_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid180_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid180_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid181_mux_data[] = { ++ { ++ .parent = 12, ++ .chan_ids = nvsw_bmc_hid181_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid181_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid181_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid181_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid189_mux_data[] = { ++ { ++ .parent = 3, ++ .chan_ids = nvsw_bmc_hid189_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid189_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 6, ++ .chan_ids = nvsw_bmc_hid189_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid189_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid191_mux_data[] = { ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid191_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid191_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 12, ++ .chan_ids = nvsw_bmc_hid191_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid191_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid193_mux_data[] = { ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid193_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid193_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 12, ++ .chan_ids = nvsw_bmc_hid193_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid193_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data *mux_data[NVSW_MUX_MAX]; ++static struct i2c_board_info *mux_brdinfo[NVSW_MUX_MAX]; ++ ++/* Mux board info. */ ++static struct i2c_board_info nvsw_bmc_hid162_mux_brdinfo = { ++ I2C_BOARD_INFO("i2c-mux-mlxcpld", 0x32), ++}; ++ ++/* Platform hotplug data */ ++static struct mlxreg_core_data nvsw_bmc_hid162_events_items_data[] = { ++ { ++ .label = "power_button", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "amb_temp_sense", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_AMB_TEMP_SENSE_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "graceful_power_off_req", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_GRACEFUL_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_POWER_OFF_READY_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_RESET_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "apml_smb_alert", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_APML_SMB_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_unexp_power_off", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "uid_push_button", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_UID_PUSH_BUTTON_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_events_items_data[] = { ++ { ++ .label = "holder6_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder7_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_AMB_TEMP_SENSE_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "graceful_power_off_req", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_GRACEFUL_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_POWER_OFF_READY_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_RESET_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "apml_smb_alert", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_APML_SMB_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_unexp_power_off", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_events_items_data[] = { ++ { ++ .label = "graceful_power_off_req", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_GRACEFUL_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_POWER_OFF_READY_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_RESET_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "apml_smb_alert", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_APML_SMB_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_unexp_power_off", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_asic1_items_data[] = { ++ { ++ .label = "asic1_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_asic2_items_data[] = { ++ { ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid177_asic3_items_data[] = { ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC3_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_cartridge_items_data[] = { ++ { ++ .label = "cartridge1", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cartridge2", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cartridge3", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cartridge4", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_leakage_items_data[] = { ++ { ++ .label = "leakage1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage3", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage4", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage5", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage6", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_pwr_good_items_data[] = { ++ { ++ .label = "rtc", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = NVSW_RTC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "hot_swap_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = NVSW_HOT_SWAP_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_alarms_items_data[] = { ++ { ++ .label = "ssd_i2c_alert", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "wd_exp", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_WD_EXP_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "51v_usb", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_5V_USB_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pcb_temp_sense_1", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_PCB_TEMP1_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pcb_temp_sense_2", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_PCB_TEMP2_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "sgmii_phy", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SGMII_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ssd_pw_good", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SDD_PG_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_aggr", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_LEAK_AGGR_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_erot_ap_items_data[] = { ++ { ++ .label = "erot_asic1_ap", ++ .reg = NVSW_REG_EROT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "erot_asic2_ap", ++ .reg = NVSW_REG_EROT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "erot_cpu_ap", ++ .reg = NVSW_REG_EROT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid162_erot_error_items_data[] = { ++ { ++ .label = "erot_asic1_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "erot_asic2_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "erot_cpu_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid176_erot_error_items_data[] = { ++ { ++ .label = "erot_cpu_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "erot_asic1_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "erot_asic2_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "erot_asic3_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid162_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid162_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK | NVSW_UID_PUSH_BUTTON_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_asic1_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_asic1_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_asic2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_asic2_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_cartridge_items_data, ++ .aggr_mask = GENMASK(3, 0), ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = NVSW_REG_FRU1_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_cartridge_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = NVSW_LEAK_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_pwr_good_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = NVSW_RTC_MASK | NVSW_HOT_SWAP_ALERT_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_pwr_good_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK | NVSW_WD_EXP_MASK | ++ NVSW_5V_USB_MASK | NVSW_PCB_TEMP1_MASK | ++ NVSW_PCB_TEMP2_MASK | NVSW_SGMII_MASK | ++ NVSW_SDD_PG_MASK | NVSW_LEAK_AGGR_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_erot_ap_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_EROT_OFFSET, ++ .mask = NVSW_EROT_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_erot_ap_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_erot_error_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = NVSW_EROT_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_erot_error_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid162_hotplug = { ++ .items = nvsw_bmc_hid162_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = NVSW_LOW_AGGR_MASK_LOW | NVSW_LOW_AGGR_MASK_ASIC2 | ++ NVSW_LOW_AGGR_MASK_ASIC1, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid176_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid162_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK | NVSW_UID_PUSH_BUTTON_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_asic1_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_asic1_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_asic2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_asic2_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = NVSW_LEAK_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_pwr_good_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = NVSW_RTC_MASK | NVSW_HOT_SWAP_ALERT_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_pwr_good_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK | NVSW_WD_EXP_MASK | ++ NVSW_5V_USB_MASK | NVSW_PCB_TEMP1_MASK | ++ NVSW_PCB_TEMP2_MASK | NVSW_SGMII_MASK | ++ NVSW_SDD_PG_MASK | NVSW_LEAK_AGGR_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid176_erot_error_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = NVSW_EROT_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid176_erot_error_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid176_hotplug = { ++ .items = nvsw_bmc_hid176_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid176_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = NVSW_LOW_AGGR_MASK_LOW | NVSW_LOW_AGGR_MASK_ASIC2 | ++ NVSW_LOW_AGGR_MASK_ASIC1, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid177_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid162_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK | NVSW_UID_PUSH_BUTTON_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_asic1_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_asic1_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_asic2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_asic2_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid177_asic3_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC3_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid177_asic3_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = NVSW_LEAK_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_pwr_good_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = NVSW_RTC_MASK | NVSW_HOT_SWAP_ALERT_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_pwr_good_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK | NVSW_WD_EXP_MASK | ++ NVSW_5V_USB_MASK | NVSW_PCB_TEMP1_MASK | ++ NVSW_PCB_TEMP2_MASK | NVSW_SGMII_MASK | ++ NVSW_SDD_PG_MASK | NVSW_LEAK_AGGR_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid176_erot_error_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = NVSW_EROT_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid176_erot_error_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid177_hotplug = { ++ .items = nvsw_bmc_hid177_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid177_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = NVSW_LOW_AGGR_MASK_LOW | NVSW_LOW_AGGR_MASK_ASIC3 | ++ NVSW_LOW_AGGR_MASK_ASIC2 | NVSW_LOW_AGGR_MASK_ASIC1, ++}; ++ ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_pg1_events_items_data[] = { ++ { ++ .label = "smbus_alt_pwrconv_1", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder6_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_pl0_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_pl1_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hvdd_avcc_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "1v8_vddio_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder7_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_pg2_events_items_data[] = { ++ { ++ .label = "vddcr_soc_s5_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_1v8_s5_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_1v8_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ddr_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "12v_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_3v3_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "3v3_s5_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vddcr_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_pg3_events_items_data[] = { ++ { ++ .label = "smbus_alt_hotswap", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "3v3_pb_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_dvdd_pl0_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_dvdd_pl1_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_hvdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "1v8_cpld_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder1_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_pg4_events_items_data[] = { ++ { ++ .label = "mbus_alt_pwrconv_2", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder2_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_vdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_dvdd_pl0_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_dvdd_pl1_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_hvdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder3_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdrv_asic_3_4_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_pg5_events_items_data[] = { ++ { ++ .label = "holder4_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder5_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_dvdd_pl0_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_dvdd_pl1_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_hvdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdrv_6_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_asic_items_data[] = { ++ { ++ .label = "asic1_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC2_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC3_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC4_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_asic_temp_items_data[] = { ++ { ++ .label = "asic1_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_leakage_items_data[] = { ++ { ++ .label = "leakage1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_alarms_items_data[] = { ++ { ++ .label = "ssd_i2c_alert", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "wd_exp", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_WD_EXP_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "51v_usb", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_5V_USB_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ssd_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_aggr", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_alarms2_items_data[] = { ++ { ++ .label = "psys_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_mem", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "svi2_vr_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "thermtrip_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_cpu_items_data[] = { ++ { ++ .label = "cpu_rst", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bios_started", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bios_ended", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_vr1_pwr_alert_items_data[] = { ++ { ++ .label = "asic1_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_avdd_dvdd_pl0_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_avdd_dvdd_pl1_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hvdd_avcc_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_avdd_dvdd_pl0_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_avdd_dvdd_pl1_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_hvdd_avcc_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_vr2_pwr_alert_items_data[] = { ++ { ++ .label = "asic3_vdd_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_avdd_dvdd_pl0_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_avdd_dvdd_pl1_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_hvdd_avcc_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_avdd_dvdd_pl0_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_avdd_dvdd_pl1_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_hvdd_avcc_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_erot_error_items_data[] = { ++ { ++ .label = "erot_cpu_error", ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid180_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid180_pg1_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_pg1_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid180_pg2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_pg2_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid180_pg3_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_pg3_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid180_pg4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = NVSW_AGGR_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_pg4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid180_pg5_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_pg5_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid180_asic_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASICS_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid180_asic_temp_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = NVSW_ASICS_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_asic_temp_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid180_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid180_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid180_alarms2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = GENMASK(3, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_alarms2_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid180_cpu_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = GENMASK(4, 2), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_cpu_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid180_vr1_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_vr1_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid180_vr2_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_vr2_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid180_erot_error_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .mask = BIT(0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_erot_error_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid162_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK | NVSW_UID_PUSH_BUTTON_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid162_cartridge_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = NVSW_REG_FRU1_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid162_cartridge_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++}; ++ ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid180_hotplug = { ++ .items = nvsw_bmc_hid180_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid180_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = GENMASK(6, 0), ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_events_items_data[] = { ++ { ++ .label = "holder6_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder7_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_AMB_TEMP_SENSE_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "graceful_power_off_req", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_GRACEFUL_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_POWER_OFF_READY_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_RESET_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "apml_smb_alert", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_APML_SMB_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_unexp_power_off", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic1_pg_events_items_data[] = { ++ { ++ .label = "asic1_vddscc_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_oe_pwr", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_clk_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vcore_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hvdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_1v8_vddio_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdrv", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic2_pg_events_items_data[] = { ++ { ++ .label = "asic2_vddscc_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "aisc2_oe_pwr", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_clk_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vcore_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_dvdd_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_hvdd_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_1v8_vddio_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdrv", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic3_pg_events_items_data[] = { ++ { ++ .label = "asic3_vddscc_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "aisc3_oe_pwr", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_clk_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_vcore_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_dvdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_hvdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_1v8_vddio_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_vdrv", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic4_pg_events_items_data[] = { ++ { ++ .label = "asic4_vddscc_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "aisc4_oe_pwr", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_clk_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vcore_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_dvdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_hvdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_1v8_vddio_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdrv", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_brd2_events_items_data[] = { ++ { ++ .label = "global_wp_disable", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pch_hot", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder8_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "dimm_ab", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_brd4_events_items_data[] = { ++ { ++ .label = "3v3s_fail", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vccio_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vccst_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ddr_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "12v_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "3v3_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "1v05_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vcc_core_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic_items_data[] = { ++ { ++ .label = "asic1_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC2_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC3_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC4_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic_temp_items_data[] = { ++ { ++ .label = "asic1_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_leakage_items_data[] = { ++ { ++ .label = "leakage_csm1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm3", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm4", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_mgmt", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_alarms_items_data[] = { ++ { ++ .label = "ssd_i2c_alert", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "wd_exp", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_WD_EXP_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "5v_usb", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_5V_USB_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder10_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder11_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "sgmii_phy", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ssd_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_aggr", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_alarms2_items_data[] = { ++ { ++ .label = "comex_tps_upgrade_done", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder12_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder13_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder14_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder15_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vr13_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "therm_trip", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "soc_caterr", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_vr1_pwr_alert_items_data[] = { ++ { ++ .label = "asic1_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_vr2_pwr_alert_items_data[] = { ++ { ++ .label = "asic3_vdd_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid181_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid181_asic1_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic1_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic2_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic2_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic3_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic3_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic4_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic4_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_brd2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = GENMASK(3, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_brd2_events_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_brd4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_brd4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASICS_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic_temp_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = NVSW_ASICS_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic_temp_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_alarms2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_alarms2_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_vr1_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_vr1_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_vr2_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_vr2_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++}; ++ ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid181_hotplug = { ++ .items = nvsw_bmc_hid181_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = GENMASK(6, 0), ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg1_events_items_data[] = { ++ { ++ .label = "asic1_hsc0_smb_alert", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_oes_pwr", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_gp_vdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_pl_dvdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_pl_avdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vddhbid_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg2_events_items_data[] = { ++ { ++ .label = "vdd_misc_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_3v3_bmc_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_1v8_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ddr_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "12v_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_3v3_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_alw_pwrgd", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vddcr_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_brd2_events_items_data[] = { ++ { ++ .label = "bmc_tpm_pirq", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "smb_alert", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset_deassert", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bios_started", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bios_boot_completed", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg3_events_items_data[] = { ++ { ++ .label = "asic2_hsc0_smb_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_oes_pwr", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_gp_vdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_pl_dvdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_pl_avdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vddhbid_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg4_events_items_data[] = { ++ { ++ .label = "asic3_hsc0_smb_alert", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_oes_pwr", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_gp_vdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_pl_dvdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_pl_avdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_vddhbid_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_asic4_events_items_data[] = { ++ { ++ .label = "asic4_hsc0_smb_alert", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_oes_pwr", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_gp_vdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_pl_dvdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_pl_avdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vddhbid_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_asic_items_data[] = { ++ { ++ .label = "asic1_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC2_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC3_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC4_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_asic_temp_items_data[] = { ++ { ++ .label = "asic1_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_vr1_pwr_alert_items_data[] = { ++ { ++ .label = "asic1_vdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_avdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_dvdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_avdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_vr2_pwr_alert_items_data[] = { ++ { ++ .label = "asic3_vdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_dvdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_avdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_dvdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_avdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_leakage_items_data[] = { ++ { ++ .label = "leakage_mgmt", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm3", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm4", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_dc_ok_events_items_data[] = { ++ { ++ .label = "pdb0_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb2_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb3_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb0_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb2_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb3_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_alarms_items_data[] = { ++ { ++ .label = "ssd_i2c_alert", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "wd_exp", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_WD_EXP_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "5v_usb", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_5V_USB_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder9_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder11_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bmc_phy_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ssd_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_aggr", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_alarms2_items_data[] = { ++ { ++ .label = "psys_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_mem", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "svi3_vr_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "thermtrip_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vddq_vrhot", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid191_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid191_pg1_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg1_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg2_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_asic4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_asic4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_asic_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_asic_temp_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_asic_temp_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_vr1_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_vr1_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_vr2_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_vr2_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_dc_ok_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_dc_ok_events_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_brd2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_brd2_events_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg3_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg3_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms2_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++}; ++ ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid191_hotplug = { ++ .items = nvsw_bmc_hid191_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = GENMASK(5, 0), ++ .deferred_nr = 5, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_pg1_events_items_data[] = { ++ { ++ .label = "gp_vdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_vdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_pl_dvdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_pl_avdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_pl_hvdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_vddhbid_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "1v8_vddio_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_1v8_vddqps_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_pg3_events_items_data[] = { ++ { ++ .label = "hsc0_smb_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "hsc1_smb_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb_fsd", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_pg4_events_items_data[] = { ++ { ++ .label = "gp_3v3_osc_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_vdrv_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_asic_items_data[] = { ++ { ++ .label = "asic1_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_asic_temp_items_data[] = { ++ { ++ .label = "asic1_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_vr1_pwr_alert_items_data[] = { ++ { ++ .label = "asic1_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdd_t0_7_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_t0_7_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_avdd_t0_7_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hvdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdddhbid_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_leakage_items_data[] = { ++ { ++ .label = "leakage1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_dc_ok_events_items_data[] = { ++ { ++ .label = "pdb0_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder16_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder17_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb0_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++ ++static struct mlxreg_core_item nvsw_bmc_hid193_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid193_pg1_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_pg1_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg2_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_pg4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_pg4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_asic_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_asic_temp_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_asic_temp_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_dc_ok_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(5, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_dc_ok_events_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid193_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_brd2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_brd2_events_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_pg3_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(2, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_pg3_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms2_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid193_vr1_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = GENMASK(5, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_vr1_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++}; ++ ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid193_hotplug = { ++ .items = nvsw_bmc_hid193_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = GENMASK(6, 0), ++ .deferred_nr = 5, ++}; ++ ++/* Platform register access data. */ ++static struct mlxreg_core_data nvsw_bmc_hid162_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "fan_dir", ++ .reg = NVSW_REG_GP0_RO_OFFSET, ++ .mask = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "fan_present", ++ .reg = NVSW_REG_FAN_OFFSET, ++ .mask = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "leakage_conn_en", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_shutdown_unlock", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ { ++ .label = "main_brd_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "nic_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_asic3_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_cpu_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_asic1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_asic2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_asic3_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_cpu_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_asic1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_asic2_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_button_halt", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_button_or_leak_con", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_carrier", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_reload", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_erot", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_erot", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_power", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = NVSW_BIOS_STATUS_MASK, ++ .bit = 2, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "ufm_version", ++ .reg = NVSW_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "clk_brd_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "geo_addr", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid162_regio = { ++ .data = nvsw_bmc_hid162_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid162_regio_data), ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_shutdown_unlock", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_button_halt", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_cpu_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_cpu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_button", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_erot", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "leak_con", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_cpu_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "transport_status", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "tpm_present", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "asics_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_cpu_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu2_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_tps_upgrade", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_spi_ctrl", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(4, 2), ++ .bit = 4, ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "leakage_conn_en", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version", ++ .reg = NVSW_REG_CPLD5_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version", ++ .reg = NVSW_REG_CPLD6_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version", ++ .reg = NVSW_REG_CPLD8_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version", ++ .reg = NVSW_REG_CPLD9_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version", ++ .reg = NVSW_REG_CPLD10_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld5_pn", ++ .reg = NVSW_REG_CPLD5_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld6_pn", ++ .reg = NVSW_REG_CPLD6_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld7_pn", ++ .reg = NVSW_REG_CPLD7_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld8_pn", ++ .reg = NVSW_REG_CPLD8_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld9_pn", ++ .reg = NVSW_REG_CPLD9_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld10_pn", ++ .reg = NVSW_REG_CPLD10_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_dbg_led", ++ .reg = NVSW_REG_CPLD1_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld2_dbg_led", ++ .reg = NVSW_REG_CPLD2_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld3_dbg_led", ++ .reg = NVSW_REG_CPLD3_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld4_dbg_led", ++ .reg = NVSW_REG_CPLD4_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld7_version", ++ .reg = NVSW_REG_CPLD7_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version_min", ++ .reg = NVSW_REG_CPLD5_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version_min", ++ .reg = NVSW_REG_CPLD6_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version_min", ++ .reg = NVSW_REG_CPLD7_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version_min", ++ .reg = NVSW_REG_CPLD8_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version_min", ++ .reg = NVSW_REG_CPLD9_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version_min", ++ .reg = NVSW_REG_CPLD10_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "ufm_version", ++ .reg = NVSW_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "global_wp_disable_req", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_monitor", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "smbus_vpd", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "serirq_mode", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "gpio_cpld_fu", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "b2b_vpd", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "comm_chnl_ready", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "smb2i2c", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "sys_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic4_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic3_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "main_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "ser_irq_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "i219_pe_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_comex", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_sw_cmd", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_main_brd", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_cpld", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_carrier_brd", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_power", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_48v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_plt_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "comex_tps", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(5, 4), ++ .bit = 5, ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic2_clk_brd_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_pg", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic1_clk_brd_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_pg", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic4_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic3_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "smb_erot", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "me_reboot", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic4_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "clk_brd_program", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic3_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic1_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_tps_upgrade", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_spi_ctrl", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(4, 2), ++ .bit = 4, ++ .mode = 0644, ++ }, ++ { ++ .label = "sml_connection", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "current_temp_ro", ++ .reg = NVSW_REG_CURRENT_TEMP_RO, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "i2c_freq", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(5, 4), ++ .bit = 5, ++ .mode = 0444, ++ }, ++ { ++ .label = "wd_type", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "avlbl_reg_new_format", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version", ++ .reg = NVSW_REG_CPLD5_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld5_pn", ++ .reg = NVSW_REG_CPLD5_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version_min", ++ .reg = NVSW_REG_CPLD5_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_dbg_led", ++ .reg = NVSW_REG_CPLD1_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld2_dbg_led", ++ .reg = NVSW_REG_CPLD2_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld3_dbg_led", ++ .reg = NVSW_REG_CPLD3_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld4_dbg_led", ++ .reg = NVSW_REG_CPLD4_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld6_version", ++ .reg = NVSW_REG_CPLD6_PN_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_pn", ++ .reg = NVSW_REG_CPLD6_PN1_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld7_version", ++ .reg = NVSW_REG_CPLD7_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_pn", ++ .reg = NVSW_REG_CPLD7_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld8_version", ++ .reg = NVSW_REG_CPLD8_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_pn", ++ .reg = NVSW_REG_CPLD8_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld9_version", ++ .reg = NVSW_REG_CPLD9_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_pn", ++ .reg = NVSW_REG_CPLD9B_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld10_version", ++ .reg = NVSW_REG_CPLD10_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_pn", ++ .reg = NVSW_REG_CPLD10_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld6_version_min", ++ .reg = NVSW_REG_CPLD6_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version_min", ++ .reg = NVSW_REG_CPLD7_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version_min", ++ .reg = NVSW_REG_CPLD8_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version_min", ++ .reg = NVSW_REG_CPLD9_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version_min", ++ .reg = NVSW_REG_CPLD10_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic1_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic1_clk_brd_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_pg", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic2_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic2_clk_brd_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_pg", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic3_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic4_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "sb_asic3_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic3_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic4_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic4_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status_ro", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_req_conf_flash_updt", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_shutdown_unlock", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "uart_baud_rate", ++ .reg = NVSW_REG_UART_BAUD_OFFSET, ++ .mask = NVSW_UART_BAUD_MASK, ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "sys_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic3_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic4_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "gp_cpld_rtc_clr", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy2_rst", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy1_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "main_12v_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_swb_mgmt_present", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_io_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_bmc_presnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_bmc_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(2, 0), ++ .bit = 2, ++ .mode = 0644, ++ }, ++ { ++ .label = "conf_flash_updt_done", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_req_conf_flash_updt", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_vdd_spd_dis", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_spd_b_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_spd_a_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "sb_clk_brd_prog_en", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_spi_ctrl", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(4, 2), ++ .bit = 4, ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "psu_qty", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(3, 0), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "i2c_freq", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(5, 4), ++ .bit = 5, ++ .mode = 0444, ++ }, ++ { ++ .label = "wd_type", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "avlbl_reg_new_format", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status_ro", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_req_conf_flash_updt", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_shutdown_unlock", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "uart_baud_rate", ++ .reg = NVSW_REG_UART_BAUD_OFFSET, ++ .mask = NVSW_UART_BAUD_MASK, ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_button_halt", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "gp_cpld_rtc_clr", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy2_rst", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy1_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_swb_mgmt_present", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_io_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_bmc_presnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_clk_brd_asic_ci_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_bmc_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_clk_brd_asic_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asics_pg", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(2, 0), ++ .bit = 2, ++ .mode = 0644, ++ }, ++ { ++ .label = "conf_flash_updt_done", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_req_conf_flash_updt", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_vdd_spd_dis", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_spd_b_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_spd_a_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "sb_clk_brd_prog_en", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_spi_ctrl", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(4, 2), ++ .bit = 4, ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_clk_brd_hw_id", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .mask = GENMASK(2, 0), ++ .bit = 2, ++ .mode = 0444, ++ }, ++ { ++ .label = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .mask = GENMASK(7, 3), ++ .bit = 7, ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid180_regio = { ++ .data = nvsw_bmc_hid180_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid180_regio_data), ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid181_regio = { ++ .data = nvsw_bmc_hid181_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid181_regio_data), ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid191_regio = { ++ .data = nvsw_bmc_hid191_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid191_regio_data), ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid193_regio = { ++ .data = nvsw_bmc_hid193_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid193_regio_data), ++}; ++ ++/* Platform fan data. */ ++static struct mlxreg_core_data nvsw_bmc_hid162_fan_data[] = { ++ { ++ .label = "pwm1", ++ .reg = NVSW_REG_PWM1_OFFSET, ++ }, ++ { ++ .label = "tacho1", ++ .reg = NVSW_REG_TACHO1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 1, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho2", ++ .reg = NVSW_REG_TACHO2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 2, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho3", ++ .reg = NVSW_REG_TACHO3_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 3, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho4", ++ .reg = NVSW_REG_TACHO4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 4, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho5", ++ .reg = NVSW_REG_TACHO5_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 5, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho6", ++ .reg = NVSW_REG_TACHO6_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 6, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho7", ++ .reg = NVSW_REG_TACHO7_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 7, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho8", ++ .reg = NVSW_REG_TACHO8_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 8, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho9", ++ .reg = NVSW_REG_TACHO9_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 9, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho10", ++ .reg = NVSW_REG_TACHO10_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 10, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho11", ++ .reg = NVSW_REG_TACHO11_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 11, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "tacho12", ++ .reg = NVSW_REG_TACHO12_OFFSET, ++ .mask = GENMASK(7, 0), ++ .capability = NVSW_REG_FAN_CAP1_OFFSET, ++ .slot = 12, ++ .reg_prsnt = NVSW_REG_FAN_OFFSET, ++ }, ++ { ++ .label = "conf", ++ .mask = NVSW_HID162_TACHO_SAMPLES, ++ .bit = NVSW_HID162_TACHO_DIV, ++ .capability = NVSW_REG_TACHO_SPEED_OFFSET, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid162_fan = { ++ .data = nvsw_bmc_hid162_fan_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid162_fan_data), ++ .capability = NVSW_REG_FAN_DRW_CAP_OFFSET, ++ .version = 1, ++}; ++ ++/* Platform led data for HI162 system type. */ ++static struct mlxreg_core_data nvsw_bmc_hid162_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = NVSW_REG_LED5_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan:green", ++ .reg = NVSW_REG_LED6_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan:amber", ++ .reg = NVSW_REG_LED6_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid162_led = { ++ .data = nvsw_bmc_hid162_led_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid162_led_data), ++}; ++ ++/* Platform led data for HI176 system type. */ ++static struct mlxreg_core_data nvsw_bmc_hid176_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = NVSW_REG_LED5_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "leakage:green", ++ .reg = NVSW_REG_LED7_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "leakage:amber", ++ .reg = NVSW_REG_LED7_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid176_led = { ++ .data = nvsw_bmc_hid176_led_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid176_led_data), ++}; ++ ++/* Platform led data for HI177 system type. */ ++static struct mlxreg_core_data nvsw_bmc_hid177_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = NVSW_REG_LED5_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid177_led = { ++ .data = nvsw_bmc_hid177_led_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid177_led_data), ++}; ++ ++/* Mux init/exit callbacks. */ ++static int nvsw_bmc_hid162_mux_topology_init(struct nvsw_core *nvsw_core) ++{ ++ int i, err; ++ ++ /* Create mux infrastructure. */ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i]->regmap = nvsw_core->regmap; ++ nvsw_core->mux[i] = platform_device_register_resndata(nvsw_core->dev, ++ "i2c-mux-regmap", i, NULL, 0, ++ mux_data[i], ++ sizeof(*mux_data[i])); ++ if (IS_ERR(nvsw_core->mux[i])) { ++ dev_err(nvsw_core->dev, "Failed to create mux infra\n"); ++ err = PTR_ERR(nvsw_core->mux[i]); ++ goto fail_platform_mux_register; ++ } ++ } ++ ++ return 0; ++fail_platform_mux_register: ++ while (--i >= 0) ++ platform_device_unregister(nvsw_core->mux[i]); ++ return err; ++} ++ ++static void nvsw_bmc_hid162_mux_topology_exit(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ if (nvsw_core->mux[i]) ++ platform_device_unregister(nvsw_core->mux[i]); ++ } ++} ++ ++/* Callback to set initial values for specific registers. */ ++static int nvsw_bmc_hid162_set_reg_default(struct regmap *regmap) ++{ ++ u32 regval; ++ int err; ++ ++ err = regmap_read(regmap, NVSW_REG_GP6_OFFSET, ®val); ++ if (err) ++ return err; ++ ++ return regmap_write(regmap, NVSW_REG_GP6_OFFSET, regval | NVSW_REG_RESET_MASK); ++} ++ ++static int nvsw_bmc_hid180_set_reg_default(struct regmap *regmap) ++{ ++ return regmap_write(regmap, NVSW_REG_AGGRCO_MASK_OFFSET, GENMASK(5, 0)); ++} ++ ++static int nvsw_bmc_hid191_set_reg_default(struct regmap *regmap) ++{ ++ return regmap_write(regmap, NVSW_REG_AGGRCO_MASK_OFFSET, GENMASK(6, 0)); ++} ++ ++static int nvsw_bmc_hid193_set_reg_default(struct regmap *regmap) ++{ ++ return regmap_write(regmap, NVSW_REG_AGGRCO_MASK_OFFSET, GENMASK(6, 0)); ++} ++ ++/* Callback is used to indicate that all adapter devices has been created. */ ++static int ++nvsw_bmc_hid162_completion_notify(void *handle, struct i2c_adapter *parent, ++ struct i2c_adapter *adapters[]) ++{ ++ /* struct nvsw_core *nvsw_core = handle; */ ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid162_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID162; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid162_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid162_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid162_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid162_led; ++ nvsw_core->fan_data = &nvsw_bmc_hid162_fan; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid162_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid162_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid176_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID176; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid176_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid162_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid176_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid176_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid162_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid177_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID177; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid176_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid162_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid177_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid162_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid180_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID180; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid180_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid180_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid180_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid180_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid180_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid181_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID181; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid181_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid181_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid181_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid181_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid162_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid189_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID189; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid189_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid189_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid193_regio; /* hid189 uses info from 193 */ ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid180_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid191_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID191; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid191_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid191_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid191_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid191_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid191_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid193_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID193; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid193_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid193_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid193_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid193_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_platform_data_init(struct nvsw_core *nvsw_core, enum nvsw_core_hid_type type) ++{ ++ switch (type) { ++ case HID162: ++ return nvsw_bmc_hid162_platform_data_init(nvsw_core); ++ case HID176: ++ return nvsw_bmc_hid176_platform_data_init(nvsw_core); ++ case HID177: ++ return nvsw_bmc_hid177_platform_data_init(nvsw_core); ++ case HID180: ++ return nvsw_bmc_hid180_platform_data_init(nvsw_core); ++ case HID181: ++ return nvsw_bmc_hid181_platform_data_init(nvsw_core); ++ case HID189: ++ return nvsw_bmc_hid189_platform_data_init(nvsw_core); ++ case HID191: ++ return nvsw_bmc_hid191_platform_data_init(nvsw_core); ++ case HID193: ++ return nvsw_bmc_hid193_platform_data_init(nvsw_core); ++ default: ++ return -ENODEV; ++ } ++} ++ ++static int nvsw_bmc_hid162_probe(struct i2c_client *client) ++{ ++ struct device_node *np = client->dev.of_node; ++ enum nvsw_core_hid_type type; ++ struct nvsw_core *nvsw_core; ++ int err; ++ ++ if (!np) ++ return -ENODEV; ++ ++ if (of_device_is_compatible(np, "nvidia,hid162")) ++ type = HID162; ++ else if (of_device_is_compatible(np, "nvidia,hid176")) ++ type = HID176; ++ else if (of_device_is_compatible(np, "nvidia,hid177")) ++ type = HID177; ++ else if (of_device_is_compatible(np, "nvidia,hid180")) ++ type = HID180; ++ else if (of_device_is_compatible(np, "nvidia,hid181")) ++ type = HID181; ++ else if (of_device_is_compatible(np, "nvidia,hid189")) ++ type = HID189; ++ else if (of_device_is_compatible(np, "nvidia,hid191")) ++ type = HID191; ++ else if (of_device_is_compatible(np, "nvidia,hid193")) ++ type = HID193; ++ else ++ return -ENODEV; ++ ++ nvsw_core = devm_kzalloc(&client->dev, sizeof(*nvsw_core), GFP_KERNEL); ++ if (!nvsw_core) ++ return -ENOMEM; ++ ++ nvsw_core->dev = &client->dev; ++ nvsw_core->client = client; ++ nvsw_core->np = np; ++ nvsw_core->regmap_type = REGMAP_I2C; ++ i2c_set_clientdata(client, nvsw_core); ++ err = nvsw_bmc_platform_data_init(nvsw_core, type); ++ if (err) ++ return err; ++ ++ return nvsw_core_init(nvsw_core); ++} ++ ++static void nvsw_bmc_hid162_remove(struct i2c_client *client) ++{ ++ struct nvsw_core *nvsw_core = i2c_get_clientdata(client); ++ ++ nvsw_core_exit(nvsw_core); ++} ++ ++static const struct i2c_device_id nvsw_bmc_hid162_id[] = { ++ { "hid162", HID162 }, ++ { "hid176", HID176 }, ++ { "hid177", HID177 }, ++ { "hid180", HID180 }, ++ { "hid181", HID181 }, ++ { "hid189", HID189 }, ++ { "hid191", HID191 }, ++ { "hid193", HID193 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(i2c, nvsw_bmc_hid162_id); ++ ++static const struct of_device_id nvsw_bmc_hid162_dt_match[] = { ++ { .compatible = "nvidia,hid162" }, ++ { .compatible = "nvidia,hid176" }, ++ { .compatible = "nvidia,hid177" }, ++ { .compatible = "nvidia,hid180" }, ++ { .compatible = "nvidia,hid181" }, ++ { .compatible = "nvidia,hid189" }, ++ { .compatible = "nvidia,hid191" }, ++ { .compatible = "nvidia,hid193" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, nvsw_bmc_hid162_dt_match); ++ ++static struct i2c_driver nvsw_bmc_hid162_driver = { ++ .driver = { ++ .name = "nvsw-bmc-hid162", ++ .of_match_table = of_match_ptr(nvsw_bmc_hid162_dt_match), ++ }, ++ .probe = nvsw_bmc_hid162_probe, ++ .remove = nvsw_bmc_hid162_remove, ++ .id_table = nvsw_bmc_hid162_id, ++}; ++ ++module_i2c_driver(nvsw_bmc_hid162_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Nvidia platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/platform/mellanox/nvsw-core.c b/drivers/platform/mellanox/nvsw-core.c +new file mode 100644 +index 000000000..1bd2dfc62 +--- /dev/null ++++ b/drivers/platform/mellanox/nvsw-core.c +@@ -0,0 +1,717 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Nvidia BMC platform driver ++ * ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "nvsw.h" ++ ++static bool nvsw_writeable_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case NVSW_REG_CPLD1_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD2_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD3_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD4_DBG_LED_OFFSET: ++ case NVSW_REG_PG1_EVENT_OFFSET: ++ case NVSW_REG_PG1_MASK_OFFSET: ++ case NVSW_REG_PG2_EVENT_OFFSET: ++ case NVSW_REG_PG2_MASK_OFFSET: ++ case NVSW_REG_PG3_EVENT_OFFSET: ++ case NVSW_REG_PG3_MASK_OFFSET: ++ case NVSW_REG_PG4_EVENT_OFFSET: ++ case NVSW_REG_PG4_MASK_OFFSET: ++ case NVSW_REG_RESET_GP1_OFFSET: ++ case NVSW_REG_FIELD_UPGRADE: ++ case NVSW_REG_GP0_OFFSET: ++ case NVSW_REG_GP1_OFFSET: ++ case NVSW_REG_GP7_OFFSET: ++ case NVSW_REG_UART_BAUD_OFFSET: ++ case NVSW_REG_PWM_CONTROL_OFFSET: ++ case NVSW_REG_RESET_GP2_OFFSET: ++ case NVSW_REG_GP4_OFFSET: ++ case NVSW_REG_GP5_OFFSET: ++ case NVSW_REG_GP6_OFFSET: ++ case NVSW_REG_LED1_OFFSET: ++ case NVSW_REG_LED5_OFFSET: ++ case NVSW_REG_LED6_OFFSET: ++ case NVSW_REG_LED7_OFFSET: ++ case NVSW_REG_AGGRCO_MASK_OFFSET: ++ case NVSW_REG_HEALTH_EVENT_OFFSET: ++ case NVSW_REG_HEALTH_MASK_OFFSET: ++ case NVSW_REG_AGGR_MASK_OFFSET: ++ case NVSW_REG_FU_CAP_OFFSET: ++ case NVSW_REG_BRD4_EVENT_OFFSET: ++ case NVSW_REG_BRD4_MASK_OFFSET: ++ case NVSW_REG_AGGRLO_MASK_OFFSET: ++ case NVSW_REG_BRD1_EVENT_OFFSET: ++ case NVSW_REG_BRD1_MASK_OFFSET: ++ case NVSW_REG_BRD2_EVENT_OFFSET: ++ case NVSW_REG_BRD2_MASK_OFFSET: ++ case NVSW_REG_ASIC1_EVENT_OFFSET: ++ case NVSW_REG_ASIC1_MASK_OFFSET: ++ case NVSW_REG_ASIC2_EVENT_OFFSET: ++ case NVSW_REG_ASIC2_MASK_OFFSET: ++ case NVSW_REG_ASIC3_EVENT_OFFSET: ++ case NVSW_REG_ASIC3_MASK_OFFSET: ++ case NVSW_REG_ASIC4_EVENT_OFFSET: ++ case NVSW_REG_ASIC4_MASK_OFFSET: ++ case NVSW_REG_VR1_ALERT_EVENT_OFFSET: ++ case NVSW_REG_VR1_ALERT_MASK_OFFSET: ++ case NVSW_REG_VR2_ALERT_EVENT_OFFSET: ++ case NVSW_REG_VR2_ALERT_MASK_OFFSET: ++ case NVSW_REG_PS_DC_OK_EVENT_OFFSET: ++ case NVSW_REG_PS_DC_OK_MASK_OFFSET: ++ case NVSW_REG_FAN_EVENT_OFFSET: ++ case NVSW_REG_FAN_MASK_OFFSET: ++ case NVSW_REG_PWRB_EVENT_OFFSET: ++ case NVSW_REG_PWRB_MASK_OFFSET: ++ case NVSW_REG_EROT_EVENT_OFFSET: ++ case NVSW_REG_EROT_MASK_OFFSET: ++ case NVSW_REG_EROT_ERR_EVENT_OFFSET: ++ case NVSW_REG_EROT_ERR_MASK_OFFSET: ++ case NVSW_REG_FRU1_EVENT_OFFSET: ++ case NVSW_REG_FRU1_MASK_OFFSET: ++ case NVSW_REG_LEAK_EVENT_OFFSET: ++ case NVSW_REG_LEAK_MASK_OFFSET: ++ case NVSW_REG_SPI_CHNL_SELECT: ++ case NVSW_REG_WD2_TMR_OFFSET: ++ case NVSW_REG_WD2_TLEFT_OFFSET: ++ case NVSW_REG_WD2_ACT_OFFSET: ++ case NVSW_REG_WD3_TMR_OFFSET: ++ case NVSW_REG_WD3_TLEFT_OFFSET: ++ case NVSW_REG_WD3_ACT_OFFSET: ++ case NVSW_REG_PWM1_OFFSET: ++ case NVSW_REG_MUX0_OFFSET: ++ case NVSW_REG_MUX1_OFFSET: ++ case NVSW_REG_MUX2_OFFSET: ++ return true; ++ } ++ return false; ++} ++ ++static bool nvsw_readable_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case NVSW_REG_CPLD1_VER_OFFSET: ++ case NVSW_REG_CPLD1_PN_OFFSET: ++ case NVSW_REG_CPLD1_PN1_OFFSET: ++ case NVSW_REG_CPLD2_VER_OFFSET: ++ case NVSW_REG_CPLD2_PN_OFFSET: ++ case NVSW_REG_CPLD2_PN1_OFFSET: ++ case NVSW_REG_CPLD3_VER_OFFSET: ++ case NVSW_REG_CPLD3_PN_OFFSET: ++ case NVSW_REG_CPLD3_PN1_OFFSET: ++ case NVSW_REG_CPLD4_VER_OFFSET: ++ case NVSW_REG_CPLD4_PN_OFFSET: ++ case NVSW_REG_CPLD4_PN1_OFFSET: ++ case NVSW_REG_CPLD1_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD2_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD3_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD4_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD7_VER_OFFSET: ++ case NVSW_REG_PG1_OFFSET: ++ case NVSW_REG_PG1_EVENT_OFFSET: ++ case NVSW_REG_PG1_MASK_OFFSET: ++ case NVSW_REG_PG2_OFFSET: ++ case NVSW_REG_PG2_EVENT_OFFSET: ++ case NVSW_REG_PG2_MASK_OFFSET: ++ case NVSW_REG_PG3_OFFSET: ++ case NVSW_REG_PG3_EVENT_OFFSET: ++ case NVSW_REG_PG3_MASK_OFFSET: ++ case NVSW_REG_PG4_OFFSET: ++ case NVSW_REG_PG4_EVENT_OFFSET: ++ case NVSW_REG_PG4_MASK_OFFSET: ++ case NVSW_REG_CPLD6_VER_OFFSET: ++ case NVSW_REG_CPLD6_PN_OFFSET: ++ case NVSW_REG_CPLD6_PN1_OFFSET: ++ case NVSW_REG_CPLD9_VER_OFFSET: ++ case NVSW_REG_CPLD10_PN_OFFSET: ++ case NVSW_REG_CPLD10_PN1_OFFSET: ++ case NVSW_REG_RESET_GP1_OFFSET: ++ case NVSW_REG_FIELD_UPGRADE: ++ case NVSW_REG_SAFE_BIOS_OFFSET: ++ case NVSW_REG_RESET_CAUSE_OFFSET: ++ case NVSW_REG_RESET_CAUSE1_OFFSET: ++ case NVSW_REG_RESET_CAUSE2_OFFSET: ++ case NVSW_REG_LED1_OFFSET: ++ case NVSW_REG_LED5_OFFSET: ++ case NVSW_REG_LED6_OFFSET: ++ case NVSW_REG_LED7_OFFSET: ++ case NVSW_REG_CPLD7_PN_OFFSET: ++ case NVSW_REG_CPLD7_PN1_OFFSET: ++ case NVSW_REG_RESET_GP2_OFFSET: ++ case NVSW_REG_GP0_RO_OFFSET: ++ case NVSW_REG_GP1_RO_OFFSET: ++ case NVSW_REG_GP2_RO_OFFSET: ++ case NVSW_REG_GP4_RO_OFFSET: ++ case NVSW_REG_GP5_RO_OFFSET: ++ case NVSW_REG_GPCOM0_OFFSET: ++ case NVSW_REG_GP0_OFFSET: ++ case NVSW_REG_GP1_OFFSET: ++ case NVSW_REG_GP7_OFFSET: ++ case NVSW_REG_UART_BAUD_OFFSET: ++ case NVSW_REG_PWM_CONTROL_OFFSET: ++ case NVSW_REG_GP4_OFFSET: ++ case NVSW_REG_GP5_OFFSET: ++ case NVSW_REG_GP6_OFFSET: ++ case NVSW_REG_AGGRCO_OFFSET: ++ case NVSW_REG_AGGRCO_MASK_OFFSET: ++ case NVSW_REG_HEALTH_OFFSET: ++ case NVSW_REG_HEALTH_EVENT_OFFSET: ++ case NVSW_REG_HEALTH_MASK_OFFSET: ++ case NVSW_REG_AGGR_OFFSET: ++ case NVSW_REG_AGGR_MASK_OFFSET: ++ case NVSW_REG_FU_CAP_OFFSET: ++ case NVSW_REG_BRD4_OFFSET: ++ case NVSW_REG_BRD4_EVENT_OFFSET: ++ case NVSW_REG_BRD4_MASK_OFFSET: ++ case NVSW_REG_AGGRLO_OFFSET: ++ case NVSW_REG_AGGRLO_MASK_OFFSET: ++ case NVSW_REG_BRD1_OFFSET: ++ case NVSW_REG_BRD1_EVENT_OFFSET: ++ case NVSW_REG_BRD1_MASK_OFFSET: ++ case NVSW_REG_BRD2_OFFSET: ++ case NVSW_REG_BRD2_EVENT_OFFSET: ++ case NVSW_REG_BRD2_MASK_OFFSET: ++ case NVSW_REG_ASIC1_HEALTH_OFFSET: ++ case NVSW_REG_ASIC1_EVENT_OFFSET: ++ case NVSW_REG_ASIC1_MASK_OFFSET: ++ case NVSW_REG_ASIC2_HEALTH_OFFSET: ++ case NVSW_REG_ASIC2_EVENT_OFFSET: ++ case NVSW_REG_ASIC2_MASK_OFFSET: ++ case NVSW_REG_ASIC3_HEALTH_OFFSET: ++ case NVSW_REG_ASIC3_EVENT_OFFSET: ++ case NVSW_REG_ASIC3_MASK_OFFSET: ++ case NVSW_REG_ASIC4_HEALTH_OFFSET: ++ case NVSW_REG_ASIC4_EVENT_OFFSET: ++ case NVSW_REG_ASIC4_MASK_OFFSET: ++ case NVSW_REG_VR1_ALERT_OFFSET: ++ case NVSW_REG_VR1_ALERT_EVENT_OFFSET: ++ case NVSW_REG_VR1_ALERT_MASK_OFFSET: ++ case NVSW_REG_VR2_ALERT_OFFSET: ++ case NVSW_REG_VR2_ALERT_EVENT_OFFSET: ++ case NVSW_REG_VR2_ALERT_MASK_OFFSET: ++ case NVSW_REG_PS_DC_OK_OFFSET: ++ case NVSW_REG_PS_DC_OK_EVENT_OFFSET: ++ case NVSW_REG_PS_DC_OK_MASK_OFFSET: ++ case NVSW_REG_CPLD8_PN_OFFSET: ++ case NVSW_REG_CPLD8_PN1_OFFSET: ++ case NVSW_REG_CPLD8_VER_OFFSET: ++ case NVSW_REG_FAN_OFFSET: ++ case NVSW_REG_FAN_EVENT_OFFSET: ++ case NVSW_REG_FAN_MASK_OFFSET: ++ case NVSW_REG_CPLD5_VER_OFFSET: ++ case NVSW_REG_CPLD5_PN_OFFSET: ++ case NVSW_REG_CPLD5_PN1_OFFSET: ++ case NVSW_REG_EROT_OFFSET: ++ case NVSW_REG_EROT_EVENT_OFFSET: ++ case NVSW_REG_EROT_MASK_OFFSET: ++ case NVSW_REG_EROT_ERR_OFFSET: ++ case NVSW_REG_EROT_ERR_EVENT_OFFSET: ++ case NVSW_REG_EROT_ERR_MASK_OFFSET: ++ case NVSW_REG_PWRB_OFFSET: ++ case NVSW_REG_PWRB_EVENT_OFFSET: ++ case NVSW_REG_PWRB_MASK_OFFSET: ++ case NVSW_REG_FRU1_OFFSET: ++ case NVSW_REG_FRU1_EVENT_OFFSET: ++ case NVSW_REG_FRU1_MASK_OFFSET: ++ case NVSW_REG_LEAK_OFFSET: ++ case NVSW_REG_LEAK_EVENT_OFFSET: ++ case NVSW_REG_LEAK_MASK_OFFSET: ++ case NVSW_REG_CPLD1_MVER_OFFSET: ++ case NVSW_REG_CPLD2_MVER_OFFSET: ++ case NVSW_REG_CPLD3_MVER_OFFSET: ++ case NVSW_REG_CPLD4_MVER_OFFSET: ++ case NVSW_REG_PWM1_OFFSET: ++ case NVSW_REG_TACHO1_OFFSET: ++ case NVSW_REG_TACHO2_OFFSET: ++ case NVSW_REG_TACHO3_OFFSET: ++ case NVSW_REG_TACHO4_OFFSET: ++ case NVSW_REG_TACHO5_OFFSET: ++ case NVSW_REG_TACHO6_OFFSET: ++ case NVSW_REG_TACHO7_OFFSET: ++ case NVSW_REG_TACHO8_OFFSET: ++ case NVSW_REG_TACHO9_OFFSET: ++ case NVSW_REG_TACHO10_OFFSET: ++ case NVSW_REG_TACHO11_OFFSET: ++ case NVSW_REG_TACHO12_OFFSET: ++ case NVSW_REG_FAN_CAP1_OFFSET: ++ case NVSW_REG_FAN_DRW_CAP_OFFSET: ++ case NVSW_REG_TACHO_SPEED_OFFSET: ++ case NVSW_REG_CONFIG1_OFFSET: ++ case NVSW_REG_CONFIG2_OFFSET: ++ case NVSW_REG_CONFIG3_OFFSET: ++ case NVSW_REG_SPI_CHNL_SELECT: ++ case NVSW_REG_CPLD5_MVER_OFFSET: ++ case NVSW_REG_CPLD9_PN_OFFSET: ++ case NVSW_REG_CPLD9_PN1_OFFSET: ++ case NVSW_REG_CPLD10_VER_OFFSET: ++ case NVSW_REG_WD2_TMR_OFFSET: ++ case NVSW_REG_WD2_TLEFT_OFFSET: ++ case NVSW_REG_WD2_ACT_OFFSET: ++ case NVSW_REG_WD3_TMR_OFFSET: ++ case NVSW_REG_WD3_TLEFT_OFFSET: ++ case NVSW_REG_WD3_ACT_OFFSET: ++ case NVSW_REG_CPLD7_MVER_OFFSET: ++ case NVSW_REG_CPLD8_MVER_OFFSET: ++ case NVSW_REG_CPLD9_MVER_OFFSET: ++ case NVSW_REG_CPLD10_MVER_OFFSET: ++ case NVSW_REG_CPLD6_MVER_OFFSET: ++ case NVSW_REG_MUX0_OFFSET: ++ case NVSW_REG_MUX1_OFFSET: ++ case NVSW_REG_MUX2_OFFSET: ++ case NVSW_REG_UFM_VERSION_OFFSET: ++ case NVSW_REG_PSU_I2C_CAP_OFFSET: ++ return true; ++ } ++ return false; ++} ++ ++static bool nvsw_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case NVSW_REG_CPLD1_VER_OFFSET: ++ case NVSW_REG_CPLD1_PN_OFFSET: ++ case NVSW_REG_CPLD1_PN1_OFFSET: ++ case NVSW_REG_CPLD2_VER_OFFSET: ++ case NVSW_REG_CPLD2_PN_OFFSET: ++ case NVSW_REG_CPLD2_PN1_OFFSET: ++ case NVSW_REG_CPLD3_VER_OFFSET: ++ case NVSW_REG_CPLD3_PN_OFFSET: ++ case NVSW_REG_CPLD3_PN1_OFFSET: ++ case NVSW_REG_CPLD4_VER_OFFSET: ++ case NVSW_REG_CPLD4_PN_OFFSET: ++ case NVSW_REG_CPLD4_PN1_OFFSET: ++ case NVSW_REG_CPLD7_VER_OFFSET: ++ case NVSW_REG_PG1_OFFSET: ++ case NVSW_REG_PG1_EVENT_OFFSET: ++ case NVSW_REG_PG1_MASK_OFFSET: ++ case NVSW_REG_PG2_OFFSET: ++ case NVSW_REG_PG2_EVENT_OFFSET: ++ case NVSW_REG_PG2_MASK_OFFSET: ++ case NVSW_REG_PG3_OFFSET: ++ case NVSW_REG_PG3_EVENT_OFFSET: ++ case NVSW_REG_PG3_MASK_OFFSET: ++ case NVSW_REG_PG4_OFFSET: ++ case NVSW_REG_PG4_EVENT_OFFSET: ++ case NVSW_REG_PG4_MASK_OFFSET: ++ case NVSW_REG_CPLD6_VER_OFFSET: ++ case NVSW_REG_CPLD6_PN_OFFSET: ++ case NVSW_REG_CPLD6_PN1_OFFSET: ++ case NVSW_REG_CPLD9_VER_OFFSET: ++ case NVSW_REG_CPLD10_PN_OFFSET: ++ case NVSW_REG_CPLD10_PN1_OFFSET: ++ case NVSW_REG_RESET_GP1_OFFSET: ++ case NVSW_REG_FIELD_UPGRADE: ++ case NVSW_REG_SAFE_BIOS_OFFSET: ++ case NVSW_REG_RESET_CAUSE_OFFSET: ++ case NVSW_REG_RESET_CAUSE1_OFFSET: ++ case NVSW_REG_RESET_CAUSE2_OFFSET: ++ case NVSW_REG_LED1_OFFSET: ++ case NVSW_REG_LED5_OFFSET: ++ case NVSW_REG_LED6_OFFSET: ++ case NVSW_REG_LED7_OFFSET: ++ case NVSW_REG_CPLD7_PN_OFFSET: ++ case NVSW_REG_CPLD7_PN1_OFFSET: ++ case NVSW_REG_RESET_GP2_OFFSET: ++ case NVSW_REG_GP0_RO_OFFSET: ++ case NVSW_REG_GP1_RO_OFFSET: ++ case NVSW_REG_GPCOM0_OFFSET: ++ case NVSW_REG_GP0_OFFSET: ++ case NVSW_REG_GP1_OFFSET: ++ case NVSW_REG_GP7_OFFSET: ++ case NVSW_REG_PWM_CONTROL_OFFSET: ++ case NVSW_REG_GP4_OFFSET: ++ case NVSW_REG_GP5_OFFSET: ++ case NVSW_REG_GP6_OFFSET: ++ case NVSW_REG_AGGRCO_OFFSET: ++ case NVSW_REG_AGGRCO_MASK_OFFSET: ++ case NVSW_REG_HEALTH_OFFSET: ++ case NVSW_REG_HEALTH_EVENT_OFFSET: ++ case NVSW_REG_HEALTH_MASK_OFFSET: ++ case NVSW_REG_AGGR_OFFSET: ++ case NVSW_REG_AGGR_MASK_OFFSET: ++ case NVSW_REG_FU_CAP_OFFSET: ++ case NVSW_REG_BRD4_OFFSET: ++ case NVSW_REG_BRD4_EVENT_OFFSET: ++ case NVSW_REG_BRD4_MASK_OFFSET: ++ case NVSW_REG_AGGRLO_OFFSET: ++ case NVSW_REG_AGGRLO_MASK_OFFSET: ++ case NVSW_REG_BRD1_OFFSET: ++ case NVSW_REG_BRD1_EVENT_OFFSET: ++ case NVSW_REG_BRD1_MASK_OFFSET: ++ case NVSW_REG_BRD2_OFFSET: ++ case NVSW_REG_BRD2_EVENT_OFFSET: ++ case NVSW_REG_BRD2_MASK_OFFSET: ++ case NVSW_REG_ASIC1_HEALTH_OFFSET: ++ case NVSW_REG_ASIC1_EVENT_OFFSET: ++ case NVSW_REG_ASIC1_MASK_OFFSET: ++ case NVSW_REG_ASIC2_HEALTH_OFFSET: ++ case NVSW_REG_ASIC2_EVENT_OFFSET: ++ case NVSW_REG_ASIC2_MASK_OFFSET: ++ case NVSW_REG_ASIC3_HEALTH_OFFSET: ++ case NVSW_REG_ASIC3_EVENT_OFFSET: ++ case NVSW_REG_ASIC3_MASK_OFFSET: ++ case NVSW_REG_VR1_ALERT_OFFSET: ++ case NVSW_REG_VR1_ALERT_EVENT_OFFSET: ++ case NVSW_REG_VR1_ALERT_MASK_OFFSET: ++ case NVSW_REG_VR2_ALERT_OFFSET: ++ case NVSW_REG_VR2_ALERT_EVENT_OFFSET: ++ case NVSW_REG_VR2_ALERT_MASK_OFFSET: ++ case NVSW_REG_PS_DC_OK_OFFSET: ++ case NVSW_REG_PS_DC_OK_EVENT_OFFSET: ++ case NVSW_REG_PS_DC_OK_MASK_OFFSET: ++ case NVSW_REG_CPLD8_PN_OFFSET: ++ case NVSW_REG_CPLD8_PN1_OFFSET: ++ case NVSW_REG_CPLD8_VER_OFFSET: ++ case NVSW_REG_FAN_OFFSET: ++ case NVSW_REG_FAN_EVENT_OFFSET: ++ case NVSW_REG_FAN_MASK_OFFSET: ++ case NVSW_REG_CPLD5_VER_OFFSET: ++ case NVSW_REG_CPLD5_PN_OFFSET: ++ case NVSW_REG_CPLD5_PN1_OFFSET: ++ case NVSW_REG_EROT_OFFSET: ++ case NVSW_REG_EROT_EVENT_OFFSET: ++ case NVSW_REG_EROT_MASK_OFFSET: ++ case NVSW_REG_EROT_ERR_OFFSET: ++ case NVSW_REG_EROT_ERR_EVENT_OFFSET: ++ case NVSW_REG_EROT_ERR_MASK_OFFSET: ++ case NVSW_REG_PWRB_OFFSET: ++ case NVSW_REG_PWRB_EVENT_OFFSET: ++ case NVSW_REG_PWRB_MASK_OFFSET: ++ case NVSW_REG_FRU1_OFFSET: ++ case NVSW_REG_FRU1_EVENT_OFFSET: ++ case NVSW_REG_FRU1_MASK_OFFSET: ++ case NVSW_REG_LEAK_OFFSET: ++ case NVSW_REG_LEAK_EVENT_OFFSET: ++ case NVSW_REG_LEAK_MASK_OFFSET: ++ case NVSW_REG_GP4_RO_OFFSET: ++ case NVSW_REG_GP5_RO_OFFSET: ++ case NVSW_REG_CPLD1_MVER_OFFSET: ++ case NVSW_REG_CPLD2_MVER_OFFSET: ++ case NVSW_REG_CPLD3_MVER_OFFSET: ++ case NVSW_REG_CPLD4_MVER_OFFSET: ++ case NVSW_REG_PWM1_OFFSET: ++ case NVSW_REG_TACHO1_OFFSET: ++ case NVSW_REG_TACHO2_OFFSET: ++ case NVSW_REG_TACHO3_OFFSET: ++ case NVSW_REG_TACHO4_OFFSET: ++ case NVSW_REG_TACHO5_OFFSET: ++ case NVSW_REG_TACHO6_OFFSET: ++ case NVSW_REG_TACHO7_OFFSET: ++ case NVSW_REG_TACHO8_OFFSET: ++ case NVSW_REG_TACHO9_OFFSET: ++ case NVSW_REG_TACHO10_OFFSET: ++ case NVSW_REG_TACHO11_OFFSET: ++ case NVSW_REG_TACHO12_OFFSET: ++ case NVSW_REG_FAN_CAP1_OFFSET: ++ case NVSW_REG_FAN_DRW_CAP_OFFSET: ++ case NVSW_REG_TACHO_SPEED_OFFSET: ++ case NVSW_REG_CONFIG1_OFFSET: ++ case NVSW_REG_CONFIG2_OFFSET: ++ case NVSW_REG_CONFIG3_OFFSET: ++ case NVSW_REG_SPI_CHNL_SELECT: ++ case NVSW_REG_CPLD5_MVER_OFFSET: ++ case NVSW_REG_CPLD9_PN_OFFSET: ++ case NVSW_REG_CPLD9_PN1_OFFSET: ++ case NVSW_REG_CPLD10_VER_OFFSET: ++ case NVSW_REG_WD2_TMR_OFFSET: ++ case NVSW_REG_WD2_TLEFT_OFFSET: ++ case NVSW_REG_WD2_ACT_OFFSET: ++ case NVSW_REG_CPLD7_MVER_OFFSET: ++ case NVSW_REG_CPLD8_MVER_OFFSET: ++ case NVSW_REG_CPLD9_MVER_OFFSET: ++ case NVSW_REG_CPLD10_MVER_OFFSET: ++ case NVSW_REG_CPLD6_MVER_OFFSET: ++ case NVSW_REG_WD3_TMR_OFFSET: ++ case NVSW_REG_WD3_TLEFT_OFFSET: ++ case NVSW_REG_WD3_ACT_OFFSET: ++ case NVSW_REG_MUX0_OFFSET: ++ case NVSW_REG_MUX1_OFFSET: ++ case NVSW_REG_MUX2_OFFSET: ++ case NVSW_REG_UFM_VERSION_OFFSET: ++ case NVSW_REG_SYS_CPBLTY0: ++ return true; ++ } ++ return false; ++} ++ ++/* Configuration for the register map of a device with 2 bytes address space. */ ++static const struct reg_default nvsw_core_reg_def[] = { ++ { NVSW_REG_AGGRCO_MASK_OFFSET, GENMASK(1, 0)}, ++ { NVSW_REG_PWM_CONTROL_OFFSET, 0x00 }, ++}; ++ ++static const struct regmap_config nvsw_regmap_i2c_conf = { ++ .reg_bits = 16, ++ .val_bits = 8, ++ .max_register = NVSW_REG_MAX, ++ .cache_type = REGCACHE_FLAT, ++ .reg_defaults = nvsw_core_reg_def, ++ .num_reg_defaults = ARRAY_SIZE(nvsw_core_reg_def), ++ .writeable_reg = nvsw_writeable_reg, ++ .readable_reg = nvsw_readable_reg, ++ .volatile_reg = nvsw_volatile_reg, ++}; ++ ++static const struct reg_default nvsw_reg_def_l1[] = { ++ { NVSW_REG_PWM_CONTROL_OFFSET, 0x00 }, ++ { NVSW_REG_WD2_ACT_OFFSET, 0x00 }, ++ { NVSW_REG_WD3_ACT_OFFSET, 0x00 }, ++ { NVSW_REG_LEAK_MASK_OFFSET, 0x3f }, ++}; ++ ++struct nvsw_io_regmap_context { ++ void __iomem *base; ++}; ++ ++static struct nvsw_io_regmap_context nvsw_io_regmap_ctx; ++ ++static int nvsw_reg_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ struct nvsw_io_regmap_context *ctx = context; ++ ++ *val = ioread8(ctx->base - NVSW_REG_MIN + reg); ++ return 0; ++} ++ ++static int nvsw_reg_write(void *context, unsigned int reg, unsigned int val) ++{ ++ struct nvsw_io_regmap_context *ctx = context; ++ ++ iowrite8(val, ctx->base - NVSW_REG_MIN + reg); ++ return 0; ++} ++ ++static const struct regmap_config nvsw_regmap_conf_l1 = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = NVSW_REG_MAX, ++ .cache_type = REGCACHE_FLAT, ++ .writeable_reg = nvsw_writeable_reg, ++ .readable_reg = nvsw_readable_reg, ++ .volatile_reg = nvsw_volatile_reg, ++ .reg_defaults = nvsw_reg_def_l1, ++ .num_reg_defaults = ARRAY_SIZE(nvsw_reg_def_l1), ++ .reg_read = nvsw_reg_read, ++ .reg_write = nvsw_reg_write, ++}; ++ ++static int nvsw_core_platform_init(struct nvsw_core *nvsw_core) ++{ ++ int i, err; ++ ++ /* Add registers io access driver. */ ++ if (nvsw_core->regio_data) { ++ nvsw_core->regio_data->regmap = nvsw_core->regmap; ++ nvsw_core->regio = ++ platform_device_register_resndata(nvsw_core->dev, "mlxreg-io", ++ PLATFORM_DEVID_NONE, NULL, 0, ++ nvsw_core->regio_data, ++ sizeof(*nvsw_core->regio_data)); ++ if (IS_ERR(nvsw_core->regio)) { ++ err = PTR_ERR(nvsw_core->regio); ++ dev_err(nvsw_core->dev, "Failed to register mlxreg-io driver\n"); ++ goto fail_platform_io_register; ++ } ++ } ++ ++ /* Add FAN driver. */ ++ if (nvsw_core->fan_data) { ++ nvsw_core->fan_data->regmap = nvsw_core->regmap; ++ nvsw_core->fan = platform_device_register_resndata(nvsw_core->dev, "mlxreg-fan", ++ PLATFORM_DEVID_NONE, NULL, 0, ++ nvsw_core->fan_data, ++ sizeof(*nvsw_core->fan_data)); ++ if (IS_ERR(nvsw_core->fan)) { ++ err = PTR_ERR(nvsw_core->fan); ++ dev_err(nvsw_core->dev, "Failed to register mlxreg-fan driver\n"); ++ goto fail_platform_fan_register; ++ } ++ } ++ ++ /* Add LED driver. */ ++ if (nvsw_core->led_data) { ++ nvsw_core->led_data->regmap = nvsw_core->regmap; ++ nvsw_core->led = platform_device_register_resndata(nvsw_core->dev, "leds-mlxreg", ++ PLATFORM_DEVID_NONE, NULL, 0, ++ nvsw_core->led_data, ++ sizeof(*nvsw_core->led_data)); ++ if (IS_ERR(nvsw_core->led)) { ++ err = PTR_ERR(nvsw_core->led); ++ dev_err(nvsw_core->dev, "Failed to register leds-mlxreg driver\n"); ++ goto fail_platform_leds_register; ++ } ++ } ++ ++ /* Add hotplug driver. */ ++ if (nvsw_core->hotplug_data && nvsw_core->np) { ++ nvsw_core->hotplug_data->irq = nvsw_core->client->irq; ++ dev_info(nvsw_core->dev, "irq %d\n", nvsw_core->hotplug_data->irq); ++ nvsw_core->hotplug_data->regmap = nvsw_core->regmap; ++ nvsw_core->hotplug = ++ platform_device_register_resndata(nvsw_core->dev, "mlxreg-hotplug", ++ PLATFORM_DEVID_NONE, NULL, 0, ++ nvsw_core->hotplug_data, ++ sizeof(*nvsw_core->hotplug_data)); ++ if (IS_ERR(nvsw_core->hotplug)) { ++ err = PTR_ERR(nvsw_core->hotplug); ++ dev_err(nvsw_core->dev, "Failed to register mlxreg-hotplug driver\n"); ++ goto fail_platform_hotplug_register; ++ } ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(nvsw_core->wd_data); i++) { ++ if (nvsw_core->wd_data[i]) { ++ nvsw_core->wd_data[i]->regmap = nvsw_core->regmap; ++ nvsw_core->wd[i] = ++ platform_device_register_resndata(nvsw_core->dev, "mlx-wdt", i, ++ NULL, 0, nvsw_core->wd_data[i], ++ sizeof(*nvsw_core->wd_data[i])); ++ if (IS_ERR(nvsw_core->wd[i])) { ++ err = PTR_ERR(nvsw_core->wd[i]); ++ dev_err(nvsw_core->dev, "Failed to register mlx-wdt driver\n"); ++ goto fail_platform_wd_register; ++ } ++ } ++ } ++ ++ return 0; ++ ++fail_platform_wd_register: ++ while (i--) ++ platform_device_unregister(nvsw_core->wd[i]); ++ if (nvsw_core->hotplug_data) ++ platform_device_unregister(nvsw_core->hotplug); ++fail_platform_hotplug_register: ++ if (nvsw_core->led_data) ++ platform_device_unregister(nvsw_core->led); ++fail_platform_leds_register: ++ if (nvsw_core->fan_data) ++ platform_device_unregister(nvsw_core->fan); ++fail_platform_fan_register: ++ if (nvsw_core->regio_data) ++ platform_device_unregister(nvsw_core->regio); ++fail_platform_io_register: ++ return err; ++} ++ ++static void nvsw_core_platform_exit(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ for (i = NVSW_WD_MAX - 1; i >= 0; i--) ++ platform_device_unregister(nvsw_core->wd[i]); ++ if (nvsw_core->hotplug_data) ++ platform_device_unregister(nvsw_core->hotplug); ++ if (nvsw_core->led_data) ++ platform_device_unregister(nvsw_core->led); ++ if (nvsw_core->fan_data) ++ platform_device_unregister(nvsw_core->fan); ++ if (nvsw_core->regio_data) ++ platform_device_unregister(nvsw_core->regio); ++} ++ ++static int nvsw_core_mux_topology_init(struct nvsw_core *nvsw_core) ++{ ++ return nvsw_core->mux_init(nvsw_core); ++} ++ ++static void nvsw_core_mux_topology_exit(struct nvsw_core *nvsw_core) ++{ ++ nvsw_core->mux_exit(nvsw_core); ++} ++ ++static int nvsw_core_regmap_init(struct nvsw_core *nvsw_core) ++{ ++ int err; ++ ++ switch (nvsw_core->regmap_type) { ++ case REGMAP_I2C: ++ nvsw_core->regmap = devm_regmap_init_i2c(nvsw_core->client, &nvsw_regmap_i2c_conf); ++ break; ++ case REGMAP_IO: ++ if (nvsw_core->port_map) { ++ nvsw_io_regmap_ctx.base = nvsw_core->port_map(nvsw_core); ++ if (!nvsw_io_regmap_ctx.base) ++ return -ENOMEM; ++ } ++ nvsw_core->regmap = devm_regmap_init(nvsw_core->dev, NULL, &nvsw_io_regmap_ctx, ++ &nvsw_regmap_conf_l1); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (IS_ERR(nvsw_core->regmap)) { ++ dev_err(nvsw_core->dev, "Failed to create regmap\n"); ++ return PTR_ERR(nvsw_core->regmap); ++ } ++ ++ /* Sync registers with hardware. */ ++ regcache_mark_dirty(nvsw_core->regmap); ++ err = regcache_sync(nvsw_core->regmap); ++ if (err) { ++ dev_err(nvsw_core->dev, "Failed to sync regmap\n"); ++ return err; ++ } ++ ++ /* Set registers default values. */ ++ if (nvsw_core->set_reg_default) { ++ err = nvsw_core->set_reg_default(nvsw_core->regmap); ++ if (err) { ++ dev_err(nvsw_core->dev, "Failed to set default regmap\n"); ++ return err; ++ } ++ } ++ ++ return 0; ++} ++ ++int nvsw_core_init(struct nvsw_core *nvsw_core) ++{ ++ int err; ++ ++ err = nvsw_core_regmap_init(nvsw_core); ++ if (err) ++ return err; ++ ++ err = nvsw_core_mux_topology_init(nvsw_core); ++ if (err) ++ goto nvsw_core_mux_topology_init_fail; ++ ++ err = nvsw_core_platform_init(nvsw_core); ++ if (err) ++ goto nvsw_core_platform_init_fail; ++ ++ return 0; ++nvsw_core_platform_init_fail: ++ nvsw_core_mux_topology_exit(nvsw_core); ++nvsw_core_mux_topology_init_fail: ++ return err; ++} ++EXPORT_SYMBOL(nvsw_core_init); ++ ++void nvsw_core_exit(struct nvsw_core *nvsw_core) ++{ ++ nvsw_core_platform_exit(nvsw_core); ++ nvsw_core_mux_topology_exit(nvsw_core); ++ if (nvsw_core->set_reg_default) ++ nvsw_core->set_reg_default(nvsw_core->regmap); ++} ++EXPORT_SYMBOL(nvsw_core_exit); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Nvidia platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/platform/mellanox/nvsw-host-l1.c b/drivers/platform/mellanox/nvsw-host-l1.c +new file mode 100644 +index 000000000..6fb8cf07a +--- /dev/null ++++ b/drivers/platform/mellanox/nvsw-host-l1.c +@@ -0,0 +1,735 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Nvidia L1 host platform driver ++ * ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nvsw.h" ++ ++#define NVSW_HOST_DEVICE_NAME "mlxplat" ++ ++/* LPC bus IO offsets */ ++#define NVSW_I2C_BASE_ADRR 0x2000 ++#define NVSW_REG_BASE_ADRR 0x2500 ++#define NVSW_LPC_IO_RANGE 0x100 ++#define NVSW_LPC_PIO_OFFSET 0x10000UL ++#define NVSW_REG_MUX1 (NVSW_REG_MUX1_OFFSET | NVSW_LPC_PIO_OFFSET) ++#define NVSW_REG_MUX2 (NVSW_REG_MUX0_OFFSET | NVSW_LPC_PIO_OFFSET) ++ ++/* Start channel numbers */ ++#define NVSW_PARENT_CH_L1 0 ++#define NVSW_CH1_L1 5 ++ ++/* Regions for LPC I2C controller and LPC base register space */ ++static const struct resource nvsw_host_io_resources[] = { ++ [0] = DEFINE_RES_NAMED(NVSW_I2C_BASE_ADRR, NVSW_LPC_IO_RANGE, ++ "nvsw_cpld_lpc_i2c_ctrl", IORESOURCE_IO), ++ [1] = DEFINE_RES_NAMED(NVSW_REG_BASE_ADRR, NVSW_LPC_IO_RANGE, ++ "nvsw_cpld_lpc_regs", IORESOURCE_IO), ++}; ++ ++/* Platform channels for L1 scale out system family */ ++static const int nvsw_host_l1_mgmt_channels[] = { ++ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ++ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ++ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, ++ 63, 64, 65, 66, 67, ++}; ++ ++/* Platform L1 scale out mux data */ ++static struct i2c_mux_reg_platform_data nvsw_host_l1_mux_data[] = { ++ { ++ .parent = NVSW_PARENT_CH_L1, ++ .base_nr = NVSW_CH1_L1, ++ .write_only = 1, ++ .reg = (void __iomem *)NVSW_REG_MUX1, ++ .reg_size = 1, ++ .idle_in_use = 1, ++ .values = nvsw_host_l1_mgmt_channels, ++ .n_values = ARRAY_SIZE(nvsw_host_l1_mgmt_channels), ++ }, ++}; ++ ++static struct platform_device *nvsw_host_dev; ++static struct i2c_mux_reg_platform_data *nvsw_host_mux_data[NVSW_MUX_MAX]; ++static struct mlxreg_core_platform_data *nvsw_led_data; ++static struct mlxreg_core_platform_data *nvsw_regs_io_data; ++static struct mlxreg_core_platform_data *nvsw_wd_data[NVSW_WD_MAX]; ++static int mux_num; ++static enum nvsw_core_hid_type nvsw_host_hid; ++ ++/* Platform register access for l1 systems families data */ ++static struct mlxreg_core_data nvsw_host_l1_regs_io_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0244, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "hotswap_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cartridge1", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cartridge2", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "cartridge3", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "cartridge4", ++ .reg = NVSW_REG_FRU1_OFFSET, ++ .mask = BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_erot_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_reload", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_button", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_cpu_erot", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_leak_con", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_cpu_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC2_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC3_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC4_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_pwr_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_l1_regs_io = { ++ .data = nvsw_host_l1_regs_io_data, ++ .counter = ARRAY_SIZE(nvsw_host_l1_regs_io_data), ++}; ++ ++/* Platform led data for L1 switch systems with liquid cooling (without FANs) */ ++static struct mlxreg_core_data nvsw_host_l1_liquid_coling_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "status:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "power:green", ++ .mode = 0444, ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = NVSW_REG_LED5_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_l1_liquid_coling_led = { ++ .data = nvsw_host_l1_liquid_coling_led_data, ++ .counter = ARRAY_SIZE(nvsw_host_l1_liquid_coling_led_data), ++}; ++ ++/* Watchdog type3 platform data */ ++static struct mlxreg_core_data nvsw_host_wd_main_regs_type3[] = { ++ { ++ .label = "action", ++ .reg = NVSW_REG_WD2_ACT_OFFSET, ++ .mask = NVSW_WD_RESET_ACT_MASK, ++ .bit = 0, ++ }, ++ { ++ .label = "timeout", ++ .reg = NVSW_REG_WD2_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ .health_cntr = NVSW_WD3_DFLT_TIMEOUT, ++ }, ++ { ++ .label = "timeleft", ++ .reg = NVSW_REG_WD2_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ }, ++ { ++ .label = "ping", ++ .reg = NVSW_REG_WD2_ACT_OFFSET, ++ .mask = NVSW_WD_RESET_ACT_MASK, ++ .bit = 0, ++ }, ++ { ++ .label = "reset", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .bit = 6, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_host_wd_aux_regs_type3[] = { ++ { ++ .label = "action", ++ .reg = NVSW_REG_WD3_ACT_OFFSET, ++ .mask = NVSW_WD_FAN_ACT_MASK, ++ .bit = 4, ++ }, ++ { ++ .label = "timeout", ++ .reg = NVSW_REG_WD3_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ .health_cntr = NVSW_WD3_DFLT_TIMEOUT, ++ }, ++ { ++ .label = "timeleft", ++ .reg = NVSW_REG_WD3_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ }, ++ { ++ .label = "ping", ++ .reg = NVSW_REG_WD3_ACT_OFFSET, ++ .mask = NVSW_WD_FAN_ACT_MASK, ++ .bit = 4, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_wd_set_type3[] = { ++ { ++ .data = nvsw_host_wd_main_regs_type3, ++ .counter = ARRAY_SIZE(nvsw_host_wd_main_regs_type3), ++ .version = MLX_WDT_TYPE3, ++ .identity = "mlx-wdt-main", ++ }, ++ { ++ .data = nvsw_host_wd_aux_regs_type3, ++ .counter = ARRAY_SIZE(nvsw_host_wd_aux_regs_type3), ++ .version = MLX_WDT_TYPE3, ++ .identity = "mlx-wdt-aux", ++ }, ++}; ++ ++/* IO port mapping callback. */ ++static void __iomem *nvsw_host_l1_port_map(struct nvsw_core *nvsw_core) ++{ ++ return devm_ioport_map(nvsw_core->dev, nvsw_host_io_resources[1].start, 1); ++} ++ ++/* Mux init/exit callbacks. */ ++static int nvsw_host_l1_mux_topology_init(struct nvsw_core *nvsw_core) ++{ ++ int i, err; ++ ++ /* Create mux infrastructure. */ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ nvsw_core->mux[i] = ++ platform_device_register_resndata(nvsw_core->dev, "i2c-mux-reg", i, NULL, ++ 0, nvsw_host_mux_data[i], ++ sizeof(*nvsw_host_mux_data[i])); ++ if (IS_ERR(nvsw_core->mux[i])) { ++ dev_err(nvsw_core->dev, "Failed to create mux infra\n"); ++ err = PTR_ERR(nvsw_core->mux[i]); ++ goto fail_platform_mux_register; ++ } ++ } ++ ++ return 0; ++fail_platform_mux_register: ++ while (--i >= 0) ++ platform_device_unregister(nvsw_core->mux[i]); ++ return err; ++} ++ ++static void nvsw_host_l1_mux_topology_exit(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ if (nvsw_core->mux[i]) ++ platform_device_unregister(nvsw_core->mux[i]); ++ } ++} ++ ++static int __init nvsw_host_register_platform_device(void) ++{ ++ nvsw_host_dev = platform_device_register_simple(NVSW_HOST_DEVICE_NAME, -1, ++ nvsw_host_io_resources, ++ ARRAY_SIZE(nvsw_host_io_resources)); ++ if (IS_ERR(nvsw_host_dev)) ++ return PTR_ERR(nvsw_host_dev); ++ return 1; ++} ++ ++static int __init nvsw_host_dmi_l1_switch_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_host_hid = HID180; ++ mux_num = ARRAY_SIZE(nvsw_host_l1_mux_data); ++ for (i = 0; i < mux_num; i++) ++ nvsw_host_mux_data[i] = &nvsw_host_l1_mux_data[i]; ++ nvsw_led_data = &nvsw_host_l1_liquid_coling_led; ++ nvsw_regs_io_data = &nvsw_host_l1_regs_io; ++ for (i = 0; i < ARRAY_SIZE(nvsw_host_wd_set_type3); i++) ++ nvsw_wd_data[i] = &nvsw_host_wd_set_type3[i]; ++ ++ return nvsw_host_register_platform_device(); ++} ++ ++static const struct dmi_system_id nvsw_host_dmi_table[] __initconst = { ++ { ++ .callback = nvsw_host_dmi_l1_switch_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0023"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI180"), ++ }, ++ }, ++ { ++ .callback = nvsw_host_dmi_l1_switch_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0023"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI185"), ++ }, ++ }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(dmi, nvsw_host_dmi_table); ++ ++static int nvsw_host_probe(struct platform_device *pdev) ++{ ++ struct nvsw_core *nvsw_core; ++ int i; ++ ++ nvsw_core = devm_kzalloc(&nvsw_host_dev->dev, sizeof(*nvsw_core), GFP_KERNEL); ++ if (!nvsw_core) ++ return -ENOMEM; ++ ++ /* Set system configuration. */ ++ nvsw_core->dev = &nvsw_host_dev->dev; ++ nvsw_core->hid = nvsw_host_hid; ++ nvsw_core->regmap_type = REGMAP_IO; ++ nvsw_core->mux_num = mux_num; ++ for (i = 0; i < ARRAY_SIZE(nvsw_wd_data); i++) ++ nvsw_core->wd_data[i] = nvsw_wd_data[i]; ++ nvsw_core->regio_data = nvsw_regs_io_data; ++ nvsw_core->led_data = nvsw_led_data; ++ nvsw_core->port_map = nvsw_host_l1_port_map; ++ nvsw_core->mux_init = nvsw_host_l1_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_host_l1_mux_topology_exit; ++ platform_set_drvdata(nvsw_host_dev, nvsw_core); ++ ++ return nvsw_core_init(nvsw_core); ++} ++ ++static void nvsw_host_remove(struct platform_device *pdev) ++{ ++ struct nvsw_core *nvsw_core = platform_get_drvdata(nvsw_host_dev); ++ ++ return nvsw_core_exit(nvsw_core); ++} ++ ++static struct platform_driver nvsw_host_driver = { ++ .driver = { ++ .name = NVSW_HOST_DEVICE_NAME, ++ .probe_type = PROBE_FORCE_SYNCHRONOUS, ++ }, ++ .probe = nvsw_host_probe, ++ .remove = nvsw_host_remove, ++}; ++ ++static int __init nvsw_host_init(void) ++{ ++ if (!dmi_check_system(nvsw_host_dmi_table)) ++ return -ENODEV; ++ ++ return platform_driver_register(&nvsw_host_driver); ++} ++module_init(nvsw_host_init); ++ ++static void __exit nvsw_host_exit(void) ++{ ++ if (nvsw_host_dev) ++ platform_device_unregister(nvsw_host_dev); ++ ++ platform_driver_unregister(&nvsw_host_driver); ++} ++module_exit(nvsw_host_exit); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Nvidia platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ +diff --git a/drivers/platform/mellanox/nvsw-host-spc5.c b/drivers/platform/mellanox/nvsw-host-spc5.c +new file mode 100644 +index 000000000..e54bd4109 +--- /dev/null ++++ b/drivers/platform/mellanox/nvsw-host-spc5.c +@@ -0,0 +1,943 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Nvidia BMC platform driver ++ * ++ * Copyright (C) 2025 Nvidia Technologies Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nvsw.h" ++ ++#define NVSW_HOST_DEVICE_NAME "mlxplat" ++ ++/* LPC bus IO offsets */ ++#define NVSW_I2C_BASE_ADRR 0x2000 ++#define NVSW_REG_BASE_ADRR 0x2500 ++#define NVSW_LPC_IO_RANGE 0x100 ++#define NVSW_LPC_PIO_OFFSET 0x10000UL ++#define NVSW_REG_MUX1 (NVSW_REG_MUX1_OFFSET | NVSW_LPC_PIO_OFFSET) ++#define NVSW_REG_MUX2 (NVSW_REG_MUX0_OFFSET | NVSW_LPC_PIO_OFFSET) ++ ++/* Default I2C parent bus number */ ++#define NVSW_CPLD_PHYS_ADAPTER_DEF_NR 5 ++ ++/* Start channel numbers */ ++#define NVSW_PARENT_CH NVSW_CPLD_PHYS_ADAPTER_DEF_NR ++#define NVSW_MUX1_CH NVSW_CPLD_PHYS_ADAPTER_DEF_NR + 1 ++#define NVSW_MUX2_CH 64 ++ ++ ++/* Regions for LPC I2C controller and LPC base register space */ ++static const struct resource nvsw_host_io_resources[] = { ++ [0] = DEFINE_RES_NAMED(NVSW_I2C_BASE_ADRR, NVSW_LPC_IO_RANGE, ++ "nvsw_cpld_lpc_i2c_ctrl", IORESOURCE_IO), ++ [1] = DEFINE_RES_NAMED(NVSW_REG_BASE_ADRR, NVSW_LPC_IO_RANGE, ++ "nvsw_cpld_lpc_regs", IORESOURCE_IO), ++}; ++ ++/* SPC5 platform mgmt board mux channels */ ++static const int nvsw_host_spc5_mgmt_channels[] = { ++ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ++ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ++ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58 ++}; ++ ++/* SPC5 platform ComEx board mux channels */ ++static const int nvsw_host_spc5_comex_channels[] = { ++ 1, 2, 3, 4, 5, 6, 7, 8 ++}; ++ ++/* SPC5 platform mux data */ ++static struct i2c_mux_reg_platform_data nvsw_host_spc5_mux_data[] = { ++ { ++ .parent = NVSW_PARENT_CH, ++ .base_nr = NVSW_MUX1_CH, ++ .write_only = 1, ++ .reg = (void __iomem *)NVSW_REG_MUX1, ++ .reg_size = 1, ++ .idle_in_use = 1, ++ .values = nvsw_host_spc5_mgmt_channels, ++ .n_values = ARRAY_SIZE(nvsw_host_spc5_mgmt_channels), ++ }, ++ { ++ .parent = NVSW_PARENT_CH, ++ .base_nr = NVSW_MUX2_CH, ++ .write_only = 1, ++ .reg = (void __iomem *)NVSW_REG_MUX2, ++ .reg_size = 1, ++ .idle_in_use = 1, ++ .values = nvsw_host_spc5_comex_channels, ++ .n_values = ARRAY_SIZE(nvsw_host_spc5_comex_channels), ++ }, ++}; ++ ++/* SPC5 platform i2c frequency data */ ++static struct mlxreg_core_data nvsw_mlxcpld_i2c_items_data[] = { ++ { ++ .reg = NVSW_REG_PSU_I2C_CAP_OFFSET, ++ .mask = NVSW_I2C_CAP_MASK, ++ .bit = NVSW_I2C_CAP_BIT, ++ }, ++}; ++ ++static struct mlxreg_core_item nvsw_mlxcpld_i2c_items[] = { ++ { ++ .data = nvsw_mlxcpld_i2c_items_data, ++ }, ++}; ++ ++/* SPC5 platform i2c data */ ++static struct mlxreg_core_hotplug_platform_data nvsw_mlxcpld_i2c_data = { ++ .items = nvsw_mlxcpld_i2c_items, ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRCO_OFFSET, ++ .mask_low = NVSW_LOW_AGGR_MASK_I2C, ++}; ++ ++static struct platform_device *nvsw_host_dev; ++static struct platform_device *nvsw_i2c_dev; ++static struct i2c_mux_reg_platform_data *nvsw_host_mux_data[NVSW_MUX_MAX]; ++static struct mlxreg_core_platform_data *nvsw_led_data; ++static struct mlxreg_core_platform_data *nvsw_regs_io_data; ++static struct mlxreg_core_hotplug_platform_data *nvsw_i2c_data; ++static struct mlxreg_core_platform_data *nvsw_wd_data[NVSW_WD_MAX]; ++static int mux_num; ++static enum nvsw_core_hid_type nvsw_host_hid; ++ ++/* Platform register access for SPC5 systems families data */ ++static struct mlxreg_core_data nvsw_host_spc5_regs_io_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version", ++ .reg = NVSW_REG_CPLD5_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version", ++ .reg = NVSW_REG_CPLD6_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version", ++ .reg = NVSW_REG_CPLD7_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version", ++ .reg = NVSW_REG_CPLD8_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version", ++ .reg = NVSW_REG_CPLD9_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version", ++ .reg = NVSW_REG_CPLD10_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld5_pn", ++ .reg = NVSW_REG_CPLD5_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld6_pn", ++ .reg = NVSW_REG_CPLD6_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld7_pn", ++ .reg = NVSW_REG_CPLD7_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld8_pn", ++ .reg = NVSW_REG_CPLD8_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld9_pn", ++ .reg = NVSW_REG_CPLD9_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld10_pn", ++ .reg = NVSW_REG_CPLD10_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version_min", ++ .reg = NVSW_REG_CPLD5_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version_min", ++ .reg = NVSW_REG_CPLD6_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version_min", ++ .reg = NVSW_REG_CPLD7_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version_min", ++ .reg = NVSW_REG_CPLD8_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version_min", ++ .reg = NVSW_REG_CPLD9_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version_min", ++ .reg = NVSW_REG_CPLD10_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_pwr_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0244, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "leakage1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage3", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage4", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage5", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic3_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic4_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_reload", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC2_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC3_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC4_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_pg_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_pg_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb1_pwr_status", ++ .reg = NVSW_REG_GP0_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb2_pwr_status", ++ .reg = NVSW_REG_GP0_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb3_pwr_status", ++ .reg = NVSW_REG_GP0_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb4_pwr_status", ++ .reg = NVSW_REG_GP0_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_spc5_regs_io = { ++ .data = nvsw_host_spc5_regs_io_data, ++ .counter = ARRAY_SIZE(nvsw_host_spc5_regs_io_data), ++}; ++ ++/* SPC5 platform led data */ ++static struct mlxreg_core_data nvsw_host_spc5_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "status:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "power:green", ++ .mode = 0444, ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = NVSW_REG_LED5_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_spc5_led = { ++ .data = nvsw_host_spc5_led_data, ++ .counter = ARRAY_SIZE(nvsw_host_spc5_led_data), ++}; ++ ++/* Watchdog type3 platform data */ ++static struct mlxreg_core_data nvsw_host_wd_main_regs_type3[] = { ++ { ++ .label = "action", ++ .reg = NVSW_REG_WD2_ACT_OFFSET, ++ .mask = NVSW_WD_RESET_ACT_MASK, ++ .bit = 0, ++ }, ++ { ++ .label = "timeout", ++ .reg = NVSW_REG_WD2_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ .health_cntr = NVSW_WD3_DFLT_TIMEOUT, ++ }, ++ { ++ .label = "timeleft", ++ .reg = NVSW_REG_WD2_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ }, ++ { ++ .label = "ping", ++ .reg = NVSW_REG_WD2_ACT_OFFSET, ++ .mask = NVSW_WD_RESET_ACT_MASK, ++ .bit = 0, ++ }, ++ { ++ .label = "reset", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .bit = 6, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_host_wd_aux_regs_type3[] = { ++ { ++ .label = "action", ++ .reg = NVSW_REG_WD3_ACT_OFFSET, ++ .mask = NVSW_WD_FAN_ACT_MASK, ++ .bit = 4, ++ }, ++ { ++ .label = "timeout", ++ .reg = NVSW_REG_WD3_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ .health_cntr = NVSW_WD3_DFLT_TIMEOUT, ++ }, ++ { ++ .label = "timeleft", ++ .reg = NVSW_REG_WD3_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ }, ++ { ++ .label = "ping", ++ .reg = NVSW_REG_WD3_ACT_OFFSET, ++ .mask = NVSW_WD_FAN_ACT_MASK, ++ .bit = 4, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_wd_set_type3[] = { ++ { ++ .data = nvsw_host_wd_main_regs_type3, ++ .counter = ARRAY_SIZE(nvsw_host_wd_main_regs_type3), ++ .version = MLX_WDT_TYPE3, ++ .identity = "mlx-wdt-main", ++ }, ++ { ++ .data = nvsw_host_wd_aux_regs_type3, ++ .counter = ARRAY_SIZE(nvsw_host_wd_aux_regs_type3), ++ .version = MLX_WDT_TYPE3, ++ .identity = "mlx-wdt-aux", ++ }, ++}; ++ ++/* IO port mapping callback. */ ++static void __iomem *nvsw_host_spc5_port_map(struct nvsw_core *nvsw_core) ++{ ++ return devm_ioport_map(nvsw_core->dev, nvsw_host_io_resources[1].start, 1); ++} ++ ++/* Mux init/exit callbacks. */ ++static int nvsw_host_spc5_mux_topology_init(struct nvsw_core *nvsw_core) ++{ ++ int i, err; ++ ++ if (!nvsw_i2c_data) ++ return 0; ++ ++ /* Create i2c infrastructure. */ ++ nvsw_i2c_data->regmap = nvsw_core->regmap; ++ nvsw_i2c_data->handle = nvsw_core; ++ ++ nvsw_i2c_dev = platform_device_register_resndata(nvsw_core->dev, "i2c_mlxcpld", ++ NVSW_CPLD_PHYS_ADAPTER_DEF_NR, NULL, 0, ++ nvsw_i2c_data, sizeof(*nvsw_i2c_data)); ++ if (IS_ERR(nvsw_i2c_dev)) { ++ err = PTR_ERR(nvsw_i2c_dev); ++ return err; ++ } ++ ++ /* Create mux infrastructure. */ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ nvsw_core->mux[i] = ++ platform_device_register_resndata(nvsw_core->dev, "i2c-mux-reg", i, NULL, ++ 0, nvsw_host_mux_data[i], ++ sizeof(*nvsw_host_mux_data[i])); ++ if (IS_ERR(nvsw_core->mux[i])) { ++ dev_err(nvsw_core->dev, "Failed to create mux infra\n"); ++ err = PTR_ERR(nvsw_core->mux[i]); ++ goto fail_platform_mux_register; ++ } ++ } ++ ++ return 0; ++fail_platform_mux_register: ++ while (--i >= 0) ++ platform_device_unregister(nvsw_core->mux[i]); ++ return err; ++} ++ ++static void nvsw_host_spc5_mux_topology_exit(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ if (nvsw_core->mux[i]) ++ platform_device_unregister(nvsw_core->mux[i]); ++ } ++ ++ if (nvsw_i2c_dev) ++ platform_device_unregister(nvsw_i2c_dev); ++} ++ ++static int __init nvsw_host_register_platform_device(void) ++{ ++ nvsw_host_dev = platform_device_register_simple(NVSW_HOST_DEVICE_NAME, -1, ++ nvsw_host_io_resources, ++ ARRAY_SIZE(nvsw_host_io_resources)); ++ if (IS_ERR(nvsw_host_dev)) ++ return PTR_ERR(nvsw_host_dev); ++ return 1; ++} ++ ++static int __init nvsw_host_dmi_spc5_switch_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_host_hid = HID181; ++ mux_num = ARRAY_SIZE(nvsw_host_spc5_mux_data); ++ for (i = 0; i < mux_num; i++) ++ nvsw_host_mux_data[i] = &nvsw_host_spc5_mux_data[i]; ++ nvsw_led_data = &nvsw_host_spc5_led; ++ nvsw_regs_io_data = &nvsw_host_spc5_regs_io; ++ for (i = 0; i < ARRAY_SIZE(nvsw_host_wd_set_type3); i++) ++ nvsw_wd_data[i] = &nvsw_host_wd_set_type3[i]; ++ nvsw_i2c_data = &nvsw_mlxcpld_i2c_data; ++ ++ return nvsw_host_register_platform_device(); ++} ++ ++static const struct dmi_system_id nvsw_host_dmi_table[] __initconst = { ++ { ++ .callback = nvsw_host_dmi_spc5_switch_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0024"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI181"), ++ }, ++ }, ++ { ++ .callback = nvsw_host_dmi_spc5_switch_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0024"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI182"), ++ }, ++ }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(dmi, nvsw_host_dmi_table); ++ ++static int nvsw_host_probe(struct platform_device *pdev) ++{ ++ struct nvsw_core *nvsw_core; ++ int i; ++ ++ nvsw_core = devm_kzalloc(&nvsw_host_dev->dev, sizeof(*nvsw_core), GFP_KERNEL); ++ if (!nvsw_core) ++ return -ENOMEM; ++ ++ /* Set system configuration. */ ++ nvsw_core->dev = &nvsw_host_dev->dev; ++ nvsw_core->hid = nvsw_host_hid; ++ nvsw_core->regmap_type = REGMAP_IO; ++ nvsw_core->mux_num = mux_num; ++ for (i = 0; i < ARRAY_SIZE(nvsw_wd_data); i++) ++ nvsw_core->wd_data[i] = nvsw_wd_data[i]; ++ nvsw_core->regio_data = nvsw_regs_io_data; ++ nvsw_core->led_data = nvsw_led_data; ++ nvsw_core->port_map = nvsw_host_spc5_port_map; ++ nvsw_core->mux_init = nvsw_host_spc5_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_host_spc5_mux_topology_exit; ++ platform_set_drvdata(nvsw_host_dev, nvsw_core); ++ ++ return nvsw_core_init(nvsw_core); ++} ++ ++static void nvsw_host_remove(struct platform_device *pdev) ++{ ++ struct nvsw_core *nvsw_core = platform_get_drvdata(nvsw_host_dev); ++ ++ nvsw_core_exit(nvsw_core); ++} ++ ++static struct platform_driver nvsw_host_driver = { ++ .driver = { ++ .name = NVSW_HOST_DEVICE_NAME, ++ .probe_type = PROBE_FORCE_SYNCHRONOUS, ++ }, ++ .probe = nvsw_host_probe, ++ .remove = nvsw_host_remove, ++}; ++ ++static int __init nvsw_host_init(void) ++{ ++ if (!dmi_check_system(nvsw_host_dmi_table)) ++ return -ENODEV; ++ ++ return platform_driver_register(&nvsw_host_driver); ++} ++module_init(nvsw_host_init); ++ ++static void __exit nvsw_host_exit(void) ++{ ++ if (nvsw_host_dev) ++ platform_device_unregister(nvsw_host_dev); ++ ++ platform_driver_unregister(&nvsw_host_driver); ++} ++module_exit(nvsw_host_exit); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Nvidia platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ +diff --git a/drivers/platform/mellanox/nvsw-host-spc6.c b/drivers/platform/mellanox/nvsw-host-spc6.c +new file mode 100644 +index 000000000..449ba5ffd +--- /dev/null ++++ b/drivers/platform/mellanox/nvsw-host-spc6.c +@@ -0,0 +1,852 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Nvidia SPC6 host platform driver ++ * ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nvsw.h" ++ ++#define NVSW_HOST_DEVICE_NAME "mlxplat" ++ ++/* Bus number of mux parent device */ ++#define NVSW_I2C_MUX_PARENT_BUS_NR 2 ++ ++/* Bus number of the last host I2C bus */ ++#define NVSW_I2C_LAST_HOST_BUS_NR 3 ++ ++/* Platform mux channels */ ++static int nvsw_host_spc6_mux_channels[] = { ++ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ++ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ++ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, ++ 63, 64, 65, 66, 67, ++}; ++ ++/* Platform mux data */ ++static struct mlxcpld_mux_plat_data nvsw_host_spc6_mux_data[] = { ++ { ++ .chan_ids = nvsw_host_spc6_mux_channels, ++ .num_adaps = ARRAY_SIZE(nvsw_host_spc6_mux_channels), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 2, ++ }, ++}; ++ ++/* Platform mux board info */ ++static struct i2c_board_info nvsw_host_spc6_mux_brdinfo = { ++ I2C_BOARD_INFO("i2c-mux-mlxcpld", 0x33), ++}; ++ ++static struct i2c_adapter *nvsw_host_mux_i2c_adapter; ++static struct i2c_client *nvsw_host_mux_i2c_client; ++static struct mlxcpld_mux_plat_data *nvsw_host_mux_data[NVSW_MUX_MAX]; ++static struct i2c_board_info *nvsw_host_mux_brdinfo; ++static struct mlxreg_core_platform_data *nvsw_led_data; ++static struct mlxreg_core_platform_data *nvsw_regs_io_data; ++static struct mlxreg_core_platform_data *nvsw_wd_data[NVSW_WD_MAX]; ++static int mux_num; ++static enum nvsw_core_hid_type nvsw_host_hid; ++static struct platform_device *nvsw_host_pdev; ++ ++/* Platform register access data */ ++static struct mlxreg_core_data nvsw_host_spc6_regs_io_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version", ++ .reg = NVSW_REG_CPLD5_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version", ++ .reg = NVSW_REG_CPLD6_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version", ++ .reg = NVSW_REG_CPLD7_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version", ++ .reg = NVSW_REG_CPLD8_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version", ++ .reg = NVSW_REG_CPLD9_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version", ++ .reg = NVSW_REG_CPLD10_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld5_pn", ++ .reg = NVSW_REG_CPLD5_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld6_pn", ++ .reg = NVSW_REG_CPLD6_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld7_pn", ++ .reg = NVSW_REG_CPLD7_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld8_pn", ++ .reg = NVSW_REG_CPLD8_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld9_pn", ++ .reg = NVSW_REG_CPLD9_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld10_pn", ++ .reg = NVSW_REG_CPLD10_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version_min", ++ .reg = NVSW_REG_CPLD5_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version_min", ++ .reg = NVSW_REG_CPLD6_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version_min", ++ .reg = NVSW_REG_CPLD7_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version_min", ++ .reg = NVSW_REG_CPLD8_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version_min", ++ .reg = NVSW_REG_CPLD9_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version_min", ++ .reg = NVSW_REG_CPLD10_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_pwr_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0244, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "leakage1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage3", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage4", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "leakage5", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_reload", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_cpu_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb1_pwr_status", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb2_pwr_status", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb3_pwr_status", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "pdb4_pwr_status", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_spc6_regs_io = { ++ .data = nvsw_host_spc6_regs_io_data, ++ .counter = ARRAY_SIZE(nvsw_host_spc6_regs_io_data), ++}; ++ ++/* Platform led data */ ++static struct mlxreg_core_data nvsw_host_spc6_lc_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "status:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "power:green", ++ .mode = 0444, ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "power:amber", ++ .reg = NVSW_REG_LED1_OFFSET, ++ .mask = NVSW_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = NVSW_REG_LED5_OFFSET, ++ .mask = NVSW_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_spc6_lc_led = { ++ .data = nvsw_host_spc6_lc_led_data, ++ .counter = ARRAY_SIZE(nvsw_host_spc6_lc_led_data), ++}; ++ ++/* Watchdog type3 platform data */ ++static struct mlxreg_core_data nvsw_host_wd_main_regs_type3[] = { ++ { ++ .label = "action", ++ .reg = NVSW_REG_WD2_ACT_OFFSET, ++ .mask = NVSW_WD_RESET_ACT_MASK, ++ .bit = 0, ++ }, ++ { ++ .label = "timeout", ++ .reg = NVSW_REG_WD2_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ .health_cntr = NVSW_WD3_DFLT_TIMEOUT, ++ }, ++ { ++ .label = "timeleft", ++ .reg = NVSW_REG_WD2_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ }, ++ { ++ .label = "ping", ++ .reg = NVSW_REG_WD2_ACT_OFFSET, ++ .mask = NVSW_WD_RESET_ACT_MASK, ++ .bit = 0, ++ }, ++ { ++ .label = "reset", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .bit = 6, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_host_wd_aux_regs_type3[] = { ++ { ++ .label = "action", ++ .reg = NVSW_REG_WD3_ACT_OFFSET, ++ .mask = NVSW_WD_FAN_ACT_MASK, ++ .bit = 4, ++ }, ++ { ++ .label = "timeout", ++ .reg = NVSW_REG_WD3_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ .health_cntr = NVSW_WD3_DFLT_TIMEOUT, ++ }, ++ { ++ .label = "timeleft", ++ .reg = NVSW_REG_WD3_TMR_OFFSET, ++ .mask = NVSW_WD_TYPE2_TO_MASK, ++ }, ++ { ++ .label = "ping", ++ .reg = NVSW_REG_WD3_ACT_OFFSET, ++ .mask = NVSW_WD_FAN_ACT_MASK, ++ .bit = 4, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_host_wd_set_type3[] = { ++ { ++ .data = nvsw_host_wd_main_regs_type3, ++ .counter = ARRAY_SIZE(nvsw_host_wd_main_regs_type3), ++ .version = MLX_WDT_TYPE3, ++ .identity = "mlx-wdt-main", ++ }, ++ { ++ .data = nvsw_host_wd_aux_regs_type3, ++ .counter = ARRAY_SIZE(nvsw_host_wd_aux_regs_type3), ++ .version = MLX_WDT_TYPE3, ++ .identity = "mlx-wdt-aux", ++ }, ++}; ++ ++/* Callback is used to indicate that all adapter devices have been created. */ ++static int ++nvsw_host_spc6_completion_notify(void *handle, struct i2c_adapter *parent, ++ struct i2c_adapter *adapters[]) ++{ ++ return 0; ++} ++ ++/* Mux init/exit callbacks. */ ++static int nvsw_host_spc6_mux_topology_init(struct nvsw_core *nvsw_core) ++{ ++ int i, err; ++ ++ /* Create mux infrastructure. */ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ nvsw_host_mux_data[i]->handle = nvsw_core; ++ nvsw_host_mux_data[i]->completion_notify = ++ nvsw_host_spc6_completion_notify; ++ nvsw_core->mux[i] = ++ platform_device_register_resndata(&nvsw_host_mux_i2c_client->dev, ++ "i2c-mux-mlxcpld", i, NULL, ++ 0, nvsw_host_mux_data[i], ++ sizeof(*nvsw_host_mux_data[i])); ++ if (IS_ERR(nvsw_core->mux[i])) { ++ dev_err(nvsw_core->dev, "Failed to create mux infra\n"); ++ err = PTR_ERR(nvsw_core->mux[i]); ++ goto platform_mux_register_fail; ++ } ++ } ++ ++ return 0; ++ ++platform_mux_register_fail: ++ while (--i >= 0) ++ platform_device_unregister(nvsw_core->mux[i]); ++ i2c_unregister_device(nvsw_host_mux_i2c_client); ++ nvsw_host_mux_i2c_client = NULL; ++ ++ return err; ++} ++ ++static void nvsw_host_spc6_mux_topology_exit(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ if (nvsw_core->mux[i]) ++ platform_device_unregister(nvsw_core->mux[i]); ++ } ++ ++ if (nvsw_core->client) { ++ i2c_unregister_device(nvsw_core->client); ++ nvsw_host_mux_i2c_client = NULL; ++ i2c_put_adapter(nvsw_host_mux_i2c_adapter); ++ nvsw_host_mux_i2c_adapter = NULL; ++ } ++} ++ ++static int __init nvsw_host_dmi_spc6_switch_matched(const struct dmi_system_id *dmi) ++{ ++ int i, err; ++ ++ /* Allocate the platform device structure */ ++ nvsw_host_pdev = ++ platform_device_alloc(NVSW_HOST_DEVICE_NAME, PLATFORM_DEVID_NONE); ++ if (!nvsw_host_pdev) ++ return -ENOMEM; ++ ++ /* Register the device with the platform bus */ ++ err = platform_device_add(nvsw_host_pdev); ++ if (err) { ++ platform_device_put(nvsw_host_pdev); ++ return err; ++ } ++ ++ /* Set system configuration. */ ++ nvsw_host_hid = HID193; ++ mux_num = ARRAY_SIZE(nvsw_host_spc6_mux_data); ++ for (i = 0; i < mux_num; i++) ++ nvsw_host_mux_data[i] = &nvsw_host_spc6_mux_data[i]; ++ nvsw_host_mux_brdinfo = &nvsw_host_spc6_mux_brdinfo; ++ nvsw_led_data = &nvsw_host_spc6_lc_led; ++ nvsw_regs_io_data = &nvsw_host_spc6_regs_io; ++ for (i = 0; i < ARRAY_SIZE(nvsw_host_wd_set_type3); i++) ++ nvsw_wd_data[i] = &nvsw_host_wd_set_type3[i]; ++ ++ return 0; ++} ++ ++static const struct dmi_system_id nvsw_host_dmi_table[] __initconst = { ++ { ++ .callback = nvsw_host_dmi_spc6_switch_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0025"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI193"), ++ }, ++ }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(dmi, nvsw_host_dmi_table); ++ ++static int nvsw_host_probe(struct platform_device *pdev) ++{ ++ struct i2c_adapter *last_host_adapter; ++ struct nvsw_core *nvsw_core; ++ int i, err; ++ ++ /* Ensure the I2C host driver has finished registering ++ * all 4 buses (0-3) to prevent the mux from stealing bus 3 ++ */ ++ last_host_adapter = i2c_get_adapter(NVSW_I2C_LAST_HOST_BUS_NR); ++ if (!last_host_adapter) { ++ dev_info(&pdev->dev, ++ "I2C adapter for bus %d is not ready, deferring probe\n", ++ NVSW_I2C_LAST_HOST_BUS_NR); ++ return -EPROBE_DEFER; ++ } ++ i2c_put_adapter(last_host_adapter); ++ ++ /* Get I2C mux parent adapter */ ++ nvsw_host_mux_i2c_adapter = i2c_get_adapter(NVSW_I2C_MUX_PARENT_BUS_NR); ++ if (!nvsw_host_mux_i2c_adapter) { ++ dev_err(&pdev->dev, ++ "I2C adapter for bus %d is not available\n", ++ NVSW_I2C_MUX_PARENT_BUS_NR); ++ return -ENODEV; ++ } ++ ++ /* Create device at the top of host I2C tree */ ++ nvsw_host_mux_brdinfo->platform_data = nvsw_host_mux_data; ++ nvsw_host_mux_i2c_client = i2c_new_client_device(nvsw_host_mux_i2c_adapter, ++ nvsw_host_mux_brdinfo); ++ if (IS_ERR(nvsw_host_mux_i2c_client)) { ++ dev_err(&pdev->dev, ++ "Failed to create client %s at bus %d at addr 0x%02x\n", ++ nvsw_host_mux_brdinfo->type, NVSW_I2C_MUX_PARENT_BUS_NR, ++ nvsw_host_mux_brdinfo->addr); ++ err = PTR_ERR(nvsw_host_mux_i2c_client); ++ goto i2c_new_client_device_fail; ++ } ++ ++ nvsw_core = devm_kzalloc(&pdev->dev, sizeof(*nvsw_core), GFP_KERNEL); ++ if (!nvsw_core) { ++ err = -ENOMEM; ++ goto nvsw_host_probe_fail; ++ } ++ ++ /* Set system configuration. */ ++ nvsw_core->dev = &pdev->dev; ++ nvsw_core->hid = nvsw_host_hid; ++ nvsw_core->regmap_type = REGMAP_I2C; ++ nvsw_core->client = nvsw_host_mux_i2c_client; ++ nvsw_core->mux_num = mux_num; ++ for (i = 0; i < ARRAY_SIZE(nvsw_wd_data); i++) ++ nvsw_core->wd_data[i] = nvsw_wd_data[i]; ++ nvsw_core->regio_data = nvsw_regs_io_data; ++ nvsw_core->led_data = nvsw_led_data; ++ nvsw_core->mux_init = nvsw_host_spc6_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_host_spc6_mux_topology_exit; ++ platform_set_drvdata(pdev, nvsw_core); ++ ++ err = nvsw_core_init(nvsw_core); ++ if (!err) ++ return 0; ++ ++nvsw_host_probe_fail: ++ i2c_unregister_device(nvsw_host_mux_i2c_client); ++i2c_new_client_device_fail: ++ i2c_put_adapter(nvsw_host_mux_i2c_adapter); ++ nvsw_host_mux_i2c_adapter = NULL; ++ return err; ++} ++ ++static void nvsw_host_remove(struct platform_device *pdev) ++{ ++ struct nvsw_core *nvsw_core = platform_get_drvdata(pdev); ++ ++ nvsw_core_exit(nvsw_core); ++ i2c_unregister_device(nvsw_host_mux_i2c_client); ++ i2c_put_adapter(nvsw_host_mux_i2c_adapter); ++ nvsw_host_mux_i2c_adapter = NULL; ++} ++ ++static struct platform_driver nvsw_host_driver = { ++ .driver = { ++ .name = NVSW_HOST_DEVICE_NAME, ++ .probe_type = PROBE_FORCE_SYNCHRONOUS, ++ }, ++ .probe = nvsw_host_probe, ++ .remove = nvsw_host_remove, ++}; ++ ++static int __init nvsw_host_init(void) ++{ ++ int err; ++ ++ if (!dmi_check_system(nvsw_host_dmi_table)) ++ return -ENODEV; ++ ++ err = platform_driver_register(&nvsw_host_driver); ++ if (err) { ++ if (nvsw_host_pdev) ++ platform_device_unregister(nvsw_host_pdev); ++ } ++ ++ return err; ++} ++module_init(nvsw_host_init); ++ ++static void __exit nvsw_host_exit(void) ++{ ++ platform_driver_unregister(&nvsw_host_driver); ++ ++ if (nvsw_host_pdev) ++ platform_device_unregister(nvsw_host_pdev); ++} ++module_exit(nvsw_host_exit); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Nvidia platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ +diff --git a/drivers/platform/mellanox/nvsw.h b/drivers/platform/mellanox/nvsw.h +new file mode 100644 +index 000000000..4fa0d9d31 +--- /dev/null ++++ b/drivers/platform/mellanox/nvsw.h +@@ -0,0 +1,318 @@ ++/* SPDX-License-Identifier: GPL-2.0+ ++ * ++ * Nvidia BMC platform driver ++ * ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. ++ */ ++ ++#ifndef __NVSW_H__ ++#define __NVSW_H__ ++ ++/* I2C bus IO offsets */ ++#define NVSW_REG_CPLD1_VER_OFFSET 0x2500 ++#define NVSW_REG_CPLD2_VER_OFFSET 0x2501 ++#define NVSW_REG_CPLD3_VER_OFFSET 0x2502 ++#define NVSW_REG_CPLD4_VER_OFFSET 0x2503 ++#define NVSW_REG_CPLD1_PN_OFFSET 0x2504 ++#define NVSW_REG_CPLD1_PN1_OFFSET 0x2505 ++#define NVSW_REG_CPLD2_PN_OFFSET 0x2506 ++#define NVSW_REG_CPLD2_PN1_OFFSET 0x2507 ++#define NVSW_REG_CPLD3_PN_OFFSET 0x2508 ++#define NVSW_REG_CPLD3_PN1_OFFSET 0x2509 ++#define NVSW_REG_CPLD4_PN_OFFSET 0x250a ++#define NVSW_REG_CPLD4_PN1_OFFSET 0x250b ++#define NVSW_REG_CPLD1_DBG_LED_OFFSET 0x250c ++#define NVSW_REG_CPLD2_DBG_LED_OFFSET 0x250d ++#define NVSW_REG_CPLD3_DBG_LED_OFFSET 0x250e ++#define NVSW_REG_CPLD4_DBG_LED_OFFSET 0x250f ++#define NVSW_REG_CPLD7_VER_OFFSET 0x2510 ++#define NVSW_REG_PG1_OFFSET 0x2511 ++#define NVSW_REG_PG1_EVENT_OFFSET 0x2512 ++#define NVSW_REG_PG1_MASK_OFFSET 0x2513 ++#define NVSW_REG_PG2_OFFSET 0x2514 ++#define NVSW_REG_PG2_EVENT_OFFSET 0x2515 ++#define NVSW_REG_PG2_MASK_OFFSET 0x2516 ++#define NVSW_REG_RESET_GP1_OFFSET 0x2517 ++#define NVSW_REG_RESET_GP2_OFFSET 0x2519 ++#define NVSW_REG_RESET_CAUSE_OFFSET 0x251d ++#define NVSW_REG_RESET_CAUSE1_OFFSET 0x251e ++#define NVSW_REG_RESET_CAUSE2_OFFSET 0x251f ++#define NVSW_REG_LED1_OFFSET 0x2520 ++#define NVSW_REG_LED5_OFFSET 0x2524 ++#define NVSW_REG_LED6_OFFSET 0x2525 ++#define NVSW_REG_LED7_OFFSET 0x2526 ++#define NVSW_REG_CPLD7_PN_OFFSET 0x2528 ++#define NVSW_REG_CPLD7_PN1_OFFSET 0x2529 ++#define NVSW_REG_GP0_RO_OFFSET 0x252a ++#define NVSW_REG_GP2_RO_OFFSET 0x252b ++#define NVSW_REG_GP1_RO_OFFSET 0x252c ++#define NVSW_REG_GPCOM0_OFFSET 0x252d ++#define NVSW_REG_GP0_OFFSET 0x252e ++#define NVSW_REG_GP7_OFFSET 0x252f ++#define NVSW_REG_GP1_OFFSET 0x2530 ++#define NVSW_REG_UART_BAUD_OFFSET 0x2531 ++#define NVSW_REG_GP4_OFFSET 0x2532 ++#define NVSW_REG_GP5_RO_OFFSET 0x2533 ++#define NVSW_REG_FIELD_UPGRADE 0x2534 ++#define NVSW_REG_SAFE_BIOS_OFFSET 0x2535 ++#define NVSW_REG_PWM_CONTROL_OFFSET 0x2537 ++#define NVSW_REG_GP5_OFFSET 0x2538 ++#define NVSW_REG_GP6_OFFSET 0x2539 ++#define NVSW_REG_AGGR_OFFSET 0x253a ++#define NVSW_REG_AGGR_MASK_OFFSET 0x253b ++#define NVSW_REG_FU_CAP_OFFSET 0x253c ++#define NVSW_REG_BRD4_OFFSET 0x253d ++#define NVSW_REG_BRD4_EVENT_OFFSET 0x253e ++#define NVSW_REG_BRD4_MASK_OFFSET 0x253f ++#define NVSW_REG_AGGRLO_OFFSET 0x2540 ++#define NVSW_REG_AGGRLO_MASK_OFFSET 0x2541 ++#define NVSW_REG_AGGRCO_OFFSET 0x2542 ++#define NVSW_REG_AGGRCO_MASK_OFFSET 0x2543 ++#define NVSW_REG_BRD1_OFFSET 0x2547 ++#define NVSW_REG_BRD1_EVENT_OFFSET 0x2548 ++#define NVSW_REG_BRD1_MASK_OFFSET 0x2549 ++#define NVSW_REG_BRD2_OFFSET 0x254a ++#define NVSW_REG_BRD2_EVENT_OFFSET 0x254b ++#define NVSW_REG_BRD2_MASK_OFFSET 0x254c ++#define NVSW_REG_HEALTH_OFFSET 0x254d ++#define NVSW_REG_HEALTH_EVENT_OFFSET 0x254e ++#define NVSW_REG_HEALTH_MASK_OFFSET 0x254f ++#define NVSW_REG_ASIC1_HEALTH_OFFSET 0x2550 ++#define NVSW_REG_ASIC1_EVENT_OFFSET 0x2551 ++#define NVSW_REG_ASIC1_MASK_OFFSET 0x2552 ++#define NVSW_REG_ASIC2_HEALTH_OFFSET 0x2553 ++#define NVSW_REG_ASIC2_EVENT_OFFSET 0x2554 ++#define NVSW_REG_ASIC2_MASK_OFFSET 0x2555 ++#define NVSW_REG_VR1_ALERT_OFFSET 0x2558 ++#define NVSW_REG_VR1_ALERT_EVENT_OFFSET 0x2559 ++#define NVSW_REG_VR1_ALERT_MASK_OFFSET 0x255a ++#define NVSW_REG_VR2_ALERT_OFFSET 0x255e ++#define NVSW_REG_VR2_ALERT_EVENT_OFFSET 0x255f ++#define NVSW_REG_VR2_ALERT_MASK_OFFSET 0x2560 ++#define NVSW_REG_PS_DC_OK_OFFSET 0x2567 ++#define NVSW_REG_PS_DC_OK_EVENT_OFFSET 0x2568 ++#define NVSW_REG_PS_DC_OK_MASK_OFFSET 0x2569 ++#define NVSW_REG_CPLD8_PN_OFFSET 0x2573 ++#define NVSW_REG_CPLD8_PN1_OFFSET 0x2574 ++#define NVSW_REG_CPLD6_VER_OFFSET 0x2575 ++#define NVSW_REG_PG3_OFFSET 0x2576 ++#define NVSW_REG_PG3_EVENT_OFFSET 0x2577 ++#define NVSW_REG_PG3_MASK_OFFSET 0x2578 ++#define NVSW_REG_PG4_OFFSET 0x2579 ++#define NVSW_REG_PG4_EVENT_OFFSET 0x257a ++#define NVSW_REG_PG4_MASK_OFFSET 0x257b ++#define NVSW_REG_CPLD6_PN_OFFSET 0x257c ++#define NVSW_REG_CPLD6_PN1_OFFSET 0x257d ++#define NVSW_REG_CPLD9_PN_OFFSET 0x257e ++#define NVSW_REG_CPLD9_PN1_OFFSET 0x257f ++#define NVSW_REG_CPLD10_PN_OFFSET 0x2580 ++#define NVSW_REG_CPLD10_PN1_OFFSET 0x2581 ++#define NVSW_REG_ASIC3_HEALTH_OFFSET 0x2582 ++#define NVSW_REG_ASIC3_EVENT_OFFSET 0x2583 ++#define NVSW_REG_ASIC3_MASK_OFFSET 0x2584 ++#define NVSW_REG_ASIC4_HEALTH_OFFSET 0x2585 ++#define NVSW_REG_ASIC4_EVENT_OFFSET 0x2586 ++#define NVSW_REG_ASIC4_MASK_OFFSET 0x2587 ++#define NVSW_REG_FAN_OFFSET 0x2588 ++#define NVSW_REG_FAN_EVENT_OFFSET 0x2589 ++#define NVSW_REG_FAN_MASK_OFFSET 0x258a ++#define NVSW_REG_CPLD5_VER_OFFSET 0x258e ++#define NVSW_REG_CPLD5_PN_OFFSET 0x258f ++#define NVSW_REG_CPLD5_PN1_OFFSET 0x2590 ++#define NVSW_REG_EROT_OFFSET 0x2591 ++#define NVSW_REG_EROT_EVENT_OFFSET 0x2592 ++#define NVSW_REG_EROT_MASK_OFFSET 0x2593 ++#define NVSW_REG_EROT_ERR_OFFSET 0x2594 ++#define NVSW_REG_EROT_ERR_EVENT_OFFSET 0x2595 ++#define NVSW_REG_EROT_ERR_MASK_OFFSET 0x2596 ++#define NVSW_REG_PWRB_OFFSET 0x2597 ++#define NVSW_REG_PWRB_EVENT_OFFSET 0x2598 ++#define NVSW_REG_PWRB_MASK_OFFSET 0x2599 ++#define NVSW_REG_FRU1_OFFSET 0x25ac ++#define NVSW_REG_FRU1_EVENT_OFFSET 0x25ad ++#define NVSW_REG_FRU1_MASK_OFFSET 0x25ae ++#define NVSW_REG_LEAK_OFFSET 0x25af ++#define NVSW_REG_LEAK_EVENT_OFFSET 0x25b0 ++#define NVSW_REG_LEAK_MASK_OFFSET 0x25b1 ++#define NVSW_REG_CURRENT_TEMP_RO 0x25c0 ++#define NVSW_REG_SWITCH_IC_QTY 0x25c1 ++#define NVSW_REG_GP4_RO_OFFSET 0x25c2 ++#define NVSW_REG_SPI_CHNL_SELECT 0x25c3 ++#define NVSW_REG_CPLD5_MVER_OFFSET 0x25c4 ++#define NVSW_REG_CPLD8_VER_OFFSET 0x25c8 ++#define NVSW_REG_CPLD9_VER_OFFSET 0x25c9 ++#define NVSW_REG_CPLD10_VER_OFFSET 0x25ca ++#define NVSW_REG_CPLD9B_PN_OFFSET 0x25cb ++#define NVSW_REG_CPLD9B_PN1_OFFSET 0x25cc ++#define NVSW_REG_WD2_TMR_OFFSET 0x25cd ++#define NVSW_REG_WD2_TLEFT_OFFSET 0x25ce ++#define NVSW_REG_WD2_ACT_OFFSET 0x25cf ++#define NVSW_REG_WD3_TMR_OFFSET 0x25d1 ++#define NVSW_REG_WD3_TLEFT_OFFSET 0x25d2 ++#define NVSW_REG_WD3_ACT_OFFSET 0x25d3 ++#define NVSW_REG_CPLD7_MVER_OFFSET 0x25d5 ++#define NVSW_REG_CPLD8_MVER_OFFSET 0x25d6 ++#define NVSW_REG_CPLD9_MVER_OFFSET 0x25d7 ++#define NVSW_REG_CPLD10_MVER_OFFSET 0x25d8 ++#define NVSW_REG_CPLD6_MVER_OFFSET 0x25d9 ++#define NVSW_REG_MUX0_OFFSET 0x25da ++#define NVSW_REG_MUX1_OFFSET 0x25db ++#define NVSW_REG_MUX2_OFFSET 0x25dc ++#define NVSW_REG_CPLD1_MVER_OFFSET 0x25de ++#define NVSW_REG_CPLD2_MVER_OFFSET 0x25df ++#define NVSW_REG_CPLD3_MVER_OFFSET 0x25e0 ++#define NVSW_REG_CPLD4_MVER_OFFSET 0x25e1 ++#define NVSW_REG_UFM_VERSION_OFFSET 0x25e2 ++#define NVSW_REG_PWM1_OFFSET 0x25e3 ++#define NVSW_REG_TACHO1_OFFSET 0x25e4 ++#define NVSW_REG_TACHO2_OFFSET 0x25e5 ++#define NVSW_REG_TACHO3_OFFSET 0x25e6 ++#define NVSW_REG_TACHO4_OFFSET 0x25e7 ++#define NVSW_REG_TACHO5_OFFSET 0x25e8 ++#define NVSW_REG_TACHO6_OFFSET 0x25e9 ++#define NVSW_REG_TACHO7_OFFSET 0x25eb ++#define NVSW_REG_TACHO8_OFFSET 0x25ec ++#define NVSW_REG_TACHO9_OFFSET 0x25ed ++#define NVSW_REG_TACHO10_OFFSET 0x25ee ++#define NVSW_REG_TACHO11_OFFSET 0x25ef ++#define NVSW_REG_TACHO12_OFFSET 0x25f0 ++#define NVSW_REG_FAN_CAP1_OFFSET 0x25f5 ++#define NVSW_REG_FAN_DRW_CAP_OFFSET 0x25f7 ++#define NVSW_REG_TACHO_SPEED_OFFSET 0x25f8 ++#define NVSW_REG_PSU_I2C_CAP_OFFSET 0x25f9 ++#define NVSW_REG_CONFIG1_OFFSET 0x25fb ++#define NVSW_REG_CONFIG2_OFFSET 0x25fc ++#define NVSW_REG_CONFIG3_OFFSET 0x25fd ++#define NVSW_REG_SYS_CPBLTY0 NVSW_REG_PSU_I2C_CAP_OFFSET ++#define NVSW_REG_MIN 0x2500 ++#define NVSW_REG_MAX 0x26ff ++ ++#define NVSW_AGGR_MASK_COMEX BIT(0) ++#define NVSW_LOW_AGGR_MASK_I2C BIT(6) ++#define NVSW_AGGR_MASK BIT(2) ++#define NVSW_LOW_AGGR_MASK_LOW 0xe0 ++#define NVSW_REG_FRU1_MASK GENMASK(3, 0) ++#define NVSW_LEAK_MASK GENMASK(5, 0) ++#define NVSW_SSD_I2C_ALERT_MASK BIT(0) ++#define NVSW_WD_EXP_MASK BIT(1) ++#define NVSW_5V_USB_MASK BIT(2) ++#define NVSW_PCB_TEMP1_MASK BIT(3) ++#define NVSW_PCB_TEMP2_MASK BIT(4) ++#define NVSW_SGMII_MASK BIT(5) ++#define NVSW_SDD_PG_MASK BIT(6) ++#define NVSW_LEAK_AGGR_MASK BIT(7) ++#define NVSW_EROT_MASK GENMASK(2, 0) ++#define NVSW_FAN_NG_MASK GENMASK(6, 0) ++#define NVSW_ASIC_MASK GENMASK(1, 0) ++#define NVSW_ASIC2_MASK GENMASK(3, 2) ++#define NVSW_ASIC3_MASK GENMASK(5, 4) ++#define NVSW_ASIC4_MASK GENMASK(7, 6) ++#define NVSW_ASICS_MASK GENMASK(7, 0) ++#define NVSW_LOW_AGGR_MASK_ASIC1 BIT(0) ++#define NVSW_LOW_AGGR_MASK_ASIC2 BIT(1) ++#define NVSW_LOW_AGGR_MASK_ASIC3 BIT(2) ++#define NVSW_PWR_BUTTON_MASK BIT(0) ++#define NVSW_AMB_TEMP_SENSE_MASK BIT(1) ++#define NVSW_GRACEFUL_POWER_OFF_MASK BIT(2) ++#define NVSW_CPU_POWER_OFF_READY_MASK BIT(3) ++#define NVSW_CPU_RESET_MASK BIT(4) ++#define NVSW_APML_SMB_ALERT_MASK BIT(5) ++#define NVSW_CPU_UNEXP_POWER_OFF_MASK BIT(6) ++#define NVSW_UID_PUSH_BUTTON_MASK BIT(7) ++#define NVSW_RTC_MASK BIT(0) ++#define NVSW_HOT_SWAP_ALERT_MASK BIT(1) ++#define NVSW_LED_LO_NIBBLE_MASK GENMASK(7, 4) ++#define NVSW_LED_HI_NIBBLE_MASK GENMASK(3, 0) ++#define NVSW_UART_SEL_MASK GENMASK(7, 6) ++#define NVSW_UART_BAUD_MASK GENMASK(3, 0) ++#define NVSW_BIOS_STATUS_MASK GENMASK(3, 1) ++#define NVSW_REG_RESET_MASK BIT(1) ++#define NVSW_MASTER_MASK BIT(5) ++#define NVSW_FU_CAP_MASK GENMASK(1, 0) ++#define NVSW_WD_RESET_ACT_MASK GENMASK(7, 1) ++#define NVSW_WD_FAN_ACT_MASK (GENMASK(7, 0) & ~BIT(4)) ++#define NVSW_WD_TYPE2_TO_MASK 0 ++#define NVSW_WD3_DFLT_TIMEOUT 600 ++#define NVSW_I2C_CAP_BIT 0x04 ++#define NVSW_I2C_CAP_MASK GENMASK(5, NVSW_I2C_CAP_BIT) ++#define NVSW_I2C_TRAN_END_L BIT(5) ++ ++#define NVSW_NR_NONE -1 ++#define NVSW_MUX_MAX 2 ++#define NVSW_WD_MAX 2 ++ ++ /* The system type. */ ++enum nvsw_core_hid_type { ++ HID162, ++ HID176, ++ HID177, ++ HID180, ++ HID181, ++ HID182, ++ HID185, ++ HID189, ++ HID191, ++ HID193, ++}; ++ ++ /* The system register map type. */ ++enum nvsw_core_regmap_type { ++ REGMAP_IO, ++ REGMAP_I2C, ++}; ++ ++/* nvsw_core - device private data ++ * hid: hardware Id; ++ * regmap_type: register map type; ++ * @device: device; ++ * @client: I2C client device; ++ * @np: device node; ++ * @regmap: device register map; ++ * @regio_data: register access platform data; ++ * @regio: register access device; ++ * @hotplug_data: hotplug platform data; ++ * @hotplug: hotplug device; ++ * @led: led devices; ++ * @led_data: led platform data; ++ * @fan - fan device; ++ * @fan_data - fan platform data; ++ * @wd - watchdog device; ++ * @wd_data - watchdog platform data; ++ * @port_map - io port mapping; ++ * @set_reg_default - set default registers callback; ++ * @mux_init: mux initialization callback; ++ * @mux_exit: mux de-initialization callback; ++ * @mux: mux devices; ++ * @mux_num: number of mux device; ++ */ ++struct nvsw_core { ++ enum nvsw_core_hid_type hid; ++ enum nvsw_core_regmap_type regmap_type; ++ struct device *dev; ++ struct i2c_client *client; ++ struct device_node *np; ++ struct regmap *regmap; ++ struct mlxreg_core_platform_data *regio_data; ++ struct platform_device *regio; ++ struct mlxreg_core_hotplug_platform_data *hotplug_data; ++ struct platform_device *hotplug; ++ struct platform_device *led; ++ struct mlxreg_core_platform_data *led_data; ++ struct platform_device *fan; ++ struct mlxreg_core_platform_data *fan_data; ++ struct platform_device *wd[NVSW_WD_MAX]; ++ struct mlxreg_core_platform_data *wd_data[NVSW_WD_MAX]; ++ void __iomem *(*port_map)(struct nvsw_core *nvsw_core); ++ int (*set_reg_default)(struct regmap *regmap); ++ int (*mux_init)(struct nvsw_core *nvsw_core); ++ void (*mux_exit)(struct nvsw_core *nvsw_core); ++ struct platform_device *mux[NVSW_MUX_MAX]; ++ u32 mux_num; ++}; ++ ++/* Platform core init/exit functions. */ ++int nvsw_core_init(struct nvsw_core *nvsw_core); ++void nvsw_core_exit(struct nvsw_core *nvsw_core); ++ ++#endif /* !defined(__NVSW_H__) */ ++ +-- +2.34.1 + diff --git a/patches-sonic/0047-hwmon-mlxreg-fan-Prevent-fans-from-getting-stuck-at-.patch b/patches-sonic/0047-hwmon-mlxreg-fan-Prevent-fans-from-getting-stuck-at-.patch new file mode 100644 index 000000000..e17e68ccf --- /dev/null +++ b/patches-sonic/0047-hwmon-mlxreg-fan-Prevent-fans-from-getting-stuck-at-.patch @@ -0,0 +1,50 @@ +From 270544d85808eae4a873cd2506451a3d96f23678 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Wed, 30 Jul 2025 20:41:33 +0300 +Subject: [PATCH hwmon 2/2] hwmon: mlxreg-fan: Prevent fans from getting stuck + at 0 RPM + +The fans controlled by the driver can get stuck at 0 RPM if they are +configured below a 20% duty cycle. The driver tries to avoid this by +enforcing a minimum duty cycle of 20%, but this is done after the fans +are registered with the thermal subsystem. This is too late as the +thermal subsystem can set their current state before the driver is able +to enforce the minimum duty cycle. + +Fix by setting the minimum duty cycle before registering the fans with +the thermal subsystem. + +Fixes: d7efb2ebc7b3 ("hwmon: (mlxreg-fan) Extend driver to support multiply cooling devices") +Reported-by: Nikolay Aleksandrov +Tested-by: Nikolay Aleksandrov +Signed-off-by: Ido Schimmel +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/mlxreg-fan.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c +index 0e9ce70a..2cd0b9cc 100644 +--- a/drivers/hwmon/mlxreg-fan.c ++++ b/drivers/hwmon/mlxreg-fan.c +@@ -581,15 +581,14 @@ static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan) + if (!pwm->connected) + continue; + pwm->fan = fan; ++ /* Set minimal PWM speed. */ ++ pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY); + pwm->cdev = devm_thermal_of_cooling_device_register(dev, NULL, mlxreg_fan_name[i], + pwm, &mlxreg_fan_cooling_ops); + if (IS_ERR(pwm->cdev)) { + dev_err(dev, "Failed to register cooling device\n"); + return PTR_ERR(pwm->cdev); + } +- +- /* Set minimal PWM speed. */ +- pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY); + } + + return 0; +-- +2.34.1 + diff --git a/patches-sonic/0052-platform-mellanox-Introduce-support-for-switches-equ.patch b/patches-sonic/0052-platform-mellanox-Introduce-support-for-switches-equ.patch new file mode 100644 index 000000000..91ff31d94 --- /dev/null +++ b/patches-sonic/0052-platform-mellanox-Introduce-support-for-switches-equ.patch @@ -0,0 +1,313 @@ +From 8434a03c6ba631dcfa6e1bbe5d4d42cdeb84caaf Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 24 Jul 2023 11:10:50 +0000 +Subject: [PATCH 52/78] platform: mellanox: Introduce support for switches + equipped with new FPGA device + +Add support for Nvidia MQM97xx and MSN47xx family switches equipped with +new FPGA device. + +These switches are based on previous generation of MQM97xx and MSN47xx +switches, but COMe module uses new FPGA device. + +Platform configuration for new switches is based on the new VMOD0016 +class. Configuration is extended to support new register map with +callbacks supporting indirect addressing for PCIe-to-LPC bridge. +This bridge provides interface between FPGA at COMe board (directly +connected to CPU PCIe root complex) to CPLDs on switch board (which +cannot be connected directly to PCIe root complex). + +Signed-off-by: Vadim Pasternak +Reviewed-by: Michael Shych +--- + drivers/platform/mellanox/mlx-platform.c | 197 ++++++++++++++++++++++++++++++- + 1 file changed, 196 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index 4242b13..c7c3aaa 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -200,7 +200,9 @@ + #define MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET 0xfd + #define MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET 0xfe + #define MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET 0xff +- ++#define MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET 0x100 ++#define MLXPLAT_CPLD_LPC_REG_EXT_MID_OFFSET 0x195 ++#define MLXPLAT_CPLD_LPC_REG_EXT_MAX_OFFSET 0x1ff + #define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 + + #define MLXPLAT_CPLD_LPC_PIO_OFFSET 0x10000UL +@@ -310,6 +312,7 @@ + /* Maximum number of possible physical buses equipped on system */ + #define MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM 16 + #define MLXPLAT_CPLD_MAX_PHYS_EXT_ADAPTER_NUM 24 ++#define MLXPLAT_CPLD_DEFAULT_MUX_HOTPLUG_VECTOR 0 + + /* Number of channels in group */ + #define MLXPLAT_CPLD_GRP_CHNL_NUM 8 +@@ -375,6 +378,21 @@ + #define PCI_DEVICE_ID_LATTICE_I2C_BRIDGE 0x9c2f + #define PCI_DEVICE_ID_LATTICE_JTAG_BRIDGE 0x9c30 + #define PCI_DEVICE_ID_LATTICE_LPC_BRIDGE 0x9c32 ++#define MLXPLAT_FPGA_PCI_BAR0_SIZE 0x4000 ++#define MLXPLAT_FPGA_PCI_BASE_OFFSET 0x00000000 ++#define MLXPLAT_FPGA_PCI_MSB_ADDR 0x25 ++#define MLXPLAT_FPGA_PCI_MSB_EXT_ADDR 0x20 ++#define MLXPLAT_FPGA_PCI_LSB_ADDR_OFFSET MLXPLAT_FPGA_PCI_BASE_OFFSET ++#define MLXPLAT_FPGA_PCI_MSB_ADDR_OFFSET (MLXPLAT_FPGA_PCI_BASE_OFFSET + 0x01) ++#define MLXPLAT_FPGA_PCI_DATA_OUT_OFFSET (MLXPLAT_FPGA_PCI_BASE_OFFSET + 0x02) ++#define MLXPLAT_FPGA_PCI_DATA_IN_OFFSET (MLXPLAT_FPGA_PCI_BASE_OFFSET + 0x03) ++#define MLXPLAT_FPGA_PCI_CTRL_OFFSET (MLXPLAT_FPGA_PCI_BASE_OFFSET + 0x04) ++#define MLXPLAT_FPGA_PCI_STAT_OFFSET (MLXPLAT_FPGA_PCI_BASE_OFFSET + 0x05) ++ ++#define MLXPLAT_FPGA_PCI_CTRL_READ BIT(0) ++#define MLXPLAT_FPGA_PCI_CTRL_WRITE BIT(1) ++#define MLXPLAT_FPGA_PCI_COMPLETED GENMASK(1, 0) ++#define MLXPLAT_FPGA_PCI_TO 50 /* usec */ + + /* mlxplat_priv - platform private data + * @pdev_i2c - i2c controller platform device +@@ -492,6 +510,28 @@ static struct i2c_mux_reg_platform_data mlxplat_default_mux_data[] = { + + }; + ++/* Default channels vector for regmap mux. */ ++static int mlxplat_default_regmap_mux_chan[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; ++ ++/* Platform regmap mux data */ ++static struct i2c_mux_regmap_platform_data mlxplat_default_regmap_mux_data[] = { ++ { ++ .parent = 1, ++ .chan_ids = mlxplat_default_regmap_mux_chan, ++ .num_adaps = ARRAY_SIZE(mlxplat_default_regmap_mux_chan), ++ .sel_reg_addr = MLXPLAT_CPLD_LPC_REG_I2C_CH1_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 1, ++ .chan_ids = mlxplat_default_regmap_mux_chan, ++ .num_adaps = ARRAY_SIZE(mlxplat_default_regmap_mux_chan), ++ .sel_reg_addr = MLXPLAT_CPLD_LPC_REG_I2C_CH2_OFFSET, ++ .reg_size = 1, ++ }, ++ ++}; ++ + /* Platform mux configuration variables */ + static int mlxplat_max_adap_num; + static int mlxplat_mux_num; +@@ -4266,6 +4306,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0200, + }, + { ++ .label = "kexec_activated", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { + .label = "erot1_reset", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), +@@ -6456,6 +6502,7 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_PWM3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWM4_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET ... MLXPLAT_CPLD_LPC_REG_EXT_MAX_OFFSET: + return true; + } + return false; +@@ -6639,6 +6686,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET ... MLXPLAT_CPLD_LPC_REG_EXT_MAX_OFFSET: + return true; + } + return false; +@@ -6811,6 +6859,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET: + case MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET ... MLXPLAT_CPLD_LPC_REG_EXT_MAX_OFFSET: + return true; + } + return false; +@@ -6841,6 +6890,14 @@ static const struct reg_default mlxplat_mlxcpld_regmap_ng400[] = { + { MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, 0x00 }, + }; + ++static const struct reg_default mlxplat_mlxcpld_regmap_bf3[] = { ++ { MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, 0xc1 }, ++ { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, 0x00 }, ++}; ++ + static const struct reg_default mlxplat_mlxcpld_regmap_rack_switch[] = { + { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, MLXPLAT_REGMAP_NVSWITCH_PWM_DEFAULT }, + { MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, 0x00 }, +@@ -6990,6 +7047,114 @@ static const struct regmap_config mlxplat_mlxcpld_regmap_config_smart_switch = { + .reg_write = mlxplat_mlxcpld_reg_write, + }; + ++/* Wait completion routine for indirect access for register map */ ++static int mlxplat_fpga_completion_wait(struct mlxplat_mlxcpld_regmap_context *ctx) ++{ ++ unsigned long end; ++ u8 status; ++ ++ end = jiffies + msecs_to_jiffies(MLXPLAT_FPGA_PCI_TO); ++ do { ++ status = ioread8(ctx->base + MLXPLAT_FPGA_PCI_STAT_OFFSET); ++ if (!(status & MLXPLAT_FPGA_PCI_COMPLETED)) ++ return 0; ++ cond_resched(); ++ } while (time_before(jiffies, end)); ++ ++ return -EIO; ++} ++ ++/* Read callback for indirect register map access */ ++static int mlxplat_fpga_reg_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ struct mlxplat_mlxcpld_regmap_context *ctx = context; ++ unsigned int msb_off = MLXPLAT_FPGA_PCI_MSB_ADDR; ++ int err; ++ ++ if (reg >= MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET) { ++ if (reg <= MLXPLAT_CPLD_LPC_REG_EXT_MID_OFFSET) { ++ /* Access to 2-nd FPGA bank */ ++ *val = ioread8(i2c_bridge_addr + reg - ++ MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET); ++ return 0; ++ } ++ /* Access to 3-rd FPGA bank */ ++ reg -= MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET; ++ msb_off = MLXPLAT_FPGA_PCI_MSB_EXT_ADDR; ++ } ++ ++ /* Verify there is no pending transactions */ ++ err = mlxplat_fpga_completion_wait(ctx); ++ if (err) ++ return err; ++ ++ /* Set address in register space */ ++ iowrite8(msb_off, ctx->base + MLXPLAT_FPGA_PCI_MSB_ADDR_OFFSET); ++ iowrite8(reg, ctx->base + MLXPLAT_FPGA_PCI_LSB_ADDR_OFFSET); ++ /* Activate read operation */ ++ iowrite8(MLXPLAT_FPGA_PCI_CTRL_READ, ctx->base + MLXPLAT_FPGA_PCI_CTRL_OFFSET); ++ /* Verify transaction completion */ ++ err = mlxplat_fpga_completion_wait(ctx); ++ if (err) ++ return err; ++ ++ /* Read data */ ++ *val = ioread8(ctx->base + MLXPLAT_FPGA_PCI_DATA_IN_OFFSET); ++ ++ return 0; ++} ++ ++/* Write callback for indirect register map access */ ++static int mlxplat_fpga_reg_write(void *context, unsigned int reg, unsigned int val) ++{ ++ struct mlxplat_mlxcpld_regmap_context *ctx = context; ++ unsigned int msb_off = MLXPLAT_FPGA_PCI_MSB_ADDR; ++ int err; ++ ++ if (reg >= MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET) { ++ if (reg <= MLXPLAT_CPLD_LPC_REG_EXT_MID_OFFSET) { ++ /* Access to 2-nd FPGA bank */ ++ iowrite8(val, i2c_bridge_addr + reg - MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET); ++ /* Flush modification */ ++ wmb(); ++ return 0; ++ } ++ ++ /* Access to 3-rd FPGA bank */ ++ reg -= MLXPLAT_CPLD_LPC_REG_EXT_MIN_OFFSET; ++ msb_off = MLXPLAT_FPGA_PCI_MSB_EXT_ADDR; ++ } ++ ++ /* Verify there is no pending transactions */ ++ err = mlxplat_fpga_completion_wait(ctx); ++ if (err) ++ return err; ++ ++ /* Set address in register space */ ++ iowrite8(msb_off, ctx->base + MLXPLAT_FPGA_PCI_MSB_ADDR_OFFSET); ++ iowrite8(reg, ctx->base + MLXPLAT_FPGA_PCI_LSB_ADDR_OFFSET); ++ /* Set data to be written */ ++ iowrite8(val, ctx->base + MLXPLAT_FPGA_PCI_DATA_OUT_OFFSET); ++ /* Activate write operation */ ++ iowrite8(MLXPLAT_FPGA_PCI_CTRL_WRITE, ctx->base + MLXPLAT_FPGA_PCI_CTRL_OFFSET); ++ ++ return mlxplat_fpga_completion_wait(ctx); ++} ++ ++static const struct regmap_config mlxplat_fpga_regmap_config_bf3_comex_default = { ++ .reg_bits = 9, ++ .val_bits = 8, ++ .max_register = 511, ++ .cache_type = REGCACHE_FLAT, ++ .writeable_reg = mlxplat_mlxcpld_writeable_reg, ++ .readable_reg = mlxplat_mlxcpld_readable_reg, ++ .volatile_reg = mlxplat_mlxcpld_volatile_reg, ++ .reg_defaults = mlxplat_mlxcpld_regmap_bf3, ++ .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_bf3), ++ .reg_read = mlxplat_fpga_reg_read, ++ .reg_write = mlxplat_fpga_reg_write, ++}; ++ + static struct resource mlxplat_mlxcpld_resources[] = { + [0] = DEFINE_RES_IRQ_NAMED(MLXPLAT_CPLD_LPC_SYSIRQ, "mlxreg-hotplug"), + }; +@@ -7398,6 +7563,30 @@ static int __init mlxplat_dmi_smart_switch_matched(const struct dmi_system_id *d + return mlxplat_register_platform_device(); + } + ++static int __init mlxplat_dmi_bf3_comex_default_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; ++ mlxplat_mux_hotplug_num = MLXPLAT_CPLD_DEFAULT_MUX_HOTPLUG_VECTOR; ++ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_regmap_mux_data); ++ mlxplat_mux_regmap_data = mlxplat_default_regmap_mux_data; ++ mlxplat_hotplug = &mlxplat_mlxcpld_ext_data; ++ mlxplat_hotplug->deferred_nr = ++ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; ++ mlxplat_led = &mlxplat_default_ng_led_data; ++ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; ++ mlxplat_fan = &mlxplat_default_fan_data; ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) ++ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; ++ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; ++ mlxplat_regmap_config = &mlxplat_fpga_regmap_config_bf3_comex_default; ++ mlxplat_reboot_nb = &mlxplat_reboot_default_nb; ++ pm_power_off = mlxplat_poweroff; ++ ++ return 1; ++} ++ + static int __init mlxplat_dmi_ng400_hi171_matched(const struct dmi_system_id *dmi) + { + unsigned int i; +@@ -7510,6 +7699,12 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + }, + }, + { ++ .callback = mlxplat_dmi_bf3_comex_default_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0016"), ++ }, ++ }, ++ { + .callback = mlxplat_dmi_l1_switch_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0017"), +-- +2.8.4 + diff --git a/patches-sonic/0053-platform-mellanox-mlx-platform-Add-support-for-new-X.patch b/patches-sonic/0053-platform-mellanox-mlx-platform-Add-support-for-new-X.patch new file mode 100644 index 000000000..e42d1b202 --- /dev/null +++ b/patches-sonic/0053-platform-mellanox-mlx-platform-Add-support-for-new-X.patch @@ -0,0 +1,898 @@ +From 09599160f84a34b5158368ccea34c036c66f480d Mon Sep 17 00:00:00 2001 +From: Oleksandr Shamray +Date: Mon, 4 Aug 2025 19:38:39 +0300 +Subject: [PATCH 53/78] platform: mellanox: mlx-platform: Add support for new + XDR systems + +Add support for QM3400 and QM3000, Nvidia XDR switches. + +QM3400 is a 57.6Tbps switch based on Nvidia Quantum-3 ASIC. +It provides up-to 800Gbps full bidirectional bandwidth per port. +The system supports 36 OSFP cages and fits into standard 2U racks. + +QM3400 Features: + - 36 OSFP ports supporting 2.5Gbps - 800Gbps speeds. + - Air-cooled with 4 + 1 redundant fan units. + - 2 + 2 redundant 2000W PSUs. + - System management board based on Intel Coffee Lake CPU + with secure-boot support. + +QM3000 is a 115.2Tbps switch based on Nvidia Quantum-3 ASIC. +It provides up-to 800Gbps full bidirectional bandwidth per port. +The system supports 72 OSFP cages and fits into standard 4U racks. + +QM3000 Features: + - 72 OSFP ports or 144 aggregated XDR ports supporting 800Gbps speeds. + - Air-cooled with 9 + 1 redundant fan units. + - 4 + 4 redundant 2000W PSUs. + - System management board based on Intel Coffee Lake CPU + with secure-boot support. + +Signed-off-by: Felix Radensky +Reviewed-by: Vadim Pasternak +--- + drivers/platform/mellanox/mlx-platform.c | 421 +++++++++++++++++++++++++++++-- + 1 file changed, 406 insertions(+), 15 deletions(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index c7c3aaa..de3dbaf 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -102,12 +102,24 @@ + #define MLXPLAT_CPLD_LPC_REG_LC_IN_OFFSET 0x70 + #define MLXPLAT_CPLD_LPC_REG_LC_IN_EVENT_OFFSET 0x71 + #define MLXPLAT_CPLD_LPC_REG_LC_IN_MASK_OFFSET 0x72 ++#define MLXPLAT_CPLD_LPC_REG_CPLD6_VER_OFFSET 0x7c ++#define MLXPLAT_CPLD_LPC_REG_CPLD6_PN_OFFSET 0x7d ++#define MLXPLAT_CPLD_LPC_REG_CPLD6_PN1_OFFSET 0x7e ++#define MLXPLAT_CPLD_LPC_REG_ASIC3_HEALTH_OFFSET 0x82 ++#define MLXPLAT_CPLD_LPC_REG_ASIC3_EVENT_OFFSET 0x83 ++#define MLXPLAT_CPLD_LPC_REG_ASIC3_MASK_OFFSET 0x84 ++#define MLXPLAT_CPLD_LPC_REG_ASIC4_HEALTH_OFFSET 0x85 ++#define MLXPLAT_CPLD_LPC_REG_ASIC4_EVENT_OFFSET 0x86 ++#define MLXPLAT_CPLD_LPC_REG_ASIC4_MASK_OFFSET 0x87 + #define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88 + #define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89 + #define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a + #define MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET 0x8b + #define MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET 0x8c + #define MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET 0x8d ++#define MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET 0x8b ++#define MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET 0x8c ++#define MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET 0x8d + #define MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET 0x8e + #define MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET 0x8f + #define MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET 0x90 +@@ -258,6 +270,8 @@ + #define MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2 BIT(2) + #define MLXPLAT_CPLD_LOW_AGGR_MASK_PWR_BUT GENMASK(5, 4) + #define MLXPLAT_CPLD_LOW_AGGR_MASK_I2C BIT(6) ++#define MLXPLAT_CPLD_LOW_AGGR_MASK_MULTI_ASICS GENMASK(3, 0) ++#define MLXPLAT_CPLD_LOW_AGGR_MASK_FRU BIT(7) + #define MLXPLAT_CPLD_PSU_MASK GENMASK(1, 0) + #define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0) + #define MLXPLAT_CPLD_PSU_EXT_MASK GENMASK(3, 0) +@@ -268,13 +282,18 @@ + #define MLXPLAT_CPLD_ASIC_MASK GENMASK(1, 0) + #define MLXPLAT_CPLD_ASIC_XDR_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_FAN_NG_MASK GENMASK(6, 0) ++#define MLXPLAT_CPLD_FAN_QMB8700_MASK GENMASK(5, 0) + #define MLXPLAT_CPLD_FAN_XDR_MASK GENMASK(7, 0) ++#define MLXPLAT_CPLD_FAN_XDR_EXT_MASK GENMASK(1, 0) + #define MLXPLAT_CPLD_LED_LO_NIBBLE_MASK GENMASK(7, 4) + #define MLXPLAT_CPLD_LED_HI_NIBBLE_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_VOLTREG_UPD_MASK GENMASK(5, 4) + #define MLXPLAT_CPLD_GWP_MASK GENMASK(0, 0) + #define MLXPLAT_CPLD_EROT_MASK GENMASK(1, 0) + #define MLXPLAT_CPLD_FU_CAP_MASK GENMASK(1, 0) ++#define MLXPLAT_CPLD_PSU_CAP_MASK GENMASK(3, 0) ++#define MLXPLAT_CPLD_FAN_CAP_MASK GENMASK(7, 0) ++#define MLXPLAT_CPLD_ASIC_CAP_MASK GENMASK(7, 0) + #define MLXPLAT_CPLD_BIOS_STATUS_MASK GENMASK(3, 1) + #define MLXPLAT_CPLD_DPU_MASK GENMASK(3, 0) + #define MLXPLAT_CPLD_PWR_BUTTON_MASK BIT(0) +@@ -326,6 +345,7 @@ + #define MLXPLAT_CPLD_CH4_ETH_MODULAR 51 + #define MLXPLAT_CPLD_CH2_RACK_SWITCH 18 + #define MLXPLAT_CPLD_CH2_NG800 34 ++#define MLXPLAT_CPLD_CH2_XDR 66 + + /* Number of LPC attached MUX platform devices */ + #define MLXPLAT_CPLD_LPC_MUX_DEVS 4 +@@ -682,6 +702,38 @@ static struct i2c_mux_reg_platform_data mlxplat_ng800_mux_data[] = { + + }; + ++/* Platform channels for XDR system family */ ++static const int mlxplat_xdr_channels[] = { ++ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ++ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ++ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, ++ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 ++}; ++ ++/* Platform XDR mux data */ ++static struct i2c_mux_reg_platform_data mlxplat_xdr_mux_data[] = { ++ { ++ .parent = 1, ++ .base_nr = MLXPLAT_CPLD_CH1, ++ .write_only = 1, ++ .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG1, ++ .reg_size = 1, ++ .idle_in_use = 1, ++ .values = mlxplat_xdr_channels, ++ .n_values = ARRAY_SIZE(mlxplat_xdr_channels), ++ }, ++ { ++ .parent = 1, ++ .base_nr = MLXPLAT_CPLD_CH2_XDR, ++ .write_only = 1, ++ .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG2, ++ .reg_size = 1, ++ .idle_in_use = 1, ++ .values = mlxplat_msn21xx_channels, ++ .n_values = ARRAY_SIZE(mlxplat_msn21xx_channels), ++ }, ++}; ++ + /* Platform hotplug devices */ + static struct i2c_board_info mlxplat_mlxcpld_pwr[] = { + { +@@ -2471,7 +2523,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_rack_switch_data = { + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, + }; + +-/* Platform hotplug XDR and smart switch system family data */ ++/* Platform hotplug XDR system family data */ + static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + { + .label = "psu1", +@@ -2479,6 +2531,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +@@ -2487,6 +2540,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +@@ -2495,6 +2549,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +@@ -2503,6 +2558,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +@@ -2511,6 +2567,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(4), + .slot = 5, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +@@ -2519,6 +2576,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(5), + .slot = 6, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +@@ -2527,6 +2585,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(6), + .slot = 7, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { +@@ -2535,6 +2594,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + .mask = BIT(7), + .slot = 8, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + }; +@@ -2546,6 +2606,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, +@@ -2555,6 +2616,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, +@@ -2564,6 +2626,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_ext_pwr[0], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, +@@ -2573,6 +2636,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_ext_pwr[1], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, +@@ -2582,6 +2646,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(4), + .slot = 5, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[0], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, +@@ -2591,6 +2656,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(5), + .slot = 6, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[1], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, +@@ -2600,6 +2666,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(6), + .slot = 7, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[2], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, +@@ -2609,11 +2676,23 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + .mask = BIT(7), + .slot = 8, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[3], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, + }; + ++static int mlxplat_mlxcpld_xdr_pwr_nr_fixup[] = { ++ MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ MLXPLAT_CPLD_PSU_XDR_NR, ++ MLXPLAT_CPLD_PSU_XDR_NR, ++ MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ MLXPLAT_CPLD_PSU_XDR_NR, ++ MLXPLAT_CPLD_PSU_XDR_NR, ++}; ++ + static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + { + .label = "fan1", +@@ -2621,6 +2700,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(0), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +@@ -2630,6 +2710,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(1), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +@@ -2639,6 +2720,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(2), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +@@ -2648,6 +2730,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(3), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +@@ -2657,6 +2740,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(4), + .slot = 5, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(4), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +@@ -2666,6 +2750,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(5), + .slot = 6, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(5), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +@@ -2675,6 +2760,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(6), + .slot = 7, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(6), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +@@ -2684,11 +2770,55 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + .mask = BIT(7), + .slot = 8, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .bit = BIT(7), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + }; + ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_ext_fan_items_data[] = { ++ { ++ .label = "fan9", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ .mask = BIT(0), ++ .slot = 9, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, ++ .bit = BIT(0), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ }, ++ { ++ .label = "fan10", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ .mask = BIT(1), ++ .slot = 10, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, ++ .bit = BIT(1), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ }, ++ { ++ .label = "fan11", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ .mask = BIT(2), ++ .slot = 11, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, ++ .bit = BIT(2), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ }, ++ { ++ .label = "fan12", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ .mask = BIT(3), ++ .slot = 12, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_XDR_MASK, ++ .bit = BIT(3), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ }, ++}; ++ + static struct mlxreg_core_data mlxplat_mlxcpld_xdr_asic1_items_data[] = { + { + .label = "asic1", +@@ -2696,10 +2826,148 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_asic1_items_data[] = { + .mask = MLXPLAT_CPLD_ASIC_MASK, + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + } + }; + ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_asic2_items_data[] = { ++ { ++ .label = "asic2", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC2_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .slot = 2, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ } ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_asic3_items_data[] = { ++ { ++ .label = "asic3", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC3_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .slot = 3, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ } ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_xdr_asic4_items_data[] = { ++ { ++ .label = "asic4", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC4_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, ++ .slot = 4, ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ } ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_xdr_items[] = { ++ { ++ .data = mlxplat_mlxcpld_xdr_psu_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = MLXPLAT_CPLD_PSU_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_psu_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = MLXPLAT_CPLD_PWR_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_PSU_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_pwr_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = MLXPLAT_CPLD_FAN_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_fan_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_ext_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, ++ .mask = MLXPLAT_CPLD_FAN_XDR_EXT_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_FAN_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_ext_fan_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_asic1_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_asic1_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_asic2_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC2_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_asic2_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_asic3_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC3_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_asic3_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = mlxplat_mlxcpld_xdr_asic4_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC4_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_XDR_MASK, ++ .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, ++ .capability_mask = MLXPLAT_CPLD_ASIC_CAP_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_asic4_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_xdr_data = { ++ .items = mlxplat_mlxcpld_xdr_items, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_FRU | MLXPLAT_CPLD_LOW_AGGR_MASK_MULTI_ASICS, ++}; ++ + /* Platform hotplug for smart switch systems families data */ + static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_ready_data[] = { + { +@@ -3758,7 +4026,7 @@ static struct mlxreg_core_platform_data mlxplat_l1_switch_led_data = { + .counter = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_led_data), + }; + +-/* Platform led data for XDR and smart switch systems */ ++/* Platform led data for XDR systems */ + static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + { + .label = "status:green", +@@ -3766,9 +4034,9 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + { +- .label = "status:orange", ++ .label = "status:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, +- .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK + }, + { + .label = "psu:green", +@@ -3776,7 +4044,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + }, + { +- .label = "psu:orange", ++ .label = "psu:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + }, +@@ -3788,7 +4056,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 1, + }, + { +- .label = "fan1:orange", ++ .label = "fan1:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3802,7 +4070,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 2, + }, + { +- .label = "fan2:orange", ++ .label = "fan2:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3816,7 +4084,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 3, + }, + { +- .label = "fan3:orange", ++ .label = "fan3:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3830,7 +4098,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 4, + }, + { +- .label = "fan4:orange", ++ .label = "fan4:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3844,7 +4112,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 5, + }, + { +- .label = "fan5:orange", ++ .label = "fan5:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3858,7 +4126,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 6, + }, + { +- .label = "fan6:orange", ++ .label = "fan6:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3872,7 +4140,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 7, + }, + { +- .label = "fan7:orange", ++ .label = "fan7:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3886,7 +4154,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 8, + }, + { +- .label = "fan8:orange", ++ .label = "fan8:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3900,7 +4168,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 9, + }, + { +- .label = "fan9:orange", ++ .label = "fan9:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -3914,7 +4182,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + .slot = 10, + }, + { +- .label = "fan10:orange", ++ .label = "fan10:amber", + .reg = MLXPLAT_CPLD_LPC_REG_LED8_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, +@@ -4229,6 +4497,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0444, + }, + { ++ .label = "cpld6_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD6_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { + .label = "cpld1_pn", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, + .bit = GENMASK(15, 0), +@@ -4264,6 +4538,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .regnum = 2, + }, + { ++ .label = "cpld6_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD6_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { + .label = "cpld1_version_min", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, + .bit = GENMASK(7, 0), +@@ -4294,6 +4575,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0444, + }, + { ++ .label = "cpld6_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD6_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { + .label = "asic_reset", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), +@@ -4483,6 +4770,43 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0200, + }, + { ++ .label = "psu3_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu4_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu5_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ ++ { ++ .label = "psu6_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu7_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "psu8_on", ++ .reg = MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { + .label = "pwr_cycle", + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), +@@ -4558,6 +4882,20 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { + .mode = 0444, + }, + { ++ .label = "asic3_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC3_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC4_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { + .label = "fan_dir", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, + .bit = GENMASK(7, 0), +@@ -6449,6 +6787,10 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC2_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC2_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC3_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC3_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC4_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC4_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET: +@@ -6516,6 +6858,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_VER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD1_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET: +@@ -6526,6 +6869,8 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_PN_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: +@@ -6581,6 +6926,10 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_ASIC2_HEALTH_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC2_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC2_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC3_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC3_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC4_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC4_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: +@@ -6700,6 +7049,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_VER_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD1_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET: +@@ -6710,6 +7060,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_PN_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_CPLD6_PN1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: +@@ -6763,6 +7115,10 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) + case MLXPLAT_CPLD_LPC_REG_ASIC2_HEALTH_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC2_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC2_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC3_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC3_MASK_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC4_EVENT_OFFSET: ++ case MLXPLAT_CPLD_LPC_REG_ASIC4_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: +@@ -7610,6 +7966,35 @@ static int __init mlxplat_dmi_ng400_hi171_matched(const struct dmi_system_id *dm + return mlxplat_register_platform_device(); + } + ++static int __init mlxplat_dmi_xdr_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ const char *sku; ++ ++ sku = dmi_get_system_info(DMI_PRODUCT_SKU); ++ if (!strcmp(sku, "HI158")) { ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_xdr_pwr_items_data); i++) ++ mlxplat_mlxcpld_xdr_pwr_items_data[i].hpdev.nr = ++ mlxplat_mlxcpld_xdr_pwr_nr_fixup[i]; ++ } ++ ++ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; ++ mlxplat_mux_num = ARRAY_SIZE(mlxplat_xdr_mux_data); ++ mlxplat_mux_data = mlxplat_xdr_mux_data; ++ mlxplat_hotplug = &mlxplat_mlxcpld_xdr_data; ++ mlxplat_hotplug->deferred_nr = ++ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; ++ mlxplat_led = &mlxplat_xdr_led_data; ++ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; ++ mlxplat_fan = &mlxplat_xdr_fan_data; ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) ++ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; ++ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; ++ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; ++ ++ return mlxplat_register_platform_device(); ++} ++ + static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + { + .callback = mlxplat_dmi_default_wc_matched, +@@ -7711,6 +8096,12 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + }, + }, + { ++ .callback = mlxplat_dmi_xdr_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0018"), ++ }, ++ }, ++ { + .callback = mlxplat_dmi_smart_switch_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0019"), +-- +2.8.4 + diff --git a/patches-sonic/0054-1-platform-mellanox-Downstream-Add-support-for-new-Nvi.patch b/patches-sonic/0054-1-platform-mellanox-Downstream-Add-support-for-new-Nvi.patch new file mode 100644 index 000000000..7abef897d --- /dev/null +++ b/patches-sonic/0054-1-platform-mellanox-Downstream-Add-support-for-new-Nvi.patch @@ -0,0 +1,529 @@ +From e9caf9cf3e69dff922c98c0e5e0ffe7a2fd0a7c7 Mon Sep 17 00:00:00 2001 +From: Oleksandr Shamray +Date: Mon, 9 Dec 2024 11:42:37 +0200 +Subject: [PATCH 17/40] platform: mellanox: Downstream: Add support for new + Nvidia IB DGX system based on class VMOD0010 + +This system is based on Nvidia QM9700 64x400G QTM-2 IB switch, with the +following key changes: + +Key changes: + 1.Power Supply: Rack busbar input power ORv3 DC 48V-54V + 2.Dimensions MGX/DGX 1U compliance + +Signed-off-by: Oleksandr Shamray +Reviewed-by: Vadim Pasternak +--- + drivers/platform/mellanox/mlx-platform.c | 454 +++++++++++++++++++++++++++++++ + 1 file changed, 454 insertions(+) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index b2ea7e8..4c1073b 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -824,6 +824,16 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { + }, + }; + ++/* Platform hotplug dgx data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pdb_items_data[] = { ++ { ++ .label = "pdb1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE, ++ }, ++}; ++ + static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = { + { + .label = "pwr1", +@@ -873,6 +883,15 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_ng800_items_data[] = + }, + }; + ++static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pwr_items_data[] = { ++ { ++ .label = "pwr1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = MLXPLAT_CPLD_NR_NONE , ++ }, ++}; ++ + static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = { + { + .label = "fan1", +@@ -1496,6 +1515,45 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ext_items[] = { + } + }; + ++static struct mlxreg_core_item mlxplat_mlxcpld_ext_dgx_items[] = { ++ { ++ .data = mlxplat_mlxcpld_dgx_pdb_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, ++ .mask = MLXPLAT_CPLD_PSU_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pdb_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_dgx_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, ++ .mask = MLXPLAT_CPLD_PWR_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pwr_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_ng_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, ++ .mask = MLXPLAT_CPLD_FAN_NG_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_asic_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++}; ++ + static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = { + { + .data = mlxplat_mlxcpld_default_ng_psu_items_data, +@@ -1548,6 +1606,16 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { + }; + + static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_dgx_ext_data = { ++ .items = mlxplat_mlxcpld_ext_dgx_items, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_dgx_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2, ++}; ++ ++static + struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = { + .items = mlxplat_mlxcpld_ng800_items, + .count = ARRAY_SIZE(mlxplat_mlxcpld_ng800_items), +@@ -5037,6 +5105,359 @@ static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = { + .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data), + }; + ++/* Platform register access for next generation systems families data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_dgx_ng_regs_io_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_ref", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_asic", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_wd", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_thermal", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_reload_bios", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pdb_pwr_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "pdb_reset_stby", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "deep_pwr_cycle", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "latch_reset", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET, ++ .mask = MLXPLAT_CPLD_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg1", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg2", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg3", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "dbg4", ++ .reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_health", ++ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, ++ .mask = MLXPLAT_CPLD_ASIC_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "fan_dir", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_safe_mode", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_auth_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "voltreg_update_status", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET, ++ .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK, ++ .bit = 5, ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "config1", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "ufm_version", ++ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_dgx_ng_regs_io_data = { ++ .data = mlxplat_mlxcpld_dgx_ng_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_dgx_ng_regs_io_data), ++}; ++ + /* Platform register access for modular systems families data */ + static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = { + { +@@ -7787,6 +8208,32 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi) + return mlxplat_register_platform_device(); + } + ++static int __init mlxplat_dmi_ng400_dgx_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; ++ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); ++ mlxplat_mux_data = mlxplat_default_mux_data; ++ for (i = 0; i < mlxplat_mux_num; i++) { ++ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; ++ mlxplat_mux_data[i].n_values = ++ ARRAY_SIZE(mlxplat_msn21xx_channels); ++ } ++ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data; ++ mlxplat_hotplug->deferred_nr = ++ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; ++ mlxplat_led = &mlxplat_default_ng_led_data; ++ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data; ++ mlxplat_fan = &mlxplat_default_fan_data; ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) ++ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; ++ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; ++ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; ++ ++ return mlxplat_register_platform_device(); ++} ++ + static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi) + { + int i; +@@ -8060,6 +8507,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + }, + }, + { ++ .callback = mlxplat_dmi_ng400_dgx_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI173"), ++ }, ++ }, ++ { + .callback = mlxplat_dmi_ng400_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"), +-- +2.8.4 + diff --git a/patches-sonic/0054-i2c-asf-Introduce-MCTP-support-over-ASF-controller.patch b/patches-sonic/0054-i2c-asf-Introduce-MCTP-support-over-ASF-controller.patch new file mode 100644 index 000000000..6aa7281fe --- /dev/null +++ b/patches-sonic/0054-i2c-asf-Introduce-MCTP-support-over-ASF-controller.patch @@ -0,0 +1,993 @@ +From ad078b328cac385d8cce76f525e30c97d7bf2b18 Mon Sep 17 00:00:00 2001 +From: amlakshm +Date: Tue, 30 Jan 2024 14:18:35 +0530 +Subject: [PATCH 54/78] i2c: asf: Introduce MCTP support over ASF controller + +Provide driver for AMD ASF controller working as SMBus +master and slave. + +Signed-off-by: Sudheesh Mavila +Signed-off-by: Vadim Pasternak +--- + drivers/i2c/busses/Kconfig | 10 + + drivers/i2c/busses/Makefile | 1 + + drivers/i2c/busses/i2c-asf.c | 934 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 945 insertions(+) + create mode 100644 drivers/i2c/busses/i2c-asf.c + +diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +index 2254abd..7469cf2 100644 +--- a/drivers/i2c/busses/Kconfig ++++ b/drivers/i2c/busses/Kconfig +@@ -95,6 +95,16 @@ config I2C_AMD_MP2 + This driver can also be built as modules. If so, the modules will + be called i2c-amd-mp2-pci and i2c-amd-mp2-plat. + ++config I2C_ASF ++ tristate "AMD ASF controller works as SMBus master and slave" ++ depends on PCI && ACPI ++ help ++ If you say yes to this option, support will be included for the ++ SMBus controller found in the AMD EPIC family. ++ ++ This driver can also be built as modules. If so, the modules will ++ be called i2c-asf. ++ + config I2C_HIX5HD2 + tristate "Hix5hd2 high-speed I2C driver" + depends on ARCH_HISI || ARCH_HIX5HD2 || COMPILE_TEST +diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +index ecc07c5..35486db 100644 +--- a/drivers/i2c/busses/Makefile ++++ b/drivers/i2c/busses/Makefile +@@ -144,6 +144,7 @@ obj-$(CONFIG_I2C_VIPERBOARD) += i2c-viperboard.o + + # Other I2C/SMBus bus drivers + obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o ++obj-$(CONFIG_I2C_ASF) += i2c-asf.o + obj-$(CONFIG_I2C_BCM_KONA) += i2c-bcm-kona.o + obj-$(CONFIG_I2C_BRCMSTB) += i2c-brcmstb.o + obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += i2c-cros-ec-tunnel.o +diff --git a/drivers/i2c/busses/i2c-asf.c b/drivers/i2c/busses/i2c-asf.c +new file mode 100644 +index 00000000..815d3eb +--- /dev/null ++++ b/drivers/i2c/busses/i2c-asf.c +@@ -0,0 +1,934 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 1998 - 2002 Frodo Looijaard and ++ * Philip Edelbrock ++ * Copyright (c) 2024 AMD Inc ++ */ ++ ++/* ++ * Supports: ++ * AMD E3K ++ * ++ * Note: we assume there can only be one device, with one or more ++ * SMBus interfaces. ++ * ++ * The driver is written by using i2c-piix4.c as reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ASF SMBus address offsets */ ++#define SMBHSTSTS (0 + asf_smba) ++#define SMBHSLVSTS (1 + asf_smba) ++#define SMBHSTCNT (2 + asf_smba) ++#define SMBHSTCMD (3 + asf_smba) ++#define SMBHSTADD (4 + asf_smba) ++#define SMBHSTDAT0 (5 + asf_smba) ++#define SMBHSTDAT1 (6 + asf_smba) ++#define SMBBLKDAT (7 + asf_smba) ++#define ASFINDEX (7 + asf_smba) ++#define ASFLISADDR (9 + asf_smba) ++#define ASFSTA (0xA + asf_smba) ++#define ASFSTAMASK (0xB + asf_smba) ++#define ASFSTAMASK1 (0xC + asf_smba) ++#define ASFSLVSTA (0xD + asf_smba) ++#define ASFDATARDPTR (0x10 + asf_smba) ++#define ASFDATARWPTR (0x11 + asf_smba) ++#define ASFSETDATARDPTR (0x12 + asf_smba) ++#define ASFDATABNKSEL (0x13 + asf_smba) ++#define ASFSLVEN (0x15 + asf_smba) ++ ++/* count for request_region */ ++#define SMBIOSIZE 9 ++ ++/* PCI Address Constants */ ++#define SMBBA 0x090 ++#define SMBHSTCFG 0x0D2 ++#define SMBSLVC 0x0D3 ++#define SMBSHDW1 0x0D4 ++#define SMBSHDW2 0x0D5 ++#define SMBREV 0x0D6 ++ ++/* Other settings */ ++#define MAX_TIMEOUT 500 ++#define ENABLE_INT9 0 ++ ++/* ASF constants */ ++#define ASF_QUICK 0x00 ++#define ASF_BYTE 0x04 ++#define ASF_BYTE_DATA 0x08 ++#define ASF_WORD_DATA 0x0C ++#define ASF_BLOCK_DATA 0x14 ++ ++/* Multi-port constants */ ++#define ASF_MAX_ADAPTERS 1 ++ ++/* SB800 constants */ ++#define SB800_ASF_SMB_IDX 0xcd6 ++#define SB800_ASF_SMB_MAP_SIZE 2 ++ ++#define KERNCZ_IMC_IDX 0x3e ++#define KERNCZ_IMC_DATA 0x3f ++ ++/* ++ * SB800 port is selected by bits 2:1 of the smb_en register (0x2c) ++ * or the smb_sel register (0x2e), depending on bit 0 of register 0x2f. ++ * Hudson-2/Bolton port is always selected by bits 2:1 of register 0x2f. ++ */ ++#define SB800_ASF_PORT_IDX 0x2c ++#define SB800_ASF_PORT_IDX_ALT 0x2e ++#define SB800_ASF_PORT_IDX_SEL 0x2f ++#define SB800_ASF_PORT_IDX_MASK 0x06 ++#define SB800_ASF_PORT_IDX_SHIFT 1 ++ ++/* On kerncz and Hudson2, SmBus0Sel is at bit 20:19 of PMx00 DecodeEn */ ++#define SB800_ASF_PORT_IDX_KERNCZ 0x02 ++#define SB800_ASF_PORT_IDX_MASK_KERNCZ 0x18 ++#define SB800_ASF_PORT_IDX_SHIFT_KERNCZ 3 ++ ++#define SB800_ASF_FCH_PM_ADDR 0xFED80300 ++#define SB800_ASF_FCH_PM_SIZE 8 ++ ++static struct pci_driver asf_driver; ++ ++/* ++ * SB800 globals ++ */ ++static u8 asf_port_sel_sb800; ++static u8 asf_port_mask_sb800; ++static u8 asf_port_shift_sb800; ++static const char *asf_main_port_names_sb800[ASF_MAX_ADAPTERS] = { ++ " port 0" ++}; ++ ++struct sb800_mmio_cfg { ++ void __iomem *addr; ++ bool use_mmio; ++}; ++ ++struct i2c_asf_adapdata { ++ unsigned short smba; ++ u8 port; /* Port number, shifted */ ++ struct sb800_mmio_cfg mmio_cfg; ++ struct timer_list slave_timer; ++ struct i2c_client *slave; ++}; ++ ++static spinlock_t asf_spinlock; ++static void __iomem *asf_scl_muxbase; ++static void __iomem *asf_sda_muxbase; ++ ++/* ++ * This gets called whenever an I2C slave interrupt/status change ++ * occurs. ++ */ ++static int asf_slave_process(struct i2c_asf_adapdata *adapdata) ++{ ++ u8 data[72], val = 0, cmd, reg_datab_sel, reg_sta, count; ++ int nextdatabank, databank0status, databank1status; ++ unsigned short asf_smba = adapdata->smba; ++ unsigned long flags; ++ int i = 0; ++ ++ spin_lock_irqsave(&asf_spinlock, flags); ++ reg_sta = inb_p(ASFSTA); ++ if ((reg_sta & 0x40) == 0) { ++ spin_unlock_irqrestore(&asf_spinlock, flags); ++ return 0; ++ } ++ outb_p(reg_sta | 0x40, ASFSTA); ++ ++ while (inb_p(ASFSTA) & 0x80); ++ ++ reg_datab_sel = inb_p(ASFDATABNKSEL); ++ databank0status = (reg_datab_sel & BIT(2)) >> 2; ++ databank1status = (reg_datab_sel & BIT(3)) >> 3; ++ ++ if (databank0status || databank1status) { ++ reg_sta = inb_p(ASFSLVSTA); ++ if (!(reg_sta & 0xE)) { ++ /* no error */ ++ if ((databank0status == 0 && databank1status == 1) || ++ (databank0status == 1 && databank1status == 0)) { ++ if (databank1status == 1) { ++ reg_datab_sel = reg_datab_sel | BIT(4); ++ reg_datab_sel = reg_datab_sel & ~BIT(3); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ while(!(inb_p(ASFDATABNKSEL) & 0x10)); ++ ++ cmd = inb_p(ASFINDEX); ++ count = inb_p(0x11 + asf_smba); ++ for (i = 0; i < count; i++) ++ data[i] = inb_p(ASFINDEX); ++ reg_datab_sel = reg_datab_sel | BIT(3); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ } else { ++ reg_datab_sel = reg_datab_sel & ~BIT(4); ++ reg_datab_sel = reg_datab_sel & ~BIT(2); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ while((inb_p(ASFDATABNKSEL) & 0x10)); ++ ++ cmd = inb_p(ASFINDEX); ++ count = inb_p(0x11 + asf_smba); ++ for (i = 0; i < count; i++) ++ data[i] = inb_p(ASFINDEX); ++ reg_datab_sel = reg_datab_sel | BIT(2); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ } ++ } else if (((databank0status == 1) && (databank1status == 1))) { ++ nextdatabank = reg_datab_sel & 1; ++ if (!nextdatabank) { ++ reg_datab_sel = reg_datab_sel & ~BIT(4); ++ reg_datab_sel = reg_datab_sel & ~BIT(2); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ ++ reg_datab_sel = reg_datab_sel | BIT(2); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ } else { ++ reg_datab_sel = reg_datab_sel | BIT(4); ++ reg_datab_sel = reg_datab_sel & ~BIT(3); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ ++ reg_datab_sel = reg_datab_sel | BIT(3); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ } ++ } ++ } else { ++ reg_datab_sel = reg_datab_sel | (BIT(3) | BIT(2)); ++ outb_p(reg_datab_sel, ASFDATABNKSEL); ++ outb_p(0xF, ASFSLVSTA); ++ } ++ } ++ outb_p(0, 0x12 + asf_smba); ++ outb_p(0xF, ASFSLVSTA); ++ spin_unlock_irqrestore(&asf_spinlock, flags); ++ ++ if ((cmd & 1) == 0) { ++ i2c_slave_event(adapdata->slave, I2C_SLAVE_WRITE_REQUESTED, &val); ++ for (i = 0; i < count; i++) { ++ val = data[i]; ++ i2c_slave_event(adapdata->slave, I2C_SLAVE_WRITE_RECEIVED, &val); ++ } ++ i2c_slave_event(adapdata->slave, I2C_SLAVE_STOP, &val); ++ } ++ return 0; ++} ++ ++static void slave_event_timer(struct timer_list *t) ++{ ++ struct i2c_asf_adapdata *adapdata = from_timer(adapdata, t, slave_timer); ++ ++ asf_slave_process(adapdata); ++ ++ mod_timer(&adapdata->slave_timer, jiffies + msecs_to_jiffies(100)); ++} ++ ++static int asf_sb800_region_request(struct device *dev, ++ struct sb800_mmio_cfg *mmio_cfg) ++{ ++ if (mmio_cfg->use_mmio) { ++ void __iomem *addr; ++ ++ if (!request_mem_region_muxed(SB800_ASF_FCH_PM_ADDR, ++ SB800_ASF_FCH_PM_SIZE, ++ "sb800_asf_smb")) { ++ dev_err(dev, ++ "SMBus base address memory region 0x%x already in use.\n", ++ SB800_ASF_FCH_PM_ADDR); ++ return -EBUSY; ++ } ++ ++ addr = ioremap(SB800_ASF_FCH_PM_ADDR, ++ SB800_ASF_FCH_PM_SIZE); ++ if (!addr) { ++ release_mem_region(SB800_ASF_FCH_PM_ADDR, ++ SB800_ASF_FCH_PM_SIZE); ++ dev_err(dev, "SMBus base address mapping failed.\n"); ++ return -ENOMEM; ++ } ++ ++ mmio_cfg->addr = addr; ++ ++ return 0; ++ } ++ ++ if (!request_muxed_region(SB800_ASF_SMB_IDX, SB800_ASF_SMB_MAP_SIZE, ++ "sb800_asf_smb")) { ++ dev_err(dev, ++ "SMBus base address index region 0x%x already in use.\n", ++ SB800_ASF_SMB_IDX); ++ return -EBUSY; ++ } ++ ++ return 0; ++} ++ ++static void asf_sb800_region_release(struct device *dev, ++ struct sb800_mmio_cfg *mmio_cfg) ++{ ++ if (mmio_cfg->use_mmio) { ++ iounmap(mmio_cfg->addr); ++ release_mem_region(SB800_ASF_FCH_PM_ADDR, ++ SB800_ASF_FCH_PM_SIZE); ++ return; ++ } ++ ++ release_region(SB800_ASF_SMB_IDX, SB800_ASF_SMB_MAP_SIZE); ++} ++ ++static bool asf_sb800_use_mmio(struct pci_dev *ASF_dev) ++{ ++ /* ++ * cd6h/cd7h port I/O accesses can be disabled on AMD processors ++ * w/ SMBus PCI revision ID 0x51 or greater. MMIO is supported on ++ * the same processors and is the recommended access method. ++ */ ++ return (ASF_dev->vendor == PCI_VENDOR_ID_AMD && ++ ASF_dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS && ++ ASF_dev->revision >= 0x51); ++} ++ ++static int ++asf_setup_sb800_smba(struct pci_dev *ASF_dev, u8 smb_en, u8 aux, u8 *smb_en_status, ++ unsigned short *asf_smba) ++{ ++ struct sb800_mmio_cfg mmio_cfg; ++ u8 smba_en_lo, smba_en_hi; ++ int reg, retval; ++ ++ asf_scl_muxbase = ioremap(0xFED80D13, 1); ++ iowrite8(0, asf_scl_muxbase); ++ asf_sda_muxbase = ioremap(0xFED80D14, 1); ++ iowrite8(0, asf_sda_muxbase); ++ ++ mmio_cfg.use_mmio = asf_sb800_use_mmio(ASF_dev); ++ retval = asf_sb800_region_request(&ASF_dev->dev, &mmio_cfg); ++ if (retval) ++ return retval; ++ ++ if (mmio_cfg.use_mmio) { ++ smba_en_lo = ioread8(mmio_cfg.addr); ++ smba_en_hi = ioread8(mmio_cfg.addr + 1); ++ } ++ ++ reg = ioread32(mmio_cfg.addr); ++ reg = reg | BIT(16); ++ iowrite32(reg, mmio_cfg.addr); ++ ++ asf_sb800_region_release(&ASF_dev->dev, &mmio_cfg); ++ ++ *smb_en_status = smba_en_lo & 0x10; ++ *asf_smba = smba_en_hi << 8; ++ ++ if (!*smb_en_status) { ++ dev_err(&ASF_dev->dev, "SMBus Host Controller not enabled!\n"); ++ return -ENODEV; ++ } ++ ++ *asf_smba |= 0x20; ++ ++ return 0; ++} ++ ++static int asf_setup_sb800(struct pci_dev *ASF_dev, const struct pci_device_id *id, u8 aux) ++{ ++ u8 i2ccfg_offset = 0x10, i2ccfg, smb_en, smb_en_status; ++ unsigned short asf_smba; ++ int retval; ++ ++ /* Determine the address of the SMBus areas */ ++ if ((ASF_dev->vendor == PCI_VENDOR_ID_AMD && ++ ASF_dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS && ASF_dev->revision >= 0x41) || ++ (ASF_dev->vendor == PCI_VENDOR_ID_AMD && ++ ASF_dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS && ASF_dev->revision >= 0x49) || ++ (ASF_dev->vendor == PCI_VENDOR_ID_HYGON && ++ ASF_dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS)) ++ smb_en = 0x00; ++ else ++ return -1; ++ ++ retval = asf_setup_sb800_smba(ASF_dev, smb_en, aux, &smb_en_status, &asf_smba); ++ ++ if (retval) ++ return retval; ++ ++ if (acpi_check_region(asf_smba, SMBIOSIZE, asf_driver.name)) ++ return -ENODEV; ++ ++ if (!request_region(asf_smba, SMBIOSIZE, asf_driver.name)) { ++ dev_err(&ASF_dev->dev, "SMBus region 0x%x already in use!\n", asf_smba); ++ return -EBUSY; ++ } ++ ++ /* Request the SMBus I2C bus config region */ ++ if (!request_region(asf_smba + i2ccfg_offset, 1, "i2ccfg")) { ++ dev_err(&ASF_dev->dev, "SMBus I2C bus config region 0x%x already in use!\n", ++ asf_smba + i2ccfg_offset); ++ release_region(asf_smba, SMBIOSIZE); ++ return -EBUSY; ++ } ++ i2ccfg = inb_p(asf_smba + i2ccfg_offset); ++ ++ release_region(asf_smba + i2ccfg_offset, 1); ++ ++ if (i2ccfg & 1) ++ dev_dbg(&ASF_dev->dev, "Using IRQ for SMBus\n"); ++ else ++ dev_dbg(&ASF_dev->dev, "Using SMI# for SMBus\n"); ++ ++ dev_info(&ASF_dev->dev, "SMBus Host Controller at 0x%x, revision %d\n", ++ asf_smba, i2ccfg >> 4); ++ ++ /* Find which register is used for port selection */ ++ if (ASF_dev->vendor == PCI_VENDOR_ID_AMD || ++ ASF_dev->vendor == PCI_VENDOR_ID_HYGON) { ++ if (ASF_dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS || ++ (ASF_dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS && ++ ASF_dev->revision >= 0x1F)) { ++ asf_port_sel_sb800 = SB800_ASF_PORT_IDX_KERNCZ; ++ asf_port_mask_sb800 = SB800_ASF_PORT_IDX_MASK_KERNCZ; ++ asf_port_shift_sb800 = SB800_ASF_PORT_IDX_SHIFT_KERNCZ; ++ } else { ++ asf_port_sel_sb800 = SB800_ASF_PORT_IDX_ALT; ++ asf_port_mask_sb800 = SB800_ASF_PORT_IDX_MASK; ++ asf_port_shift_sb800 = SB800_ASF_PORT_IDX_SHIFT; ++ } ++ } ++ dev_info(&ASF_dev->dev, "Using register 0x%02x for SMBus port selection\n", ++ (unsigned int)asf_port_sel_sb800); ++ release_region(asf_smba, SMBIOSIZE); ++ return asf_smba; ++} ++ ++static int asf_transaction(struct i2c_adapter *asf_adapter) ++{ ++ struct i2c_asf_adapdata *adapdata = i2c_get_adapdata(asf_adapter); ++ unsigned short asf_smba = adapdata->smba; ++ int result = 0, timeout = 0, temp; ++ ++ dev_dbg(&asf_adapter->dev, "Transaction (pre): CNT=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", ++ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++ ++ /* Make sure the SMBus host is ready to start transmitting */ ++ temp = inb_p(SMBHSTSTS); ++ if (temp) { ++ dev_dbg(&asf_adapter->dev, "SMBus busy (%02x). Resetting...\n", temp); ++ outb_p(temp, SMBHSTSTS); ++ temp = inb_p(SMBHSTSTS); ++ if (temp) { ++ dev_err(&asf_adapter->dev, "Failed! (%02x)\n", temp); ++ return -EBUSY; ++ } ++ dev_dbg(&asf_adapter->dev, "Successful!\n"); ++ } ++ ++ /* start the transaction by setting bit 6 */ ++ outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT); ++ ++ /* We will always wait for a fraction of a second! (See ASF docs errata) */ ++ udelay(250); ++ ++ while ((++timeout < MAX_TIMEOUT) && ++ ((temp = inb_p(SMBHSTSTS)) & 0x01)) ++ udelay(250); ++ ++ /* If the SMBus is still busy, we give up */ ++ if (timeout == MAX_TIMEOUT) { ++ dev_err(&asf_adapter->dev, "SMBus Timeout!\n"); ++ result = -ETIMEDOUT; ++ } ++ ++ if (temp & 0x10) { ++ result = -EIO; ++ dev_err(&asf_adapter->dev, "Error: Failed bus transaction\n"); ++ } ++ ++ if (temp & 0x08) { ++ /* Clock stops and slave is stuck in mid-transmission */ ++ result = -EIO; ++ dev_dbg(&asf_adapter->dev, "Bus collision! SMBus may be locked until next hard reset.\n"); ++ } ++ ++ if (temp & 0x04) { ++ result = -ENXIO; ++ dev_dbg(&asf_adapter->dev, "Error: no response!\n"); ++ } ++ ++ if (inb_p(SMBHSTSTS) != 0x00) ++ outb_p(inb(SMBHSTSTS), SMBHSTSTS); ++ ++ temp = inb_p(SMBHSTSTS); ++ if (temp) ++ dev_err(&asf_adapter->dev, "Failed reset at end of transaction (%02x)\n", temp); ++ dev_dbg(&asf_adapter->dev, "Transaction (post): CNT=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", ++ inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), ++ inb_p(SMBHSTDAT1)); ++ return result; ++} ++ ++/* Return negative errno on error. */ ++static s32 ++asf_access(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, ++ int size, union i2c_smbus_data *data) ++{ ++ struct i2c_asf_adapdata *adapdata = i2c_get_adapdata(adap); ++ unsigned short asf_smba = adapdata->smba; ++ int i, len; ++ int status; ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ size = ASF_QUICK; ++ break; ++ case I2C_SMBUS_BYTE: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(command, SMBHSTCMD); ++ size = ASF_BYTE; ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) ++ outb_p(data->byte, SMBHSTDAT0); ++ size = ASF_BYTE_DATA; ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word & 0xff, SMBHSTDAT0); ++ outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); ++ } ++ size = ASF_WORD_DATA; ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ outb_p(command, SMBHSTCMD); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len == 0 || len > 70) ++ return -EINVAL; ++ outb_p(len, SMBHSTDAT0); ++ inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ ++ for (i = 1; i <= len; i++) ++ outb_p(data->block[i], SMBBLKDAT); ++ } ++ size = ASF_BLOCK_DATA; ++ break; ++ default: ++ dev_warn(&adap->dev, "Unsupported transaction %d\n", size); ++ return -EOPNOTSUPP; ++ } ++ ++ /* Enable PEC and PEC append */ ++ outb_p(((size & 0x1C) + (ENABLE_INT9 & 1)) | (BIT(7)) | (BIT(5)), SMBHSTCNT); ++ ++ status = asf_transaction(adap); ++ if (status) ++ return status; ++ ++ if (read_write == I2C_SMBUS_WRITE || size == ASF_QUICK) ++ return 0; ++ ++ switch (size) { ++ case ASF_BYTE: ++ case ASF_BYTE_DATA: ++ data->byte = inb_p(SMBHSTDAT0); ++ break; ++ case ASF_WORD_DATA: ++ data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); ++ break; ++ case ASF_BLOCK_DATA: ++ data->block[0] = inb_p(SMBHSTDAT0); ++ if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX) ++ return -EPROTO; ++ inb_p(SMBHSTCNT); ++ /* Reset SMBBLKDAT */ ++ for (i = 1; i <= data->block[0]; i++) ++ data->block[i] = inb_p(SMBBLKDAT); ++ break; ++ } ++ return 0; ++} ++ ++static int asf_sb800_port_sel(u8 port, struct sb800_mmio_cfg *mmio_cfg) ++{ ++ u8 smba_en_lo, val; ++ ++ if (mmio_cfg->use_mmio) { ++ smba_en_lo = ioread8(mmio_cfg->addr + asf_port_sel_sb800); ++ val = (smba_en_lo & ~asf_port_mask_sb800) | port; ++ if (smba_en_lo != val) ++ iowrite8(val, mmio_cfg->addr + asf_port_sel_sb800); ++ ++ return (smba_en_lo & asf_port_mask_sb800); ++ } ++ ++ outb_p(asf_port_sel_sb800, SB800_ASF_SMB_IDX); ++ smba_en_lo = inb_p(SB800_ASF_SMB_IDX + 1); ++ ++ val = (smba_en_lo & ~asf_port_mask_sb800) | port; ++ if (smba_en_lo != val) ++ outb_p(val, SB800_ASF_SMB_IDX + 1); ++ ++ return (smba_en_lo & asf_port_mask_sb800); ++} ++ ++/* ++ * Handles access to multiple SMBus ports on the SB800. ++ * The port is selected by bits 2:1 of the smb_en register (0x2c). ++ * Returns negative errno on error. ++ * ++ * Note: The selected port must be returned to the initial selection to avoid ++ * problems on certain systems. ++ */ ++static s32 ++asf_access_sb800(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data *data) ++{ ++ struct i2c_asf_adapdata *adapdata = i2c_get_adapdata(adap); ++ unsigned short asf_smba = adapdata->smba; ++ unsigned long lock_flags; ++ int reg, retval; ++ u8 prev_port; ++ ++ retval = asf_sb800_region_request(&adap->dev, &adapdata->mmio_cfg); ++ if (retval) ++ return retval; ++ ++ spin_lock_irqsave(&asf_spinlock, lock_flags); ++ reg = inb_p(ASFSLVEN); ++ reg = reg | (BIT(4)); ++ outb_p(reg, ASFSLVEN); ++ outb_p(0, SMBHSTCMD); ++ outb_p(0, SMBHSTDAT0); ++ outb_p(0, SMBHSTDAT1); ++ outb_p(0, ASFSLVSTA); ++ reg = ioread32(adapdata->mmio_cfg.addr); ++ reg = reg | BIT(16); ++ iowrite32(reg, adapdata->mmio_cfg.addr); ++ prev_port = asf_sb800_port_sel(adapdata->port, &adapdata->mmio_cfg); ++ retval = asf_access(adap, addr, flags, read_write, command, size, data); ++ asf_sb800_port_sel(prev_port, &adapdata->mmio_cfg); ++ ++ /* set asf as slave */ ++ outb_p(0, SMBHSTSTS); ++ outb_p(0, ASFSLVSTA); ++ outb_p(0, ASFSTA); ++ reg = ioread32(adapdata->mmio_cfg.addr); ++ reg = reg & ~BIT(16); ++ reg = reg | BIT(17); ++ iowrite32(reg, adapdata->mmio_cfg.addr); ++ reg = inb(ASFSLVEN); ++ reg = reg & ~BIT(4); ++ outb_p(reg, ASFSLVEN); ++ /* Enable PEC and PEC append */ ++ reg = inb(SMBHSTCNT); ++ reg = reg | BIT(7) | BIT(5); ++ outb_p(reg, SMBHSTCNT); ++ spin_unlock_irqrestore(&asf_spinlock, lock_flags); ++ asf_sb800_region_release(&adap->dev, &adapdata->mmio_cfg); ++ ++ return retval; ++} ++ ++static int asf_access_i2c(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) ++{ ++ unsigned short flags = 0; ++ struct i2c_msg *msg; ++ char read_write; ++ int size, ret; ++ u8 command; ++ u16 addr; ++ union i2c_mctp_data { ++ __u8 byte; ++ __u16 word; ++ __u8 block[72 + 2]; /* block[0] is used for length */ ++ } smbus_data; ++ ++ msg = &msgs[0]; ++ if (msgs->flags & I2C_M_RD) { ++ dev_err(&adap->dev, "Read not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (num != 1) { ++ dev_err(&adap->dev, "multiple i2c messages not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (msg->len - 1 > 72) { ++ dev_err(&adap->dev, "max mtu is 68\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ addr = msg->addr; ++ read_write = 0; ++ command = msg[0].buf[0]; ++ size = I2C_SMBUS_BLOCK_DATA; ++ smbus_data.block[0] = msg->len - 1; ++ memcpy(smbus_data.block + 1, msg[0].buf + 1, msg->len - 1); ++ ret = asf_access_sb800(adap, addr, flags, read_write, command, size, ++ (union i2c_smbus_data *)&smbus_data); ++ return ret; ++} ++ ++static int asf_reg_slave(struct i2c_client *slave) ++{ ++ struct i2c_asf_adapdata *adapdata = i2c_get_adapdata(slave->adapter); ++ unsigned short asf_smba = adapdata->smba; ++ int retval, config_reg; ++ unsigned long flags; ++ u8 reg; ++ ++ if (adapdata->slave) ++ return -EBUSY; ++ ++ retval = asf_sb800_region_request(&slave->dev, &adapdata->mmio_cfg); ++ if (retval) ++ return retval; ++ ++ spin_lock_irqsave(&asf_spinlock, flags); ++ ++ reg = (slave->addr << 1) | 1; ++ outb_p(reg, ASFLISADDR); ++ outb_p(0, SMBHSTSTS); ++ outb_p(0, ASFSLVSTA); ++ outb_p(0, ASFSTA); ++ reg = inb_p(ASFSLVEN); ++ reg = reg | BIT(1) | BIT(4); ++ outb_p(reg, ASFSLVEN); ++ reg = reg & ~BIT(4); ++ outb_p(reg, ASFSLVEN); ++ adapdata->slave = slave; ++ reg = inb_p(SMBHSTCNT); ++ reg = reg | BIT(7) | BIT(5); ++ outb_p(reg, SMBHSTCNT); ++ reg = inb_p(ASFDATABNKSEL); ++ reg = reg & ~BIT(7); ++ outb_p(reg, ASFDATABNKSEL); ++ ++ config_reg = ioread32(adapdata->mmio_cfg.addr); ++ config_reg = config_reg & (~BIT(16)); ++ config_reg = config_reg | (BIT(17)); ++ iowrite32(config_reg, adapdata->mmio_cfg.addr); ++ spin_unlock_irqrestore(&asf_spinlock, flags); ++ asf_sb800_region_release(&slave->dev, &adapdata->mmio_cfg); ++ ++ timer_setup(&adapdata->slave_timer, slave_event_timer, 0); ++ mod_timer(&adapdata->slave_timer, jiffies + HZ); ++ ++ return 0; ++} ++ ++static int asf_unreg_slave(struct i2c_client *slave) ++{ ++ struct i2c_asf_adapdata *adapdata = i2c_get_adapdata(slave->adapter); ++ unsigned short asf_smba = adapdata->smba; ++ u8 reg; ++ ++ reg = inb(ASFSLVEN); ++ reg &= ~BIT(1); ++ reg = reg | BIT(4); ++ outb_p(reg, ASFSLVEN); ++ ++ adapdata->slave = NULL; ++ del_timer_sync(&adapdata->slave_timer); ++ return 0; ++} ++ ++static u32 asf_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SLAVE | I2C_FUNC_SMBUS_PEC | ++ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA; ++} ++ ++static const struct i2c_algorithm asf_smbus_algorithm_sb800 = { ++ .smbus_xfer = asf_access_sb800, ++ .master_xfer = asf_access_i2c, ++ .functionality = asf_func, ++ .reg_slave = asf_reg_slave, ++ .unreg_slave = asf_unreg_slave, ++}; ++ ++static const struct pci_device_id asf_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS) }, ++ { 0, } ++}; ++ ++MODULE_DEVICE_TABLE(pci, asf_ids); ++ ++static struct i2c_adapter *asf_main_adapters[ASF_MAX_ADAPTERS]; ++static struct i2c_adapter *asf_aux_adapter; ++static int asf_adapter_count; ++ ++static int ++asf_add_adapter(struct pci_dev *dev, unsigned short smba, u8 port, u8 hw_port_nr, const char *name, ++ struct i2c_adapter **padap) ++{ ++ struct i2c_asf_adapdata *adapdata; ++ struct i2c_adapter *adap; ++ int retval; ++ ++ adap = kzalloc(sizeof(*adap), GFP_KERNEL); ++ if (!adap) { ++ release_region(smba, SMBIOSIZE); ++ return -ENOMEM; ++ } ++ ++ adap->owner = THIS_MODULE; ++ adap->class = I2C_CLASS_HWMON; ++ adap->algo = &asf_smbus_algorithm_sb800; ++ ++ adapdata = kzalloc(sizeof(*adapdata), GFP_KERNEL); ++ if (!adapdata) { ++ kfree(adap); ++ release_region(smba, SMBIOSIZE); ++ return -ENOMEM; ++ } ++ ++ adapdata->mmio_cfg.use_mmio = asf_sb800_use_mmio(dev); ++ adapdata->smba = smba; ++ adapdata->port = port << asf_port_shift_sb800; ++ ++ /* set up the sysfs linkage to our parent device */ ++ adap->dev.parent = &dev->dev; ++ ++ if (has_acpi_companion(&dev->dev)) { ++ acpi_preset_companion(&adap->dev, ++ ACPI_COMPANION(&dev->dev), ++ hw_port_nr); ++ } ++ ++ snprintf(adap->name, sizeof(adap->name), "SMBus ASF adapter%s at %04x", name, smba); ++ i2c_set_adapdata(adap, adapdata); ++ retval = i2c_add_adapter(adap); ++ if (retval) { ++ kfree(adapdata); ++ kfree(adap); ++ release_region(smba, SMBIOSIZE); ++ return retval; ++ } ++ ++ *padap = adap; ++ return 0; ++} ++ ++static int asf_add_adapters_sb800(struct pci_dev *dev, unsigned short smba) ++{ ++ struct i2c_asf_adapdata *adapdata; ++ int port; ++ int retval; ++ ++ asf_adapter_count = ASF_MAX_ADAPTERS; ++ ++ for (port = 0; port < asf_adapter_count; port++) { ++ u8 hw_port_nr = port == 0 ? 0 : port + 1; ++ ++ retval = asf_add_adapter(dev, smba, port, hw_port_nr, ++ asf_main_port_names_sb800[port], ++ &asf_main_adapters[port]); ++ if (retval < 0) ++ goto error; ++ } ++ ++ return retval; ++ ++error: ++ dev_err(&dev->dev, "Error setting up SB800 adapters. Unregistering!\n"); ++ while (--port >= 0) { ++ adapdata = i2c_get_adapdata(asf_main_adapters[port]); ++ if (adapdata->smba) { ++ i2c_del_adapter(asf_main_adapters[port]); ++ kfree(adapdata); ++ kfree(asf_main_adapters[port]); ++ asf_main_adapters[port] = NULL; ++ } ++ } ++ ++ return retval; ++} ++ ++static int asf_probe(struct pci_dev *dev, const struct pci_device_id *id) ++{ ++ int retval; ++ ++ if ((dev->vendor == PCI_VENDOR_ID_ATI && dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && ++ dev->revision >= 0x40) || dev->vendor == PCI_VENDOR_ID_AMD) { ++ spin_lock_init(&asf_spinlock); ++ retval = asf_setup_sb800(dev, id, 0); ++ if (retval < 0) ++ return retval; ++ retval = asf_add_adapters_sb800(dev, retval); ++ if (retval < 0) ++ return retval; ++ } ++ ++ return 0; ++} ++ ++static void asf_adap_remove(struct i2c_adapter *adap) ++{ ++ struct i2c_asf_adapdata *adapdata = i2c_get_adapdata(adap); ++ ++ del_timer_sync(&adapdata->slave_timer); ++ ++ if (adapdata->smba) { ++ i2c_del_adapter(adap); ++ if (adapdata->port == (0 << asf_port_shift_sb800)) ++ release_region(adapdata->smba, SMBIOSIZE); ++ kfree(adapdata); ++ kfree(adap); ++ } ++} ++ ++static void asf_remove(struct pci_dev *dev) ++{ ++ int port = asf_adapter_count; ++ ++ while (--port >= 0) { ++ if (asf_main_adapters[port]) { ++ asf_adap_remove(asf_main_adapters[port]); ++ asf_main_adapters[port] = NULL; ++ } ++ } ++ ++ if (asf_aux_adapter) { ++ asf_adap_remove(asf_aux_adapter); ++ asf_aux_adapter = NULL; ++ } ++} ++ ++static struct pci_driver asf_driver = { ++ .name = "asf_smbus", ++ .id_table = asf_ids, ++ .probe = asf_probe, ++ .remove = asf_remove, ++}; ++ ++module_pci_driver(asf_driver); ++ ++MODULE_AUTHOR("dl.amdindia.embedded.ps.linux.all@amd.com"); ++MODULE_DESCRIPTION("ASF SMBus driver"); ++MODULE_LICENSE("GPL"); +-- +2.8.4 + diff --git a/patches-sonic/0055-1-platform-mellanox-mlx-platform-Add-support-DGX-flavo.patch b/patches-sonic/0055-1-platform-mellanox-mlx-platform-Add-support-DGX-flavo.patch new file mode 100644 index 000000000..1b399ac53 --- /dev/null +++ b/patches-sonic/0055-1-platform-mellanox-mlx-platform-Add-support-DGX-flavo.patch @@ -0,0 +1,63 @@ +From ef65e699e5077b48a501e965d19f750bd23f6a27 Mon Sep 17 00:00:00 2001 +From: Oleksandr Shamray +Date: Fri, 5 Dec 2025 12:07:48 +0200 +Subject: [PATCH 08/30] platform: mellanox: mlx-platform: Add support DGX + flavor of 400GB/s ethernet switch. + +This system is based on Nvidia SN4700 Spectrum-3 Based 32x400Gb/s ETH +Switch System, with the +following key changes: + +Key changes: +- New Power Supply: AC/DC PSUs power replaced by rack busbar input power + ORv3 DC 48V-54V. +- Dimensions MGX/DGX 1U compliance Tool-less top cover (fast cover + opening) + +System class: VMOD0010 +System SKU: HI184 + +Reviewed-by: Vadim Pasternak vadimp@mellanox.com + +Signed-off-by: Oleksandr Shamray +--- + drivers/platform/mellanox/mlx-platform.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index ed1e33cb01f4..7e5d18700e1c 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -8185,6 +8185,7 @@ static int __init mlxplat_dmi_comex_matched(const struct dmi_system_id *dmi) + static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi) + { + int i; ++ const char *sku; + + mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; + mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data); +@@ -8194,11 +8195,19 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi) + mlxplat_mux_data[i].n_values = + ARRAY_SIZE(mlxplat_msn21xx_channels); + } +- mlxplat_hotplug = &mlxplat_mlxcpld_ext_data; ++ ++ sku = dmi_get_system_info(DMI_PRODUCT_SKU); ++ if (sku && !strcmp(sku, "HI184")) { ++ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data; ++ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data; ++ } else { ++ mlxplat_hotplug = &mlxplat_mlxcpld_ext_data; ++ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; ++ } ++ + mlxplat_hotplug->deferred_nr = + mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; + mlxplat_led = &mlxplat_default_ng_led_data; +- mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; + mlxplat_fan = &mlxplat_default_fan_data; + for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) + mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; +-- +2.47.3 + diff --git a/patches-sonic/0055-platform-mellanox-Downstream-Add-support-DGX-flavor-.patch b/patches-sonic/0055-platform-mellanox-Downstream-Add-support-DGX-flavor-.patch new file mode 100644 index 000000000..5774a87da --- /dev/null +++ b/patches-sonic/0055-platform-mellanox-Downstream-Add-support-DGX-flavor-.patch @@ -0,0 +1,70 @@ +From a6c422c8a1a6416e34590988537f993b4b36e0ea Mon Sep 17 00:00:00 2001 +From: Oleksandr Shamray +Date: Mon, 9 Dec 2024 13:38:05 +0200 +Subject: [PATCH 18/40] platform: mellanox: Downstream: Add support DGX flavor + of next-generation 800GB/s ethernet switch. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This system is based on Nvidia SN5600 Spectrum™-4 Based 64x800Gb/s ETH Switch System, with the +following key changes: + +Key changes: +- New Power Supply: AC/DC PSUs power repaced by rack busbar input power ORv3 DC 48V-54V. +- Dimensions MGX/DGX 1U compliance Tool-less top cover (fast cover opening with no scre + +Signed-off-by: Oleksandr Shamray +--- + drivers/platform/mellanox/mlx-platform.c | 28 ++++++++++++++++++++++++++++ + 1 file changed, 28 insertions(+) + +diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c +index 4c1073b..54f52d7 100644 +--- a/drivers/platform/mellanox/mlx-platform.c ++++ b/drivers/platform/mellanox/mlx-platform.c +@@ -8318,6 +8318,27 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi) + return mlxplat_register_platform_device(); + } + ++static int __init mlxplat_dmi_ng800_dgx_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; ++ mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data); ++ mlxplat_mux_data = mlxplat_ng800_mux_data; ++ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data; ++ mlxplat_hotplug->deferred_nr = ++ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; ++ mlxplat_led = &mlxplat_default_ng_led_data; ++ mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; ++ mlxplat_fan = &mlxplat_default_fan_data; ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) ++ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; ++ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; ++ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; ++ ++ return mlxplat_register_platform_device(); ++} ++ + static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi) + { + int i; +@@ -8526,6 +8547,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + }, + }, + { ++ .callback = mlxplat_dmi_ng800_dgx_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI174"), ++ }, ++ }, ++ { + .callback = mlxplat_dmi_ng800_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"), +-- +2.8.4 + diff --git a/patches-sonic/0056-hwmon-pmbus-mp2845-Add-support-for-MP2845-device.patch b/patches-sonic/0056-hwmon-pmbus-mp2845-Add-support-for-MP2845-device.patch new file mode 100644 index 000000000..460aadee5 --- /dev/null +++ b/patches-sonic/0056-hwmon-pmbus-mp2845-Add-support-for-MP2845-device.patch @@ -0,0 +1,554 @@ +From 5ba8fda8e39bd295fe065d2c84e60b59e94c2760 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Wed, 3 Dec 2025 17:07:17 +0200 +Subject: [PATCH 1/2] hwmon: (pmbus/mp2845): Add support for MP2845 device + +Add support for MPS device MP2845 - Digital Multi-Phase Power +Controller. + +The MP2845 is a Digital, Multi-Phase PWM Controller with PMBus and +PWM-VID manufactured by Monolithic Power Systems (MPS) designed for +efficient power management in high-performance computing applications. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/pmbus/Kconfig | 9 + + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/mp2845.c | 493 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 503 insertions(+) + create mode 100644 drivers/hwmon/pmbus/mp2845.c + +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +index 369e409..946613e 100644 +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -319,6 +319,15 @@ config SENSORS_MAX8688 + This driver can also be built as a module. If so, the module will + be called max8688. + ++config SENSORS_MP2845 ++ tristate "MPS MP2845" ++ help ++ If you say yes here you get hardware monitoring support for MPS ++ MP2845 Digital Multi-Phase Power Controller. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mp2845. ++ + config SENSORS_MP2855 + tristate "MPS MP2855" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +index a95fc50..c716461 100644 +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -34,6 +34,7 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o + obj-$(CONFIG_SENSORS_MAX31785) += max31785.o + obj-$(CONFIG_SENSORS_MAX34440) += max34440.o + obj-$(CONFIG_SENSORS_MAX8688) += max8688.o ++obj-$(CONFIG_SENSORS_MP2845) += mp2845.o + obj-$(CONFIG_SENSORS_MP2855) += mp2855.o + obj-$(CONFIG_SENSORS_MP2856) += mp2856.o + obj-$(CONFIG_SENSORS_MP2869) += mp2869.o +diff --git a/drivers/hwmon/pmbus/mp2845.c b/drivers/hwmon/pmbus/mp2845.c +new file mode 100644 +index 00000000..96d3dc5 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mp2845.c +@@ -0,0 +1,493 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Digital Controller(MP2845) ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MP2845_IOUT_SCALE_MASK1 GENMASK(5, 3) ++#define MP2845_IOUT_SCALE_MASK2 GENMASK(13, 11) ++ ++#define MFR_VIN_OV_UV_SET 0x71 ++#define MFR_OVUV_OCWARN_THRES 0x75 ++#define MFR_TOTAL_OCP_SET 0x76 ++#define MFR_PROTECT_STATUS1 0x80 ++#define MFR_PROTECT_STATUS2 0x81 ++ ++#define MP2845_VIN_LIMIT_UINT 125 ++#define MP2845_READ_VIN_UINT 3125 ++#define MP2845_READ_VIN_DIV 100 ++#define MP2845_READ_IOUT_UINT 3125 ++#define MP2845_READ_IOUT_DIV 100 ++#define MP2845_READ_VOUT_UINT 5 ++#define MP2845_TEMP_UINT 1000 ++ ++#define MFR_READ_VIN 0xA6 ++#define MFR_READ_VOUT 0xA7 ++#define MFR_READ_IOUT 0xA8 ++#define MFR_READ_TEMP 0xA9 ++#define MFR_MFG_ID_SCALE_VI1 0x77 ++#define MFR_MFG_ID_SCALE_VI2 0x78 ++ ++struct mp2845_data { ++ struct i2c_client *client; ++ int iout_gain[4]; ++ /* lock for preventing concurrency issue */ ++ struct mutex lock; ++}; ++ ++static umode_t mp2845_is_visible(const void *data, enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ switch (type) { ++ case hwmon_temp: ++ case hwmon_in: ++ case hwmon_curr: ++ return 0444; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++static int mp2845_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ struct mp2845_data *data; ++ int ret = 0; ++ ++ data = dev_get_drvdata(dev); ++ ++ mutex_lock(&data->lock); ++ ++ switch (type) { ++ case hwmon_in: ++ if (channel == 0) ++ *str = "vin"; ++ else if (channel == 1) ++ *str = "vout1"; ++ else if (channel == 2) ++ *str = "vout2"; ++ else if (channel == 3) ++ *str = "vout3"; ++ else ++ *str = "vout4"; ++ break; ++ case hwmon_curr: ++ if (channel == 0) ++ *str = "iout1"; ++ else if (channel == 1) ++ *str = "iout2"; ++ else if (channel == 2) ++ *str = "iout3"; ++ else ++ *str = "iout4"; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ mutex_unlock(&data->lock); ++ ++ return ret; ++} ++ ++static int mp2845_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ int ret; ++ struct mp2845_data *data; ++ ++ data = dev_get_drvdata(dev); ++ if (!data) ++ return -ENOMEM; ++ ++ mutex_lock(&data->lock); ++ ++ switch (type) { ++ case hwmon_in: ++ if (channel == 0) { ++ if (attr == hwmon_in_input) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_READ_VIN); ++ if (ret < 0) ++ break; ++ ++ *val = DIV_ROUND_CLOSEST((ret & GENMASK(9, 0)) * ++ MP2845_READ_VIN_UINT, ++ MP2845_READ_VIN_DIV); ++ } else if (attr == hwmon_in_crit) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_VIN_OV_UV_SET); ++ if (ret < 0) ++ break; ++ ++ *val = FIELD_GET(GENMASK(15, 8), ret) * MP2845_VIN_LIMIT_UINT; ++ } else if (attr == hwmon_in_min) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_VIN_OV_UV_SET); ++ if (ret < 0) ++ break; ++ ++ *val = FIELD_GET(GENMASK(7, 0), ret) * MP2845_VIN_LIMIT_UINT; ++ } else if (attr == hwmon_in_crit_alarm) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS2); ++ if (ret < 0) ++ break; ++ ++ *val = FIELD_GET(GENMASK(5, 5), ret); ++ } else { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS2); ++ if (ret < 0) ++ break; ++ ++ *val = FIELD_GET(GENMASK(4, 4), ret); ++ } ++ } else { ++ if (attr == hwmon_in_input) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, channel - 1); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_READ_VOUT); ++ if (ret < 0) ++ break; ++ ++ *val = (ret & GENMASK(9, 0)) * MP2845_READ_VOUT_UINT; ++ } else if (attr == hwmon_in_crit_alarm) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS1); ++ if (ret < 0) ++ break; ++ ++ if (channel == 1) ++ *val = FIELD_GET(GENMASK(10, 10), ret); ++ else if (channel == 2) ++ *val = FIELD_GET(GENMASK(7, 7), ret); ++ else if (channel == 3) ++ *val = FIELD_GET(GENMASK(4, 4), ret); ++ else ++ *val = FIELD_GET(GENMASK(1, 1), ret); ++ } else { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS1); ++ if (ret < 0) ++ break; ++ ++ if (channel == 1) ++ *val = FIELD_GET(GENMASK(11, 11), ret); ++ else if (channel == 2) ++ *val = FIELD_GET(GENMASK(8, 8), ret); ++ else if (channel == 3) ++ *val = FIELD_GET(GENMASK(5, 5), ret); ++ else ++ *val = FIELD_GET(GENMASK(2, 2), ret); ++ } ++ } ++ break; ++ case hwmon_temp: ++ if (attr == hwmon_temp_input) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, channel); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_READ_TEMP); ++ if (ret < 0) ++ break; ++ ++ *val = ((ret & GENMASK(7, 0)) - 40) * MP2845_TEMP_UINT; ++ } else { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS1); ++ if (ret < 0) ++ break; ++ ++ if (channel == 0) { ++ *val = FIELD_GET(GENMASK(12, 12), ret); ++ } else if (channel == 1) { ++ *val = FIELD_GET(GENMASK(14, 14), ret); ++ } else if (channel == 2) { ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS2); ++ if (ret < 0) ++ break; ++ ++ *val = FIELD_GET(GENMASK(0, 0), ret); ++ } else { ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS2); ++ if (ret < 0) ++ break; ++ ++ *val = FIELD_GET(GENMASK(2, 2), ret); ++ } ++ } ++ break; ++ case hwmon_curr: ++ if (attr == hwmon_curr_input) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, channel); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_READ_IOUT); ++ if (ret < 0) ++ break; ++ ++ *val = DIV_ROUND_CLOSEST((ret & GENMASK(9, 0)) * data->iout_gain[channel] * ++ MP2845_READ_IOUT_UINT, MP2845_READ_IOUT_DIV); ++ } else if (attr == hwmon_curr_max) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, channel); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_OVUV_OCWARN_THRES); ++ if (ret < 0) ++ break; ++ ++ *val = DIV_ROUND_CLOSEST(FIELD_GET(GENMASK(15, 8), ret) * ++ data->iout_gain[channel] * MP2845_READ_IOUT_UINT * ++ 4 * 2, MP2845_READ_IOUT_DIV); ++ } else if (attr == hwmon_curr_crit) { ++ ret = i2c_smbus_write_byte_data(data->client, 0, channel); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_TOTAL_OCP_SET); ++ if (ret < 0) ++ break; ++ ++ *val = DIV_ROUND_CLOSEST(FIELD_GET(GENMASK(15, 8), ret) * ++ data->iout_gain[channel] * MP2845_READ_IOUT_UINT * ++ 4 * 2, MP2845_READ_IOUT_DIV); ++ } else { ++ ret = i2c_smbus_write_byte_data(data->client, 0, 0); ++ if (ret < 0) ++ break; ++ ++ ret = i2c_smbus_read_word_data(data->client, MFR_PROTECT_STATUS1); ++ if (ret < 0) ++ break; ++ ++ if (channel == 0) ++ *val = FIELD_GET(GENMASK(9, 9), ret); ++ else if (channel == 2) ++ *val = FIELD_GET(GENMASK(6, 6), ret); ++ else if (channel == 3) ++ *val = FIELD_GET(GENMASK(3, 3), ret); ++ else ++ *val = FIELD_GET(GENMASK(0, 0), ret); ++ } ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ mutex_unlock(&data->lock); ++ ++ return ret; ++} ++ ++static int ++mp2845_identify_iout_scale(struct mp2845_data *data, int page) ++{ ++ int gain; ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(data->client, 0x00, page); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(data->client, page == 0 ? MFR_MFG_ID_SCALE_VI2 : ++ MFR_MFG_ID_SCALE_VI1); ++ if (ret < 0) ++ return ret; ++ ++ gain = page == 0 ? FIELD_GET(MP2845_IOUT_SCALE_MASK1, ret) : ++ FIELD_GET(MP2845_IOUT_SCALE_MASK2, ret); ++ switch (gain) { ++ case 1: ++ data->iout_gain[page] = 1; ++ break; ++ case 2: ++ data->iout_gain[page] = 2; ++ break; ++ case 3: ++ data->iout_gain[page] = 4; ++ break; ++ case 4: ++ data->iout_gain[page] = 8; ++ break; ++ case 5: ++ data->iout_gain[page] = 16; ++ break; ++ case 6: ++ data->iout_gain[page] = 32; ++ break; ++ case 7: ++ data->iout_gain[page] = 64; ++ break; ++ default: ++ data->iout_gain[page] = 1; ++ break; ++ } ++ ++ return 0; ++} ++ ++static const struct hwmon_channel_info *mp2845_info[] = { ++ HWMON_CHANNEL_INFO(in, ++ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_CRIT | HWMON_I_CRIT_ALARM | ++ HWMON_I_LCRIT_ALARM | HWMON_I_LABEL, ++ HWMON_I_INPUT | HWMON_I_CRIT_ALARM | HWMON_I_LCRIT_ALARM | ++ HWMON_I_LABEL, ++ HWMON_I_INPUT | HWMON_I_CRIT_ALARM | HWMON_I_LCRIT_ALARM | ++ HWMON_I_LABEL, ++ HWMON_I_INPUT | HWMON_I_CRIT_ALARM | HWMON_I_LCRIT_ALARM | ++ HWMON_I_LABEL, ++ HWMON_I_INPUT | HWMON_I_CRIT_ALARM | HWMON_I_LCRIT_ALARM | ++ HWMON_I_LABEL), ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT | HWMON_T_CRIT_ALARM, ++ HWMON_T_INPUT | HWMON_T_CRIT_ALARM, ++ HWMON_T_INPUT | HWMON_T_CRIT_ALARM, ++ HWMON_T_INPUT | HWMON_T_CRIT_ALARM), ++ HWMON_CHANNEL_INFO(curr, ++ HWMON_C_INPUT | HWMON_C_CRIT | HWMON_C_CRIT_ALARM | HWMON_C_MAX | ++ HWMON_C_LABEL, ++ HWMON_C_INPUT | HWMON_C_CRIT | HWMON_C_CRIT_ALARM | HWMON_C_MAX | ++ HWMON_C_LABEL, ++ HWMON_C_INPUT | HWMON_C_CRIT | HWMON_C_CRIT_ALARM | HWMON_C_MAX | ++ HWMON_C_LABEL, ++ HWMON_C_INPUT | HWMON_C_CRIT | HWMON_C_CRIT_ALARM | HWMON_C_MAX | ++ HWMON_C_LABEL), ++ NULL ++}; ++ ++static const struct hwmon_ops mp2845_hwmon_ops = { ++ .is_visible = mp2845_is_visible, ++ .read = mp2845_read, ++ .read_string = mp2845_read_string, ++}; ++ ++static const struct hwmon_chip_info mp2845_chip_info = { ++ .ops = &mp2845_hwmon_ops, ++ .info = mp2845_info, ++}; ++ ++static int mp2845_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct device *hwmon_dev; ++ struct mp2845_data *data; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA)) { ++ dev_err(dev, "check failed, smbus byte and/or word data not supported!\n"); ++ return -ENODEV; ++ } ++ ++ data = devm_kzalloc(dev, sizeof(struct mp2845_data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ mutex_init(&data->lock); ++ data->client = client; ++ ++ ret = mp2845_identify_iout_scale(data, 0); ++ if (ret < 0) { ++ dev_err(dev, "unable to identify rail1 iout scale, errno = %d\n", ret); ++ return ret; ++ } ++ ++ ret = mp2845_identify_iout_scale(data, 1); ++ if (ret < 0) { ++ dev_err(dev, "unable to identify rail2 iout scale, errno = %d\n", ret); ++ return ret; ++ } ++ ++ ret = mp2845_identify_iout_scale(data, 2); ++ if (ret < 0) { ++ dev_err(dev, "unable to identify rail3 iout scale, errno = %d\n", ret); ++ return ret; ++ } ++ ++ ret = mp2845_identify_iout_scale(data, 3); ++ if (ret < 0) { ++ dev_err(dev, "unable to identify rail4 iout scale, errno = %d\n", ret); ++ return ret; ++ } ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, ++ data, &mp2845_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) { ++ dev_err(dev, "unable to register mp2845 hwmon device\n"); ++ return PTR_ERR(hwmon_dev); ++ } ++ ++ dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id mp2845_ids[] = { ++ {"mp2845", 0}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mp2845_ids); ++ ++static const struct of_device_id __maybe_unused mp2845_of_match[] = { ++ {.compatible = "mps,mp2845"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mp2845_of_match); ++ ++static struct i2c_driver mp2845_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "mp2845", ++ .of_match_table = mp2845_of_match, ++ }, ++ .probe = mp2845_probe, ++ .id_table = mp2845_ids, ++}; ++module_i2c_driver(mp2845_driver); ++ ++MODULE_AUTHOR("Noah Wang "); ++MODULE_DESCRIPTION("MP2845 driver"); ++MODULE_LICENSE("GPL"); +-- +2.8.4 + diff --git a/patches-sonic/0057-hwmon-pmbus-mp5926-Add-support-for-MP5926-device.patch b/patches-sonic/0057-hwmon-pmbus-mp5926-Add-support-for-MP5926-device.patch new file mode 100644 index 000000000..64caa7736 --- /dev/null +++ b/patches-sonic/0057-hwmon-pmbus-mp5926-Add-support-for-MP5926-device.patch @@ -0,0 +1,252 @@ +From e5d678169c052940009201338af7832fdc672ca2 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Wed, 3 Dec 2025 19:18:38 +0200 +Subject: [PATCH 2/2] hwmon: (pmbus/mp5926): Add support for device MP5926 + +Add support for MPS device MP5926. + +The MP5926 is a high voltage hot-swap controller capable of operating +with supplies 8V to 80V. The MP5926 provides a controlled soft start +during hot insertion to ensure a controlled output voltage ramp occurs. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/pmbus/Kconfig | 9 ++ + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/mp5926.c | 192 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 202 insertions(+) + create mode 100644 drivers/hwmon/pmbus/mp5926.c + +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +index 946613e..08c9e7b 100644 +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -426,6 +426,15 @@ config SENSORS_MP5920 + This driver can also be built as a module. If so, the module will + be called mp5920. + ++config SENSORS_MP5926 ++ tristate "MPS MP5926" ++ help ++ If you say yes here you get hardware monitoring support for MPS ++ MP5926 Controller. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mp5926. ++ + config SENSORS_MP5990 + tristate "MPS MP5990" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +index c716461..1a641d9 100644 +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_MP2975) += mp2975.o + obj-$(CONFIG_SENSORS_MP2993) += mp2993.o + obj-$(CONFIG_SENSORS_MP5023) += mp5023.o + obj-$(CONFIG_SENSORS_MP5920) += mp5920.o ++obj-$(CONFIG_SENSORS_MP5926) += mp5926.o + obj-$(CONFIG_SENSORS_MP5990) += mp5990.o + obj-$(CONFIG_SENSORS_MP9941) += mp9941.o + obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o +diff --git a/drivers/hwmon/pmbus/mp5926.c b/drivers/hwmon/pmbus/mp5926.c +new file mode 100644 +index 00000000..626d945 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mp5926.c +@@ -0,0 +1,192 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++// ++// mp5926.c - pmbus driver for mps mp5926 ++// ++// Copyright 2025 Monolithic Power Systems, Inc ++// ++// Author: Yuxi Wang ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++/*Common Register*/ ++#define PAGE 0x01 ++#define EFUSE_CFG 0xCF ++#define I_SCALE_SEL 0xC6 ++#define MP5926_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | \ ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_VOUT) ++ ++static int mp5926_read_word_data(struct i2c_client *client, int page, int phase, int reg) ++{ ++ int ret; ++ s16 exponent; ++ s32 mantissa; ++ s64 val; ++ ++ switch (reg) { ++ case PMBUS_READ_VIN...PMBUS_READ_VCAP: ++ case PMBUS_READ_IOUT...PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_READ_PIN: ++ case PMBUS_STATUS_WORD: ++ ret = -ENODATA; ++ break; ++ case PMBUS_READ_VOUT: ++ /* The Vout format used by the chip is linear11 and not linear16. ++ * So we transform the value into the direct format defined by PMBus. ++ */ ++ ret = i2c_smbus_read_word_data(client, EFUSE_CFG); ++ if (ret < 0) ++ return ret; ++ if (ret & BIT(12)) { ++ ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT); ++ if (ret < 0) ++ return ret; ++ exponent = ((s16)ret) >> 11; ++ mantissa = ((s16)((ret & 0x7ff) << 5)) >> 5; ++ val = mantissa * 1000; ++ if (exponent >= 0) ++ val <<= exponent; ++ else ++ val >>= -exponent; ++ return div_s64(val * 10 + 313L, 625L); ++ } ++ ret = -ENODATA; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mp5926_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_STATUS_BYTE: ++ case PMBUS_STATUS_VOUT: ++ case PMBUS_STATUS_INPUT: ++ case PMBUS_STATUS_TEMPERATURE: ++ case PMBUS_STATUS_CML: ++ case PMBUS_STATUS_MFR_SPECIFIC: ++ ret = -ENODATA; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ return ret; ++} ++ ++static struct pmbus_driver_info mp5926_info_linear = { ++ .pages = PAGE, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_CURRENT_IN] = linear, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = linear, ++ .format[PSC_POWER] = linear, ++ ++ .m[PSC_VOLTAGE_OUT] = 16, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 0, ++ ++ .read_word_data = mp5926_read_word_data, ++ .read_byte_data = mp5926_read_byte_data, ++ .func[0] = MP5926_FUNC, ++}; ++ ++static struct pmbus_driver_info mp5926_info_direct = { ++ .pages = PAGE, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_CURRENT_IN] = direct, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_POWER] = direct, ++ ++ .m[PSC_VOLTAGE_IN] = 16, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 0, ++ ++ .m[PSC_CURRENT_IN] = 16, ++ .b[PSC_CURRENT_IN] = 0, ++ .R[PSC_CURRENT_IN] = 0, ++ ++ .m[PSC_VOLTAGE_OUT] = 16, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 0, ++ ++ .m[PSC_TEMPERATURE] = 4, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 3, ++ ++ .m[PSC_POWER] = 25, ++ .b[PSC_POWER] = 0, ++ .R[PSC_POWER] = -2, ++ ++ .read_word_data = mp5926_read_word_data, ++ .read_byte_data = mp5926_read_byte_data, ++ .func[0] = MP5926_FUNC, ++}; ++ ++static int mp5926_probe(struct i2c_client *client) ++{ ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA)) ++ return -ENODEV; ++ ++ ret = i2c_smbus_read_word_data(client, EFUSE_CFG); ++ ++ if (ret < 0) ++ return ret; ++ ++ if (ret & BIT(12)) { ++ ret = pmbus_do_probe(client, &mp5926_info_linear); ++ } else { ++ ret = i2c_smbus_read_word_data(client, I_SCALE_SEL); ++ if (ret < 0) ++ return ret; ++ if (ret & BIT(6)) ++ mp5926_info_direct.m[PSC_CURRENT_IN] = 4; ++ ret = pmbus_do_probe(client, &mp5926_info_direct); ++ } ++ if (!ret) ++ dev_info(&client->dev, "%s chip found\n", client->name); ++ return ret; ++} ++ ++static const struct i2c_device_id mp5926_id[] = { ++ { "mp5926", 0 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mp5926_id); ++ ++static const struct of_device_id mp5926_of_match[] = { ++ { .compatible = "mps,mp5926" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mp5926_of_match); ++ ++static struct i2c_driver mp5926_driver = { ++ .probe = mp5926_probe, ++ .driver = { ++ .name = "mp5926", ++ .of_match_table = mp5926_of_match, ++ }, ++ .id_table = mp5926_id, ++}; ++ ++module_i2c_driver(mp5926_driver); ++MODULE_AUTHOR("Yuxi Wang "); ++MODULE_DESCRIPTION("MPS MP5926 HWMON driver"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS(PMBUS); +-- +2.8.4 + diff --git a/patches-sonic/0058-PCI-DOE-Poll-DOE-Busy-bit-for-up-to-1-second-in-pci.patch b/patches-sonic/0058-PCI-DOE-Poll-DOE-Busy-bit-for-up-to-1-second-in-pci.patch new file mode 100644 index 000000000..c8e71f722 --- /dev/null +++ b/patches-sonic/0058-PCI-DOE-Poll-DOE-Busy-bit-for-up-to-1-second-in-pci.patch @@ -0,0 +1,86 @@ +From 86efc62d031307e53ad4011e0aa8898e029cef47 Mon Sep 17 00:00:00 2001 +From: Gregory Price +Date: Fri, 4 Oct 2024 12:28:28 -0400 +Subject: [PATCH] PCI/DOE: Poll DOE Busy bit for up to 1 second in + pci_doe_send_req() + +During initial device probe, the PCI DOE busy bit for some CXL devices may +be left set for a longer period than expected by the current driver logic. +Despite local comments stating DOE Busy is unlikely to be detected, it +appears commonly specifically during boot when CXL devices are being +probed. + +The symptom was messages like this: + + endpoint6: DOE failed -EBUSY + +produced by cxl_cdat_get_length() or cxl_cdat_read_table(). + +This was observed on a single socket AMD platform with 2 CXL memory +expanders attached to the single socket. It was not the case that +concurrent accesses were being made, as validated by monitoring mailbox +commands on the device side. + +This behavior has been observed with multiple CXL memory expanders from +different vendors - so it appears unrelated to the model. + +In all observed tests, only a small period of the retry window is actually +used - typically only a handful of loop iterations. + +Polling on the PCI DOE Busy Bit for (at max) one PCI DOE timeout interval +(1 second), resolves this issue cleanly. + +Per PCIe r6.2 sec 6.30.3, the DOE Busy Bit being cleared does not raise an +interrupt, so polling is the best option in this scenario. + +Subsequent code in doe_statemachine_work() and abort paths also wait for up +to 1 PCI DOE timeout interval, so this order of (potential) additional +delay is presumed acceptable. + +Suggested-by: Lukas Wunner +Link: https://lore.kernel.org/r/20241004162828.314-1-gourry@gourry.net +Signed-off-by: Gregory Price +[bhelgaas: fix nits and add error message to commit log] +Signed-off-by: Bjorn Helgaas +Reviewed-by: Lukas Wunner +Reviewed-by: Jonathan Cameron +--- + drivers/pci/doe.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c +index 652d63df9d22..7bd7892c5222 100644 +--- a/drivers/pci/doe.c ++++ b/drivers/pci/doe.c +@@ -146,6 +146,7 @@ static int pci_doe_send_req(struct pci_doe_mb *doe_mb, + { + struct pci_dev *pdev = doe_mb->pdev; + int offset = doe_mb->cap_offset; ++ unsigned long timeout_jiffies; + size_t length, remainder; + u32 val; + int i; +@@ -155,8 +156,19 @@ static int pci_doe_send_req(struct pci_doe_mb *doe_mb, + * someone other than Linux (e.g. firmware) is using the mailbox. Note + * it is expected that firmware and OS will negotiate access rights via + * an, as yet to be defined, method. ++ * ++ * Wait up to one PCI_DOE_TIMEOUT period to allow the prior command to ++ * finish. Otherwise, simply error out as unable to field the request. ++ * ++ * PCIe r6.2 sec 6.30.3 states no interrupt is raised when the DOE Busy ++ * bit is cleared, so polling here is our best option for the moment. + */ +- pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val); ++ timeout_jiffies = jiffies + PCI_DOE_TIMEOUT; ++ do { ++ pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val); ++ } while (FIELD_GET(PCI_DOE_STATUS_BUSY, val) && ++ !time_after(jiffies, timeout_jiffies)); ++ + if (FIELD_GET(PCI_DOE_STATUS_BUSY, val)) + return -EBUSY; + +-- +2.34.1 + diff --git a/patches-sonic/0059-hwmon-pmbus-xdpe1a2g7b-Add-support-for-XDPE1A2G7B-5B.patch b/patches-sonic/0059-hwmon-pmbus-xdpe1a2g7b-Add-support-for-XDPE1A2G7B-5B.patch new file mode 100644 index 000000000..4412fc995 --- /dev/null +++ b/patches-sonic/0059-hwmon-pmbus-xdpe1a2g7b-Add-support-for-XDPE1A2G7B-5B.patch @@ -0,0 +1,199 @@ +From 880de7f04e7066f8c86f8ee3914e35de09404830 Mon Sep 17 00:00:00 2001 +From: Vasily Vityukov +Date: Tue, 10 Feb 2026 15:29:17 +0200 +Subject: [PATCH] hwmon: (pmbus/xdpe1a2g7b) Add support for XDPE1A2G7B/5B + devices + +--- + drivers/hwmon/pmbus/Kconfig | 9 +++ + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/pmbus.h | 2 +- + drivers/hwmon/pmbus/pmbus_core.c | 4 ++ + drivers/hwmon/pmbus/xdpe1a2g7b.c | 116 +++++++++++++++++++++++++++++++ + 5 files changed, 131 insertions(+), 1 deletion(-) + create mode 100644 drivers/hwmon/pmbus/xdpe1a2g7b.c + +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +index 08c9e7b6e9b0..5ddb715fad8b 100644 +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -621,6 +621,15 @@ config SENSORS_XDPE152 + This driver can also be built as a module. If so, the module will + be called xdpe152c4. + ++config SENSORS_XDPE1A2G7B ++ tristate "Infineon XDPE1A2G7B" ++ help ++ If you say yes here you get hardware monitoring support for Infineon ++ XDPE1A2G7B and XDPE1A2G5B. ++ ++ This driver can also be built as a module. If so, the module will ++ be called xdpe1a2g7b. ++ + config SENSORS_XDPE122 + tristate "Infineon XDPE122 family" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +index 1a641d9a1819..9ebd0524a958 100644 +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -64,5 +64,6 @@ obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o + obj-$(CONFIG_SENSORS_XDP710) += xdp710.o + obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o + obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o ++obj-$(CONFIG_SENSORS_XDPE1A2G7B) += xdpe1a2g7b.o + obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o + obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o +diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h +index d605412a3173..1f6c46ed597b 100644 +--- a/drivers/hwmon/pmbus/pmbus.h ++++ b/drivers/hwmon/pmbus/pmbus.h +@@ -416,7 +416,7 @@ enum pmbus_sensor_classes { + #define PMBUS_PAGE_VIRTUAL BIT(31) /* Page is virtual */ + + enum pmbus_data_format { linear = 0, ieee754, direct, vid }; +-enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv }; ++enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; + + /* PMBus revision identifiers */ + #define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +index 943118e5d3a8..dfab7f5506d5 100644 +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -884,6 +884,10 @@ static s64 pmbus_reg2data_vid(struct pmbus_data *data, + if (val >= 0x0 && val <= 0xd8) + rv = DIV_ROUND_CLOSEST(155000 - val * 625, 100); + break; ++ case nvidia195mv: ++ if (val >= 0x01) ++ rv = 195 + (val - 1) * 5; /* VID step is 5mv */ ++ break; + } + return rv; + } +diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c +new file mode 100644 +index 000000000000..8bd0ba0abb72 +--- /dev/null ++++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c +@@ -0,0 +1,116 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Hardware monitoring driver for Infineon Multi-phase Digital XDPE1A2G7B ++ * and XDPE1A2G5B Controllers ++ * ++ * Copyright (c) 2026 Infineon Technologies. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define XDPE1A2G7B_PAGE_NUM 2 ++#define XDPE1A2G7B_NVIDIA_195MV 0x1E /* NVIDIA mode 1.95mV, VID step is 5mV */ ++ ++static int xdpe1a2g7b_identify(struct i2c_client *client, ++ struct pmbus_driver_info *info) ++{ ++ u8 vout_params; ++ int i, ret, vout_mode; ++ ++ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); ++ if (vout_mode >= 0 && vout_mode != 0xff) { ++ switch (vout_mode >> 5) { ++ case 0: ++ info->format[PSC_VOLTAGE_OUT] = linear; ++ return 0; ++ case 1: ++ info->format[PSC_VOLTAGE_OUT] = vid; ++ break; ++ default: ++ return -ENODEV; ++ } ++ } ++ ++ for (i = 0; i < info->pages; i++) { ++ /* Read the VOUT_MODE register for VID Code Type. */ ++ ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE); ++ if (ret < 0) ++ return ret; ++ ++ vout_params = ret & GENMASK(4, 0); ++ switch (vout_params) { ++ case XDPE1A2G7B_NVIDIA_195MV: ++ info->vrm_version[i] = nvidia195mv; ++ break; ++ default: ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static struct pmbus_driver_info xdpe1a2g7b_info = { ++ .pages = XDPE1A2G7B_PAGE_NUM, ++ .identify = xdpe1a2g7b_identify, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_VOLTAGE_OUT] = linear, ++ .format[PSC_TEMPERATURE] = linear, ++ .format[PSC_CURRENT_IN] = linear, ++ .format[PSC_CURRENT_OUT] = linear, ++ .format[PSC_POWER] = linear, ++ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, ++ .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IIN | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_PIN | ++ PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, ++}; ++ ++static int xdpe1a2g7b_probe(struct i2c_client *client) ++{ ++ struct pmbus_driver_info *info; ++ ++ info = devm_kmemdup(&client->dev, &xdpe1a2g7b_info, sizeof(*info), ++ GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id xdpe1a2g7b_id[] = { { "xdpe1a2g7b" }, ++ { "xdpe1a2g5b" }, ++ {} }; ++ ++MODULE_DEVICE_TABLE(i2c, xdpe1a2g7b_id); ++ ++static const struct of_device_id __maybe_unused xdpe1a2g7b_of_match[] = { ++ { .compatible = "infineon,xdpe1a2g7b" }, ++ { .compatible = "infineon,xdpe1a2g5b" }, ++ {} ++}; ++ ++MODULE_DEVICE_TABLE(of, xdpe1a2g7b_of_match); ++ ++static struct i2c_driver xdpe1a2g7b_driver = { ++ .driver = { ++ .name = "xdpe1a2g7b", ++ .of_match_table = of_match_ptr(xdpe1a2g7b_of_match), ++ }, ++ .probe = xdpe1a2g7b_probe, ++ .id_table = xdpe1a2g7b_id, ++}; ++ ++module_i2c_driver(xdpe1a2g7b_driver); ++ ++MODULE_AUTHOR("Ashish Yadav "); ++MODULE_DESCRIPTION("PMBus driver for Infineon XDPE1A2G7B/5B"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS(PMBUS); +-- +2.34.1 + diff --git a/patches-sonic/0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch b/patches-sonic/0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch new file mode 100644 index 000000000..2e4b4c10c --- /dev/null +++ b/patches-sonic/0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch @@ -0,0 +1,84 @@ +From 4db801c656712234c840883b68429e6d45080ea3 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Tue, 6 Jul 2021 18:38:29 +0000 +Subject: [PATCH backport v5.10.43 49/67] leds: mlxreg: Provide conversion for + hardware LED color code + +In case register is set by hardware, convert hardware color code to +expose correct color to "sysfs". +For some LED color at initial state is set by hardware. Hardware +controls LED color until the first software write access to any LED +register - the first software access cancels hardware control. +If LED is under hardware control - detect the color in brightness_get() +function. + +Signed-off-by: Vadim Pasternak +--- + drivers/leds/leds-mlxreg.c | 27 ++++++++++++++++++++++----- + 1 file changed, 22 insertions(+), 5 deletions(-) + +diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +index a6470480f..67337d9c8 100644 +--- a/drivers/leds/leds-mlxreg.c ++++ b/drivers/leds/leds-mlxreg.c +@@ -17,7 +17,9 @@ + #define MLXREG_LED_OFFSET_BLINK_3HZ 0x01 /* Offset from solid: 3Hz blink */ + #define MLXREG_LED_OFFSET_BLINK_6HZ 0x02 /* Offset from solid: 6Hz blink */ + #define MLXREG_LED_IS_OFF 0x00 /* Off */ ++#define MLXREG_LED_RED_SOLID_HW 0x01 /* Solid red or orange by hardware */ + #define MLXREG_LED_RED_SOLID 0x05 /* Solid red */ ++#define MLXREG_LED_GREEN_SOLID_HW 0x09 /* Solid green by hardware */ + #define MLXREG_LED_GREEN_SOLID 0x0D /* Solid green */ + #define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */ + #define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */ +@@ -29,6 +31,7 @@ + * @data: led configuration data; + * @led_cdev: led class data; + * @base_color: base led color (other colors have constant offset from base); ++ * @base_color_hw: base led color set by hardware; + * @data_parent: pointer to private device control data of parent; + * @led_cdev_name: class device name + */ +@@ -36,6 +39,7 @@ struct mlxreg_led_data { + struct mlxreg_core_data *data; + struct led_classdev led_cdev; + u8 base_color; ++ u8 base_color_hw; + void *data_parent; + char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE]; + }; +@@ -123,8 +127,17 @@ mlxreg_led_get_hw(struct mlxreg_led_data *led_data) + regval = regval & ~data->mask; + regval = (ror32(data->mask, data->bit) == 0xf0) ? ror32(regval, + data->bit) : ror32(regval, data->bit + 4); +- if (regval >= led_data->base_color && +- regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ)) ++ ++ /* ++ * For some LED color at initial state is set by hardware. Hardware controls LED color ++ * until the first write access to any LED register. If LED is under hardware control - ++ * convert the value to the software mask to expose correct color. The first LED set by ++ * software cancels hardware control. ++ */ ++ if ((regval >= led_data->base_color && ++ regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ)) || ++ (led_data->base_color_hw && regval >= led_data->base_color_hw && ++ regval <= (led_data->base_color_hw + MLXREG_LED_OFFSET_BLINK_6HZ))) + return LED_FULL; + + return LED_OFF; +@@ -250,9 +263,11 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) + strstr(data->label, "amber")) { + brightness = LED_OFF; + led_data->base_color = MLXREG_LED_RED_SOLID; ++ led_data->base_color_hw = MLXREG_LED_RED_SOLID_HW; + } else { + brightness = LED_OFF; + led_data->base_color = MLXREG_LED_GREEN_SOLID; ++ led_data->base_color_hw = MLXREG_LED_GREEN_SOLID_HW; + } + snprintf(led_data->led_cdev_name, sizeof(led_data->led_cdev_name), + "mlxreg:%s", data->label); +-- +2.20.1 + diff --git a/patches-sonic/0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch b/patches-sonic/0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch new file mode 100644 index 000000000..361b023b7 --- /dev/null +++ b/patches-sonic/0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch @@ -0,0 +1,186 @@ +From ac1dcaac74bbfc305add230936df03a161110334 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 23 Mar 2026 17:31:25 +0200 +Subject: [PATCH 6.12 2/4] platform/mellanox: nvsw-bmc: Downstream: Add + protection for mux access + +Since mux ownership could be moved between CPU and BMC sides, mux setting +should be allowed only by designated owner. +Otherwise, not granted access could cause I2C transaction cutoff during +execution. + +Add callback to validate if mux setting is allowed. +If not - just skip setting operation. + +Upstream-Status: Pending + +Signed-off-by: Vadim Pasternak +--- + drivers/i2c/muxes/i2c-mux-regmap.c | 13 ++++++ + drivers/platform/mellanox/nvsw-bmc-hid162.c | 45 ++++++++++++++++++++ + include/linux/platform_data/i2c-mux-regmap.h | 2 + + 3 files changed, 60 insertions(+) + +diff --git a/drivers/i2c/muxes/i2c-mux-regmap.c b/drivers/i2c/muxes/i2c-mux-regmap.c +index fb5e89c82423..cd8b45fa4dba 100644 +--- a/drivers/i2c/muxes/i2c-mux-regmap.c ++++ b/drivers/i2c/muxes/i2c-mux-regmap.c +@@ -30,6 +30,12 @@ static int i2c_mux_regmap_select_chan(struct i2c_mux_core *muxc, u32 chan) + struct i2c_mux_regmap *mux = i2c_mux_priv(muxc); + int err = 0; + ++ if (mux->pdata.mux_access_grant) { ++ err = mux->pdata.mux_access_grant(mux->pdata.handle); ++ if (err <= 0) ++ return err; ++ } ++ + /* Only select the channel if its different from the last channel */ + if (mux->last_val != chan) { + err = regmap_write(mux->pdata.regmap, mux->pdata.sel_reg_addr, chan); +@@ -42,6 +48,13 @@ static int i2c_mux_regmap_select_chan(struct i2c_mux_core *muxc, u32 chan) + static int i2c_mux_regmap_deselect(struct i2c_mux_core *muxc, u32 chan) + { + struct i2c_mux_regmap *mux = i2c_mux_priv(muxc); ++ int err; ++ ++ if (mux->pdata.mux_access_grant) { ++ err = mux->pdata.mux_access_grant(mux->pdata.handle); ++ if (err <= 0) ++ return err; ++ } + + /* Deselect active channel */ + mux->last_val = -1; +diff --git a/drivers/platform/mellanox/nvsw-bmc-hid162.c b/drivers/platform/mellanox/nvsw-bmc-hid162.c +index 89343d7660a4..1a0bd9093dd9 100644 +--- a/drivers/platform/mellanox/nvsw-bmc-hid162.c ++++ b/drivers/platform/mellanox/nvsw-bmc-hid162.c +@@ -6965,6 +6965,19 @@ nvsw_bmc_hid162_completion_notify(void *handle, struct i2c_adapter *parent, + return 0; + } + ++static int nvsw_bmc_hid162_mux_access_grant(void *handle) ++{ ++ struct nvsw_core *nvsw_core = handle; ++ u32 regval; ++ int err; ++ ++ err = regmap_read(nvsw_core->regmap, NVSW_REG_GP1_OFFSET, ®val); ++ if (err) ++ return err; ++ ++ return regval & NVSW_MASTER_MASK; ++} ++ + static int nvsw_bmc_hid162_platform_data_init(struct nvsw_core *nvsw_core) + { + int i; +@@ -6976,6 +6989,7 @@ static int nvsw_bmc_hid162_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid162_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7002,6 +7016,7 @@ static int nvsw_bmc_hid176_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7027,6 +7042,7 @@ static int nvsw_bmc_hid177_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7052,6 +7068,7 @@ static int nvsw_bmc_hid180_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid180_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7077,10 +7094,20 @@ static int nvsw_bmc_hid181_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid181_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } + ++ /* ++ * Mux access locking is required for NVSW_REG_MUX1. ++ * NVSW_REG_MUX2 CPLD reg, which controls the leak detector A2Ds, ++ * is accessed also when CPU controls the I2C muxing. ++ * Therefore, we only set mux_access_grant for the second cell in ++ * the i2c_mux_regmap_platform_data array. ++ */ ++ mux_data[0]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; ++ + nvsw_core->regio_data = &nvsw_bmc_hid181_regio; + nvsw_core->led_data = &nvsw_bmc_hid177_led; + nvsw_core->hotplug_data = &nvsw_bmc_hid181_hotplug; +@@ -7106,6 +7133,15 @@ static int nvsw_bmc_hid189_platform_data_init(struct nvsw_core *nvsw_core) + mux_brdinfo[i]->platform_data = mux_data[i]; + } + ++ /* ++ * Mux access locking is required for NVSW_REG_MUX1. ++ * NVSW_REG_MUX2 CPLD reg, which controls the leak detector A2Ds, ++ * is accessed also when CPU controls the I2C muxing. ++ * Therefore, we only set mux_access_grant for the second cell in ++ * the i2c_mux_regmap_platform_data array. ++ */ ++ mux_data[0]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; ++ + nvsw_core->regio_data = &nvsw_bmc_hid193_regio; /* hid189 uses info from 193 */ + nvsw_core->led_data = &nvsw_bmc_hid177_led; + nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; +@@ -7156,6 +7192,15 @@ static int nvsw_bmc_hid193_platform_data_init(struct nvsw_core *nvsw_core) + mux_brdinfo[i]->platform_data = mux_data[i]; + } + ++ /* ++ * Mux access locking is required for NVSW_REG_MUX1. ++ * NVSW_REG_MUX2 CPLD reg, which controls the leak detector A2Ds, ++ * is accessed also when CPU controls the I2C muxing. ++ * Therefore, we only set mux_access_grant for the second cell in ++ * the i2c_mux_regmap_platform_data array. ++ */ ++ mux_data[0]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; ++ + nvsw_core->regio_data = &nvsw_bmc_hid193_regio; + nvsw_core->led_data = &nvsw_bmc_hid177_led; + nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; +diff --git a/include/linux/platform_data/i2c-mux-regmap.h b/include/linux/platform_data/i2c-mux-regmap.h +index a06614e5edd2..f92db6ca7851 100644 +--- a/include/linux/platform_data/i2c-mux-regmap.h ++++ b/include/linux/platform_data/i2c-mux-regmap.h +@@ -18,6 +18,7 @@ + * @reg_size: register size in bytes + * @handle: handle to be passed by callback + * @completion_notify: callback to notify when all the adapters are created ++ * @mux_access_grant: callback to validate if mux access is granted + */ + struct i2c_mux_regmap_platform_data { + void *regmap; +@@ -29,6 +30,7 @@ struct i2c_mux_regmap_platform_data { + void *handle; + int (*completion_notify)(void *handle, struct i2c_adapter *parent, + struct i2c_adapter *adapters[]); ++ int (*mux_access_grant)(void *handle); + }; + + #endif /* __LINUX_PLATFORM_DATA_I2C_MUX_REGMAP_H */ +-- +2.34.1 + diff --git a/patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch b/patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch new file mode 100644 index 000000000..51db8f466 --- /dev/null +++ b/patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch @@ -0,0 +1,35 @@ +Upstream-Status: Pending + +From 9f11c71d770ea5a66ef3a4e6bea134aaaf62adaa Mon Sep 17 00:00:00 2001 +From: Aaron Komisar +Date: Thu, 28 Aug 2025 20:57:42 +0300 +Subject: [PATCH] platform/mellanox: mlxreg-io: Increase max supported + attributes + +Newer NVIDIA/Mellanox platforms expose more mlxreg-io register +attributes than the current limit of 128 allows, so attributes beyond +that count cannot be registered on those platforms. + +Raise MLXREG_IO_ATT_NUM from 128 to 250 to accommodate them. + +Signed-off-by: Aaron Komisar +--- + drivers/platform/mellanox/mlxreg-io.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/mellanox/mlxreg-io.c b/drivers/platform/mellanox/mlxreg-io.c +index ee7bd623ba44..ef290ac9082a 100644 +--- a/drivers/platform/mellanox/mlxreg-io.c ++++ b/drivers/platform/mellanox/mlxreg-io.c +@@ -17,7 +17,7 @@ + + /* Attribute parameters. */ + #define MLXREG_IO_ATT_SIZE 10 +-#define MLXREG_IO_ATT_NUM 128 ++#define MLXREG_IO_ATT_NUM 250 + + /** + * struct mlxreg_io_priv_data - driver's private data: +-- +2.34.1 + diff --git a/patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch b/patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch new file mode 100644 index 000000000..afeb02b99 --- /dev/null +++ b/patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch @@ -0,0 +1,752 @@ +From cdd736d2668627456cda0ca9eaf2256fd4011f9c Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 26 Jan 2026 11:28:09 +0200 +Subject: [PATCH RTC 1/1] Add support for NXP's PCF85053A RTC chip. + +Add a driver for NXP's PCF85053A I2C real-time clock (RTC). + +Introduce the rtc-pcf85053a driver together with its +CONFIG_RTC_DRV_PCF85053A Kconfig and Makefile entries, so the chip can +be used as a standard Linux RTC device on platforms that include it. + +Upstream-Status: Pending + +Signed-off-by: Carlos Menin +Reviewed-by: Sergio Prado +--- + drivers/rtc/Kconfig | 9 + + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-pcf85053a.c | 689 ++++++++++++++++++++++++++++++++++++ + 3 files changed, 699 insertions(+) + create mode 100644 drivers/rtc/rtc-pcf85053a.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 66eb1122248b..7906d4e8ee86 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -473,6 +473,15 @@ config RTC_DRV_PCF8523 + This driver can also be built as a module. If so, the module + will be called rtc-pcf8523. + ++config RTC_DRV_PCF85053A ++ tristate "NXP PCF85053A" ++ select REGMAP_I2C ++ help ++ If you say yes here you get support for the PCF85053A RTC chip ++ ++ This driver can also be built as a module. If so, the module ++ will be called rtc-pcf85053a. ++ + config RTC_DRV_PCF85063 + tristate "NXP PCF85063" + select REGMAP_I2C +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index f62340ecc534..979d1141e5cc 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -125,6 +125,7 @@ obj-$(CONFIG_RTC_DRV_PCAP) += rtc-pcap.o + obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o + obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o + obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o ++obj-$(CONFIG_RTC_DRV_PCF85053A) += rtc-pcf85053a.o + obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o + obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o + obj-$(CONFIG_RTC_DRV_PCF85363) += rtc-pcf85363.o +diff --git a/drivers/rtc/rtc-pcf85053a.c b/drivers/rtc/rtc-pcf85053a.c +new file mode 100644 +index 000000000000..e255407ea796 +--- /dev/null ++++ b/drivers/rtc/rtc-pcf85053a.c +@@ -0,0 +1,689 @@ ++// SPDX-License-Identifier: GPL-2.0 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ADDR_NVMEM 0x57 ++ ++#define REG_SECS 0x00 ++#define REG_MINUTES 0x02 ++#define REG_HOURS 0x04 ++#define REG_WEEKDAYS 0x06 ++#define REG_DAYS 0x07 ++#define REG_MONTHS 0x08 ++#define REG_YEARS 0x09 ++ ++#define REG_SECOND_ALM 0x01 ++#define REG_MINUTE_ALM 0x03 ++#define REG_HOUR_ALM 0x05 ++ ++#define REG_CTRL 0x0a ++#define REG_CTRL_ST BIT(7) ++#define REG_CTRL_DM BIT(6) ++#define REG_CTRL_HF BIT(5) ++#define REG_CTRL_DSM BIT(4) ++#define REG_CTRL_AIE BIT(3) ++#define REG_CTRL_OFIE BIT(2) ++#define REG_CTRL_CIE BIT(1) ++#define REG_CTRL_TWO BIT(0) ++ ++#define REG_STATUS 0x0b ++#define REG_STATUS_AF BIT(7) ++#define REG_STATUS_OF BIT(6) ++#define REG_STATUS_RTCF BIT(5) ++#define REG_STATUS_CIF BIT(4) ++#define REG_STATUS_BVL GENMASK(2, 0) ++ ++#define REG_CLKOUT 0x0c ++#define REG_CLKOUT_CKE BIT(7) ++#define REG_CLKOUT_CKD GENMASK(1, 0) ++ ++#define REG_CTRL2 0x0d ++#define REG_CTRL2_MWO BIT(0) ++ ++#define REG_SCRATCHPAD 0x0e ++ ++#define REG_VERSION 0x0f ++#define REG_VENDOR 0x10 ++#define REG_MODEL 0x11 ++ ++#define REG_OFFSET 0x12 ++ ++#define REG_OSCILLATOR 0x13 ++#define REG_OSC_CLKIV BIT(7) ++#define REG_OSC_OFFM BIT(6) ++#define REG_OSC_LOWJ BIT(4) ++#define REG_OSC_OSCD GENMASK(3, 2) ++#define REG_OSC_CL GENMASK(1, 0) ++ ++#define REG_ACCESS 0x14 ++#define REG_ACCESS_XCLK BIT(7) ++ ++#define REG_SEC_TS 0x15 ++#define REG_MIN_TS 0x16 ++#define REG_HOUR_TS 0x17 ++#define REG_DAYWK_TS 0x18 ++#define REG_DAYMON_TS 0x19 ++#define REG_MON_TS 0x1a ++#define REG_YEAR_TS 0x1b ++ ++#define REG_RCODE1 0x1c ++#define REG_RCODE2 0x1d ++ ++#define OFFSET_STEP0 2170 ++#define OFFSET_STEP1 2034 ++ ++struct pcf85053a { ++ struct rtc_device *rtc; ++ struct regmap *regmap; ++ struct regmap *regmap_nvmem; ++}; ++ ++struct pcf85053a_config { ++ struct regmap_config regmap; ++ struct regmap_config regmap_nvmem; ++}; ++ ++static int pcf85053a_read_offset(struct device *dev, long *offset) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ long val; ++ u32 reg_offset, reg_oscillator; ++ int ret; ++ ++ ret = regmap_read(pcf85053a->regmap, REG_OFFSET, ®_offset); ++ if (ret) ++ return -EIO; ++ ++ ret = regmap_read(pcf85053a->regmap, REG_OSCILLATOR, ®_oscillator); ++ if (ret) ++ return -EIO; ++ ++ val = sign_extend32(reg_offset, 7); ++ ++ if (reg_oscillator & REG_OSC_OFFM) ++ *offset = val * OFFSET_STEP1; ++ else ++ *offset = val * OFFSET_STEP0; ++ ++ return 0; ++} ++ ++static int pcf85053a_set_offset(struct device *dev, long offset) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ s8 mode0, mode1, reg_offset; ++ unsigned int ret, error0, error1; ++ ++ if (offset > OFFSET_STEP0 * 127) ++ return -ERANGE; ++ if (offset < OFFSET_STEP0 * -128) ++ return -ERANGE; ++ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_ACCESS, REG_ACCESS_XCLK); ++ if (ret) ++ return -EIO; ++ ++ mode0 = DIV_ROUND_CLOSEST(offset, OFFSET_STEP0); ++ mode1 = DIV_ROUND_CLOSEST(offset, OFFSET_STEP1); ++ ++ error0 = abs(offset - (mode0 * OFFSET_STEP0)); ++ error1 = abs(offset - (mode1 * OFFSET_STEP1)); ++ if (error0 < error1) { ++ reg_offset = mode0; ++ ret = regmap_clear_bits(pcf85053a->regmap, REG_OSCILLATOR, ++ REG_OSC_OFFM); ++ } else { ++ reg_offset = mode1; ++ ret = regmap_set_bits(pcf85053a->regmap, REG_OSCILLATOR, ++ REG_OSC_OFFM); ++ } ++ if (ret) ++ return -EIO; ++ ++ ret = regmap_write(pcf85053a->regmap, REG_OFFSET, reg_offset); ++ ++ return ret; ++} ++ ++static int pcf85053a_rtc_check_reliability(struct device *dev, u8 status_reg) ++{ ++ int ret = 0; ++ ++ if (status_reg & REG_STATUS_CIF) { ++ dev_warn(dev, "tamper detected," ++ " date/time is not reliable\n"); ++ ret = -EINVAL; ++ } ++ ++ if (status_reg & REG_STATUS_OF) { ++ dev_warn(dev, "oscillator fail detected," ++ " date/time is not reliable.\n"); ++ ret = -EINVAL; ++ } ++ ++ if (status_reg & REG_STATUS_RTCF) { ++ dev_warn(dev, "power loss detected," ++ " date/time is not reliable.\n"); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int pcf85053a_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ u8 buf[REG_STATUS + 1]; ++ int ret, len = sizeof(buf); ++ ++ ret = regmap_bulk_read(pcf85053a->regmap, REG_SECS, buf, len); ++ if (ret) { ++ dev_err(dev, "%s: error %d\n", __func__, ret); ++ return ret; ++ } ++ ++ ret = pcf85053a_rtc_check_reliability(dev, buf[REG_STATUS]); ++ if (ret) ++ return ret; ++ ++ tm->tm_year = buf[REG_YEARS]; ++ /* adjust for 1900 base of rtc_time */ ++ tm->tm_year += 100; ++ ++ tm->tm_wday = (buf[REG_WEEKDAYS] - 1) & 7; /* 1 - 7 */ ++ tm->tm_sec = buf[REG_SECS]; ++ tm->tm_min = buf[REG_MINUTES]; ++ tm->tm_hour = buf[REG_HOURS]; ++ tm->tm_mday = buf[REG_DAYS]; ++ tm->tm_mon = buf[REG_MONTHS] - 1; /* 1 - 12 */ ++ ++ return 0; ++} ++ ++static int pcf85053a_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ struct reg_sequence regs[] = { ++ REG_SEQ0(REG_SECS, tm->tm_sec), ++ REG_SEQ0(REG_MINUTES, tm->tm_min), ++ REG_SEQ0(REG_HOURS, tm->tm_hour), ++ REG_SEQ0(REG_WEEKDAYS, (tm->tm_wday + 1) & 0x7), /* 1 - 7 */ ++ REG_SEQ0(REG_DAYS, tm->tm_mday), ++ REG_SEQ0(REG_MONTHS, tm->tm_mon + 1), /* 1 - 12 */ ++ REG_SEQ0(REG_YEARS, tm->tm_year % 100), ++ }; ++ ++ /* tamper event will clear this bit */ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_CTRL, REG_CTRL_TWO); ++ if (ret) ++ return ret; ++ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_CTRL, REG_CTRL_ST); ++ if (ret) ++ return ret; ++ ++ ret = regmap_multi_reg_write(pcf85053a->regmap, regs, ARRAY_SIZE(regs)); ++ if (ret) ++ return ret; ++ ++ ret = regmap_clear_bits(pcf85053a->regmap, REG_CTRL, REG_CTRL_ST); ++ if (ret) ++ return ret; ++ ++ ret = regmap_clear_bits(pcf85053a->regmap, REG_STATUS, REG_STATUS_OF); ++ ++ return ret; ++} ++ ++static int pcf85053a_bvl_to_mv(unsigned int bvl) ++{ ++ long mv_table[] = { ++ 1700, ++ 1900, ++ 2100, ++ 2300, ++ 2500, ++ 2700, ++ 2900, ++ 3100, ++ }; ++ return mv_table[bvl & 7]; ++} ++ ++static int pcf85053a_hwmon_read_in(struct device *dev, long *mV) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ unsigned int status; ++ int ret; ++ ++ ret = regmap_read(pcf85053a->regmap, REG_STATUS, &status); ++ if (ret) ++ return ret; ++ ++ *mV = pcf85053a_bvl_to_mv(status & REG_STATUS_BVL); ++ return 0; ++} ++ ++static umode_t pcf85053a_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ if (type != hwmon_in) ++ return 0; ++ ++ switch (attr) { ++ case hwmon_in_input: ++ return 0444; ++ default: ++ return 0; ++ } ++} ++ ++static int pcf85053a_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ int ret; ++ ++ switch (attr) { ++ case hwmon_in_input: ++ ret = pcf85053a_hwmon_read_in(dev, val); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ break; ++ } ++ ++ return ret; ++} ++ ++static u32 pcf85053a_hwmon_in_config[] = { ++ HWMON_I_INPUT, ++ 0 ++}; ++ ++static const struct hwmon_channel_info pcf85053a_hwmon_in = { ++ .type = hwmon_in, ++ .config = pcf85053a_hwmon_in_config, ++}; ++ ++static const struct hwmon_channel_info *pcf85053a_hwmon_info[] = { ++ &pcf85053a_hwmon_in, ++ 0 ++}; ++ ++static const struct hwmon_ops pcf85053a_hwmon_ops = { ++ .is_visible = pcf85053a_hwmon_is_visible, ++ .read = pcf85053a_hwmon_read, ++}; ++ ++static const struct hwmon_chip_info pcf85053a_hwmon_chip_info = { ++ .ops = &pcf85053a_hwmon_ops, ++ .info = pcf85053a_hwmon_info, ++}; ++ ++static int pcf85053a_hwmon_register(struct device *dev, const char *name) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ struct device *hwmon_dev; ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(dev, name, pcf85053a, ++ &pcf85053a_hwmon_chip_info, ++ 0); ++ if (IS_ERR(hwmon_dev)) { ++ dev_err(dev, "unable to register hwmon device: %ld\n", ++ PTR_ERR(hwmon_dev)); ++ return PTR_ERR(hwmon_dev); ++ } ++ ++ return 0; ++} ++ ++static int pcf85053a_nvmem_read(void *priv, unsigned int offset, void *val, ++ size_t num) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a = priv; ++ struct regmap *regmap_nvmem = pcf85053a->regmap_nvmem; ++ ++ ret = regmap_bulk_read(regmap_nvmem, offset, val, num); ++ if (ret) ++ pr_warn("%s: failed to read nvmem: %d\n", __func__, ret); ++ ++ return ret; ++} ++ ++static int pcf85053a_nvmem_write(void *priv, unsigned int offset, void *val, ++ size_t num) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a = priv; ++ struct regmap *regmap_nvmem = pcf85053a->regmap_nvmem; ++ ++ /* tamper event will clear this bit */ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_CTRL2, REG_CTRL2_MWO); ++ if (ret) { ++ pr_warn("%s: failed to enable nvmem write: %d", __func__, ret); ++ return ret; ++ } ++ ++ ret = regmap_bulk_write(regmap_nvmem, offset, val, num); ++ if (ret) ++ pr_warn("%s: failed to write nvmem: %d\n", __func__, ret); ++ ++ return ret; ++} ++ ++static ssize_t attr_flag_clear(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count, ++ u8 reg, u8 flag) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev->parent); ++ int ret; ++ ++ (void)attr; ++ (void)buf; ++ (void)count; ++ ++ ret = regmap_clear_bits(pcf85053a->regmap, reg, flag); ++ if (ret) ++ return -EIO; ++ ++ return count; ++} ++ ++static ssize_t attr_flag_read(struct device *dev, ++ struct device_attribute *attr, ++ char *buf, ++ u8 reg, u8 flag) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev->parent); ++ unsigned int status, val; ++ int ret; ++ ++ (void)attr; ++ ret = regmap_read(pcf85053a->regmap, reg, &status); ++ if (ret) ++ return -EIO; ++ ++ val = (status & flag) != 0; ++ ++ return sprintf(buf, "%u\n", val); ++} ++ ++/* flags that can be read or written to be cleared */ ++#define PCF85053A_ATTR_FLAG_RWC(name, reg, flag) \ ++ static ssize_t name ## _store( \ ++ struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, \ ++ size_t count) \ ++ { \ ++ return attr_flag_clear(dev, attr, buf, count, \ ++ REG_ ## reg, REG_ ## reg ## _ ## flag); \ ++ } \ ++ static ssize_t name ## _show( \ ++ struct device *dev, \ ++ struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ return attr_flag_read(dev, attr, buf, \ ++ REG_ ## reg, REG_ ## reg ## _ ## flag); \ ++ } \ ++ static DEVICE_ATTR_RW(name) ++ ++PCF85053A_ATTR_FLAG_RWC(rtc_fail, STATUS, RTCF); ++PCF85053A_ATTR_FLAG_RWC(oscillator_fail, STATUS, OF); ++PCF85053A_ATTR_FLAG_RWC(rtc_clear, STATUS, CIF); ++ ++static struct attribute *pcf85053a_attrs_flags[] = { ++ &dev_attr_rtc_fail.attr, ++ &dev_attr_oscillator_fail.attr, ++ &dev_attr_rtc_clear.attr, ++ 0, ++}; ++ ++static const struct attribute_group pcf85053a_attr_group = { ++ .name = "flags", ++ .attrs = pcf85053a_attrs_flags, ++}; ++ ++static const struct rtc_class_ops pcf85053a_rtc_ops = { ++ .read_offset = pcf85053a_read_offset, ++ .set_offset = pcf85053a_set_offset, ++ .read_time = pcf85053a_rtc_read_time, ++ .set_time = pcf85053a_rtc_set_time, ++}; ++ ++static const struct pcf85053a_config pcf85053a_config = { ++ .regmap = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x1d, ++ }, ++ .regmap_nvmem = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0xff, ++ }, ++}; ++ ++static int pcf85053a_add_nvmem(struct i2c_client *client, ++ struct pcf85053a *pcf85053a) ++{ ++ int ret; ++ const struct pcf85053a_config *config = &pcf85053a_config; ++ struct i2c_client *client_nvmem; ++ struct nvmem_config nvmem_cfg = { ++ .name = "pcf85053a_nvmem", ++ .reg_read = pcf85053a_nvmem_read, ++ .reg_write = pcf85053a_nvmem_write, ++ .type = NVMEM_TYPE_BATTERY_BACKED, ++ .size = 128, ++ }; ++ ++ client_nvmem = devm_i2c_new_dummy_device(&client->dev, client->adapter, ++ ADDR_NVMEM); ++ if (IS_ERR(client_nvmem)) { ++ dev_warn(&client->dev, "failed to create nvmem i2c device\n"); ++ return -ENODEV; ++ } ++ ++ pcf85053a->regmap_nvmem = devm_regmap_init_i2c(client_nvmem, ++ &config->regmap_nvmem); ++ if (IS_ERR(pcf85053a->regmap_nvmem)) { ++ dev_warn(&client->dev, "failed to init nvmem regmap\n"); ++ return -EIO; ++ } ++ ++ nvmem_cfg.priv = pcf85053a; ++ ret = devm_rtc_nvmem_register(pcf85053a->rtc, &nvmem_cfg); ++ ++ return ret; ++} ++ ++static void pcf85053a_set_load_capacitance(struct device *dev, u8 *reg_ctrl) ++{ ++ int ret; ++ u32 val; ++ u8 regval; ++ ++ ret = of_property_read_u32(dev->of_node, "quartz-load-femtofarads", ++ &val); ++ if (ret) { ++ dev_warn(dev, "failed to read quartz-load-femtofarads property," ++ " assuming 12500"); ++ val = 12500; ++ } ++ ++ switch (val) { ++ case 7000: ++ regval = 0; ++ break; ++ case 6000: ++ regval = 1; ++ break; ++ default: ++ dev_warn(dev, "invalid quartz-load-femtofarads value: %u," ++ " assuming 12500", val); ++ fallthrough; ++ case 12500: ++ regval = 2; ++ break; ++ } ++ ++ *reg_ctrl |= regval; ++} ++ ++static void pcf85053a_set_drive_control(struct device *dev, u8 *reg_ctrl) ++{ ++ int ret; ++ const char *val; ++ u8 regval; ++ ++ ret = of_property_read_string(dev->of_node, "nxp,quartz-drive-control", ++ &val); ++ if (ret) { ++ dev_warn(dev, "failed to read nxp,quartz-drive-control property," ++ " assuming 'normal' drive"); ++ val = "normal"; ++ } ++ ++ if (!strcmp(val, "normal")) { ++ regval = 0; ++ } else if (!strcmp(val, "low")) { ++ regval = 1; ++ } else if (!strcmp(val, "high")) { ++ regval = 2; ++ } else { ++ dev_warn(dev, "invalid nxp,quartz-drive-control value: %s," ++ " assuming 'normal' drive", val); ++ regval = 0; ++ } ++ ++ *reg_ctrl |= (regval << 2); ++} ++ ++static void pcf85053a_set_low_jitter(struct device *dev, u8 *reg_ctrl) ++{ ++ bool val; ++ u8 regval; ++ ++ val = of_property_read_bool(dev->of_node, "nxp,low-jitter-mode"); ++ ++ regval = val ? 1 : 0; ++ *reg_ctrl |= (regval << 4); ++} ++ ++static void pcf85053a_set_clk_inverted(struct device *dev, u8 *reg_ctrl) ++{ ++ bool val; ++ u8 regval; ++ ++ val = of_property_read_bool(dev->of_node, "nxp,clk-inverted"); ++ ++ regval = val ? 1 : 0; ++ *reg_ctrl |= (regval << 7); ++} ++ ++static int pcf85053a_probe(struct i2c_client *client) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a; ++ const struct pcf85053a_config *config = &pcf85053a_config; ++ u8 reg_ctrl; ++ ++ pcf85053a = devm_kzalloc(&client->dev, sizeof(*pcf85053a), GFP_KERNEL); ++ if (!pcf85053a) { ++ dev_err(&client->dev, "failed to allocate device: no memory"); ++ return -ENOMEM; ++ } ++ ++ pcf85053a->regmap = devm_regmap_init_i2c(client, &config->regmap); ++ if (IS_ERR(pcf85053a->regmap)) { ++ dev_err(&client->dev, "failed to allocate regmap: %ld\n", ++ PTR_ERR(pcf85053a->regmap)); ++ return PTR_ERR(pcf85053a->regmap); ++ } ++ ++ i2c_set_clientdata(client, pcf85053a); ++ ++ pcf85053a->rtc = devm_rtc_allocate_device(&client->dev); ++ if (IS_ERR(pcf85053a->rtc)) { ++ dev_err(&client->dev, "failed to allocate rtc: %ld\n", ++ PTR_ERR(pcf85053a->rtc)); ++ return PTR_ERR(pcf85053a->rtc); ++ } ++ ++ pcf85053a->rtc->ops = &pcf85053a_rtc_ops; ++ pcf85053a->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; ++ pcf85053a->rtc->range_max = RTC_TIMESTAMP_END_2099; ++ ++ reg_ctrl = REG_CTRL_DM | REG_CTRL_HF | REG_CTRL_CIE; ++ pcf85053a_set_load_capacitance(&client->dev, ®_ctrl); ++ pcf85053a_set_drive_control(&client->dev, ®_ctrl); ++ pcf85053a_set_low_jitter(&client->dev, ®_ctrl); ++ pcf85053a_set_clk_inverted(&client->dev, ®_ctrl); ++ ++ ret = regmap_write(pcf85053a->regmap, REG_CTRL, reg_ctrl); ++ if (ret) { ++ dev_err(&client->dev, "failed to configure rtc: %d\n", ret); ++ return ret; ++ } ++ ++ ret = rtc_add_group(pcf85053a->rtc, &pcf85053a_attr_group); ++ if (ret) { ++ dev_err(&client->dev, "failed to add sysfs entry: %d\n", ret); ++ return ret; ++ } ++ ++ ret = devm_rtc_register_device(pcf85053a->rtc); ++ if (ret) { ++ dev_err(&client->dev, "failed to register rtc: %d\n", ret); ++ return ret; ++ } ++ ++ ret = pcf85053a_add_nvmem(client, pcf85053a); ++ if (ret) { ++ dev_err(&client->dev, "failed to register nvmem: %d\n", ret); ++ return ret; ++ } ++ ++ ret = pcf85053a_hwmon_register(&client->dev, client->name); ++ if (ret) ++ dev_err(&client->dev, "failed to register hwmon: %d\n", ret); ++ ++ return ret; ++} ++ ++static const __maybe_unused struct of_device_id dev_ids[] = { ++ { .compatible = "nxp,pcf85053a", .data = &pcf85053a_config }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, dev_ids); ++ ++static struct i2c_driver pcf85053a_driver = { ++ .driver = { ++ .name = "pcf85053a", ++ .of_match_table = of_match_ptr(dev_ids), ++ }, ++ .probe = &pcf85053a_probe, ++}; ++ ++module_i2c_driver(pcf85053a_driver); ++ ++MODULE_AUTHOR("Carlos Menin "); ++MODULE_DESCRIPTION("PCF85053A I2C RTC driver"); ++MODULE_LICENSE("GPL"); +-- +2.34.1 + diff --git a/patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch b/patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch new file mode 100644 index 000000000..716b69572 --- /dev/null +++ b/patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch @@ -0,0 +1,187 @@ +From d380e09439f8576e4a89dd1f5c32850ce1c14d62 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 2 Apr 2026 12:24:00 +0300 +Subject: [PATCH BMC Realtek PHY 1/1] net: phy: realtek: reset PHY using polling function instead of register write + +When resetting the Realtek PHY, the driver previously triggered the +reset by writing the reset bit directly to a PHY register and continued +without confirming that the reset had actually completed. Because the +reset takes a finite, non-zero time, configuration accesses that ran +immediately afterwards could observe the PHY in an intermediate state, +leading to intermittent, hard-to-reproduce link/initialization failures. + +Replace the bare register write with a polling-based reset that triggers +the reset and then waits until the PHY reports that the reset has +completed before returning. This closes the timing window between the +reset request and the subsequent PHY access. + +Signed-off-by: Mohammad Abu Gazala +--- + drivers/net/phy/realtek.c | 115 +++++++++++++++++++++++++++++++++++++- + 1 file changed, 114 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c +index 8ce5705a..b05e150d 100644 +--- a/drivers/net/phy/realtek.c ++++ b/drivers/net/phy/realtek.c +@@ -44,6 +44,12 @@ + #define RTL8211F_TX_DELAY BIT(8) + #define RTL8211F_RX_DELAY BIT(3) + ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++#define RTL8211F_MODE_SEL (BIT(2) | BIT(1) | BIT(0)) ++#define RTL8211F_SGMII_RGMII_PM 0x4 ++#define RTL8211F_SGMII_RGMII_MP 0x5 ++#endif ++ + #define RTL8211F_ALDPS_PLL_OFF BIT(1) + #define RTL8211F_ALDPS_ENABLE BIT(2) + #define RTL8211F_ALDPS_XTAL_OFF BIT(12) +@@ -120,6 +126,26 @@ static int rtl821x_write_page(struct phy_device *phydev, int page) + return __phy_write(phydev, RTL821x_PAGE_SELECT, page); + } + ++#ifdef CONFIG_REALTEK_600MV_DRIVE_STRENGTH ++static void set_600mv_drive_strength(struct phy_device *phydev){ ++ int ret; ++ struct device *dev = &phydev->mdio.dev; ++ dev_info(dev, "Writing drive strength indirect \n"); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1b, 0xdcd0); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1c, 0x1096); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1b, 0xdcd2); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1c, 0xB490); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1f, 0x0); ++ ++ dev_info(dev, "Done writing drive strength indirect\n"); ++} ++#endif ++ + static int rtl821x_probe(struct phy_device *phydev) + { + struct device *dev = &phydev->mdio.dev; +@@ -156,7 +182,9 @@ static int rtl821x_probe(struct phy_device *phydev) + } + + phydev->priv = priv; +- ++#ifdef CONFIG_REALTEK_600MV_DRIVE_STRENGTH ++ set_600mv_drive_strength(phydev); ++#endif + return 0; + } + +@@ -409,6 +437,49 @@ static int rtl8211f_config_init(struct phy_device *phydev) + return 0; + } + ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++ /* RTL8211F SGMII to RGMII MODE verification - NVIDIA BMC */ ++ ret = phy_read_paged(phydev, 0xd40, 0x10); ++ if(ret < 0) { ++ dev_err(dev, "Failed to read Mode Selection\n"); ++ return ret; ++ } ++ ++ ret = ret & RTL8211F_MODE_SEL; ++ ++ if (ret == RTL8211F_SGMII_RGMII_PM || ret == RTL8211F_SGMII_RGMII_MP) { ++ ++ /* Force hardware straps 3'b100, SGMII(PHY Side) to RGMII(MAC Side) for NVIDIA BMC*/ ++ ret = phy_modify_paged_changed(phydev, 0xd40, 0x10, RTL8211F_MODE_SEL, RTL8211F_SGMII_RGMII_PM); ++ if(ret < 0) { ++ dev_err(dev, "Failed to update the Mode selection \n"); ++ return ret; ++ } ++ ++ ret = genphy_soft_reset(phydev); ++ if(ret < 0) { ++ dev_err(dev, "Failed to reset PHY \n"); ++ return ret; ++ } ++ ++ /* SGMII ANAR (SGMII Auto-Negotiation Advertising Register) */ ++ /*Link status : set to 1 , Duplex Mode : Full Duplex*/ ++ ret = phy_modify_paged_changed(phydev, 0xd08, 0x10, BIT(3) | BIT(2), BIT(3) | BIT(2)); ++ if(ret < 0) { ++ dev_err(dev, "Failed to update the Link & Duplex \n"); ++ return ret; ++ } ++ ++ /* Speed : 1000Mbps */ ++ ret = phy_modify_paged_changed(phydev, 0xd08, 0x10, BIT(1) | BIT(0), 0x2); ++ if(ret < 0){ ++ dev_err(dev, "Failed to update the Speed \n"); ++ return ret; ++ } ++ ++ } ++#endif ++ + ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, + val_txdly); + if (ret < 0) { +@@ -687,6 +758,44 @@ static void rtlgen_decode_speed(struct phy_device *phydev, int val) + } + } + ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++static int rtlgen_read_status(struct phy_device *phydev); ++ ++static int rtl8211f_rtlgen_read_status(struct phy_device *phydev) ++{ ++ int ret; ++ ++ /* RTL8211F SGMII to RGMII MODE verification - NVIDIA BMC */ ++ ret = phy_read_paged(phydev, 0xd40, 0x10); ++ if(ret < 0) { ++ dev_err(&phydev->mdio.dev, "Failed to read Mode Selection\n"); ++ return ret; ++ } ++ ++ ret = ret & RTL8211F_MODE_SEL; ++ ++ if (ret == RTL8211F_SGMII_RGMII_PM || ret == RTL8211F_SGMII_RGMII_MP) { ++ ++ /* Check Link status */ ++ ret = phy_read_paged(phydev, 0xdcf, 0x15); ++ if (ret < 0) { ++ dev_err(&phydev->mdio.dev,"failed to read link status \n"); ++ return ret; ++ } ++ /* Link is Up */ ++ if (ret & BIT(4)) { ++ phydev->link = 1; ++ phydev->speed = 1000; ++ phydev->duplex=DUPLEX_FULL; ++ } ++ } else { ++ return rtlgen_read_status(phydev); ++ } ++ ++ return 0; ++} ++#endif ++ + static int rtlgen_read_status(struct phy_device *phydev) + { + int ret, val; +@@ -1338,7 +1447,11 @@ static struct phy_driver realtek_drvs[] = { + .name = "RTL8211F Gigabit Ethernet", + .probe = rtl821x_probe, + .config_init = &rtl8211f_config_init, ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++ .read_status = rtl8211f_rtlgen_read_status, ++#else + .read_status = rtlgen_read_status, ++#endif + .config_intr = &rtl8211f_config_intr, + .handle_interrupt = rtl8211f_handle_interrupt, + .suspend = rtl821x_suspend, +-- +2.34.1 + diff --git a/patches-sonic/0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch b/patches-sonic/0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch new file mode 100644 index 000000000..5146195e8 --- /dev/null +++ b/patches-sonic/0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch @@ -0,0 +1,66 @@ +From cc38b3dd3d977941a93831f44cd0326a8e8ab34b Mon Sep 17 00:00:00 2001 +From: Vasily Vityukov +Date: Mon, 20 Apr 2026 12:19:20 +0300 +Subject: [PATCH] hwmon: pmbus/isl68137: Add support for Renesas RAA228942 and + RAA228943 + +Add I2C device IDs for Renesas RAA228942 and RAA228943. + +At the Linux PMBus hwmon interface level currently supported by this +driver, these devices are compatible with the existing 2-rail non-TC +controllers, so devicetree will use fallback compatibles and no +dedicated OF match entries are needed. + +Signed-off-by: Dawei Liu +--- + Documentation/hwmon/isl68137.rst | 20 ++++++++++++++++++++ + drivers/hwmon/pmbus/isl68137.c | 2 ++ + 2 files changed, 22 insertions(+) + +diff --git a/Documentation/hwmon/isl68137.rst b/Documentation/hwmon/isl68137.rst +index 0e71b22047f8..1a1424dd640f 100644 +--- a/Documentation/hwmon/isl68137.rst ++++ b/Documentation/hwmon/isl68137.rst +@@ -374,6 +374,26 @@ Supported chips: + + Publicly available (after August 2020 launch) at the Renesas website + ++ * Renesas RAA228942 ++ ++ Prefix: 'raa228942' ++ ++ Addresses scanned: - ++ ++ Datasheet: ++ ++ Provided by Renesas upon request and NDA ++ ++ * Renesas RAA228943 ++ ++ Prefix: 'raa228943' ++ ++ Addresses scanned: - ++ ++ Datasheet: ++ ++ Provided by Renesas upon request and NDA ++ + * Renesas RAA229001 + + Prefix: 'raa229001' +diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c +index 7e53fb1d5ea3..f3baaee6227a 100644 +--- a/drivers/hwmon/pmbus/isl68137.c ++++ b/drivers/hwmon/pmbus/isl68137.c +@@ -311,6 +311,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = { + {"raa228004", raa_dmpvr2_hv}, + {"raa228006", raa_dmpvr2_hv}, + {"raa228228", raa_dmpvr2_2rail_nontc}, ++ {"raa228942", raa_dmpvr2_2rail_nontc}, ++ {"raa228943", raa_dmpvr2_2rail_nontc}, + {"raa229001", raa_dmpvr2_2rail}, + {"raa229004", raa_dmpvr2_2rail}, + {} +-- +2.34.1 + diff --git a/patches-sonic/0195-platform-x86-MLX_PLATFORM-select-REGMAP-instead-of-d.patch b/patches-sonic/0195-platform-x86-MLX_PLATFORM-select-REGMAP-instead-of-d.patch new file mode 100644 index 000000000..9cea6ef25 --- /dev/null +++ b/patches-sonic/0195-platform-x86-MLX_PLATFORM-select-REGMAP-instead-of-d.patch @@ -0,0 +1,46 @@ +From 7e7e1541c91615e9950d0b96bcd1806d297e970e Mon Sep 17 00:00:00 2001 +From: Randy Dunlap +Date: Sat, 25 Feb 2023 21:39:51 -0800 +Subject: [PATCH] platform: x86: MLX_PLATFORM: select REGMAP instead of + depending on it + +REGMAP is a hidden (not user visible) symbol. Users cannot set it +directly thru "make *config", so drivers should select it instead of +depending on it if they need it. + +Consistently using "select" or "depends on" can also help reduce +Kconfig circular dependency issues. + +Therefore, change the use of "depends on REGMAP" to "select REGMAP". + +Fixes: ef0f62264b2a ("platform/x86: mlx-platform: Add physical bus number auto detection") +Signed-off-by: Randy Dunlap +Cc: Vadim Pasternak +Cc: Darren Hart +Cc: Hans de Goede +Cc: Mark Gross +Cc: platform-driver-x86@vger.kernel.org +Link: https://lore.kernel.org/r/20230226053953.4681-7-rdunlap@infradead.org +Signed-off-by: Hans de Goede +Reviewed-by: Hans de Goede +--- + drivers/platform/x86/Kconfig | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index ec7c2b4e1721..4a01b315e0a9 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -955,7 +955,8 @@ config SERIAL_MULTI_INSTANTIATE + + config MLX_PLATFORM + tristate "Mellanox Technologies platform support" +- depends on I2C && REGMAP ++ depends on I2C ++ select REGMAP + help + This option enables system support for the Mellanox Technologies + platform. The Mellanox systems provide data center networking +-- +2.14.1 + diff --git a/patches-sonic/0201-i2c-mlxbf-incorrect-base-address-passed-during-io-wr.patch b/patches-sonic/0201-i2c-mlxbf-incorrect-base-address-passed-during-io-wr.patch new file mode 100644 index 000000000..1933f8217 --- /dev/null +++ b/patches-sonic/0201-i2c-mlxbf-incorrect-base-address-passed-during-io-wr.patch @@ -0,0 +1,43 @@ +From e38eed5cc30d8025b3990273c965c3142fc4733a Mon Sep 17 00:00:00 2001 +From: Asmaa Mnebhi +Date: Thu, 8 Sep 2022 13:35:38 -0400 +Subject: [PATCH backport 5.10 02/63] i2c: mlxbf: incorrect base address passed + during io write + +BugLink: https://bugs.launchpad.net/bugs/1991551 + +Correct the base address used during io write. +This bug had no impact over the overall functionality of the read and write +transactions. MLXBF_I2C_CAUSE_OR_CLEAR=0x18 so writing to (smbus->io + 0x18) +instead of (mst_cause->ioi + 0x18) actually writes to the sc_low_timeout +register which just sets the timeout value before a read/write aborts. + +Fixes: b5b5b32081cd206b (i2c: mlxbf: I2C SMBus driver for Mellanox BlueField SoC) +Reviewed-by: Khalil Blaiech +Signed-off-by: Asmaa Mnebhi +Signed-off-by: Wolfram Sang +(cherry picked from commit 2a5be6d1340c0fefcee8a6489cff7fd88a0d5b85) +Signed-off-by: Asmaa Mnebhi +Acked-by: Tim Gardner +Acked-by: Bartlomiej Zolnierkiewicz +Signed-off-by: Bartlomiej Zolnierkiewicz +--- + drivers/i2c/busses/i2c-mlxbf.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c +index ab261d762..042c83b90 100644 +--- a/drivers/i2c/busses/i2c-mlxbf.c ++++ b/drivers/i2c/busses/i2c-mlxbf.c +@@ -675,7 +675,7 @@ static int mlxbf_i2c_smbus_enable(struct mlxbf_i2c_priv *priv, u8 slave, + /* Clear status bits. */ + writel(0x0, priv->smbus->io + MLXBF_I2C_SMBUS_MASTER_STATUS); + /* Set the cause data. */ +- writel(~0x0, priv->smbus->io + MLXBF_I2C_CAUSE_OR_CLEAR); ++ writel(~0x0, priv->mst_cause->io + MLXBF_I2C_CAUSE_OR_CLEAR); + /* Zero PEC byte. */ + writel(0x0, priv->smbus->io + MLXBF_I2C_SMBUS_MASTER_PEC); + /* Zero byte count. */ +-- +2.20.1 + diff --git a/patches-sonic/0202-i2c-mlxbf-prevent-stack-overflow-in-mlxbf_i2c_smbus_.patch b/patches-sonic/0202-i2c-mlxbf-prevent-stack-overflow-in-mlxbf_i2c_smbus_.patch new file mode 100644 index 000000000..59db34bc6 --- /dev/null +++ b/patches-sonic/0202-i2c-mlxbf-prevent-stack-overflow-in-mlxbf_i2c_smbus_.patch @@ -0,0 +1,41 @@ +From cf835e1581883f7e90fa322efe2dd91a510e51df Mon Sep 17 00:00:00 2001 +From: Asmaa Mnebhi +Date: Thu, 8 Sep 2022 13:35:39 -0400 +Subject: [PATCH backport 5.10 03/63] i2c: mlxbf: prevent stack overflow in + mlxbf_i2c_smbus_start_transaction() + +BugLink: https://bugs.launchpad.net/bugs/1991551 + +memcpy() is called in a loop while 'operation->length' upper bound +is not checked and 'data_idx' also increments. + +Fixes: b5b5b32081cd206b ("i2c: mlxbf: I2C SMBus driver for Mellanox BlueField SoC") +Reviewed-by: Khalil Blaiech +Signed-off-by: Asmaa Mnebhi +Signed-off-by: Wolfram Sang +(cherry picked from commit de24aceb07d426b6f1c59f33889d6a964770547b) +Signed-off-by: Asmaa Mnebhi +Acked-by: Tim Gardner +Acked-by: Bartlomiej Zolnierkiewicz +Signed-off-by: Bartlomiej Zolnierkiewicz +--- + drivers/i2c/busses/i2c-mlxbf.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c +index 042c83b90..d78fb24d5 100644 +--- a/drivers/i2c/busses/i2c-mlxbf.c ++++ b/drivers/i2c/busses/i2c-mlxbf.c +@@ -744,6 +744,9 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv, + if (flags & MLXBF_I2C_F_WRITE) { + write_en = 1; + write_len += operation->length; ++ if (data_idx + operation->length > ++ MLXBF_I2C_MASTER_DATA_DESC_SIZE) ++ return -ENOBUFS; + memcpy(data_desc + data_idx, + operation->buffer, operation->length); + data_idx += operation->length; +-- +2.20.1 + diff --git a/patches-sonic/0204-i2c-mlxbf-Fix-frequency-calculation.patch b/patches-sonic/0204-i2c-mlxbf-Fix-frequency-calculation.patch new file mode 100644 index 000000000..36905d7bb --- /dev/null +++ b/patches-sonic/0204-i2c-mlxbf-Fix-frequency-calculation.patch @@ -0,0 +1,190 @@ +From 5c14aec2f54faa6400d0cfd3255853106afbd2d7 Mon Sep 17 00:00:00 2001 +From: Asmaa Mnebhi +Date: Tue, 20 Sep 2022 13:47:29 -0400 +Subject: [PATCH backport 5.10 05/63] i2c: mlxbf: Fix frequency calculation + +BugLink: https://bugs.launchpad.net/bugs/1991551 + +The i2c-mlxbf.c driver is currently broken because there is a bug +in the calculation of the frequency. core_f, core_r and core_od +are components read from hardware registers and are used to +compute the frequency used to compute different timing parameters. +The shifting mechanism used to get core_f, core_r and core_od is +wrong. Use FIELD_GET to mask and shift the bitfields properly. + +Fixes: b5b5b32081cd206b (i2c: mlxbf: I2C SMBus driver for Mellanox BlueField SoC) +Reviewed-by: Khalil Blaiech +Signed-off-by: Asmaa Mnebhi +Signed-off-by: Wolfram Sang +(cherry picked from commit 37f071ec327b04c83d47637c5e5c2199b39899ca) +Signed-off-by: Asmaa Mnebhi +Acked-by: Tim Gardner +Acked-by: Bartlomiej Zolnierkiewicz +Signed-off-by: Bartlomiej Zolnierkiewicz +--- + drivers/i2c/busses/i2c-mlxbf.c | 63 +++++++++++++--------------------- + 1 file changed, 23 insertions(+), 40 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c +index 066bfeb8a..b4bf1e503 100644 +--- a/drivers/i2c/busses/i2c-mlxbf.c ++++ b/drivers/i2c/busses/i2c-mlxbf.c +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -63,13 +64,14 @@ + */ + #define MLXBF_I2C_TYU_PLL_OUT_FREQ (400 * 1000 * 1000) + /* Reference clock for Bluefield - 156 MHz. */ +-#define MLXBF_I2C_PLL_IN_FREQ (156 * 1000 * 1000) ++#define MLXBF_I2C_PLL_IN_FREQ 156250000ULL + + /* Constant used to determine the PLL frequency. */ +-#define MLNXBF_I2C_COREPLL_CONST 16384 ++#define MLNXBF_I2C_COREPLL_CONST 16384ULL ++ ++#define MLXBF_I2C_FREQUENCY_1GHZ 1000000000ULL + + /* PLL registers. */ +-#define MLXBF_I2C_CORE_PLL_REG0 0x0 + #define MLXBF_I2C_CORE_PLL_REG1 0x4 + #define MLXBF_I2C_CORE_PLL_REG2 0x8 + +@@ -187,22 +189,15 @@ enum { + #define MLXBF_I2C_COREPLL_FREQ MLXBF_I2C_TYU_PLL_OUT_FREQ + + /* Core PLL TYU configuration. */ +-#define MLXBF_I2C_COREPLL_CORE_F_TYU_MASK GENMASK(12, 0) +-#define MLXBF_I2C_COREPLL_CORE_OD_TYU_MASK GENMASK(3, 0) +-#define MLXBF_I2C_COREPLL_CORE_R_TYU_MASK GENMASK(5, 0) +- +-#define MLXBF_I2C_COREPLL_CORE_F_TYU_SHIFT 3 +-#define MLXBF_I2C_COREPLL_CORE_OD_TYU_SHIFT 16 +-#define MLXBF_I2C_COREPLL_CORE_R_TYU_SHIFT 20 ++#define MLXBF_I2C_COREPLL_CORE_F_TYU_MASK GENMASK(15, 3) ++#define MLXBF_I2C_COREPLL_CORE_OD_TYU_MASK GENMASK(19, 16) ++#define MLXBF_I2C_COREPLL_CORE_R_TYU_MASK GENMASK(25, 20) + + /* Core PLL YU configuration. */ + #define MLXBF_I2C_COREPLL_CORE_F_YU_MASK GENMASK(25, 0) + #define MLXBF_I2C_COREPLL_CORE_OD_YU_MASK GENMASK(3, 0) +-#define MLXBF_I2C_COREPLL_CORE_R_YU_MASK GENMASK(5, 0) ++#define MLXBF_I2C_COREPLL_CORE_R_YU_MASK GENMASK(31, 26) + +-#define MLXBF_I2C_COREPLL_CORE_F_YU_SHIFT 0 +-#define MLXBF_I2C_COREPLL_CORE_OD_YU_SHIFT 1 +-#define MLXBF_I2C_COREPLL_CORE_R_YU_SHIFT 26 + + /* Core PLL frequency. */ + static u64 mlxbf_i2c_corepll_frequency; +@@ -485,8 +480,6 @@ static struct mutex mlxbf_i2c_bus_lock; + #define MLXBF_I2C_MASK_8 GENMASK(7, 0) + #define MLXBF_I2C_MASK_16 GENMASK(15, 0) + +-#define MLXBF_I2C_FREQUENCY_1GHZ 1000000000 +- + /* + * Function to poll a set of bits at a specific address; it checks whether + * the bits are equal to zero when eq_zero is set to 'true', and not equal +@@ -1416,24 +1409,19 @@ static int mlxbf_i2c_init_master(struct platform_device *pdev, + return 0; + } + +-static u64 mlxbf_calculate_freq_from_tyu(struct mlxbf_i2c_resource *corepll_res) ++static u64 mlxbf_i2c_calculate_freq_from_tyu(struct mlxbf_i2c_resource *corepll_res) + { +- u64 core_frequency, pad_frequency; ++ u64 core_frequency; + u8 core_od, core_r; + u32 corepll_val; + u16 core_f; + +- pad_frequency = MLXBF_I2C_PLL_IN_FREQ; +- + corepll_val = readl(corepll_res->io + MLXBF_I2C_CORE_PLL_REG1); + + /* Get Core PLL configuration bits. */ +- core_f = rol32(corepll_val, MLXBF_I2C_COREPLL_CORE_F_TYU_SHIFT) & +- MLXBF_I2C_COREPLL_CORE_F_TYU_MASK; +- core_od = rol32(corepll_val, MLXBF_I2C_COREPLL_CORE_OD_TYU_SHIFT) & +- MLXBF_I2C_COREPLL_CORE_OD_TYU_MASK; +- core_r = rol32(corepll_val, MLXBF_I2C_COREPLL_CORE_R_TYU_SHIFT) & +- MLXBF_I2C_COREPLL_CORE_R_TYU_MASK; ++ core_f = FIELD_GET(MLXBF_I2C_COREPLL_CORE_F_TYU_MASK, corepll_val); ++ core_od = FIELD_GET(MLXBF_I2C_COREPLL_CORE_OD_TYU_MASK, corepll_val); ++ core_r = FIELD_GET(MLXBF_I2C_COREPLL_CORE_R_TYU_MASK, corepll_val); + + /* + * Compute PLL output frequency as follow: +@@ -1445,31 +1433,26 @@ static u64 mlxbf_calculate_freq_from_tyu(struct mlxbf_i2c_resource *corepll_res) + * Where PLL_OUT_FREQ and PLL_IN_FREQ refer to CoreFrequency + * and PadFrequency, respectively. + */ +- core_frequency = pad_frequency * (++core_f); ++ core_frequency = MLXBF_I2C_PLL_IN_FREQ * (++core_f); + core_frequency /= (++core_r) * (++core_od); + + return core_frequency; + } + +-static u64 mlxbf_calculate_freq_from_yu(struct mlxbf_i2c_resource *corepll_res) ++static u64 mlxbf_i2c_calculate_freq_from_yu(struct mlxbf_i2c_resource *corepll_res) + { + u32 corepll_reg1_val, corepll_reg2_val; +- u64 corepll_frequency, pad_frequency; ++ u64 corepll_frequency; + u8 core_od, core_r; + u32 core_f; + +- pad_frequency = MLXBF_I2C_PLL_IN_FREQ; +- + corepll_reg1_val = readl(corepll_res->io + MLXBF_I2C_CORE_PLL_REG1); + corepll_reg2_val = readl(corepll_res->io + MLXBF_I2C_CORE_PLL_REG2); + + /* Get Core PLL configuration bits */ +- core_f = rol32(corepll_reg1_val, MLXBF_I2C_COREPLL_CORE_F_YU_SHIFT) & +- MLXBF_I2C_COREPLL_CORE_F_YU_MASK; +- core_r = rol32(corepll_reg1_val, MLXBF_I2C_COREPLL_CORE_R_YU_SHIFT) & +- MLXBF_I2C_COREPLL_CORE_R_YU_MASK; +- core_od = rol32(corepll_reg2_val, MLXBF_I2C_COREPLL_CORE_OD_YU_SHIFT) & +- MLXBF_I2C_COREPLL_CORE_OD_YU_MASK; ++ core_f = FIELD_GET(MLXBF_I2C_COREPLL_CORE_F_YU_MASK, corepll_reg1_val); ++ core_r = FIELD_GET(MLXBF_I2C_COREPLL_CORE_R_YU_MASK, corepll_reg1_val); ++ core_od = FIELD_GET(MLXBF_I2C_COREPLL_CORE_OD_YU_MASK, corepll_reg2_val); + + /* + * Compute PLL output frequency as follow: +@@ -1481,7 +1464,7 @@ static u64 mlxbf_calculate_freq_from_yu(struct mlxbf_i2c_resource *corepll_res) + * Where PLL_OUT_FREQ and PLL_IN_FREQ refer to CoreFrequency + * and PadFrequency, respectively. + */ +- corepll_frequency = (pad_frequency * core_f) / MLNXBF_I2C_COREPLL_CONST; ++ corepll_frequency = (MLXBF_I2C_PLL_IN_FREQ * core_f) / MLNXBF_I2C_COREPLL_CONST; + corepll_frequency /= (++core_r) * (++core_od); + + return corepll_frequency; +@@ -2189,14 +2172,14 @@ static struct mlxbf_i2c_chip_info mlxbf_i2c_chip[] = { + [1] = &mlxbf_i2c_corepll_res[MLXBF_I2C_CHIP_TYPE_1], + [2] = &mlxbf_i2c_gpio_res[MLXBF_I2C_CHIP_TYPE_1] + }, +- .calculate_freq = mlxbf_calculate_freq_from_tyu ++ .calculate_freq = mlxbf_i2c_calculate_freq_from_tyu + }, + [MLXBF_I2C_CHIP_TYPE_2] = { + .type = MLXBF_I2C_CHIP_TYPE_2, + .shared_res = { + [0] = &mlxbf_i2c_corepll_res[MLXBF_I2C_CHIP_TYPE_2] + }, +- .calculate_freq = mlxbf_calculate_freq_from_yu ++ .calculate_freq = mlxbf_i2c_calculate_freq_from_yu + } + }; + +-- +2.20.1 + diff --git a/patches-sonic/0205-i2c-mlxbf-support-lock-mechanism.patch b/patches-sonic/0205-i2c-mlxbf-support-lock-mechanism.patch new file mode 100644 index 000000000..1314b8b28 --- /dev/null +++ b/patches-sonic/0205-i2c-mlxbf-support-lock-mechanism.patch @@ -0,0 +1,123 @@ +From 12a738e1d79fd3e37436a3477b03a778548eac64 Mon Sep 17 00:00:00 2001 +From: Asmaa Mnebhi +Date: Mon, 26 Sep 2022 15:45:04 -0400 +Subject: [PATCH backport 5.10 06/63] i2c: mlxbf: support lock mechanism + +BugLink: https://bugs.launchpad.net/bugs/1991551 + +Linux is not the only entity using the BlueField I2C busses so +support a lock mechanism provided by hardware to avoid issues +when multiple entities are trying to access the same bus. + +The lock is acquired whenever written explicitely or the lock +register is read. So make sure it is always released at the end +of a successful or failed transaction. + +Fixes: b5b5b32081cd206b (i2c: mlxbf: I2C SMBus driver for Mellanox BlueField SoC) +Reviewed-by: Khalil Blaiech +Signed-off-by: Asmaa Mnebhi +Signed-off-by: Wolfram Sang +(cherry picked from commit 86067ccfa1424a26491542d6f6d7546d40b61a10) +Signed-off-by: Asmaa Mnebhi +Acked-by: Tim Gardner +Acked-by: Bartlomiej Zolnierkiewicz +Signed-off-by: Bartlomiej Zolnierkiewicz +--- + drivers/i2c/busses/i2c-mlxbf.c | 44 ++++++++++++++++++++++++++++++---- + 1 file changed, 39 insertions(+), 5 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c +index b4bf1e503..30b798e92 100644 +--- a/drivers/i2c/busses/i2c-mlxbf.c ++++ b/drivers/i2c/busses/i2c-mlxbf.c +@@ -312,6 +312,7 @@ static u64 mlxbf_i2c_corepll_frequency; + * exact. + */ + #define MLXBF_I2C_SMBUS_TIMEOUT (300 * 1000) /* 300ms */ ++#define MLXBF_I2C_SMBUS_LOCK_POLL_TIMEOUT (300 * 1000) /* 300ms */ + + /* Encapsulates timing parameters. */ + struct mlxbf_i2c_timings { +@@ -520,6 +521,25 @@ static bool mlxbf_smbus_master_wait_for_idle(struct mlxbf_i2c_priv *priv) + return false; + } + ++/* ++ * wait for the lock to be released before acquiring it. ++ */ ++static bool mlxbf_i2c_smbus_master_lock(struct mlxbf_i2c_priv *priv) ++{ ++ if (mlxbf_smbus_poll(priv->smbus->io, MLXBF_I2C_SMBUS_MASTER_GW, ++ MLXBF_I2C_MASTER_LOCK_BIT, true, ++ MLXBF_I2C_SMBUS_LOCK_POLL_TIMEOUT)) ++ return true; ++ ++ return false; ++} ++ ++static void mlxbf_i2c_smbus_master_unlock(struct mlxbf_i2c_priv *priv) ++{ ++ /* Clear the gw to clear the lock */ ++ writel(0, priv->smbus->io + MLXBF_I2C_SMBUS_MASTER_GW); ++} ++ + static bool mlxbf_i2c_smbus_transaction_success(u32 master_status, + u32 cause_status) + { +@@ -711,10 +731,19 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv, + slave = request->slave & GENMASK(6, 0); + addr = slave << 1; + +- /* First of all, check whether the HW is idle. */ +- if (WARN_ON(!mlxbf_smbus_master_wait_for_idle(priv))) ++ /* ++ * Try to acquire the smbus gw lock before any reads of the GW register since ++ * a read sets the lock. ++ */ ++ if (WARN_ON(!mlxbf_i2c_smbus_master_lock(priv))) + return -EBUSY; + ++ /* Check whether the HW is idle */ ++ if (WARN_ON(!mlxbf_smbus_master_wait_for_idle(priv))) { ++ ret = -EBUSY; ++ goto out_unlock; ++ } ++ + /* Set first byte. */ + data_desc[data_idx++] = addr; + +@@ -738,8 +767,10 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv, + write_en = 1; + write_len += operation->length; + if (data_idx + operation->length > +- MLXBF_I2C_MASTER_DATA_DESC_SIZE) +- return -ENOBUFS; ++ MLXBF_I2C_MASTER_DATA_DESC_SIZE) { ++ ret = -ENOBUFS; ++ goto out_unlock; ++ } + memcpy(data_desc + data_idx, + operation->buffer, operation->length); + data_idx += operation->length; +@@ -771,7 +802,7 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv, + ret = mlxbf_i2c_smbus_enable(priv, slave, write_len, block_en, + pec_en, 0); + if (ret) +- return ret; ++ goto out_unlock; + } + + if (read_en) { +@@ -798,6 +829,9 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv, + priv->smbus->io + MLXBF_I2C_SMBUS_MASTER_FSM); + } + ++out_unlock: ++ mlxbf_i2c_smbus_master_unlock(priv); ++ + return ret; + } + +-- +2.20.1 + diff --git a/patches-sonic/0265-hwmon-mlxreg-fan-Return-zero-speed-for-broken-fan.patch b/patches-sonic/0265-hwmon-mlxreg-fan-Return-zero-speed-for-broken-fan.patch new file mode 100644 index 000000000..cb0440a0f --- /dev/null +++ b/patches-sonic/0265-hwmon-mlxreg-fan-Return-zero-speed-for-broken-fan.patch @@ -0,0 +1,40 @@ +From 0bb5bbc78316970c3bde085f091a70799b39b262 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 5 Feb 2023 09:34:20 +0200 +Subject: [PATCH hwmon backport 5.10 1/1] hwmon: (mlxreg-fan) Return zero speed + for broken fan + +Currently for broken fan driver returns value calculated based on error +code (0xFF) in related fan speed register. +Thus, for such fan user gets fan{n}_fault to 1 and fan{n}_input with +misleading value. + +Add check for fan fault prior return speed value and return zero if +fault is detected. + +Fixes: 65afb4c8e7e4 ("hwmon: (mlxreg-fan) Add support for Mellanox FAN driver") +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/mlxreg-fan.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c +index acba9d688..b999ca648 100644 +--- a/drivers/hwmon/mlxreg-fan.c ++++ b/drivers/hwmon/mlxreg-fan.c +@@ -158,6 +158,12 @@ mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + if (err) + return err; + ++ if (MLXREG_FAN_GET_FAULT(regval, tacho->mask)) { ++ /* FAN is broken - return zero for FAN speed. */ ++ *val = 0; ++ return 0; ++ } ++ + *val = MLXREG_FAN_GET_RPM(regval, fan->divider, + fan->samples); + break; +-- +2.20.1 + diff --git a/patches-sonic/8000-mlxsw-Use-weak-reverse-dependencies-for-firmware-fla.patch b/patches-sonic/8000-mlxsw-Use-weak-reverse-dependencies-for-firmware-fla.patch new file mode 100644 index 000000000..8ed47d5f3 --- /dev/null +++ b/patches-sonic/8000-mlxsw-Use-weak-reverse-dependencies-for-firmware-fla.patch @@ -0,0 +1,34 @@ +From 807fd2d518ea43c623268edecdc0b58361188af8 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Wed, 20 Oct 2021 09:49:28 +0000 +Subject: [PATCH 56/78] mlxsw: Use weak reverse dependencies for firmware + flashing selection + +Replace configuration options "select" by "imply". It allows to set +'MLXFW' symbol to 'n' from a direct dependency or with a visible +prompt. + +User might wish to disable this option in case only 'mlxsw_minimal' is +configured, since it does not support firmware flashing. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/ethernet/mellanox/mlxsw/Kconfig | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/Kconfig b/drivers/net/ethernet/mellanox/mlxsw/Kconfig +index 74f7e27..ae11732 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/Kconfig ++++ b/drivers/net/ethernet/mellanox/mlxsw/Kconfig +@@ -6,7 +6,7 @@ + config MLXSW_CORE + tristate "Mellanox Technologies Switch ASICs support" + select NET_DEVLINK +- select MLXFW ++ imply MLXFW + select AUXILIARY_BUS + help + This driver supports Mellanox Technologies Switch ASICs family. +-- +2.8.4 + diff --git a/patches-sonic/8002-mlxsw-i2c-SONIC-ISSU-Prevent-transaction-execution-f.patch b/patches-sonic/8002-mlxsw-i2c-SONIC-ISSU-Prevent-transaction-execution-f.patch new file mode 100644 index 000000000..87a432d9d --- /dev/null +++ b/patches-sonic/8002-mlxsw-i2c-SONIC-ISSU-Prevent-transaction-execution-f.patch @@ -0,0 +1,126 @@ +From 06d89b5634d1b54b88d63670a411868d5895c5ea Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 2 Jun 2022 15:34:53 +0300 +Subject: [PATCH 58/78] mlxsw: i2c: SONIC: ISSU: Prevent transaction execution + for special chip states + +Do not run transaction in cases chip is in reset or in-service update +states. +In such case firmware is not accessible and will reject transaction +with the relevant status "RUNNING_RESET" or "FW_ISSU_ONGOING". +In case transaction is failed do to one of these reasons, stop sending +transactions. In such case driver is about to be removed since it +cannot continue running after reset or in-service update. And +re-probed again after reset or in-service update is completed. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/ethernet/mellanox/mlxsw/cmd.h | 4 ++++ + drivers/net/ethernet/mellanox/mlxsw/i2c.c | 29 ++++++++++++++++++++++++++--- + 2 files changed, 30 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/cmd.h b/drivers/net/ethernet/mellanox/mlxsw/cmd.h +index e3271c8..89367bf 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/cmd.h ++++ b/drivers/net/ethernet/mellanox/mlxsw/cmd.h +@@ -149,6 +149,8 @@ enum mlxsw_cmd_status { + MLXSW_CMD_STATUS_BAD_NVMEM = 0x0B, + /* Device is currently running reset */ + MLXSW_CMD_STATUS_RUNNING_RESET = 0x26, ++ /* FW ISSU ongoing. */ ++ MLXSW_CMD_STATUS_FW_ISSU = 0x27, + /* Bad management packet (silently discarded). */ + MLXSW_CMD_STATUS_BAD_PKT = 0x30, + }; +@@ -180,6 +182,8 @@ static inline const char *mlxsw_cmd_status_str(u8 status) + return "BAD_NVMEM"; + case MLXSW_CMD_STATUS_RUNNING_RESET: + return "RUNNING_RESET"; ++ case MLXSW_CMD_STATUS_FW_ISSU: ++ return "FW_ISSU_ONGOING"; + case MLXSW_CMD_STATUS_BAD_PKT: + return "BAD_PKT"; + default: +diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +index 1e150ce..605a597 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +@@ -77,6 +77,7 @@ + * @pdata: device platform data; + * @irq_work: interrupts work item; + * @irq: IRQ line number; ++ * @status: status to indicate chip reset or in-service update; + */ + struct mlxsw_i2c { + struct { +@@ -93,6 +94,7 @@ struct mlxsw_i2c { + struct mlxreg_core_hotplug_platform_data *pdata; + struct work_struct irq_work; + int irq; ++ u8 status; + }; + + #define MLXSW_I2C_READ_MSG(_client, _addr_buf, _buf, _len) { \ +@@ -239,6 +241,19 @@ static int mlxsw_i2c_write_cmd(struct i2c_client *client, + return 0; + } + ++static bool ++mlxsw_i2c_cmd_status_verify(struct device *dev, struct mlxsw_i2c *mlxsw_i2c, ++ u8 status) ++{ ++ if (status == MLXSW_CMD_STATUS_FW_ISSU || ++ status == MLXSW_CMD_STATUS_RUNNING_RESET) { ++ mlxsw_i2c->status = status; ++ dev_info(dev, "FW status=%x(%s)): Access to device is not allowed in this state\n", status, mlxsw_cmd_status_str(status)); ++ return true; ++ } ++ return false; ++} ++ + /* Routine posts initialization command to ASIC through mail box. */ + static int + mlxsw_i2c_write_init_cmd(struct i2c_client *client, +@@ -422,6 +437,10 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, + + WARN_ON(in_mbox_size % sizeof(u32) || out_mbox_size % sizeof(u32)); + ++ /* Do not run transaction if chip is in reset or in-service update state. */ ++ if (mlxsw_i2c->status) ++ return 0; ++ + if (in_mbox) { + reg_size = mlxsw_i2c_get_reg_size(in_mbox); + num = DIV_ROUND_UP(reg_size, mlxsw_i2c->block_size); +@@ -494,6 +513,8 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, + + cmd_fail: + mutex_unlock(&mlxsw_i2c->cmd.lock); ++ if (mlxsw_i2c_cmd_status_verify(&client->dev, mlxsw_i2c, *status)) ++ err = 0; + return err; + } + +@@ -684,14 +705,16 @@ static int mlxsw_i2c_probe(struct i2c_client *client) + /* Wait until go bit is cleared. */ + err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, &status); + if (err) { +- dev_err(&client->dev, "HW semaphore is not released"); ++ if (!mlxsw_i2c_cmd_status_verify(&client->dev, mlxsw_i2c, status)) ++ dev_err(&client->dev, "HW semaphore is not released"); + goto errout; + } + + /* Validate transaction completion status. */ + if (status) { +- dev_err(&client->dev, "Bad transaction completion status %x\n", +- status); ++ if (!mlxsw_i2c_cmd_status_verify(&client->dev, mlxsw_i2c, status)) ++ dev_err(&client->dev, "Bad transaction completion status %x\n", ++ status); + err = -EIO; + goto errout; + } +-- +2.8.4 + diff --git a/patches-sonic/8003-mlxsw-minimal-Downstream-Ignore-error-reading-SPAD-r.patch b/patches-sonic/8003-mlxsw-minimal-Downstream-Ignore-error-reading-SPAD-r.patch new file mode 100644 index 000000000..22f363573 --- /dev/null +++ b/patches-sonic/8003-mlxsw-minimal-Downstream-Ignore-error-reading-SPAD-r.patch @@ -0,0 +1,36 @@ +From c52801a7e7ac91a3587199a69459d7c6308725a7 Mon Sep 17 00:00:00 2001 +From: root +Date: Tue, 5 Apr 2022 21:35:55 +0300 +Subject: [PATCH 59/78] mlxsw: minimal: Downstream: Ignore error reading SPAD + register + +SPAD register is not supported for IB systems. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/ethernet/mellanox/mlxsw/minimal.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/minimal.c b/drivers/net/ethernet/mellanox/mlxsw/minimal.c +index 828c650..6faebeb 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/minimal.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/minimal.c +@@ -53,6 +53,7 @@ struct mlxsw_m_port { + + static int mlxsw_m_base_mac_get(struct mlxsw_m *mlxsw_m) + { ++#if 0 + char spad_pl[MLXSW_REG_SPAD_LEN] = {0}; + int err; + +@@ -60,6 +61,7 @@ static int mlxsw_m_base_mac_get(struct mlxsw_m *mlxsw_m) + if (err) + return err; + mlxsw_reg_spad_base_mac_memcpy_from(spad_pl, mlxsw_m->base_mac); ++#endif + return 0; + } + +-- +2.8.4 + diff --git a/patches-sonic/8004-leds-leds-mlxreg-Downstream-Send-udev-event-from-led.patch b/patches-sonic/8004-leds-leds-mlxreg-Downstream-Send-udev-event-from-led.patch new file mode 100644 index 000000000..4364c3587 --- /dev/null +++ b/patches-sonic/8004-leds-leds-mlxreg-Downstream-Send-udev-event-from-led.patch @@ -0,0 +1,74 @@ +From 5122a4cbfb006f1fe5ddc069a52a1be725a21c93 Mon Sep 17 00:00:00 2001 +From: Michael Shych +Date: Mon, 11 Jul 2022 12:51:27 +0300 +Subject: [PATCH 60/78] leds: leds-mlxreg: Downstream: Send udev event from + leds-mlxreg driver. + +Add sending udev event from leds-mlxreg driver in case of color change. +This patch will not be accepted in upstream as it provides no regular flow. +It's done as a workaround of SN2201 limitation, difference, and in order +to save compatibility of NOS work with sysfs attributes. +The patch should be taken to NOSs as exceptions to the upstream procedure. + +Signed-off-by: Michael Shych +--- + drivers/leds/leds-mlxreg.c | 29 +++++++++++++++++++++++++++-- + 1 file changed, 27 insertions(+), 2 deletions(-) + +diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +index 26add3e..a647048 100644 +--- a/drivers/leds/leds-mlxreg.c ++++ b/drivers/leds/leds-mlxreg.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + + /* Codes for LEDs. */ +@@ -129,15 +130,39 @@ mlxreg_led_get_hw(struct mlxreg_led_data *led_data) + return LED_OFF; + } + ++static char *mlxreg_led_udev_envp[] = { NULL, NULL }; ++ ++static int ++mlxreg_led_udev_event_send(struct mlxreg_led_data *led_data, enum led_brightness value) ++{ ++ struct mlxreg_core_data *data = led_data->data; ++ struct kobject *kobj = &led_data->led_cdev.dev->kobj; ++ ++ char event_str[MLXREG_CORE_LABEL_MAX_SIZE + 2]; ++ char label[MLXREG_CORE_LABEL_MAX_SIZE] = { 0 }; ++ ++ mlxreg_led_udev_envp[0] = event_str; ++ string_upper(label, data->label); ++ snprintf(event_str, MLXREG_CORE_LABEL_MAX_SIZE, "%s=%d", label, value); ++ ++ return kobject_uevent_env(kobj, KOBJ_CHANGE, mlxreg_led_udev_envp); ++} ++ + static int + mlxreg_led_brightness_set(struct led_classdev *cled, enum led_brightness value) + { + struct mlxreg_led_data *led_data = cdev_to_priv(cled); ++ int err; + + if (value) +- return mlxreg_led_store_hw(led_data, led_data->base_color); ++ err = mlxreg_led_store_hw(led_data, led_data->base_color); + else +- return mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF); ++ err = mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF); ++ ++ if (!err) ++ return mlxreg_led_udev_event_send(led_data, value); ++ else ++ return err; + } + + static enum led_brightness +-- +2.8.4 + diff --git a/patches-sonic/8005-i2c-mlxcpld-Downstream-WA-to-avoid-error-for-SMBUS-r.patch b/patches-sonic/8005-i2c-mlxcpld-Downstream-WA-to-avoid-error-for-SMBUS-r.patch new file mode 100644 index 000000000..47b51ed13 --- /dev/null +++ b/patches-sonic/8005-i2c-mlxcpld-Downstream-WA-to-avoid-error-for-SMBUS-r.patch @@ -0,0 +1,30 @@ +From 9ade5581712d6bcaf7413152829f2dfc3a47ca5f Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 10 Aug 2023 12:03:31 +0000 +Subject: [PATCH 61/78] i2c: mlxcpld: Downstream WA to avoid error for SMBUS + read block command + +Due to hardware bug in I2C controller skip handling SMBUS_READ_BLOCK +command. +Remove this patch after bug in I2C controller is fixed. + +Signed-off-by: Vadim Pasternak +--- + drivers/i2c/busses/i2c-mlxcpld.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c +index ecae755..c3b22aa 100644 +--- a/drivers/i2c/busses/i2c-mlxcpld.c ++++ b/drivers/i2c/busses/i2c-mlxcpld.c +@@ -398,6 +398,7 @@ static int mlxcpld_i2c_wait_for_tc(struct mlxcpld_i2c_priv *priv) + mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_NUM_ADDR_REG, &val, + 1); + if (priv->smbus_block && (val & MLXCPLD_I2C_SMBUS_BLK_BIT)) { ++ return 0; + mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_NUM_DAT_REG, + &datalen, 1); + if (unlikely(datalen > I2C_SMBUS_BLOCK_MAX)) { +-- +2.8.4 + diff --git a/patches-sonic/8006-hwmon-mlxreg-fan-Downstream-Allow-fan-speed-setting-.patch b/patches-sonic/8006-hwmon-mlxreg-fan-Downstream-Allow-fan-speed-setting-.patch new file mode 100644 index 000000000..636f2d143 --- /dev/null +++ b/patches-sonic/8006-hwmon-mlxreg-fan-Downstream-Allow-fan-speed-setting-.patch @@ -0,0 +1,56 @@ +From fc641945e34d2a6b6bc2a5bd083372c7c71b4198 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 2 Nov 2023 11:42:47 +0000 +Subject: [PATCH 62/78] hwmon: (mlxreg-fan): Downstream: Allow fan speed + setting granularity of 1 PWM + +Currently PWM setting is allowed with 10 percent stepping. +Such configuration is aligned with thermal drivers, which are used to be +bound to "mlxreg-fan" driver. + +This binding happens when the cooling instances created by the driver are +bound to some kernel thermal driver. + +In case system is not using kernel thermal control and the cooling +instances created by the driver are not bound to any thermal driver, the +driver still does not allow setting of PWM granularity less than 10 +percent. + +Allow setting fan with one percent granularity, thus any user space +thermal application will be able to set PWM to any allowed value in range +from 51 PWM to 255 PWM. + +Note: this is downstream patch, since it can affect functionality for +the Nvidia users running kernel thermal control. So, it is not going to be +submitted to up-stream. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/mlxreg-fan.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c +index 2cd0b9c..61f592c 100644 +--- a/drivers/hwmon/mlxreg-fan.c ++++ b/drivers/hwmon/mlxreg-fan.c +@@ -15,10 +15,15 @@ + #define MLXREG_FAN_MAX_TACHO 24 + #define MLXREG_FAN_MAX_PWM 4 + #define MLXREG_FAN_PWM_NOT_CONNECTED 0xff +-#define MLXREG_FAN_MAX_STATE 10 ++#ifdef CONFIG_MLXSW_CORE_THERMAL ++#define MLXREG_FAN_MAX_STATE 10 ++#define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ ++#else ++#define MLXREG_FAN_MAX_STATE 255 ++#define MLXREG_FAN_SPEED_MIN_LEVEL 51 /* 20 percent */ ++#endif + #define MLXREG_FAN_MIN_DUTY 51 /* 20% */ + #define MLXREG_FAN_MAX_DUTY 255 /* 100% */ +-#define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ + #define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44 + #define MLXREG_FAN_TACHO_DIV_MIN 283 + #define MLXREG_FAN_TACHO_DIV_DEF (MLXREG_FAN_TACHO_DIV_MIN * 4) +-- +2.8.4 + diff --git a/patches-sonic/8007-hwmon-emc2305-Downstream-Allow-fan-speed-setting-gra.patch b/patches-sonic/8007-hwmon-emc2305-Downstream-Allow-fan-speed-setting-gra.patch new file mode 100644 index 000000000..e17b6b62e --- /dev/null +++ b/patches-sonic/8007-hwmon-emc2305-Downstream-Allow-fan-speed-setting-gra.patch @@ -0,0 +1,50 @@ +From c43e12b088a65e3d4431712f3877969854d7057c Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 2 Nov 2023 12:18:22 +0000 +Subject: [PATCH 63/78] hwmon: (emc2305): Downstream: Allow fan speed setting + granularity of 1 PWM + +Currently PWM setting is allowed with 10 percent stepping. +Such configuration is aligned with thermal drivers, which are used to be +bound to "emc2305" driver. + +This binding happens when the cooling instances created by the driver are +bound to some kernel thermal driver. + +In case system is not using kernel thermal control and the cooling +instances created by the driver are not bound to any thermal driver, the +driver still does not allow setting of PWM granularity less than 10 +percent. + +Allow setting fan with one percent granularity, thus any user space +thermal application will be able to set PWM to any allowed value in range +from 0 PWM to 255 PWM. + +Note: this is downstream patch, since it can affect functionality for +the Nvidia users running kernel thermal control. So, it is not going to be +submitted to up-stream. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/emc2305.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c +index 4d39fbd..0066138 100644 +--- a/drivers/hwmon/emc2305.c ++++ b/drivers/hwmon/emc2305.c +@@ -16,7 +16,11 @@ + #define EMC2305_REG_VENDOR 0xfe + #define EMC2305_FAN_MAX 0xff + #define EMC2305_FAN_MIN 0x00 ++#ifdef CONFIG_MLXSW_CORE_THERMAL + #define EMC2305_FAN_MAX_STATE 10 ++#else ++#define EMC2305_FAN_MAX_STATE 255 ++#endif + #define EMC2305_DEVICE 0x34 + #define EMC2305_VENDOR 0x5d + #define EMC2305_REG_PRODUCT_ID 0xfd +-- +2.8.4 + diff --git a/patches-sonic/8008-hwmon-mlxsw-Downstream-Allow-fan-speed-setting-granu.patch b/patches-sonic/8008-hwmon-mlxsw-Downstream-Allow-fan-speed-setting-granu.patch new file mode 100644 index 000000000..33ecd3ba8 --- /dev/null +++ b/patches-sonic/8008-hwmon-mlxsw-Downstream-Allow-fan-speed-setting-granu.patch @@ -0,0 +1,52 @@ +From 4b90028daea354041ff3e9d63752019f30f69760 Mon Sep 17 00:00:00 2001 +From: Oleksandr Shamray +Date: Wed, 8 Nov 2023 14:52:38 +0200 +Subject: [PATCH 64/78] hwmon: (mlxsw): Downstream: Allow fan speed setting + granularity of 1 PWM + +Currently PWM setting is allowed with 10 percent stepping. +Such configuration is aligned with thermal drivers, which are used to be +bound to "mlxsw" driver. + +This binding happens when the cooling instances created by the driver are +bound to some kernel thermal driver. + +In case system is not using kernel thermal control and the cooling +instances created by the driver are not bound to any thermal driver, the +driver still does not allow setting of PWM granularity less than 10 +percent. + +Allow setting fan with one percent granularity, thus any user space +thermal application will be able to set PWM to any allowed value in range +from 51 PWM to 255 PWM. + +Note: this is downstream patch, since it can affect functionality for +the Nvidia users running kernel thermal control. So, it is not going to be +submitted to up-stream. + +Signed-off-by: Oleksandr Shamray +--- + drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c +index e746cd9..e19f36f2e 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c +@@ -24,8 +24,13 @@ + #define MLXSW_THERMAL_MODULE_TEMP_HOT 80000 /* 80C */ + #define MLXSW_THERMAL_HYSTERESIS_TEMP 5000 /* 5C */ + #define MLXSW_THERMAL_MODULE_TEMP_SHIFT (MLXSW_THERMAL_HYSTERESIS_TEMP * 2) ++#ifdef CONFIG_MLXSW_CORE_THERMAL + #define MLXSW_THERMAL_MAX_STATE 10 + #define MLXSW_THERMAL_MIN_STATE 2 ++#else ++#define MLXSW_THERMAL_MAX_STATE 255 ++#define MLXSW_THERMAL_MIN_STATE 51 /* 20 percent */ ++#endif + #define MLXSW_THERMAL_MAX_DUTY 255 + + /* External cooling devices, allowed for binding to mlxsw thermal zones. */ +-- +2.8.4 + diff --git a/patches-sonic/8009-mlxsw-i2c-Downstream-Add-retry-mechanism-for-failed-.patch b/patches-sonic/8009-mlxsw-i2c-Downstream-Add-retry-mechanism-for-failed-.patch new file mode 100644 index 000000000..dafcd8153 --- /dev/null +++ b/patches-sonic/8009-mlxsw-i2c-Downstream-Add-retry-mechanism-for-failed-.patch @@ -0,0 +1,128 @@ +From c9c143552767e43c24b66a0c256f30c9b10abdb9 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 19 Nov 2023 13:22:27 +0000 +Subject: [PATCH 65/78] mlxsw: i2c: Downstream: Add retry mechanism for failed + transactions + +Sometimes I2C transactions could broken or non-completed because of +some noise on I2C line or because ASIC resources is busy handling +big amount of PCIe tarnsactions. + +Add re-try mechanism for re-sending transaction which was not properly +completed. +Retry up to three times and produce error log only in case the last try +is not successful. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/ethernet/mellanox/mlxsw/i2c.c | 33 ++++++++++++++++++++++--------- + 1 file changed, 24 insertions(+), 9 deletions(-) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +index 605a597..1ecf21f 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +@@ -51,6 +51,7 @@ + #define MLXSW_I2C_BLK_MAX 100 + #define MLXSW_I2C_RETRY 5 + #define MLXSW_I2C_TIMEOUT_MSECS 5000 ++#define MLXSW_I2C_CMD_RETRY_FW_ERR 3 + #define MLXSW_I2C_MAX_DATA_SIZE 256 + + /* Driver can be initialized by kernel platform driver or from the user +@@ -78,6 +79,7 @@ + * @irq_work: interrupts work item; + * @irq: IRQ line number; + * @status: status to indicate chip reset or in-service update; ++ * @retry_cntr: retry counter for failed transaction; + */ + struct mlxsw_i2c { + struct { +@@ -95,6 +97,7 @@ struct mlxsw_i2c { + struct work_struct irq_work; + int irq; + u8 status; ++ int retry_cntr; + }; + + #define MLXSW_I2C_READ_MSG(_client, _addr_buf, _buf, _len) { \ +@@ -395,7 +398,8 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, + /* Prepare and write out Command Interface Register for transaction. */ + err = mlxsw_i2c_write_cmd(client, mlxsw_i2c, 0); + if (err) { +- dev_err(&client->dev, "Could not start transaction"); ++ if (mlxsw_i2c->retry_cntr == MLXSW_I2C_CMD_RETRY_FW_ERR) ++ dev_err(&client->dev, "Could not start transaction"); + err = -EIO; + goto mlxsw_i2c_write_exit; + } +@@ -403,14 +407,16 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, + /* Wait until go bit is cleared. */ + err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, p_status); + if (err) { +- dev_err(&client->dev, "HW semaphore is not released"); ++ if (mlxsw_i2c->retry_cntr == MLXSW_I2C_CMD_RETRY_FW_ERR) ++ dev_err(&client->dev, "HW semaphore is not released"); + goto mlxsw_i2c_write_exit; + } + + /* Validate transaction completion status. */ + if (*p_status) { +- dev_err(&client->dev, "Bad transaction completion status %x\n", +- *p_status); ++ if (mlxsw_i2c->retry_cntr == MLXSW_I2C_CMD_RETRY_FW_ERR) ++ dev_err(&client->dev, "Bad transaction completion status %x\n", ++ *p_status); + err = -EIO; + } + +@@ -440,14 +446,16 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, + /* Do not run transaction if chip is in reset or in-service update state. */ + if (mlxsw_i2c->status) + return 0; +- ++retry: + if (in_mbox) { + reg_size = mlxsw_i2c_get_reg_size(in_mbox); + num = DIV_ROUND_UP(reg_size, mlxsw_i2c->block_size); + + if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { +- dev_err(&client->dev, "Could not acquire lock"); +- return -EINVAL; ++ if (mlxsw_i2c->retry_cntr == MLXSW_I2C_CMD_RETRY_FW_ERR) ++ dev_err(&client->dev, "Could not acquire lock"); ++ err = -EINVAL; ++ goto cmd_retry; + } + + err = mlxsw_i2c_write(dev, reg_size, in_mbox, num, status); +@@ -465,8 +473,10 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, + num = DIV_ROUND_UP(reg_size, mlxsw_i2c->block_size); + + if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { +- dev_err(&client->dev, "Could not acquire lock"); +- return -EINVAL; ++ if (mlxsw_i2c->retry_cntr == MLXSW_I2C_CMD_RETRY_FW_ERR) ++ dev_err(&client->dev, "Could not acquire lock"); ++ err = -EINVAL; ++ goto cmd_retry; + } + + err = mlxsw_i2c_write_init_cmd(client, mlxsw_i2c, opcode, +@@ -513,8 +523,13 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size, + + cmd_fail: + mutex_unlock(&mlxsw_i2c->cmd.lock); ++cmd_retry: + if (mlxsw_i2c_cmd_status_verify(&client->dev, mlxsw_i2c, *status)) + err = 0; ++ else if (mlxsw_i2c->retry_cntr++ < MLXSW_I2C_CMD_RETRY_FW_ERR) ++ goto retry; ++ mlxsw_i2c->retry_cntr = 0; ++ + return err; + } + +-- +2.8.4 + diff --git a/patches-sonic/8010-mlxsw-minimal-Downstream-Disable-ethtool-interface.patch b/patches-sonic/8010-mlxsw-minimal-Downstream-Disable-ethtool-interface.patch new file mode 100644 index 000000000..20792d8b3 --- /dev/null +++ b/patches-sonic/8010-mlxsw-minimal-Downstream-Disable-ethtool-interface.patch @@ -0,0 +1,50 @@ +From 38714ff080a0e0abc22d0ace3c3b12adcb474ae1 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 14 Jan 2024 16:46:03 +0000 +Subject: [PATCH 66/78] mlxsw: minimal: Downstream: Disable ethtool interface + +Disable 'ethtool' interface, since it is un-used and just created +un-necessary 'netdevice' interfaces. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/ethernet/mellanox/mlxsw/core_env.c | 1 + + drivers/net/ethernet/mellanox/mlxsw/minimal.c | 3 +++ + 2 files changed, 4 insertions(+) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_env.c b/drivers/net/ethernet/mellanox/mlxsw/core_env.c +index 294e758..21cee901 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/core_env.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/core_env.c +@@ -1474,6 +1474,7 @@ int mlxsw_env_init(struct mlxsw_core *mlxsw_core, + if (err) + goto err_linecards_event_ops_register; + ++ return 0; + err = mlxsw_env_temp_warn_event_register(mlxsw_core); + if (err) + goto err_temp_warn_event_register; +diff --git a/drivers/net/ethernet/mellanox/mlxsw/minimal.c b/drivers/net/ethernet/mellanox/mlxsw/minimal.c +index 6faebeb..b72434f 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/minimal.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/minimal.c +@@ -534,6 +534,8 @@ static int mlxsw_m_ports_create(struct mlxsw_m *mlxsw_m) + { + int err; + ++ return 0; ++ + /* Fill out module to local port mapping array */ + err = mlxsw_m_ports_module_map(mlxsw_m); + if (err) +@@ -555,6 +557,7 @@ static int mlxsw_m_ports_create(struct mlxsw_m *mlxsw_m) + + static void mlxsw_m_ports_remove(struct mlxsw_m *mlxsw_m) + { ++ return; + mlxsw_m_linecard_ports_remove(mlxsw_m, 0); + } + +-- +2.8.4 + diff --git a/patches-sonic/8011-hwmon-pmbus-Downstream-Workaround-for-psu-attributes.patch b/patches-sonic/8011-hwmon-pmbus-Downstream-Workaround-for-psu-attributes.patch new file mode 100644 index 000000000..91689b7de --- /dev/null +++ b/patches-sonic/8011-hwmon-pmbus-Downstream-Workaround-for-psu-attributes.patch @@ -0,0 +1,37 @@ +From 6108367b56ae9df24d5d5869860943a3156ae2db Mon Sep 17 00:00:00 2001 +From: Ciju Rajan K +Date: Thu, 10 Oct 2024 14:13:28 +0300 +Subject: [PATCH 67/78] hwmon: pmbus: Downstream: Workaround for psu attributes + +Starting from kernel 5.12.rc-1, pmbus reads are always +forced down to chip and get the values directly. Driver +doesn't cache the values anymore. + +This behviour change has caused problems in Delta-1.1K +PSUs, on which pmbus registers were not written. + +This patch introduces the driver caching. + +Bugs# 3874682 + +Signed-off-by: Ciju Rajan K +--- + drivers/hwmon/pmbus/pmbus_core.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +index a68b0a9..943118e 100644 +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -1248,7 +1248,7 @@ static ssize_t pmbus_set_sensor(struct device *dev, + if (ret < 0) + rv = ret; + else +- sensor->data = -ENODATA; ++ sensor->data = regval; + mutex_unlock(&data->update_lock); + return rv; + } +-- +2.8.4 + diff --git a/patches-sonic/8013-hwmon-pmbus-mp2975-Clear-interrupts-at-probe.patch b/patches-sonic/8013-hwmon-pmbus-mp2975-Clear-interrupts-at-probe.patch new file mode 100644 index 000000000..282a5e128 --- /dev/null +++ b/patches-sonic/8013-hwmon-pmbus-mp2975-Clear-interrupts-at-probe.patch @@ -0,0 +1,70 @@ +From ac4dfa5e6a8186732ad1fe31ad70052e44222c9f Mon Sep 17 00:00:00 2001 +From: Ciju Rajan K +Date: Tue, 9 Sep 2025 08:48:47 +0300 +Subject: hwmon: pmbus: mp2975: Clear interrupts at probe + +This patch clears the the registers 0xfd & 0x03 +in both the pages to clear any of the stale interrupts. + +NVBug: #5360318 + +Signed-off-by: Ciju Rajan K +--- + drivers/hwmon/pmbus/mp2975.c | 28 ++++++++++++++++++++++++++++ + 1 file changed, 28 insertions(+) + +diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c +index 280bb12f762c..fbaee50c7129 100644 +--- a/drivers/hwmon/pmbus/mp2975.c ++++ b/drivers/hwmon/pmbus/mp2975.c +@@ -38,6 +38,7 @@ + #define MP2975_MFR_UVP_SET 0xe6 + + #define MP2973_MFR_RESO_SET 0xc7 ++#define MP2975_CLEAR_CAT_FAULTS 0xfd + + #define MP2975_VOUT_FORMAT BIT(15) + #define MP2975_VID_STEP_SEL_R1 BIT(4) +@@ -939,6 +940,28 @@ mp2975_vout_per_rail_config_get(struct i2c_client *client, + return 0; + } + ++static int ++mp2975_clear_interrupts(struct i2c_client *client, ++ struct mp2975_data *data) ++{ ++ int i = 0; ++ int ret = 0; ++ ++ for (i = 0; i < data->info.pages; i++) { ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); ++ if (ret < 0) ++ return ret; ++ ret = i2c_smbus_write_byte_data(client, PMBUS_CLEAR_FAULTS, 0); ++ if (ret < 0) ++ return ret; ++ ret = i2c_smbus_write_byte_data(client, MP2975_CLEAR_CAT_FAULTS, 0); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return ret; ++} ++ + static const struct pmbus_driver_info mp2975_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, +@@ -1068,6 +1091,11 @@ static int mp2975_probe(struct i2c_client *client) + if (ret) + return ret; + ++ /* Clear interrupts. */ ++ ret = mp2975_clear_interrupts(client, data); ++ if (ret) ++ return ret; ++ + return pmbus_do_probe(client, info); + } + +-- +2.47.2 diff --git a/patches-sonic/8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch b/patches-sonic/8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch new file mode 100644 index 000000000..2411f3df5 --- /dev/null +++ b/patches-sonic/8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nadav Hugi +Date: Mon, 25 May 2026 16:52:00 +0300 +Subject: [PATCH v6.12 1/1] mlxsw: i2c: Downstream: Increase I2C timeout for + HW semaphore stability + +When taking an IR core dump on SPC1 debug kernel, the firmware halts +its IRISCs to capture a clean snapshot. This pause takes ~5 seconds +and occasionally exceeds the driver's hardcoded 5-second I2C timeout, +causing spurious "HW semaphore is not released" errors. The system +recovers normally after the dump completes — this is not a real +hardware failure. + +Increase MLXSW_I2C_TIMEOUT_MSECS from 5000 to 10000 to give the +driver enough headroom to wait out the firmware pause. + +Signed-off-by: Nadav Hugi +--- + drivers/net/ethernet/mellanox/mlxsw/i2c.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +index XXXXXXX..YYYYYYY 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +@@ -50,7 +50,7 @@ + #define MLXSW_I2C_BLK_DEF 32 + #define MLXSW_I2C_BLK_MAX 100 + #define MLXSW_I2C_RETRY 5 +-#define MLXSW_I2C_TIMEOUT_MSECS 5000 ++#define MLXSW_I2C_TIMEOUT_MSECS 10000 + #define MLXSW_I2C_CMD_RETRY_FW_ERR 3 + #define MLXSW_I2C_MAX_DATA_SIZE 256 + +-- +2.20.1 diff --git a/patches-sonic/Support-for-fullcone-nat.patch b/patches-sonic/Support-for-fullcone-nat.patch new file mode 100644 index 000000000..558dd255d --- /dev/null +++ b/patches-sonic/Support-for-fullcone-nat.patch @@ -0,0 +1,469 @@ +From d1dd893ddae49ca4dc55073449c37d5b97504c05 Mon Sep 17 00:00:00 2001 +From: Akhilesh Samineni +Date: Mon, 6 Nov 2023 11:55:58 -0800 +Subject: [PATCH] Support fullcone NAT + +Changes done in the kernel to ensure 3-tuple uniqueness of the conntrack +entries for the fullcone nat functionality. + +* Hashlist is maintained for the 3-tuple unique keys (Protocol/Source + IP/Port) for all the conntrack entries. + +* When NAT table rules are created with the fullcone option, the + SNAT/POSTROUTING stage ensures the ports from the pool are picked up in + such a way that the 3-tuple is uniquely assigned. + +* In the DNAT/POSTROUTING stage, the fullcone behavior is ensured by checking + and reusing the 3-tuple for the Source IP/Port in the original direction. + +* When the pool is exhausted of the 3-tuple assignments, the packets are + dropped, else, they will be going out of the router they being 5-tuple + unique (which is not intended). + +* Passing fullcone option using iptables is part of another PR (in + sonic-buildimage repo). + +The kernel changes mentioned above are done to counter the challenges +explained in the section *3.4.2.1 Handling NAT model mismatch between +the ASIC and the Kernel* in the NAT HLD [1]. + +[1]: https://github.com/sonic-net/SONiC/blob/master/doc/nat/nat_design_spec.md + +Signed-off-by: Akhilesh Samineni +--- + include/net/netfilter/nf_conntrack.h | 3 + + include/uapi/linux/netfilter/nf_nat.h | 3 +- + net/netfilter/nf_nat_core.c | 222 +++++++++++++++--- + 3 files changed, 197 insertions(+), 31 deletions(-) + +diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h +index 6a2019aaa..191d6367c 100644 +--- a/include/net/netfilter/nf_conntrack.h ++++ b/include/net/netfilter/nf_conntrack.h +@@ -103,6 +103,9 @@ struct nf_conn { + + #if IS_ENABLED(CONFIG_NF_NAT) + struct hlist_node nat_bysource; ++ ++ /* To optionally ensure 3-tuple uniqueness on the translated source */ ++ struct hlist_node nat_by_manip_src; + #endif + /* all members below initialized via memset */ + struct { } __nfct_init_offset; +diff --git a/include/uapi/linux/netfilter/nf_nat.h b/include/uapi/linux/netfilter/nf_nat.h +index a64586e77..d60f5a9c2 100644 +--- a/include/uapi/linux/netfilter/nf_nat.h ++++ b/include/uapi/linux/netfilter/nf_nat.h +@@ -12,6 +12,7 @@ + #define NF_NAT_RANGE_PROTO_RANDOM_FULLY (1 << 4) + #define NF_NAT_RANGE_PROTO_OFFSET (1 << 5) + #define NF_NAT_RANGE_NETMAP (1 << 6) ++#define NF_NAT_RANGE_FULLCONE (1 << 10) + + #define NF_NAT_RANGE_PROTO_RANDOM_ALL \ + (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY) +@@ -20,7 +21,7 @@ + (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED | \ + NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT | \ + NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET | \ +- NF_NAT_RANGE_NETMAP) ++ NF_NAT_RANGE_NETMAP | NF_NAT_RANGE_FULLCONE) + + struct nf_nat_ipv4_range { + unsigned int flags; +diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c +index e29e4ccb5..678b50967 100644 +--- a/net/netfilter/nf_nat_core.c ++++ b/net/netfilter/nf_nat_core.c +@@ -33,6 +33,7 @@ static DEFINE_MUTEX(nf_nat_proto_mutex); + static unsigned int nat_net_id __read_mostly; + + static struct hlist_head *nf_nat_bysource __read_mostly; ++static struct hlist_head *nf_nat_by_manip_src __read_mostly; + static unsigned int nf_nat_htable_size __read_mostly; + static siphash_aligned_key_t nf_nat_hash_rnd; + +@@ -180,6 +181,50 @@ hash_by_src(const struct net *net, + return reciprocal_scale(hash, nf_nat_htable_size); + } + ++static inline unsigned int ++hash_by_dst(const struct net *net, ++ const struct nf_conntrack_zone *zone, ++ const struct nf_conntrack_tuple *tuple) ++{ ++ unsigned int hash; ++ struct { ++ union nf_inet_addr dst_addr; ++ u32 net_mix; ++ u16 dport; ++ u32 protonum; ++ u32 zone; ++ } __aligned(SIPHASH_ALIGNMENT) combined; ++ ++ get_random_once(&nf_nat_hash_rnd, sizeof(nf_nat_hash_rnd)); ++ ++ memset(&combined, 0, sizeof(combined)); ++ ++ combined.dst_addr = tuple->dst.u3; ++ combined.net_mix = net_hash_mix(net); ++ combined.protonum = tuple->dst.protonum; ++ combined.dport = (__force __u16)tuple->dst.u.all; ++ ++ /* Zone ID can be used provided its valid for both directions */ ++ if (zone->dir == NF_CT_DEFAULT_ZONE_DIR) ++ combined.zone = zone->id; ++ ++ hash = siphash(&combined, sizeof(combined), &nf_nat_hash_rnd); ++ ++ return reciprocal_scale(hash, nf_nat_htable_size); ++} ++ ++static inline int ++same_reply_dst(const struct nf_conn *ct, ++ const struct nf_conntrack_tuple *tuple) ++{ ++ const struct nf_conntrack_tuple *t; ++ ++ t = &ct->tuplehash[IP_CT_DIR_REPLY].tuple; ++ return (t->dst.protonum == tuple->dst.protonum && ++ nf_inet_addr_cmp(&t->dst.u3, &tuple->dst.u3) && ++ t->dst.u.all == tuple->dst.u.all); ++} ++ + /* Is this tuple already taken? (not by us) */ + static int + nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple, +@@ -197,6 +242,38 @@ nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple, + return nf_conntrack_tuple_taken(&reply, ignored_conntrack); + } + ++/* Is this 3-tuple already taken? (not by us) */ ++int ++nf_nat_used_3_tuple(const struct nf_conntrack_tuple *tuple, ++ const struct nf_conn *ignored_conntrack, ++ enum nf_nat_manip_type maniptype) ++{ ++ const struct nf_conn *ct; ++ const struct nf_conntrack_zone *zone; ++ unsigned int h; ++ struct net *net = nf_ct_net(ignored_conntrack); ++ ++ /* 3-tuple uniqueness is required for translated source only */ ++ if (maniptype != NF_NAT_MANIP_SRC) { ++ return 0; ++ } ++ zone = nf_ct_zone(ignored_conntrack); ++ ++ /* The tuple passed here is the inverted reply (with translated source) */ ++ h = hash_by_src(net, zone, tuple); ++ hlist_for_each_entry_rcu(ct, &nf_nat_by_manip_src[h], nat_by_manip_src) { ++ struct nf_conntrack_tuple reply; ++ nf_ct_invert_tuple(&reply, tuple); ++ /* Compare against the destination in the reply */ ++ if (same_reply_dst(ct, &reply) && ++ net_eq(net, nf_ct_net(ct)) && ++ nf_ct_zone_equal(ct, zone, IP_CT_DIR_ORIGINAL)) { ++ return 1; ++ } ++ } ++ return 0; ++} ++ + static bool nf_nat_inet_in_range(const struct nf_conntrack_tuple *t, + const struct nf_nat_range2 *range) + { +@@ -298,6 +375,33 @@ find_appropriate_src(struct net *net, + return 0; + } + ++/* Only called for DST manip */ ++static int ++find_appropriate_dst(struct net *net, ++ const struct nf_conntrack_zone *zone, ++ const struct nf_conntrack_tuple *tuple, ++ struct nf_conntrack_tuple *result) ++{ ++ struct nf_conntrack_tuple reply; ++ unsigned int h; ++ const struct nf_conn *ct; ++ ++ nf_ct_invert_tuple(&reply, tuple); ++ h = hash_by_src(net, zone, &reply); ++ ++ hlist_for_each_entry_rcu(ct, &nf_nat_by_manip_src[h], nat_by_manip_src) { ++ if (same_reply_dst(ct, tuple) && ++ net_eq(net, nf_ct_net(ct)) && ++ nf_ct_zone_equal(ct, zone, IP_CT_DIR_REPLY)) { ++ /* Copy destination part from original tuple. */ ++ nf_ct_invert_tuple(result, ++ &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); ++ result->src = tuple->src; ++ return 1; ++ } ++ } ++ return 0; ++} + /* For [FUTURE] fragmentation handling, we want the least-used + * src-ip/dst-ip/proto triple. Fairness doesn't come into it. Thus + * if the range specifies 1.2.3.4 ports 10000-10005 and 1.2.3.5 ports +@@ -377,10 +481,10 @@ find_best_ips_proto(const struct nf_conntrack_zone *zone, + * + * Per-protocol part of tuple is initialized to the incoming packet. + */ +-static void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple, +- const struct nf_nat_range2 *range, +- enum nf_nat_manip_type maniptype, +- const struct nf_conn *ct) ++static int nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple, ++ const struct nf_nat_range2 *range, ++ enum nf_nat_manip_type maniptype, ++ const struct nf_conn *ct) + { + unsigned int range_size, min, max, i, attempts; + __be16 *keyptr; +@@ -406,7 +510,7 @@ static void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple, + /* If there is no master conntrack we are not PPTP, + do not change tuples */ + if (!ct->master) +- return; ++ return 1; + + if (maniptype == NF_NAT_MANIP_SRC) + keyptr = &tuple->src.u.gre.key; +@@ -434,14 +538,14 @@ static void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple, + + break; + default: +- return; ++ return 0; + } + + /* If no range specified... */ + if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)) { + /* If it's dst rewrite, can't change port */ + if (maniptype == NF_NAT_MANIP_DST) +- return; ++ return 0; + + if (ntohs(*keyptr) < 1024) { + /* Loose convention: >> 512 is credential passing */ +@@ -483,12 +587,18 @@ static void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple, + another_round: + for (i = 0; i < attempts; i++, off++) { + *keyptr = htons(min + off % range_size); +- if (!nf_nat_used_tuple(tuple, ct)) +- return; ++ ++ if ((range->flags & NF_NAT_RANGE_FULLCONE) && (maniptype == NF_NAT_MANIP_SRC)) { ++ if (! nf_nat_used_3_tuple(tuple, ct, maniptype)) ++ return 1; ++ } else { ++ if (! nf_nat_used_tuple(tuple, ct)) ++ return 1; ++ } + } + + if (attempts >= range_size || attempts < 16) +- return; ++ return 0; + attempts /= 2; + off = get_random_u16(); + goto another_round; +@@ -497,10 +607,15 @@ static void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple, + /* Manipulate the tuple into the range given. For NF_INET_POST_ROUTING, + * we change the source to map into the range. For NF_INET_PRE_ROUTING + * and NF_INET_LOCAL_OUT, we change the destination to map into the +- * range. It might not be possible to get a unique tuple, but we try. ++ * range. It might not be possible to get a unique 5-tuple, but we try. + * At worst (or if we race), we will end up with a final duplicate in +- * __nf_conntrack_confirm and drop the packet. */ +-static void ++ * __nf_conntrack_confirm and drop the packet. ++ * If the range is of type fullcone, if we end up with a 3-tuple ++ * duplicate, we do not wait till the packet reaches the ++ * nf_conntrack_confirm to drop the packet. Instead return the packet ++ * to be dropped at this stage. ++ */ ++static int + get_unique_tuple(struct nf_conntrack_tuple *tuple, + const struct nf_conntrack_tuple *orig_tuple, + const struct nf_nat_range2 *range, +@@ -508,8 +623,11 @@ get_unique_tuple(struct nf_conntrack_tuple *tuple, + enum nf_nat_manip_type maniptype) + { + const struct nf_conntrack_zone *zone; ++ struct nf_nat_range2 nat_range; + struct net *net = nf_ct_net(ct); + ++ memcpy(&nat_range, range, sizeof(struct nf_nat_range2)); ++ + zone = nf_ct_zone(ct); + + /* 1) If this srcip/proto/src-proto-part is currently mapped, +@@ -521,46 +639,76 @@ get_unique_tuple(struct nf_conntrack_tuple *tuple, + * manips not an issue. + */ + if (maniptype == NF_NAT_MANIP_SRC && +- !(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { ++ !(nat_range.flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { + /* try the original tuple first */ +- if (in_range(orig_tuple, range)) { ++ if (in_range(orig_tuple, &nat_range)) { + if (!nf_nat_used_tuple(orig_tuple, ct)) { + *tuple = *orig_tuple; +- return; ++ goto out; + } + } else if (find_appropriate_src(net, zone, +- orig_tuple, tuple, range)) { ++ orig_tuple, tuple, &nat_range)) { + pr_debug("get_unique_tuple: Found current src map\n"); + if (!nf_nat_used_tuple(tuple, ct)) +- return; ++ goto out; + } + } + ++ if (maniptype == NF_NAT_MANIP_DST) { ++ if (nat_range.flags & NF_NAT_RANGE_FULLCONE) { ++ /* Destination IP range does not apply when fullcone flag is set. */ ++ nat_range.min_addr.ip = nat_range.max_addr.ip = orig_tuple->dst.u3.ip; ++ nat_range.min_proto.all = nat_range.max_proto.all = 0; ++ ++ /* If this dstip/proto/dst-proto-part is mapped currently ++ * as a translated source for a given tuple, use that ++ */ ++ if (find_appropriate_dst(net, zone, ++ orig_tuple, tuple)) { ++ if (!nf_nat_used_tuple(tuple, ct)) { ++ goto out; ++ } ++ } else { ++ /* If not mapped, proceed with the original tuple */ ++ *tuple = *orig_tuple; ++ goto out; ++ } ++ } ++ } ++ + /* 2) Select the least-used IP/proto combination in the given range */ + *tuple = *orig_tuple; +- find_best_ips_proto(zone, tuple, range, ct, maniptype); ++ find_best_ips_proto(zone, tuple, &nat_range, ct, maniptype); + + /* 3) The per-protocol part of the manip is made to map into + * the range to make a unique tuple. + */ + + /* Only bother mapping if it's not already in range and unique */ +- if (!(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { +- if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) { +- if (!(range->flags & NF_NAT_RANGE_PROTO_OFFSET) && ++ if (!(nat_range.flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { ++ if (nat_range.flags & NF_NAT_RANGE_PROTO_SPECIFIED) { ++ if (!(nat_range.flags & NF_NAT_RANGE_PROTO_OFFSET) && + l4proto_in_range(tuple, maniptype, +- &range->min_proto, +- &range->max_proto) && +- (range->min_proto.all == range->max_proto.all || +- !nf_nat_used_tuple(tuple, ct))) +- return; ++ &(nat_range.min_proto), ++ &(nat_range.max_proto))) { ++ if (nat_range.flags & NF_NAT_RANGE_FULLCONE) { ++ if (!nf_nat_used_3_tuple(tuple, ct, maniptype)) ++ goto out; ++ } else { ++ if ((nat_range.min_proto.all == nat_range.max_proto.all) || ++ !nf_nat_used_tuple(tuple, ct)) ++ goto out; ++ } ++ } + } else if (!nf_nat_used_tuple(tuple, ct)) { +- return; ++ goto out; + } + } + + /* Last chance: get protocol to try to obtain unique tuple. */ +- nf_nat_l4proto_unique_tuple(tuple, range, maniptype, ct); ++ return nf_nat_l4proto_unique_tuple(tuple, &nat_range, maniptype, ct); ++out: ++ return 1; + } + + struct nf_conn_nat *nf_ct_nat_ext_add(struct nf_conn *ct) +@@ -602,7 +750,9 @@ nf_nat_setup_info(struct nf_conn *ct, + nf_ct_invert_tuple(&curr_tuple, + &ct->tuplehash[IP_CT_DIR_REPLY].tuple); + +- get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype); ++ if (! get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype)) { ++ return NF_DROP; ++ } + + if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) { + struct nf_conntrack_tuple reply; +@@ -624,12 +774,16 @@ nf_nat_setup_info(struct nf_conn *ct, + + if (maniptype == NF_NAT_MANIP_SRC) { + unsigned int srchash; ++ unsigned int manip_src_hash; + spinlock_t *lock; + ++ manip_src_hash = hash_by_src(net, nf_ct_zone(ct), &new_tuple); + srchash = hash_by_src(net, nf_ct_zone(ct), + &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); + lock = &nf_nat_locks[srchash % CONNTRACK_LOCKS]; + spin_lock_bh(lock); ++ hlist_add_head_rcu(&ct->nat_by_manip_src, ++ &nf_nat_by_manip_src[manip_src_hash]); + hlist_add_head_rcu(&ct->nat_bysource, + &nf_nat_bysource[srchash]); + spin_unlock_bh(lock); +@@ -808,6 +962,7 @@ static void nf_nat_cleanup_conntrack(struct nf_conn *ct) + h = hash_by_src(nf_ct_net(ct), nf_ct_zone(ct), &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); + spin_lock_bh(&nf_nat_locks[h % CONNTRACK_LOCKS]); + hlist_del_rcu(&ct->nat_bysource); ++ hlist_del_rcu(&ct->nat_by_manip_src); + spin_unlock_bh(&nf_nat_locks[h % CONNTRACK_LOCKS]); + } + +@@ -1138,12 +1293,17 @@ static int __init nf_nat_init(void) + if (!nf_nat_bysource) + return -ENOMEM; + ++ nf_nat_by_manip_src = nf_ct_alloc_hashtable(&nf_nat_htable_size, 0); ++ if (!nf_nat_by_manip_src) ++ return -ENOMEM; ++ + for (i = 0; i < CONNTRACK_LOCKS; i++) + spin_lock_init(&nf_nat_locks[i]); + + ret = register_pernet_subsys(&nat_net_ops); + if (ret < 0) { + kvfree(nf_nat_bysource); ++ kvfree(nf_nat_by_manip_src); + return ret; + } + +@@ -1159,6 +1319,7 @@ static int __init nf_nat_init(void) + synchronize_net(); + unregister_pernet_subsys(&nat_net_ops); + kvfree(nf_nat_bysource); ++ kvfree(nf_nat_by_manip_src); + } + + return ret; +@@ -1175,6 +1336,7 @@ static void __exit nf_nat_cleanup(void) + + synchronize_net(); + kvfree(nf_nat_bysource); ++ kvfree(nf_nat_by_manip_src); + unregister_pernet_subsys(&nat_net_ops); + } + +-- +2.18.0 + diff --git a/patches-sonic/arista_goldfinch-dts.patch b/patches-sonic/arista_goldfinch-dts.patch new file mode 100644 index 000000000..63093226a --- /dev/null +++ b/patches-sonic/arista_goldfinch-dts.patch @@ -0,0 +1,416 @@ +From 909113af7728aa52de34031c85cc7837bdbe8424 Mon Sep 17 00:00:00 2001 +From: Prajjwal Singh +Date: Thu, 4 Jun 2026 14:19:07 +0000 +Subject: [PATCH] arm64: dts: aspeed: Add Arista Goldfinch R0 device tree + +Add device tree for the Arista Goldfinch R0 board based on the Aspeed +AST2700 BMC SoC. + +Tested by building and booting on Arista Goldfinch - AST2700 eval board. + +Signed-off-by: Prajjwal Singh +--- + arch/arm64/boot/dts/aspeed/Makefile | 1 + + .../boot/dts/aspeed/arista_goldfinch-r0.dts | 378 ++++++++++++++++++ + 2 files changed, 379 insertions(+) + create mode 100644 arch/arm64/boot/dts/aspeed/arista_goldfinch-r0.dts + +diff --git a/arch/arm64/boot/dts/aspeed/Makefile b/arch/arm64/boot/dts/aspeed/Makefile +index 5caf80a7f..496ae839d 100644 +--- a/arch/arm64/boot/dts/aspeed/Makefile ++++ b/arch/arm64/boot/dts/aspeed/Makefile +@@ -4,6 +4,7 @@ dtb-$(CONFIG_ARCH_ASPEED) += \ + ast2700-evb.dtb \ + nexthop-b27-r0.dtb \ + nokia-ast2700-h6-128-r0.dtb \ ++ arista_goldfinch-r0.dtb \ + ast2700-evb_ast1700-evb.dtb \ + ast2700-evb_ast1700-evb-dual.dtb \ + ast2700-raw.dtb \ +diff --git a/arch/arm64/boot/dts/aspeed/arista_goldfinch-r0.dts b/arch/arm64/boot/dts/aspeed/arista_goldfinch-r0.dts +new file mode 100644 +index 000000000..52536fc16 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/arista_goldfinch-r0.dts +@@ -0,0 +1,378 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Arista Goldfinch R0 Device Tree ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++ ++/ { ++ model = "Goldfinch"; ++ compatible = "arista_goldfinch-r0", "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = &uart12; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ xdma_memory0: xdma0 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++ }; ++ ++ xdma_memory1: xdma1 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++ }; ++ }; ++ ++ firmware { ++ optee: optee { ++ compatible = "linaro,optee-tz"; ++ method = "smc"; ++ }; ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc-primary"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ #include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc-secondary"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "cpu-boot"; ++ spi-max-frequency = <100000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "switch-card"; ++ spi-max-frequency = <100000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++ ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ spi-aspeed-full-duplex; ++ ++ tpm0: tpmdev@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <25000000>; ++ reg = <0>; ++ status = "okay"; ++ }; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ perif-rtc-enable; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++&rtc_over_espi0 { ++ status = "okay"; ++}; ++ ++&vhuba0 { ++ status = "okay"; ++}; ++ ++&ehci0 { ++ status = "disabled"; ++}; ++ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&pinctrl0 { ++ pinctrl_emmcclk_driving: emmcclk-driving { ++ pins = "AC14"; ++ drive-strength = <3>; ++ }; ++ ++ pinctrl_emmccmd_driving: emmccmd-driving { ++ pins = "AE15"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_emmc8bit_driving: emmcdat-driving { ++ pins = "AD14", "AE14", "AF14", "AB13", ++ "AF13", "AC13", "AD13", "AE13"; ++ drive-strength = <2>; ++ }; ++}; ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++ bus-width = <8>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_emmcg8_default ++ &pinctrl_emmcclk_driving ++ &pinctrl_emmccmd_driving ++ &pinctrl_emmc8bit_driving>; ++ non-removable; ++ max-frequency = <200000000>; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc0_default &pinctrl_adc1_default ++ &pinctrl_adc2_default &pinctrl_adc3_default ++ &pinctrl_adc4_default &pinctrl_adc5_default ++ &pinctrl_adc6_default &pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc8_default &pinctrl_adc9_default ++ &pinctrl_adc10_default &pinctrl_adc11_default ++ &pinctrl_adc12_default &pinctrl_adc13_default ++ &pinctrl_adc14_default &pinctrl_adc15_default>; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "okay"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++&mctp2 { ++ status = "okay"; ++ memory-region = <&mctp2_reserved>; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; ++ ++&syscon1 { ++ assigned-clocks = <&syscon1 SCU1_CLK_MACHCLK>, ++ <&syscon1 SCU1_CLK_RGMII>, ++ <&syscon1 SCU1_CLK_RMII>; ++ assigned-clock-rates = <200000000>, <125000000>, <50000000>; ++}; ++ ++&mac0 { ++ status = "disabled"; ++}; ++ ++&mac1 { ++ status = "disabled"; ++}; ++ ++&sgmii { ++ status = "okay"; ++}; ++ ++&mac2 { ++ status = "okay"; ++ phy-mode = "sgmii"; ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ status = "okay"; ++}; ++ ++&i2c2 { ++ status = "okay"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&i2c4 { ++ status = "okay"; ++}; ++ ++&i2c5 { ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c9 { ++ status = "okay"; ++ ++ eeprom@50 { ++ compatible = "atmel,24c512"; ++ reg = <0x50>; ++ }; ++ ++ eeprom@52 { ++ compatible = "atmel,24c512"; ++ reg = <0x52>; ++ }; ++ ++ eeprom@53 { ++ compatible = "atmel,24c512"; ++ reg = <0x53>; ++ }; ++}; ++ ++&i2c10 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++}; ++ ++&i2c12 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_di2c12_default>; ++}; ++ ++&i2c13 { ++ status = "okay"; ++}; ++ ++&i2c14 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_di2c14_default>; ++ ++ eeprom@50 { ++ compatible = "atmel,24c512"; ++ reg = <0x50>; ++ }; ++}; ++ ++&i2c15 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_di2c15_default>; ++}; +-- +2.47.3 + diff --git a/patches-sonic/aspeed-spi-txrx-fix-full-duplex-capture.patch b/patches-sonic/aspeed-spi-txrx-fix-full-duplex-capture.patch new file mode 100644 index 000000000..f75c364cf --- /dev/null +++ b/patches-sonic/aspeed-spi-txrx-fix-full-duplex-capture.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Chandrasekaran Swaminathan +Date: Thu, 2 Jul 2026 00:00:00 +0000 +Subject: [PATCH] spi: aspeed-txrx: capture full-duplex RX for distinct buffers + +The aspeed-txrx controller implements its own transfer_one_message and +only latches MISO during the clocked TX loop when tx_buf == rx_buf (a +single shared buffer). For any transfer that supplies separate tx_buf +and rx_buf pointers it skipped the in-clock capture and instead issued a +second, post-transfer ioread8_rep burst from the AHB window, which reads +back zeros because the device has already returned its data. + +That shared-pointer requirement is not expressible through the standard +spidev full-duplex ABI, which always hands the driver distinct bounce +buffers. As a result userspace SPI_IOC_MESSAGE full-duplex reads (e.g. +the FPGA register bridge) always returned zeros. + +Capture MISO during the TX loop whenever an rx buffer is present, +regardless of pointer identity, and restrict the separate read burst to +pure reads (no tx_buf). Behaviour is unchanged for write-only, +read-only, and shared-buffer full-duplex (e.g. the TPM) transfers. + +Signed-off-by: Chandrasekaran Swaminathan +--- + drivers/spi/spi-aspeed-txrx.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/spi/spi-aspeed-txrx.c b/drivers/spi/spi-aspeed-txrx.c +--- a/drivers/spi/spi-aspeed-txrx.c ++++ b/drivers/spi/spi-aspeed-txrx.c +@@ -346,7 +346,7 @@ static void aspeed_spi_transfer_tx(struct aspeed_spi_host *host, const u8 *tx_buf, + for (i = 0; i < len; i++) { + writeb(tx_buf[i], dst); + +- if (rx_buf && tx_buf == rx_buf) ++ if (rx_buf) + rx_buf[i] = readb(host->ctrl_reg + SPI_FULL_DUPLEX_RX_REG); + } + } +@@ -407,6 +407,6 @@ static int aspeed_spi_transfer(struct spi_controller *ctlr, + } + +- if (rx_buf && rx_buf != tx_buf) { ++ if (rx_buf && !tx_buf) { + ctrl_val &= ~SPI_IO_MASK; + if (xfer->rx_nbits == SPI_NBITS_DUAL) + ctrl_val |= SPI_DUAL_IO_MODE; diff --git a/patch/bridge-per-port-multicast-broadcast-flood-flags.patch b/patches-sonic/bridge-per-port-multicast-broadcast-flood-flags.patch similarity index 100% rename from patch/bridge-per-port-multicast-broadcast-flood-flags.patch rename to patches-sonic/bridge-per-port-multicast-broadcast-flood-flags.patch diff --git a/patches-sonic/cisco-Enable-static-memory-reservation-for-OIRable-PCIe-de.patch b/patches-sonic/cisco-Enable-static-memory-reservation-for-OIRable-PCIe-de.patch new file mode 100644 index 000000000..cb9141b82 --- /dev/null +++ b/patches-sonic/cisco-Enable-static-memory-reservation-for-OIRable-PCIe-de.patch @@ -0,0 +1,224 @@ +From 5fb45e33edd2e5cae8ff810221b4c75c888e51a4 Mon Sep 17 00:00:00 2001 +From: Madhava Reddy Siddareddygari +Date: Thu, 14 Oct 2021 22:16:47 -0700 +Subject: [PATCH] drivers/pci: Reserve address space for devices behind bridges + +Cisco data path devices are powered off by default, they will not be +available at BIOS stage and memory for these devices is not reserved. + +This patch will reserve address space for data path devices that are +behind PCIe bridge, so that when devices are available PCIe subsystem +will be assign the address with in the specified range. + +Signed-off-by: Madhava Reddy Siddareddygari +--- + drivers/pci/setup-bus.c | 172 ++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 172 insertions(+) + +diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c +index 2ce636937..64155eddc 100644 +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -967,6 +967,154 @@ static inline resource_size_t calculate_mem_align(resource_size_t *aligns, + return true; + } + ++#define PLX_RES_MAGIC_VALUE 0xABBA ++#define PLX_RES_DS_PORT_REG0 0xC6C ++#define PLX_RES_DS_PORT_REG1 0xC70 ++#define PLX_RES_MAGIC_OFFSET 0xC76 ++#define PLX_RES_NP_MASK 0x1 ++#define PLX_RES_P_MASK 0x1F ++ ++static struct pci_dev * ++plx_find_nt_device(struct pci_bus *bus, unsigned short brg_dev_id) ++{ ++ struct pci_dev *dev, *nt_virt_dev = NULL; ++ struct pci_bus *child_bus; ++ unsigned short vendor, devid, class; ++ ++ if (!bus) ++ return NULL; ++ ++ list_for_each_entry(child_bus, &bus->children, node) { ++ list_for_each_entry(dev, &child_bus->devices, bus_list) { ++ vendor = dev->vendor; ++ devid = dev->device; ++ class = dev->class >> 8; ++ ++ if ((vendor == PCI_VENDOR_ID_PLX) && ++ (brg_dev_id == devid) && ++ (class == PCI_CLASS_BRIDGE_OTHER)) { ++ dev_dbg(&dev->dev, ++ "Found NT device 0x%x\n", ++ devid); ++ nt_virt_dev = dev; ++ break; ++ } ++ } ++ ++ if (nt_virt_dev) ++ break; ++ } ++ return nt_virt_dev; ++} ++ ++static resource_size_t ++pci_get_plx_downstream_res_size(struct pci_bus *bus, ++ unsigned long res_type) ++{ ++ int depth = 0; ++ resource_size_t size = 0; ++ struct pci_dev *dev = bus->self; ++ struct pci_bus *tmp_bus; ++ struct pci_dev *nt_virt_dev; ++ u16 res_magic = 0; ++ ++ /* ++ * 32 bits to store the memory requirement for PLX ports.' ++ * Following is the layout: ++ * np32_0:1; --> non-prefetchable port 0 ++ * p64_0:5; --> prefetchable port 0 ++ * np32_1:1; --> non-prefetchable port 1 ++ * p64_1:5; --> prefetchable port 1 ++ * np32_2:1; --> non-prefetchable port 2 ++ * p64_2:5; --> prefetchable port 2 ++ * np32_3:1; --> non-prefetchable port 3 ++ * p64_3:5; --> prefetchable port 3 ++ * np32_4:1; --> non-prefetchable port 4 ++ * p64_4:5; --> prefetchable port 4 ++ * reserved:2; ++ */ ++ unsigned int port_bitmap; ++ ++ u32 mem_res_bitmap = 0; ++ unsigned int ds_port_offset = 0; ++ unsigned short multiplier = 0; ++ unsigned short np_size = 0; ++ ++ /* ++ * PLX8713 used on FC4 and FC8 ++ * PLX8725 used on FC12 and FC18 ++ */ ++ if (!dev || dev->vendor != PCI_VENDOR_ID_PLX || ++ ((dev->device & 0xFF00) != 0x8700)) ++ return size; ++ ++ tmp_bus = bus; ++ while (tmp_bus->parent) { ++ tmp_bus = tmp_bus->parent; ++ depth++; ++ } ++ ++ /* Only for Second level bridges */ ++ if (depth != 5) ++ return size; ++ nt_virt_dev = plx_find_nt_device(bus->parent, 0x87b0); ++ if (nt_virt_dev) { ++ pci_read_config_word(nt_virt_dev, PLX_RES_MAGIC_OFFSET, ++ &res_magic); ++ dev_dbg(&nt_virt_dev->dev, ++ "Magic offset of 0x%x found in NT device\n", ++ res_magic); ++ } ++ ++ if (res_magic == PLX_RES_MAGIC_VALUE) { ++ /* ++ * The pacifics are connected on PLX ports: ++ * FC4 and FC8: #3, #4 ++ * FC12 : #3, #4, #5 ++ * FC18 : #3, #4, #5, #11 ++ */ ++ ++ /* Calculate resource based on EEPROM values */ ++ ds_port_offset = (bus->number - bus->parent->number) - 1; ++ if (ds_port_offset < 5) { ++ pci_read_config_dword(nt_virt_dev, ++ PLX_RES_DS_PORT_REG0, &mem_res_bitmap); ++ } else { ++ ds_port_offset -= 5; ++ pci_read_config_dword(nt_virt_dev, ++ PLX_RES_DS_PORT_REG1, &mem_res_bitmap); ++ } ++ port_bitmap = mem_res_bitmap; ++ dev_dbg(&bus->dev, "Port offset: 0x%x, res bitmap 0x%x\n", ++ ds_port_offset, mem_res_bitmap); ++ if (ds_port_offset < 5) { ++ u8 m[] = { 26, 20, 14, 8, 2 }; ++ u8 s[] = { 31, 25, 19, 13, 7 }; ++ ++ multiplier = (port_bitmap >> m[ds_port_offset]) ++ & PLX_RES_P_MASK; ++ np_size = (port_bitmap >> s[ds_port_offset]) ++ & PLX_RES_NP_MASK; ++ ++ dev_dbg(&bus->dev, "Multiplier: %d, np_size: %d\n", ++ multiplier, np_size); ++ ++ if (res_type & IORESOURCE_PREFETCH) { ++ size = 0x100000 << (multiplier - 1); ++ dev_dbg(&bus->dev, ++ "Pref Multiplier %d, Size 0x%llx\n", ++ multiplier, (long long) size); ++ } else if (np_size) { ++ size = 0x100000; ++ dev_dbg(&bus->dev, ++ "NP Size 0x%llx\n", (long long) size); ++ } ++ } ++} ++ ++ return size; ++} ++ + /** + * pbus_size_mem() - Size the memory window of a given bus + * +@@ -1001,6 +1149,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, + resource_size_t children_add_size = 0; + resource_size_t children_add_align = 0; + resource_size_t add_align = 0; ++ unsigned int dev_count = 0; + + if (!b_res) + return -ENOSPC; +@@ -1016,6 +1165,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, + struct resource *r; + int i; + ++ dev_count++; + pci_dev_for_each_resource(dev, r, i) { + const char *r_name = pci_resource_name(dev, i); + resource_size_t r_size; +@@ -1071,6 +1221,28 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, + } + } + ++ /* Static allocation for Cisco Fabric Card pacific */ ++ if (!size && !dev_count) { ++ size = pci_get_plx_downstream_res_size(bus, type); ++ if (size) { ++ order = __ffs(size); ++ dev_dbg(&bus->self->dev, ++ "order for %llx is %u\n", ++ (long long) size, order); ++ if (order >= 20) { ++ order -= 20; ++ if ((order < ARRAY_SIZE(aligns)) && ++ (order > max_order)) { ++ max_order = order; ++ dev_dbg(&bus->self->dev, ++ "max_order reset to %d;" ++ "size %zx\n", ++ max_order, (size_t)size); ++ } ++ } ++ } ++ } ++ + win_align = window_alignment(bus, b_res->flags); + min_align = calculate_mem_align(aligns, max_order); + min_align = max(min_align, win_align); +-- +2.26.2 + diff --git a/patches-sonic/cisco-acpi-spi-nor.patch b/patches-sonic/cisco-acpi-spi-nor.patch new file mode 100644 index 000000000..8dab722c2 --- /dev/null +++ b/patches-sonic/cisco-acpi-spi-nor.patch @@ -0,0 +1,56 @@ +From 629588d479e637c6ed7c64fe76b66f1d7f0482fe Mon Sep 17 00:00:00 2001 +From: Madhava Reddy Siddareddygari +Date: Fri, 15 Oct 2021 11:57:37 -0700 +Subject: [PATCH] mtd: spi nor core driver update to support ACPI table match + +Current spi nor core driver does not support ACPI table match. +Configuration done through ACPI tables is not recognized by driver. +Cisco 8000 platform configures NOR flash partition information +through ACPI table which is not recognized by spi nor. + +Added support in spi nor core to perform ACPI table match. + +Signed-off-by: Madhava Reddy Siddareddygari +--- + drivers/mtd/spi-nor/core.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c +index 2b26a875a..82bbd84a2 100644 +--- a/drivers/mtd/spi-nor/core.c ++++ b/drivers/mtd/spi-nor/core.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + + #include "core.h" + +@@ -3480,6 +3481,15 @@ static const struct of_device_id spi_nor_of_table[] = { + }; + MODULE_DEVICE_TABLE(of, spi_nor_of_table); + ++#ifdef CONFIG_ACPI ++static const struct acpi_device_id spi_nor_acpi_table[] = { ++ { "JEDEC,SPI-NOR", 0 }, ++ { "ACPI0000", 0 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(acpi, spi_nor_acpi_table); ++#endif ++ + /* + * REVISIT: many of these chips have deep power-down modes, which + * should clearly be entered on suspend() to minimize power use. +@@ -3490,6 +3500,7 @@ static struct spi_mem_driver spi_nor_driver = { + .name = "spi-nor", + .of_match_table = spi_nor_of_table, + .dev_groups = spi_nor_sysfs_groups, ++ .acpi_match_table = ACPI_PTR(spi_nor_acpi_table), + }, + .id_table = spi_nor_dev_ids, + }, +-- +2.26.2.dirty + diff --git a/patches-sonic/cisco-add-support-for-ltc2979-chip.patch b/patches-sonic/cisco-add-support-for-ltc2979-chip.patch new file mode 100644 index 000000000..d2a253abe --- /dev/null +++ b/patches-sonic/cisco-add-support-for-ltc2979-chip.patch @@ -0,0 +1,76 @@ +From b2a01dd5afd900448a31090cded7d9f2a378b990 Mon Sep 17 00:00:00 2001 +From: Madhava Reddy Siddareddygari +Date: Sat, 25 Sep 2021 21:58:15 -0700 +Subject: [PATCH] hwmon: (pmbus/ltc2978) Add support for new sensor + +Current ltc2978 driver does not have support for device ltc2979. + +Added support for the sensor device ltc2979. +Datasheet for the sensor is at followint path. +www.analog.com/media/en/technical-documentation/data-sheets/2979f.pdf + +This chip is used for voltage sensors on cisco-8000 platform. + +Signed-off-by: Madhava Reddy Siddareddygari +--- + drivers/hwmon/pmbus/ltc2978.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c +index 94eea2ac6..8cbaf3b83 100644 +--- a/drivers/hwmon/pmbus/ltc2978.c ++++ b/drivers/hwmon/pmbus/ltc2978.c +@@ -27,7 +27,7 @@ + #include + #include "pmbus.h" + +-enum chips { ltc2974, ltc2975, ltc2977, ltc2978, ltc2980, ltc3880, ltc3882, ++enum chips { ltc2974, ltc2975, ltc2977, ltc2978, ltc2979, ltc2980, ltc3880, ltc3882, + ltc3883, ltc3886, ltc3887, ltm2987, ltm4675, ltm4676 }; + + /* Common for all chips */ +@@ -36,6 +36,8 @@ enum chips { ltc2974, ltc2975, ltc2977, ltc2978, ltc2980, ltc3880, ltc3882, + #define LTC2978_MFR_TEMPERATURE_PEAK 0xdf + #define LTC2978_MFR_SPECIAL_ID 0xe7 /* Undocumented on LTC3882 */ + #define LTC2978_MFR_COMMON 0xef ++#define LTC2979_ID_A 0x8060 ++#define LTC2979_ID_B 0x8070 + + /* LTC2974, LTC2975, LCT2977, LTC2980, LTC2978, and LTM2987 */ + #define LTC2978_MFR_VOUT_MIN 0xfb +@@ -503,6 +505,7 @@ static const struct i2c_device_id ltc2978_id[] = { + {"ltc2975", ltc2975}, + {"ltc2977", ltc2977}, + {"ltc2978", ltc2978}, ++ {"ltc2979", ltc2979}, + {"ltc2980", ltc2980}, + {"ltc3880", ltc3880}, + {"ltc3882", ltc3882}, +@@ -569,6 +572,8 @@ static int ltc2978_get_id(struct i2c_client *client) + return ltc2977; + else if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) + return ltc2978; ++ else if (chip_id == LTC2979_ID_A || chip_id == LTC2979_ID_B) ++ return ltc2979; + else if (chip_id == LTC2980_ID_A || chip_id == LTC2980_ID_B) + return ltc2980; + else if (chip_id == LTC3880_ID) +@@ -668,6 +673,7 @@ static int ltc2978_probe(struct i2c_client *client, + break; + case ltc2977: + case ltc2978: ++ case ltc2979: + case ltc2980: + case ltm2987: + info->read_word_data = ltc2978_read_word_data; +@@ -761,6 +767,7 @@ static const struct of_device_id ltc2978_of_match[] = { + { .compatible = "lltc,ltc2975" }, + { .compatible = "lltc,ltc2977" }, + { .compatible = "lltc,ltc2978" }, ++ { .compatible = "lltc,ltc2979" }, + { .compatible = "lltc,ltc2980" }, + { .compatible = "lltc,ltc3880" }, + { .compatible = "lltc,ltc3882" }, +-- +2.26.2.dirty + diff --git a/patches-sonic/cisco-ds4424-null-of-node.patch b/patches-sonic/cisco-ds4424-null-of-node.patch new file mode 100644 index 000000000..83d8c3516 --- /dev/null +++ b/patches-sonic/cisco-ds4424-null-of-node.patch @@ -0,0 +1,45 @@ +From ec513d48cc5b25b98c15ec2a47658c71b2231f95 Mon Sep 17 00:00:00 2001 +From: Madhava Reddy Siddareddygari +Date: Sat, 25 Sep 2021 20:44:20 -0700 +Subject: [PATCH] iio: (dac/ds4424) Allow NULL of_node + +Cisco is using ACPI to read device configuration instead of +OF (device-tree) for ds4424 device. + +Kernel is in a transition period in its support for ACPI +(cisco using ACPI for kernel module configuration). The kernel +makes a distinction between device tree (of_node) and ACPI +(fw_node) in the device structure. It is unclear if they can be +used together or not, but I can definitively say that we are only +using ACPI and not device tree. In this particular driver, there +was an explicit check for having a device tree node, even though no +reference to the device tree was made within the driver. This caused +the probe routine to fail with -ENODEV in cisco environment. + +This patch removes the check for of_node. + +Signed-off-by: Madhava Reddy Siddareddygari +--- + drivers/iio/dac/ds4424.c | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/drivers/iio/dac/ds4424.c b/drivers/iio/dac/ds4424.c +index 714a97f91..ae9be7926 100644 +--- a/drivers/iio/dac/ds4424.c ++++ b/drivers/iio/dac/ds4424.c +@@ -236,12 +236,6 @@ static int ds4424_probe(struct i2c_client *client, + indio_dev->dev.of_node = client->dev.of_node; + indio_dev->dev.parent = &client->dev; + +- if (!client->dev.of_node) { +- dev_err(&client->dev, +- "Not found DT.\n"); +- return -ENODEV; +- } +- + data->vcc_reg = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(data->vcc_reg)) { + dev_err(&client->dev, +-- +2.26.2 + diff --git a/patches-sonic/cisco-mdio-mux-support-acpi.patch b/patches-sonic/cisco-mdio-mux-support-acpi.patch new file mode 100644 index 000000000..d4a95c78b --- /dev/null +++ b/patches-sonic/cisco-mdio-mux-support-acpi.patch @@ -0,0 +1,92 @@ +From 04be5041b17e03eb5b9ea474431369fa6a76b6b8 Mon Sep 17 00:00:00 2001 + +From: Madhava Reddy Siddareddygari +Date: 2021-09-29 13:53:28 -0700 + +Subject: [PATCH] support reading mdio config from ACPI tables + +Current mdio-mux does not support reading configuration +from ACPI tables. + +cisco-8000 platform configures mdio phy config through ACPI +tables. + +Added support in the mdio-mux driver to read from ACPI + +Signed-off-by: Madhava Reddy Siddareddygari +--- + drivers/net/mdio/mdio-mux.c | 27 ++++++++++++++++++--------- + 1 file changed, 18 insertions(+), 9 deletions(-) + +diff --git a/drivers/net/mdio/mdio-mux.c b/drivers/net/mdio/mdio-mux.c +index ccb3ee704..e70c8d36a 100644 +--- a/drivers/net/mdio/mdio-mux.c ++++ b/drivers/net/mdio/mdio-mux.c +@@ -101,16 +101,16 @@ int mdio_mux_init(struct device *dev, + struct mii_bus *mux_bus) + { + struct device_node *parent_bus_node; +- struct device_node *child_bus_node; ++ struct fwnode_handle *child_bus_node; + int r, ret_val; + struct mii_bus *parent_bus; + struct mdio_mux_parent_bus *pb; + struct mdio_mux_child_bus *cb; + +- if (!mux_node) +- return -ENODEV; +- + if (!mux_bus) { ++ if (!mux_node) ++ return -ENODEV; ++ + parent_bus_node = of_parse_phandle(mux_node, + "mdio-parent-bus", 0); + +@@ -141,16 +141,20 @@ int mdio_mux_init(struct device *dev, + pb->mii_bus = parent_bus; + + ret_val = -ENODEV; +- for_each_available_child_of_node(mux_node, child_bus_node) { +- int v; ++ device_for_each_child_node(dev, child_bus_node) { ++ u32 v; ++ u32 phy_mask; + +- r = of_property_read_u32(child_bus_node, "reg", &v); ++ r = fwnode_property_read_u32(child_bus_node, "reg", &v); + if (r) { + dev_err(dev, + "Error: Failed to find reg for child %pOF: %pe\n", + child_bus_node, ERR_PTR(r)); + continue; + } ++ r = fwnode_property_read_u32(child_bus_node, "phy_mask", &phy_mask); ++ if (r) ++ phy_mask = 0; + + cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL); + if (!cb) { +@@ -173,7 +177,12 @@ int mdio_mux_init(struct device *dev, + cb->mii_bus->read_c45 = mdio_mux_read_c45; + if (parent_bus->write_c45) + cb->mii_bus->write_c45 = mdio_mux_write_c45; +- r = of_mdiobus_register(cb->mii_bus, child_bus_node); ++ cb->mii_bus->phy_mask = phy_mask; ++ if (is_of_node(child_bus_node)) ++ r = of_mdiobus_register(cb->mii_bus, to_of_node(child_bus_node)); ++ else ++ r = mdiobus_register(cb->mii_bus); ++ + if (r) { + mdiobus_free(cb->mii_bus); + if (r == -EPROBE_DEFER) { +@@ -199,7 +208,7 @@ int mdio_mux_init(struct device *dev, + + err_loop: + mdio_mux_uninit_children(pb); +- of_node_put(child_bus_node); ++ fwnode_handle_put(child_bus_node); + err_pb_kz: + put_device(&parent_bus->dev); + err_parent_bus: diff --git a/patches-sonic/cisco-mtd-part.patch b/patches-sonic/cisco-mtd-part.patch new file mode 100644 index 000000000..c0aa8b075 --- /dev/null +++ b/patches-sonic/cisco-mtd-part.patch @@ -0,0 +1,33 @@ +From 157c059a2c639ecc69080ecb004a94e652467a4a Mon Sep 17 00:00:00 2001 +From: Madhava Reddy Siddareddygari +Date: Tue, 8 Jun 2021 10:19:36 -0700 +Subject: [PATCH] mtd: Add support for reading partition data from ACPI + +Current mtd driver supports reading mtd partition information from +command line and dts file, but cannot read partition information +from ACPI files. + +Added code to support new partition type "acpipart" to +default_mtd_part_types structure. This partition type can be used +to configure mtd partition data in ACPI tables. + +Signed-off-by: Madhava Reddy Siddareddygari +--- + drivers/mtd/mtdpart.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c +index 10c53364a..1ff24e5d9 100644 +--- a/drivers/mtd/mtdpart.c ++++ b/drivers/mtd/mtdpart.c +@@ -822,6 +822,7 @@ EXPORT_SYMBOL_GPL(deregister_mtd_parser); + static const char * const default_mtd_part_types[] = { + "cmdlinepart", + "ofpart", ++ "acpipart", + NULL + }; + +-- +2.26.2 + diff --git a/patches-sonic/cisco-npu-disable-other-bars.patch b/patches-sonic/cisco-npu-disable-other-bars.patch new file mode 100644 index 000000000..38a5f04b4 --- /dev/null +++ b/patches-sonic/cisco-npu-disable-other-bars.patch @@ -0,0 +1,77 @@ +From 5b025a4732297147ec5c264225e1ffb50a09bfe5 Mon Sep 17 00:00:00 2001 +From: Madhava Reddy Siddareddygari +Date: Wed, 1 Dec 2021 13:59:29 -0800 +Subject: [PATCH] NPU disable unused PCI BAR's + +For Cisco Network Processing Unit ASIC only BAR0 is valid. +Not disabling other BAR's was resulting in pci_enable_device +function failure in P0 Pacific ASIC's. Further debugging and +consultion with Hardware team, issue seems to be related to +only P0 version of ASIC and workaround suggested is to disable +unused PCI BAR. + +This patch disables unused PCI BAR of NPU ASIC. + +NPU is commonly used name for the packet forwarding ASIC's. + +Signed-off-by: Madhava Reddy Siddareddygari +--- + drivers/pci/quirks.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 45 insertions(+) + +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 16fb3d771..0efe3f62a 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6314,3 +6314,48 @@ static void apex_pci_fixup_class(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 ++ ++#define PCI_DEVICE_ID_LEABA_PACIFIC 0xabcd ++#define PCI_DEVICE_ID_LEABA_GIBRALTAR 0xa001 ++#define PCI_DEVICE_ID_LEABA_GRAPHENE 0xa003 ++#define PCI_DEVICE_ID_LEABA_PALLADIUM 0xa004 ++#define PCI_DEVICE_ID_LEABA_ARGON 0xa005 ++#define PCI_DEVICE_ID_LEABA_KRYPTON 0xa006 ++ ++/* ++ * For Pacific A0, only BAR 0 is valid ++ */ ++static void silicon_one_fixup(struct pci_dev *dev) ++{ ++ int i; ++ struct resource *r; ++ ++ for (i = 1; i <= PCI_ROM_RESOURCE; i++) { ++ r = &dev->resource[i]; ++ if (!r->start && !r->end && !r->flags) ++ continue; ++ ++ pci_info(dev, "Cisco Silicon One BAR %d %pR fixed up\n", i, r); ++ r->start = 0; ++ r->end = 0; ++ r->flags = 0; ++ } ++ ++ dev->class = PCI_CLASS_MEMORY_OTHER << 8; ++ pci_info(dev, "Cisco Silicon One class adjusted\n"); ++} ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SYNOPSYS, PCI_DEVICE_ID_LEABA_PACIFIC, ++ silicon_one_fixup); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CISCO, PCI_DEVICE_ID_LEABA_PACIFIC, ++ silicon_one_fixup); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CISCO, PCI_DEVICE_ID_LEABA_GIBRALTAR, ++ silicon_one_fixup); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CISCO, PCI_DEVICE_ID_LEABA_GRAPHENE, ++ silicon_one_fixup); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CISCO, PCI_DEVICE_ID_LEABA_PALLADIUM, ++ silicon_one_fixup); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CISCO, PCI_DEVICE_ID_LEABA_ARGON, ++ silicon_one_fixup); ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CISCO, PCI_DEVICE_ID_LEABA_KRYPTON, ++ silicon_one_fixup); ++ +-- +2.25.1 + diff --git a/patches-sonic/driver-arista-i2c-designware-shutdown.patch b/patches-sonic/driver-arista-i2c-designware-shutdown.patch new file mode 100644 index 000000000..d2dbeff2b --- /dev/null +++ b/patches-sonic/driver-arista-i2c-designware-shutdown.patch @@ -0,0 +1,54 @@ +From: Justin Oliver +Date: 2022-12-12 17:42:20 +0000 +Subject: [PATCH] i2c: designware: Fix device probe failure after kexec + +The Designware I2C driver sometimes fails to probe the I2C controller after +we boot into another kernel with kexec. Kexec will call the `shutdown` +method of each platform driver before booting the new kernel so this patch +defines a `shutdown` procedure for the Designware I2C platform driver. This +will gracefully bring down the I2C controller, allowing the driver to +successfully initialize the device with the new kernel. + +This patch addresses the following error after kexec: +i2c_designware AMDI0010:00: Unknown Synopsys component type: 0xffffffff + +Signed-off-by: Justin Oliver +Signed-off-by: Karan Jagjitkumar +--- + drivers/i2c/busses/i2c-designware-platdrv.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c +index 01234567..89abcdef 100644 +--- a/drivers/i2c/busses/i2c-designware-platdrv.c ++++ b/drivers/i2c/busses/i2c-designware-platdrv.c +@@ -333,6 +333,21 @@ static int dw_i2c_plat_remove(struct platform_device *pdev) + reset_control_assert(dev->rst); + } + ++static void dw_i2c_plat_shutdown(struct platform_device *pdev) ++{ ++ struct dw_i2c_dev *dev = platform_get_drvdata(pdev); ++ ++ pm_runtime_get_sync(&pdev->dev); ++ ++ i2c_dw_disable(dev); ++ ++ pm_runtime_dont_use_autosuspend(&pdev->dev); ++ pm_runtime_put_sync(&pdev->dev); ++ dw_i2c_plat_pm_cleanup(dev); ++ ++ reset_control_assert(dev->rst); ++} ++ + static const struct of_device_id dw_i2c_of_match[] = { + { .compatible = "snps,designware-i2c", }, + { .compatible = "mscc,ocelot-i2c", .data = (void *)MODEL_MSCC_OCELOT }, +@@ -371,6 +386,7 @@ MODULE_ALIAS("platform:i2c_designware"); + static struct platform_driver dw_i2c_driver = { + .probe = dw_i2c_plat_probe, + .remove_new = dw_i2c_plat_remove, ++ .shutdown = dw_i2c_plat_shutdown, + .driver = { + .name = "i2c_designware", + .of_match_table = dw_i2c_of_match, diff --git a/patches-sonic/driver-arista-mmcblk-not-working-on-AMD-platforms.patch b/patches-sonic/driver-arista-mmcblk-not-working-on-AMD-platforms.patch new file mode 100644 index 000000000..240a7ba43 --- /dev/null +++ b/patches-sonic/driver-arista-mmcblk-not-working-on-AMD-platforms.patch @@ -0,0 +1,58 @@ +amd/mmc: mmcblk not working on some AMD platforms + +ADMA and ADMA-64 seem to be broken on AMD. This patch enables the +following quirks (for AMD only): + SDHCI_QUIRK_BROKEN_ADMA + SDHCI_QUIRK2_BROKEN_64_BIT_DMA + +This fixes issues that would manifest in the following fashion. + +mmc0: Timeout waiting for hardware interrupt. +sdhci: =========== REGISTER DUMP (mmc0)=========== +sdhci: Sys addr: 0x00000078 | Version: 0x00001002 +sdhci: Blk size: 0x00007200 | Blk cnt: 0x00000078 +sdhci: Argument: 0x000ab148 | Trn mode: 0x0000003b +sdhci: Present: 0x01ff0001 | Host ctl: 0x00000019 +sdhci: Power: 0x0000000f | Blk gap: 0x00000000 +sdhci: Wake-up: 0x00000000 | Clock: 0x0000fa07 +sdhci: Timeout: 0x0000000c | Int stat: 0x00000000 +sdhci: Int enab: 0x02ff008b | Sig enab: 0x02ff008b +sdhci: AC12 err: 0x00000002 | Slot int: 0x000000ff +sdhci: Caps: 0x75fec8b2 | Caps_1: 0x00002501 +sdhci: Cmd: 0x0000123a | Max curr: 0x00c80064 +sdhci: Host ctl2: 0x00000000 +sdhci: ADMA Err: 0x00000000 | ADMA Ptr: 0x000000020f97b20c +sdhci: =========================================== +mmcblk0: error -110 sending status command, retrying +mmcblk0: error -110 sending status command, retrying +mmcblk0: error -110 sending status command, aborting +mmc0: cache flush error -110 +mmc0: tried to reset card, got error -110 +blk_update_request: I/O error, dev mmcblk0, sector 700744 +blk_update_request: I/O error, dev mmcblk0, sector 700752 + +Signed-off-by: Radu Rendec +Signed-off-by: Samuel Angebault +--- + drivers/mmc/host/sdhci-pci-core.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c +index bf04a08ee..c10790dd6 100644 +--- a/drivers/mmc/host/sdhci-pci-core.c ++++ b/drivers/mmc/host/sdhci-pci-core.c +@@ -1791,8 +1791,13 @@ static int amd_probe(struct sdhci_pci_chip *chip) + + pci_dev_put(smbus_dev); + +- if (gen == AMD_CHIPSET_BEFORE_ML || gen == AMD_CHIPSET_CZ) ++ dev_info(&chip->pdev->dev, "identified AMD generation %d chip\n", gen); ++ ++ if (gen == AMD_CHIPSET_BEFORE_ML || gen == AMD_CHIPSET_CZ) { ++ chip->quirks |= SDHCI_QUIRK_BROKEN_ADMA; ++ chip->quirks2 |= SDHCI_QUIRK2_BROKEN_64_BIT_DMA; + chip->quirks2 |= SDHCI_QUIRK2_CLEAR_TRANSFERMODE_REG_BEFORE_CMD; ++ } + + return 0; + } diff --git a/patch/driver-arista-net-tg3-access-regs-indirectly.patch b/patches-sonic/driver-arista-net-tg3-access-regs-indirectly.patch similarity index 85% rename from patch/driver-arista-net-tg3-access-regs-indirectly.patch rename to patches-sonic/driver-arista-net-tg3-access-regs-indirectly.patch index 759f85d06..03c88210c 100644 --- a/patch/driver-arista-net-tg3-access-regs-indirectly.patch +++ b/patches-sonic/driver-arista-net-tg3-access-regs-indirectly.patch @@ -1,15 +1,17 @@ Fix kernel hangs seen and easily reproducable using ethtool -t +From: Samuel Angebault + This issue is seen at least on Arista DCS-7050QX-32 SKUs --- drivers/net/ethernet/broadcom/tg3.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c -index d54ba3f75..8b543a27b 100644 +index 997de31f9..1d985ebf1 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c -@@ -1016,6 +1016,7 @@ static void tg3_disable_ints(struct tg3 *tp) +@@ -1005,6 +1005,7 @@ static void tg3_disable_ints(struct tg3 *tp) static void tg3_enable_ints(struct tg3 *tp) { int i; @@ -17,7 +19,7 @@ index d54ba3f75..8b543a27b 100644 tp->irq_sync = 0; wmb(); -@@ -1028,6 +1029,22 @@ static void tg3_enable_ints(struct tg3 *tp) +@@ -1017,6 +1018,22 @@ static void tg3_enable_ints(struct tg3 *tp) struct tg3_napi *tnapi = &tp->napi[i]; tw32_mailbox_f(tnapi->int_mbox, tnapi->last_tag << 24); @@ -40,7 +42,7 @@ index d54ba3f75..8b543a27b 100644 if (tg3_flag(tp, 1SHOT_MSI)) tw32_mailbox_f(tnapi->int_mbox, tnapi->last_tag << 24); -@@ -16381,6 +16398,7 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) +@@ -16369,6 +16386,7 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) if ((tp->pdev->bus->number == 0) && (tp->pdev->devfn == PCI_DEVFN(0x14, 0x6))) { tg3_flag_set(tp, 4G_DMA_ONLY); @@ -48,7 +50,7 @@ index d54ba3f75..8b543a27b 100644 } } -@@ -16640,13 +16658,6 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) +@@ -16628,13 +16646,6 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) tp->write32_mbox = tg3_write_indirect_mbox; tp->write32_tx_mbox = tg3_write_indirect_mbox; tp->write32_rx_mbox = tg3_write_indirect_mbox; diff --git a/patch/driver-arista-net-tg3-disallow-broadcom-default-mac.patch b/patches-sonic/driver-arista-net-tg3-disallow-broadcom-default-mac.patch similarity index 60% rename from patch/driver-arista-net-tg3-disallow-broadcom-default-mac.patch rename to patches-sonic/driver-arista-net-tg3-disallow-broadcom-default-mac.patch index cf7500edf..7ceaefc7b 100644 --- a/patch/driver-arista-net-tg3-disallow-broadcom-default-mac.patch +++ b/patches-sonic/driver-arista-net-tg3-disallow-broadcom-default-mac.patch @@ -1,5 +1,7 @@ For Arista platforms, after calling kexec in fast-reboot, the MAC address +From: byu343 + of management interface is reset to 00:10:18:00:00:00. This patch can recover the MAC with the one previously saved by Arista Aboot. --- @@ -7,10 +9,10 @@ the MAC with the one previously saved by Arista Aboot. 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c -index 2e6a88094..d54ba3f75 100644 +index 72198ef56..997de31f9 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c -@@ -9367,6 +9367,15 @@ static int tg3_halt(struct tg3 *tp, int kind, bool silent) +@@ -9365,6 +9365,15 @@ static int tg3_halt(struct tg3 *tp, int kind, bool silent) return err; } @@ -26,20 +28,20 @@ index 2e6a88094..d54ba3f75 100644 static int tg3_set_mac_addr(struct net_device *dev, void *p) { struct tg3 *tp = netdev_priv(dev); -@@ -17080,28 +17089,18 @@ static int tg3_get_device_address(struct tg3 *tp) - dev->dev_addr[5] = (lo >> 0) & 0xff; +@@ -17040,28 +17049,18 @@ static int tg3_get_device_address(struct tg3 *tp) + addr[5] = (lo >> 0) & 0xff; /* Some old bootcode may report a 0 MAC address in SRAM */ -- addr_ok = is_valid_ether_addr(&dev->dev_addr[0]); -+ addr_ok = is_valid_bcm_ether_addr(&dev->dev_addr[0]); +- addr_ok = is_valid_ether_addr(addr); ++ addr_ok = is_valid_bcm_ether_addr(addr); } if (!addr_ok) { - /* Next, try NVRAM. */ - if (!tg3_flag(tp, NO_NVRAM) && - !tg3_nvram_read_be32(tp, mac_offset + 0, &hi) && - !tg3_nvram_read_be32(tp, mac_offset + 4, &lo)) { -- memcpy(&dev->dev_addr[0], ((char *)&hi) + 2, 2); -- memcpy(&dev->dev_addr[2], (char *)&lo, sizeof(lo)); +- memcpy(&addr[0], ((char *)&hi) + 2, 2); +- memcpy(&addr[2], (char *)&lo, sizeof(lo)); - } - /* Finally just fetch it out of the MAC control regs. */ - else { @@ -48,19 +50,19 @@ index 2e6a88094..d54ba3f75 100644 + hi = tr32(MAC_ADDR_0_HIGH); + lo = tr32(MAC_ADDR_0_LOW); -- dev->dev_addr[5] = lo & 0xff; -- dev->dev_addr[4] = (lo >> 8) & 0xff; -- dev->dev_addr[3] = (lo >> 16) & 0xff; -- dev->dev_addr[2] = (lo >> 24) & 0xff; -- dev->dev_addr[1] = hi & 0xff; -- dev->dev_addr[0] = (hi >> 8) & 0xff; +- addr[5] = lo & 0xff; +- addr[4] = (lo >> 8) & 0xff; +- addr[3] = (lo >> 16) & 0xff; +- addr[2] = (lo >> 24) & 0xff; +- addr[1] = hi & 0xff; +- addr[0] = (hi >> 8) & 0xff; - } -+ dev->dev_addr[5] = lo & 0xff; -+ dev->dev_addr[4] = (lo >> 8) & 0xff; -+ dev->dev_addr[3] = (lo >> 16) & 0xff; -+ dev->dev_addr[2] = (lo >> 24) & 0xff; -+ dev->dev_addr[1] = hi & 0xff; -+ dev->dev_addr[0] = (hi >> 8) & 0xff; ++ addr[5] = lo & 0xff; ++ addr[4] = (lo >> 8) & 0xff; ++ addr[3] = (lo >> 16) & 0xff; ++ addr[2] = (lo >> 24) & 0xff; ++ addr[1] = hi & 0xff; ++ addr[0] = (hi >> 8) & 0xff; } - if (!is_valid_ether_addr(&dev->dev_addr[0])) { + if (!is_valid_ether_addr(addr)) diff --git a/patch/driver-arista-net-tg3-dma-mask-4g-sb800.patch b/patches-sonic/driver-arista-net-tg3-dma-mask-4g-sb800.patch similarity index 84% rename from patch/driver-arista-net-tg3-dma-mask-4g-sb800.patch rename to patches-sonic/driver-arista-net-tg3-dma-mask-4g-sb800.patch index 7daea94b1..cf5dea2ea 100644 --- a/patch/driver-arista-net-tg3-dma-mask-4g-sb800.patch +++ b/patches-sonic/driver-arista-net-tg3-dma-mask-4g-sb800.patch @@ -1,5 +1,7 @@ Force DMA accesses to be done in the 4G range due to a SB800 limitation +From: Samuel Angebault + This issue is seen on Arista DCS-7050QX-32 SKUs --- drivers/net/ethernet/broadcom/tg3.c | 17 ++++++++++++++--- @@ -7,10 +9,10 @@ This issue is seen on Arista DCS-7050QX-32 SKUs 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c -index 1af4e1607..2e6a88094 100644 +index 5143cdd0e..72198ef56 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c -@@ -16365,6 +16365,16 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) +@@ -16353,6 +16353,16 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) } while (bridge); } @@ -27,7 +29,7 @@ index 1af4e1607..2e6a88094 100644 if (tg3_asic_rev(tp) == ASIC_REV_5704 || tg3_asic_rev(tp) == ASIC_REV_5714) tp->pdev_peer = tg3_find_peer(tp); -@@ -16892,8 +16902,9 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) +@@ -16880,8 +16890,9 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent) if (tg3_asic_rev(tp) == ASIC_REV_5705 && (grc_misc_cfg == GRC_MISC_CFG_BOARD_ID_5788 || @@ -39,9 +41,9 @@ index 1af4e1607..2e6a88094 100644 if (!tg3_flag(tp, IS_5788) && tg3_asic_rev(tp) != ASIC_REV_5700) -@@ -17804,7 +17815,7 @@ static int tg3_init_one(struct pci_dev *pdev, +@@ -17796,7 +17807,7 @@ static int tg3_init_one(struct pci_dev *pdev, * On 64-bit systems without IOMMU, use 64-bit dma_mask and - * do DMA address check in tg3_start_xmit(). + * do DMA address check in __tg3_start_xmit(). */ - if (tg3_flag(tp, IS_5788)) + if (tg3_flag(tp, 4G_DMA_ONLY)) @@ -49,7 +51,7 @@ index 1af4e1607..2e6a88094 100644 else if (tg3_flag(tp, 40BIT_DMA_BUG)) { persist_dma_mask = dma_mask = DMA_BIT_MASK(40); diff --git a/drivers/net/ethernet/broadcom/tg3.h b/drivers/net/ethernet/broadcom/tg3.h -index a772a33b6..09eda72ab 100644 +index 1000c8940..3e004b0bf 100644 --- a/drivers/net/ethernet/broadcom/tg3.h +++ b/drivers/net/ethernet/broadcom/tg3.h @@ -3120,6 +3120,7 @@ enum TG3_FLAGS { diff --git a/patches-sonic/driver-arista-net-tg3-napi-enable-called-flag.patch b/patches-sonic/driver-arista-net-tg3-napi-enable-called-flag.patch new file mode 100644 index 000000000..d4d04880d --- /dev/null +++ b/patches-sonic/driver-arista-net-tg3-napi-enable-called-flag.patch @@ -0,0 +1,123 @@ +From 821f6d79ad2773e0ff1537c0bb3c7af93a694709 Mon Sep 17 00:00:00 2001 +From: Yury Murashka +Date: Thu, 8 May 2026 00:00:00 +0000 +Subject: net: tg3: guard napi_disable and pci_disable_device calls + +We need this patch to fix a soft lockup in the Linux kernel on Arista +modular chassis in the 202511 branch. + +During PCIe hot-plug events, uncorrectable errors can be reported and +AER recovery for the tg3 device is initiated by the AER kernel driver. +The tg3_io_error_detected function is the AER error recovery handler. + +From tg3_io_error_detected, we call tg3_netif_stop->tg3_napi_disable-> +napi_disable and return PCI_ERS_RESULT_NEED_RESET on non-fatal error. +We expect that during AER recovery tg3_io_slot_reset and tg3_io_resume +will be called. But AER error recovery can fail. For example, when one +of PCIe devices on the same bus reports PCI_ERS_RESULT_NO_AER_DRIVER. +As a result, tg3_io_slot_reset and tg3_io_resume are not called, PCIe +device is disabled and NAPI is disabled (pci_disable_device and +napi_disable are called from tg3_io_error_detected). Then we can try to +disable PCIe link and napi_disable will be called again: + + napi_disable+0x1b/0x1b0 + tg3_napi_disable+0x89/0xa0 [tg3] + tg3_netif_stop+0x37/0xe3 [tg3] + tg3_stop+0x30/0x160 [tg3] + tg3_close+0x2a/0x60 [tg3] + __dev_close_many+0xad/0x130 + dev_close_many+0xb2/0x190 + unregister_netdevice_many_notify+0x19d/0xa00 + unregister_netdevice_queue+0xf8/0x140 + unregister_netdev+0x1c/0x30 + tg3_remove_one+0xaa/0x150 [tg3] + pci_device_remove+0x42/0xb0 + device_release_driver_internal+0x19c/0x200 + pci_stop_bus_device+0x85/0xb0 + pci_stop_bus_device+0x2c/0xb0 + pci_stop_bus_device+0x2c/0xb0 + pci_stop_and_remove_bus_device+0x12/0x20 + pciehp_unconfigure_device+0x9f/0x160 + pciehp_disable_slot+0x67/0x100 + pciehp_handle_presence_or_link_change+0x77/0x350 + +This is not expected by napi_disable and a thread can be locked in +napi_disable forever. We have pcierr_recovery to cover a similar issue, +but for fatal errors. We cannot reuse this flag because it is reset in +tg3_io_resume, but it is not called when AER recovery fails. + +Similarly, if an AER error is reported and tg3_io_error_detected calls +pci_disable_device, a subsequent device removal via tg3_remove_one or +tg3_shutdown will call pci_disable_device again for the already-disabled +device. + +Add a napi_enabled flag to struct tg3 to track whether napi_enable has +been called. Guard tg3_napi_disable() so it returns early if NAPI was +not previously enabled. Also guard pci_disable_device() calls in +tg3_remove_one() and tg3_shutdown() with pci_is_enabled() to avoid +disabling an already-disabled device. + +Fixes: b45aa2f6192e ("tg3: Add EEH support") +Signed-off-by: Yury Murashka + +--- a/drivers/net/ethernet/broadcom/tg3.c ++++ b/drivers/net/ethernet/broadcom/tg3.c +@@ -7415,6 +7415,11 @@ static void tg3_napi_disable(struct tg3 + { + int i; + ++ if (!tp->napi_enabled) ++ return; ++ ++ tp->napi_enabled = false; ++ + for (i = tp->irq_cnt - 1; i >= 0; i--) + napi_disable(&tp->napi[i].napi); + } +@@ -7423,6 +7428,8 @@ static void tg3_napi_enable(struct tg3 * + { + int i; + ++ tp->napi_enabled = true; ++ + for (i = 0; i < tp->irq_cnt; i++) + napi_enable(&tp->napi[i].napi); + } +@@ -17715,6 +17722,7 @@ static int tg3_init_one(struct pci_dev * + tp->tx_mode = TG3_DEF_TX_MODE; + tp->irq_sync = 1; + tp->pcierr_recovery = false; ++ tp->napi_enabled = false; + + if (tg3_debug > 0) + tp->msg_enable = tg3_debug; +@@ -18096,7 +18104,8 @@ static void tg3_remove_one(struct pci_de + } + free_netdev(dev); + pci_release_regions(pdev); +- pci_disable_device(pdev); ++ if (pci_is_enabled(pdev)) ++ pci_disable_device(pdev); + } + } + +@@ -18252,7 +18261,8 @@ static void tg3_shutdown(struct pci_dev + + rtnl_unlock(); + +- pci_disable_device(pdev); ++ if (pci_is_enabled(pdev)) ++ pci_disable_device(pdev); + } + + /** +--- a/drivers/net/ethernet/broadcom/tg3.h ++++ b/drivers/net/ethernet/broadcom/tg3.h +@@ -3430,6 +3430,7 @@ struct tg3 { + struct device *hwmon_dev; + bool link_up; + bool pcierr_recovery; ++ bool napi_enabled; + + u32 ape_hb; + unsigned long ape_hb_interval; diff --git a/patches-sonic/driver-arista-pci-aer-disable-recovery.patch b/patches-sonic/driver-arista-pci-aer-disable-recovery.patch new file mode 100644 index 000000000..b477b0f7c --- /dev/null +++ b/patches-sonic/driver-arista-pci-aer-disable-recovery.patch @@ -0,0 +1,104 @@ +From: yurypm +Date: Mon, 25 May 2026 13:45:51 +0000 +Subject: Add noaer_recovery pci kernel boot option + +AER error recovery is part of the AER error handling subsystem in +the Linux kernel. AER is enabled by default in the SONiC Linux +kernel. The default Linux behavior is incompatible with Arista +chassis hardware architecture. Enabling AER recovery on large +modular systems with a complex PCIe tree could cause unexpected +behavior and side effects. It would be nice to have an option to +disable AER recovery on some chassis. + +Add pci=noaer_recovery kernel boot option to disable AER error +recovery when an uncorrectable error is reported. + +Signed-off-by: Yury Murashka +--- + Documentation/admin-guide/kernel-parameters.txt | 4 ++++ + drivers/pci/pci.c | 2 ++ + drivers/pci/pci.h | 2 ++ + drivers/pci/pcie/err.c | 15 +++++++++++++++ + 4 files changed, 23 insertions(+) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index f402bba..2bbd7ab 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -4483,6 +4483,10 @@ + noaer [PCIE] If the PCIEAER kernel config parameter is + enabled, this kernel boot option can be used to + disable the use of PCIE advanced error reporting. ++ noaer_recovery [PCIE] If the PCIEAER kernel config parameter is ++ enabled, this kernel boot option can be used to ++ disable AER error recovery when an uncorrectable ++ error is reported. + nodomains [PCI] Disable support for multiple PCI + root domains (aka PCI segments, in ACPI-speak). + nommconf [X86] Disable use of MMCONFIG for PCI +diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c +index 51a09e4..77d0786 100644 +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -6896,6 +6896,8 @@ static int __init pci_setup(char *str) + pcie_ats_disabled = true; + } else if (!strcmp(str, "noaer")) { + pci_no_aer(); ++ } else if (!strcmp(str, "noaer_recovery")) { ++ pci_no_aer_recovery(); + } else if (!strcmp(str, "earlydump")) { + pci_early_dump = true; + } else if (!strncmp(str, "realloc=", 8)) { +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index 65df6d2..551b6e8 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -826,6 +826,7 @@ static inline void of_pci_remove_node(struct pci_dev *pdev) { } + + #ifdef CONFIG_PCIEAER + void pci_no_aer(void); ++void pci_no_aer_recovery(void); + void pci_aer_init(struct pci_dev *dev); + void pci_aer_exit(struct pci_dev *dev); + extern const struct attribute_group aer_stats_attr_group; +@@ -836,6 +837,7 @@ void pci_save_aer_state(struct pci_dev *dev); + void pci_restore_aer_state(struct pci_dev *dev); + #else + static inline void pci_no_aer(void) { } ++static inline void pci_no_aer_recovery(void) { } + static inline void pci_aer_init(struct pci_dev *d) { } + static inline void pci_aer_exit(struct pci_dev *d) { } + static inline void pci_aer_clear_fatal_status(struct pci_dev *dev) { } +diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c +index 3109077..bb5ec0c 100644 +--- a/drivers/pci/pcie/err.c ++++ b/drivers/pci/pcie/err.c +@@ -21,6 +21,13 @@ + #include "portdrv.h" + #include "../pci.h" + ++static int pcie_aer_recovery_disable = 0; ++ ++void pci_no_aer_recovery(void) ++{ ++ pcie_aer_recovery_disable = 1; ++} ++ + static pci_ers_result_t merge_result(enum pci_ers_result orig, + enum pci_ers_result new) + { +@@ -197,6 +204,14 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, + pci_ers_result_t status = PCI_ERS_RESULT_CAN_RECOVER; + struct pci_host_bridge *host = pci_find_host_bridge(dev->bus); + ++ if (pcie_aer_recovery_disable) { ++ if (host->native_aer || pcie_ports_native) { ++ pcie_clear_device_status(dev); ++ pci_aer_clear_nonfatal_status(dev); ++ } ++ return status; ++ } ++ + /* + * If the error was detected by a Root Port, Downstream Port, RCEC, + * or RCiEP, recovery runs on the device itself. For Ports, that diff --git a/patches-sonic/driver-arista-pci-clear-aer-error-on-recovery-failure.patch b/patches-sonic/driver-arista-pci-clear-aer-error-on-recovery-failure.patch new file mode 100644 index 000000000..750506cdd --- /dev/null +++ b/patches-sonic/driver-arista-pci-clear-aer-error-on-recovery-failure.patch @@ -0,0 +1,140 @@ +From a24b3e9f6f67bed07efe06efb3cee2bf8457120a Mon Sep 17 00:00:00 2001 +From: Yury Murashka +Date: Mon, 18 May 2026 12:42:53 +0000 +Subject: [PATCH] Clear non-fatal errors on AER recovery failure + +pci_aer_clear_nonfatal_status() is not called when AER recovery fails. + +A PCIe switch can report an AER error with the 0000:00:00.0 AER error +source address (if the multi-error flag is not set). In this case, the +AER driver rescans the whole bus and tries to find a device reporting +the AER error. + +find_source_device() is called. When the AER error source is 0, +is_error_source() returns 'true' for any device with a reported AER +error. When is_error_source() reports 'true', the AER driver checks +the e_info->multi_error_valid flag and stops iterating in +find_device_iter(). Therefore, the error is reported only for the first +device on the bus. + +Then, the AER driver initiates AER recovery. If AER recovery fails, +AER error cleanup is not called for the devices. When any device on +the same bus reports a new AER error, the bus rescan process is +repeated, and the AER error is reported for the same first device with +the uncleaned AER error. + +This is a side effect of an AER recovery failure. We should not be +left with stale errors. We can either perform error cleanup in the +kernel immediately after a failure, or we can leave the devices as-is +and let user-space code recover the system/PCIe tree. The current +Linux kernel leaves the devices as-is, which leads to visible side +effects in the absence of user-space monitoring. + +Add a kernel boot parameter pci=aer_clear_on_recovery_failure to +enable AER error cleanup for cases where recovery fails. This prevents +stale errors from causing incorrect device identification on subsequent +AER error events. + +The kernel boot parameter allows enabling this functionality on demand. +By default, it is disabled, because there could be software that +expects to see the device in an unchanged state when AER recovery +fails. + +The patch was discussed with Linux maintainers here: +https://lore.kernel.org/linux-pci/CAPzpGcRCTCZtaX1EVaJNZ103THZKsoszZduY7=gwfYdcrMo-SQ@mail.gmail.com/ +They prefer to fix the reason for the issue - AER recovery failure. +Proposed fix introduces new crashes in the SONiC Linux kernel during +AER error recovery and cannot be used as-is. + +We are planning to enable the new functionality only for Arista +devices. + +Signed-off-by: Yury Murashka +--- + Documentation/admin-guide/kernel-parameters.txt | 4 ++++ + drivers/pci/pci.c | 2 ++ + drivers/pci/pci.h | 2 ++ + drivers/pci/pcie/err.c | 13 +++++++++++++ + 4 files changed, 21 insertions(+) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index 068891e..58ee606 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -4490,6 +4490,10 @@ + enabled, this kernel boot option can be used to + disable AER error recovery when an uncorrectable + error is reported. ++ aer_clear_on_recovery_failure [PCIE] If the PCIEAER kernel config parameter is ++ enabled, this kernel boot option can be used to ++ enable AER errors cleanup even if error recovery ++ failed. + nodomains [PCI] Disable support for multiple PCI + root domains (aka PCI segments, in ACPI-speak). + nommconf [X86] Disable use of MMCONFIG for PCI +diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c +index f6a4c2f..e6aaec1 100644 +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -6946,6 +6946,8 @@ static int __init pci_setup(char *str) + disable_acs_redir_param = str + 18; + } else if (!strncmp(str, "config_acs=", 11)) { + config_acs_param = str + 11; ++ } else if (!strncmp(str, "aer_clear_on_recovery_failure", 29)) { ++ pci_enable_aer_clear_on_recovery_failure(); + } else { + pr_err("PCI: Unknown option `%s'\n", str); + } +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index bece8b9..a6e5d1c 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -837,6 +837,7 @@ int pci_aer_clear_status(struct pci_dev *dev); + int pci_aer_raw_clear_status(struct pci_dev *dev); + void pci_save_aer_state(struct pci_dev *dev); + void pci_restore_aer_state(struct pci_dev *dev); ++void pci_enable_aer_clear_on_recovery_failure(void); + #else + static inline void pci_no_aer(void) { } + static inline void pci_no_aer_recovery(void) { } +@@ -847,6 +848,7 @@ static inline int pci_aer_clear_status(struct pci_dev *dev) { return -EINVAL; } + static inline int pci_aer_raw_clear_status(struct pci_dev *dev) { return -EINVAL; } + static inline void pci_save_aer_state(struct pci_dev *dev) { } + static inline void pci_restore_aer_state(struct pci_dev *dev) { } ++static inline void pci_enable_aer_clear_on_recovery_failure(void) { } + #endif + + #ifdef CONFIG_ACPI +diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c +index bb5ec0c..898d4d4 100644 +--- a/drivers/pci/pcie/err.c ++++ b/drivers/pci/pcie/err.c +@@ -28,6 +28,13 @@ void pci_no_aer_recovery(void) + pcie_aer_recovery_disable = 1; + } + ++static int enable_aer_clear_on_recovery_failure; ++ ++void pci_enable_aer_clear_on_recovery_failure(void) ++{ ++ enable_aer_clear_on_recovery_failure = 1; ++} ++ + static pci_ers_result_t merge_result(enum pci_ers_result orig, + enum pci_ers_result new) + { +@@ -282,6 +289,12 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, + return status; + + failed: ++ if (enable_aer_clear_on_recovery_failure && ++ (host->native_aer || pcie_ports_native)) { ++ pcie_clear_device_status(dev); ++ pci_aer_clear_nonfatal_status(dev); ++ } ++ + pci_walk_bridge(bridge, pci_pm_runtime_put, NULL); + + pci_walk_bridge(bridge, report_perm_failure_detected, NULL); +-- +2.51.0 diff --git a/patches-sonic/driver-arista-pci-dpc-disable.patch b/patches-sonic/driver-arista-pci-dpc-disable.patch new file mode 100644 index 000000000..26504c41a --- /dev/null +++ b/patches-sonic/driver-arista-pci-dpc-disable.patch @@ -0,0 +1,121 @@ +From: yurypm +Date: Mon, 25 May 2026 13:45:50 +0000 +Subject: Add nodpc pci kernel boot option + +PCI DPC (Downstream Port Containment) is enabled by default in the +SONiC Linux kernel. DPC support can be advertised by PCIe devices, +but it might not be fully supported in the firmware. The default Linux +behavior is incompatible with Arista chassis hardware architecture. +Enabling DPC could cause unexpected behavior and side effects. It +would be nice to have an option to disable DPC on some chassis. + +Add pci=nodpc kernel boot option to disable PCI DPC. + +Signed-off-by: Yury Murashka +--- + Documentation/admin-guide/kernel-parameters.txt | 3 +++ + drivers/pci/pci.c | 2 ++ + drivers/pci/pci.h | 2 ++ + drivers/pci/pcie/dpc.c | 16 +++++++++++++--- + 4 files changed, 20 insertions(+), 3 deletions(-) + +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index 2bbd7ab..068891e 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -4480,6 +4480,9 @@ + through ports 0xC000-0xCFFF). + See http://wiki.osdev.org/PCI for more info + on the configuration access mechanisms. ++ nodpc [PCIE] If the PCIE_DPC kernel config parameter is ++ enabled, this kernel boot option can be used to ++ disable the use of PCIE DPC. + noaer [PCIE] If the PCIEAER kernel config parameter is + enabled, this kernel boot option can be used to + disable the use of PCIE advanced error reporting. +diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c +index 77d0786..f6a4c2f 100644 +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -6894,6 +6894,8 @@ static int __init pci_setup(char *str) + } else if (!strncmp(str, "noats", 5)) { + pr_info("PCIe: ATS is disabled\n"); + pcie_ats_disabled = true; ++ } else if (!strcmp(str, "nodpc")) { ++ pci_no_dpc(); + } else if (!strcmp(str, "noaer")) { + pci_no_aer(); + } else if (!strcmp(str, "noaer_recovery")) { +diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h +index 551b6e8..bece8b9 100644 +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -535,6 +535,7 @@ struct rcec_ea { + #endif + + #ifdef CONFIG_PCIE_DPC ++void pci_no_dpc(void); + void pci_save_dpc_state(struct pci_dev *dev); + void pci_restore_dpc_state(struct pci_dev *dev); + void pci_dpc_init(struct pci_dev *pdev); +@@ -542,6 +543,7 @@ void dpc_process_error(struct pci_dev *pdev); + pci_ers_result_t dpc_reset_link(struct pci_dev *pdev); + bool pci_dpc_recovered(struct pci_dev *pdev); + #else ++static inline void pci_no_dpc(void) { } + static inline void pci_save_dpc_state(struct pci_dev *dev) { } + static inline void pci_restore_dpc_state(struct pci_dev *dev) { } + static inline void pci_dpc_init(struct pci_dev *pdev) { } +diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c +index cdc5431..8eb2a1f 100644 +--- a/drivers/pci/pcie/dpc.c ++++ b/drivers/pci/pcie/dpc.c +@@ -43,12 +43,19 @@ static const char * const rp_pio_error_string[] = { + "Memory Request Completion Timeout", /* Bit Position 18 */ + }; + ++static int pcie_dpc_disable = 0; ++ ++void pci_no_dpc(void) ++{ ++ pcie_dpc_disable = 1; ++} ++ + void pci_save_dpc_state(struct pci_dev *dev) + { + struct pci_cap_saved_state *save_state; + u16 *cap; + +- if (!pci_is_pcie(dev)) ++ if (pcie_dpc_disable || !pci_is_pcie(dev)) + return; + + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DPC); +@@ -64,7 +71,7 @@ void pci_restore_dpc_state(struct pci_dev *dev) + struct pci_cap_saved_state *save_state; + u16 *cap; + +- if (!pci_is_pcie(dev)) ++ if (pcie_dpc_disable || !pci_is_pcie(dev)) + return; + + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DPC); +@@ -104,7 +111,7 @@ bool pci_dpc_recovered(struct pci_dev *pdev) + { + struct pci_host_bridge *host; + +- if (!pdev->dpc_cap) ++ if (pcie_dpc_disable || !pdev->dpc_cap) + return false; + + /* +@@ -398,6 +405,9 @@ void pci_dpc_init(struct pci_dev *pdev) + { + u16 cap; + ++ if (pcie_dpc_disable) ++ return; ++ + pdev->dpc_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DPC); + if (!pdev->dpc_cap) + return; diff --git a/patch/driver-arista-pci-reassign-pref-mem.patch b/patches-sonic/driver-arista-pci-reassign-pref-mem.patch similarity index 90% rename from patch/driver-arista-pci-reassign-pref-mem.patch rename to patches-sonic/driver-arista-pci-reassign-pref-mem.patch index b7ff23df0..1d653923e 100644 --- a/patch/driver-arista-pci-reassign-pref-mem.patch +++ b/patches-sonic/driver-arista-pci-reassign-pref-mem.patch @@ -1,4 +1,7 @@ This patch releases prefecth memory resources assigned by the BIOS and + +From: Baptiste Covolato + reassigns them based on Arista platform specific values. The patch has no effect on non Arista devices. In order for it to take @@ -9,17 +12,17 @@ Signed-off-by: yxiong@arista.com Signed-off-by: Vasiliy Khoruzhick Signed-off-by: Baptiste Covolato --- - arch/x86/pci/i386.c | 1 - drivers/pci/Kconfig | 6 + - drivers/pci/Makefile | 2 - drivers/pci/hotplug/pciehp_pci.c | 6 - - drivers/pci/setup-prefmem.c | 323 +++++++++++++++++++++++++++ - include/linux/pci.h | 11 + - 8 files changed, 352 insertions(+), 2 deletions(-) + arch/x86/pci/i386.c | 1 + drivers/pci/Kconfig | 6 + + drivers/pci/Makefile | 2 + drivers/pci/hotplug/pciehp_pci.c | 6 + + drivers/pci/setup-prefmem.c | 323 ++++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 11 + + 6 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 drivers/pci/setup-prefmem.c diff --git a/arch/x86/pci/i386.c b/arch/x86/pci/i386.c -index 01234567..89abcdef 100644 +index fa855bbae..89859b8b2 100644 --- a/arch/x86/pci/i386.c +++ b/arch/x86/pci/i386.c @@ -360,6 +360,7 @@ static int __init pcibios_assign_resources(void) @@ -31,12 +34,12 @@ index 01234567..89abcdef 100644 pcibios_fw_addr_list_del(); diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig -index 01234567..89abcdef 100644 +index 0c473d75e..3c157b30d 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig -@@ -144,6 +144,12 @@ config PCI_HYPERV - The PCI device frontend driver allows the kernel to import arbitrary - PCI devices from a PCI backend to support PCI driver domains. +@@ -252,6 +252,12 @@ config PCIE_BUS_PEER2PEER + Reserves space in the kernel to maintain resource locking for + multiple GPUS. The overhead for each GPU is very small. +config PCI_REASSIGN_PREF_MEM + bool "PCI reassign prefetchable memory" @@ -48,12 +51,12 @@ index 01234567..89abcdef 100644 source "drivers/pci/controller/Kconfig" source "drivers/pci/endpoint/Kconfig" diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile -index 01234567..89abcdef 100644 +index 522d2b974..f866adb07 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile -@@ -28,6 +28,8 @@ obj-$(CONFIG_PCI_PF_STUB) += pci-pf-stub.o - obj-$(CONFIG_PCI_ECAM) += ecam.o - obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o +@@ -31,6 +31,8 @@ obj-$(CONFIG_PCI_ECAM) += ecam.o + obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o + obj-$(CONFIG_PCI_NPEM) += npem.o +obj-$(CONFIG_PCI_REASSIGN_PREF_MEM) += setup-prefmem.o + @@ -61,10 +64,10 @@ index 01234567..89abcdef 100644 obj-$(CONFIG_PCI_ENDPOINT) += endpoint/ diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c -index 01234567..89abcdef 100644 +index d17f3bf36..7b87b7d9b 100644 --- a/drivers/pci/hotplug/pciehp_pci.c +++ b/drivers/pci/hotplug/pciehp_pci.c -@@ -53,7 +53,11 @@ int pciehp_configure_device(struct slot *p_slot) +@@ -61,7 +61,11 @@ int pciehp_configure_device(struct controller *ctrl) for_each_pci_bridge(dev, parent) pci_hp_add_bridge(dev); @@ -75,11 +78,11 @@ index 01234567..89abcdef 100644 + pci_assign_unassigned_bridge_resources(bridge); + } pcie_bus_configure_settings(parent); - pci_bus_add_devices(parent); + /* diff --git a/drivers/pci/setup-prefmem.c b/drivers/pci/setup-prefmem.c new file mode 100644 -index 01234567..89abcdef +index 000000000..57cd08b35 --- /dev/null +++ b/drivers/pci/setup-prefmem.c @@ -0,0 +1,323 @@ @@ -407,12 +410,12 @@ index 01234567..89abcdef +} +EXPORT_SYMBOL(pci_reassign_prefmem_res); diff --git a/include/linux/pci.h b/include/linux/pci.h -index 01234567..89abcdef 100644 +index 22207a797..0054d62c5 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h -@@ -622,6 +622,17 @@ static inline struct pci_dev *pci_upstream_bridge(struct pci_dev *dev) - struct device *pci_get_host_bridge_device(struct pci_dev *dev); - void pci_put_host_bridge_device(struct device *dev); +@@ -684,6 +684,17 @@ static inline struct pci_dev *pci_upstream_bridge(struct pci_dev *dev) + return dev->bus->self; + } +#ifdef CONFIG_PCI_REASSIGN_PREF_MEM +extern void pci_reassign_prefmem_res(void); diff --git a/patches-sonic/driver-arista-restrict-eMMC-drive-to-50Mhz-from-userland.patch b/patches-sonic/driver-arista-restrict-eMMC-drive-to-50Mhz-from-userland.patch new file mode 100644 index 000000000..5f4978542 --- /dev/null +++ b/patches-sonic/driver-arista-restrict-eMMC-drive-to-50Mhz-from-userland.patch @@ -0,0 +1,70 @@ +mmc: restrict eMMC drive to 50Mhz from userland + +This issue was fixed for kernel 3.18 by setting sdhci.debug_quirks2=0x40 +from Aboot boot0 (conditionally, for specific Aboot versions). + +For kernel 4.9, however, we need SDHCI_QUIRK2_BROKEN_64_BIT_DMA, which +is also in quirks2. The problem is that debug_quirks2 overwrites whatever +is written to host->quirks2 during device probing (see __sdhci_read_caps). +Since we set both SDHCI_QUIRK_BROKEN_DMA and +SDHCI_QUIRK2_BROKEN_64_BIT_DMA (but the former is in quirks while the +latter is in quirks2) and Aboot overwrites quirks2, we end up with only +SDHCI_QUIRK_BROKEN_DMA being set. This causes some strange behavior with +AMD devices, where the sdhci driver stalls for a while and eventually +falls back to PIO mode. We need both quirk flags to be set in order for +the controller to work in SDMA mode. + +This patch is a workaround for the quirks2 overwrite problem. It adds a +set of new sdhci module parameters (append_quirks and append_quirks2) +that *append* bits (i.e. logical "or") instead of overwriting the +values. Then Aboot can use these parameters instead in order to set +SDHCI_QUIRK2_BROKEN_HS200. Note that both quirk2 flags are set +conditionally and independently by Aboot and the sdhci-pci probe code. + +Advantages of this approach: +* This patch by itself doesn't change any kernel behavior: it just adds + two module parameters that default to zero and will have no effect + unless explicitly set to a different value from outside the driver. +* SDHCI_QUIRK2_BROKEN_HS200 can be still controlled from Aboot and + conditionally (depending on the Aboot version). +* SDHCI_QUIRK2_BROKEN_64_BIT_DMA can be set by the sdhci-pci probing + code, independently of Aboot. + +Signed-off-by: Radu Rendec +Signed-off-by: Samuel Angebault +--- + drivers/mmc/host/sdhci.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c +index b1e1d327c..817934f04 100644 +--- a/drivers/mmc/host/sdhci.c ++++ b/drivers/mmc/host/sdhci.c +@@ -45,6 +45,8 @@ + + #define MAX_TUNING_LOOP 40 + ++static unsigned int append_quirks; ++static unsigned int append_quirks2; + static unsigned int debug_quirks = 0; + static unsigned int debug_quirks2; + +@@ -3990,6 +3992,9 @@ void __sdhci_read_caps(struct sdhci_host *host, const u16 *ver, + + host->read_caps = true; + ++ host->quirks |= append_quirks; ++ host->quirks2 |= append_quirks2; ++ + if (debug_quirks) + host->quirks = debug_quirks; + +@@ -4850,6 +4855,8 @@ static void __exit sdhci_drv_exit(void) + module_init(sdhci_drv_init); + module_exit(sdhci_drv_exit); + ++module_param(append_quirks, uint, 0444); ++module_param(append_quirks2, uint, 0444); + module_param(debug_quirks, uint, 0444); + module_param(debug_quirks2, uint, 0444); + diff --git a/patch/driver-at24-fix-odd-length-two-byte-access.patch b/patches-sonic/driver-at24-fix-odd-length-two-byte-access.patch similarity index 100% rename from patch/driver-at24-fix-odd-length-two-byte-access.patch rename to patches-sonic/driver-at24-fix-odd-length-two-byte-access.patch diff --git a/patch/driver-hwmon-lm75b-update.patch b/patches-sonic/driver-hwmon-lm75b-update.patch similarity index 100% rename from patch/driver-hwmon-lm75b-update.patch rename to patches-sonic/driver-hwmon-lm75b-update.patch diff --git a/patch/driver-hwmon-max6658-fix-write-convrate.patch b/patches-sonic/driver-hwmon-max6658-fix-write-convrate.patch similarity index 99% rename from patch/driver-hwmon-max6658-fix-write-convrate.patch rename to patches-sonic/driver-hwmon-max6658-fix-write-convrate.patch index eddb5a5f6..93d933aa4 100644 --- a/patch/driver-hwmon-max6658-fix-write-convrate.patch +++ b/patches-sonic/driver-hwmon-max6658-fix-write-convrate.patch @@ -15,7 +15,7 @@ index c2f411c29..9d1a68d99 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -580,6 +580,33 @@ static inline int lm90_select_remote_channel(struct i2c_client *client, - return 0; + return err; } +static int max6657_write_convrate(struct i2c_client *client, int val) diff --git a/patch/driver-hwmon-pmbus-add-dps460-support.patch b/patches-sonic/driver-hwmon-pmbus-add-dps460-support.patch similarity index 100% rename from patch/driver-hwmon-pmbus-add-dps460-support.patch rename to patches-sonic/driver-hwmon-pmbus-add-dps460-support.patch diff --git a/patch/driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch b/patches-sonic/driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch similarity index 96% rename from patch/driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch rename to patches-sonic/driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch index fc732bbbf..445f023c6 100644 --- a/patch/driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch +++ b/patches-sonic/driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch @@ -1,15 +1,16 @@ From 0a3a6f30eb6ea96a1642a3ad75d2de87a1448fb3 Mon Sep 17 00:00:00 2001 + From: Arun Saravanan Balachandran -Date: Thu, 11 Feb 2021 09:15:41 +0000 + Subject: [PATCH] Add attributes to retrieve PMBus status command codes Signed-off-by: Arun Saravanan Balachandran --- - drivers/hwmon/pmbus/dni_dps460.c | 152 +++++++++++++++++++++++++++++++++++++++ + drivers/hwmon/pmbus/dni_dps460.c | 152 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/drivers/hwmon/pmbus/dni_dps460.c b/drivers/hwmon/pmbus/dni_dps460.c -index d314c1f..313165a 100644 +index e7c9c8bfd..4e96a430b 100644 --- a/drivers/hwmon/pmbus/dni_dps460.c +++ b/drivers/hwmon/pmbus/dni_dps460.c @@ -166,13 +166,165 @@ static ssize_t set_pec(struct device *dev, struct device_attribute *dummy, @@ -41,7 +42,7 @@ index d314c1f..313165a 100644 + int val; + + mutex_lock(&data->update_lock); -+ val = pmbus_read_word_data(client, 0, PMBUS_STATUS_WORD); ++ val = pmbus_read_word_data(client, 0, 0xff, PMBUS_STATUS_WORD); + pmbus_clear_faults(client); + mutex_unlock(&data->update_lock); + if (val < 0) { @@ -178,6 +179,3 @@ index d314c1f..313165a 100644 NULL }; static struct attribute_group dni_dps460_attr_grp = { --- -2.7.4 - diff --git a/patch/driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch b/patches-sonic/driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch similarity index 96% rename from patch/driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch rename to patches-sonic/driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch index 032904a16..9259e7f67 100644 --- a/patch/driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch +++ b/patches-sonic/driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch @@ -1,6 +1,7 @@ From 9cfc597034ffe58527ea5ce1799dfcbad770ea30 Mon Sep 17 00:00:00 2001 + From: Arun Saravanan Balachandran -Date: Mon, 18 Jan 2021 09:00:44 +0000 + Subject: [PATCH] Update pmbus_data data structure to meet kernel implementation @@ -10,11 +11,11 @@ Update this data structure to meet current kernel (4.19.x) implementation. Signed-off-by: Arun Saravanan Balachandran --- - drivers/hwmon/pmbus/dni_dps460.c | 48 ++++++++++++++++++---------------------- + drivers/hwmon/pmbus/dni_dps460.c | 48 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/drivers/hwmon/pmbus/dni_dps460.c b/drivers/hwmon/pmbus/dni_dps460.c -index 2c67410..d314c1f 100644 +index ddd4aae54..e7c9c8bfd 100644 --- a/drivers/hwmon/pmbus/dni_dps460.c +++ b/drivers/hwmon/pmbus/dni_dps460.c @@ -19,6 +19,7 @@ @@ -111,6 +112,3 @@ index 2c67410..d314c1f 100644 mutex_lock(&data->update_lock); --- -2.7.4 - diff --git a/patch/driver-hwmon-pmbus-dni_dps460.patch b/patches-sonic/driver-hwmon-pmbus-dni_dps460.patch similarity index 94% rename from patch/driver-hwmon-pmbus-dni_dps460.patch rename to patches-sonic/driver-hwmon-pmbus-dni_dps460.patch index ac249dced..022ca7bd7 100644 --- a/patch/driver-hwmon-pmbus-dni_dps460.patch +++ b/patches-sonic/driver-hwmon-pmbus-dni_dps460.patch @@ -11,10 +11,10 @@ From: Cumulus Networks create mode 100644 drivers/hwmon/pmbus/dni_dps460.c diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig -index a82018aaf..720945848 100644 +index a25faf69f..3fd57f62a 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig -@@ -135,6 +135,16 @@ config SENSORS_MAX34440 +@@ -202,6 +202,16 @@ config SENSORS_MAX34440 This driver can also be built as a module. If so, the module will be called max34440. @@ -30,25 +30,25 @@ index a82018aaf..720945848 100644 + config SENSORS_MAX8688 tristate "Maxim MAX8688" - default n + help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile -index ea0e39518..c67f5a290 100644 +index 4c97ad0bd..6cf17031a 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile -@@ -15,6 +15,7 @@ obj-$(CONFIG_SENSORS_MAX16064) += max16064.o +@@ -23,6 +23,7 @@ obj-$(CONFIG_SENSORS_MAX20730) += max20730.o obj-$(CONFIG_SENSORS_MAX20751) += max20751.o obj-$(CONFIG_SENSORS_MAX31785) += max31785.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o +obj-$(CONFIG_SENSORS_DNI_DPS460) += dni_dps460.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o - obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o - obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o + obj-$(CONFIG_SENSORS_MP2888) += mp2888.o + obj-$(CONFIG_SENSORS_MP2975) += mp2975.o diff --git a/drivers/hwmon/pmbus/dni_dps460.c b/drivers/hwmon/pmbus/dni_dps460.c new file mode 100644 -index 000000000..ad29134c4 +index 000000000..ddd4aae54 --- /dev/null +++ b/drivers/hwmon/pmbus/dni_dps460.c -@@ -0,0 +1,253 @@ +@@ -0,0 +1,252 @@ +/* + * Hardware monitoring driver for Delta DPS460 + * @@ -158,7 +158,7 @@ index 000000000..ad29134c4 + * The slope is (FAN_RPM_MIN/FAN_VALUE_MIN) = 180 + */ + mutex_lock(&data->update_lock); -+ val = pmbus_read_word_data(client, 0, PMBUS_FAN_COMMAND_1); ++ val = pmbus_read_word_data(client, 0, 0xff, PMBUS_FAN_COMMAND_1); + pmbus_clear_faults(client); + mutex_unlock(&data->update_lock); + if (val < 0) { @@ -258,7 +258,7 @@ index 000000000..ad29134c4 + info->func[0] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + -+ ret = pmbus_do_probe(client, id, info); ++ ret = pmbus_do_probe(client, info); + if (ret < 0) + goto out; + @@ -271,7 +271,7 @@ index 000000000..ad29134c4 + return ret; +} + -+static int dni_dps460_remove(struct i2c_client *client) ++static void dni_dps460_remove(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + @@ -279,7 +279,6 @@ index 000000000..ad29134c4 + if (data->info) + kfree(data->info); + pmbus_do_remove(client); -+ return 0; +} + +static const struct i2c_device_id dni_dps460_id[] = { diff --git a/patch/driver-hwmon-pmbus-dps1900.patch b/patches-sonic/driver-hwmon-pmbus-dps1900.patch similarity index 88% rename from patch/driver-hwmon-pmbus-dps1900.patch rename to patches-sonic/driver-hwmon-pmbus-dps1900.patch index 206ca5015..42bef090a 100644 --- a/patch/driver-hwmon-pmbus-dps1900.patch +++ b/patches-sonic/driver-hwmon-pmbus-dps1900.patch @@ -2,6 +2,7 @@ dps1900 module to support Delta DPS1900 devices. From: deni64k + --- drivers/hwmon/pmbus/Kconfig | 10 ++++++ drivers/hwmon/pmbus/Makefile | 1 + @@ -10,10 +11,10 @@ From: deni64k create mode 100644 drivers/hwmon/pmbus/dps1900.c diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig -index 720945848..505ca2be0 100644 +index 3fd57f62a..91a7ce9df 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig -@@ -208,4 +208,14 @@ config SENSORS_ZL6100 +@@ -297,4 +297,14 @@ config SENSORS_ZL6100 This driver can also be built as a module. If so, the module will be called zl6100. @@ -29,17 +30,17 @@ index 720945848..505ca2be0 100644 + endif # PMBUS diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile -index c67f5a290..c76820a4d 100644 +index 6cf17031a..925f4901b 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile -@@ -22,3 +22,4 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o - obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o - obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o +@@ -33,3 +33,4 @@ obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o + obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o + obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o +obj-$(CONFIG_SENSORS_DPS1900) += dps1900.o diff --git a/drivers/hwmon/pmbus/dps1900.c b/drivers/hwmon/pmbus/dps1900.c new file mode 100644 -index 000000000..b26c1952f +index 000000000..76da10ce3 --- /dev/null +++ b/drivers/hwmon/pmbus/dps1900.c @@ -0,0 +1,70 @@ @@ -52,7 +53,7 @@ index 000000000..b26c1952f +#include +#include "pmbus.h" + -+static int dps1900_read_word_data(struct i2c_client *client, int page, int reg) ++static int dps1900_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + if (reg >= PMBUS_VIRT_BASE || + reg == PMBUS_OT_WARN_LIMIT || @@ -72,7 +73,7 @@ index 000000000..b26c1952f + reg == PMBUS_IOUT_UC_FAULT_LIMIT || + reg == PMBUS_POUT_MAX) + return -ENXIO; -+ return pmbus_read_word_data(client, page, reg); ++ return pmbus_read_word_data(client, page, phase, reg); +} + +static struct pmbus_driver_info dps1900_info = { @@ -88,7 +89,7 @@ index 000000000..b26c1952f +static int dps1900_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ -+ return pmbus_do_probe(client, id, &dps1900_info); ++ return pmbus_do_probe(client, &dps1900_info); +} + +static const struct i2c_device_id dps1900_id[] = { diff --git a/patch/driver-hwmon-pmbus-dps200.patch b/patches-sonic/driver-hwmon-pmbus-dps200.patch similarity index 93% rename from patch/driver-hwmon-pmbus-dps200.patch rename to patches-sonic/driver-hwmon-pmbus-dps200.patch index e780dc1c8..704974bd4 100644 --- a/patch/driver-hwmon-pmbus-dps200.patch +++ b/patches-sonic/driver-hwmon-pmbus-dps200.patch @@ -19,7 +19,7 @@ Signed-off-by: Saranpong Chobtrong 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/pmbus/dps1900.c b/drivers/hwmon/pmbus/dps1900.c -index f737f6480..d60c48e2c 100644 +index 76da10ce3..decbd07c6 100644 --- a/drivers/hwmon/pmbus/dps1900.c +++ b/drivers/hwmon/pmbus/dps1900.c @@ -1,3 +1,5 @@ @@ -34,14 +34,14 @@ index f737f6480..d60c48e2c 100644 +enum chips { dps1900, dps200 }; + - static int dps1900_read_word_data(struct i2c_client *client, int page, int reg) + static int dps1900_read_word_data(struct i2c_client *client, int page, int phase, int reg) { if (reg >= PMBUS_VIRT_BASE || -@@ -30,24 +34,112 @@ static int dps1900_read_word_data(struct i2c_client *client, int page, int reg) - return pmbus_read_word_data(client, page, reg); +@@ -30,24 +34,112 @@ static int dps1900_read_word_data(struct i2c_client *client, int page, int phase + return pmbus_read_word_data(client, page, phase, reg); } -+static int dps200_read_word_data(struct i2c_client *client, int page, int reg) ++static int dps200_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + + if (reg >= PMBUS_VIRT_BASE || @@ -70,7 +70,7 @@ index f737f6480..d60c48e2c 100644 + else if (reg == PMBUS_IOUT_OC_FAULT_LIMIT) + return 0xc8; + else -+ return pmbus_read_word_data(client, page, reg); ++ return pmbus_read_word_data(client, page, phase, reg); +} + static struct pmbus_driver_info dps1900_info = { @@ -126,7 +126,7 @@ index f737f6480..d60c48e2c 100644 static int dps1900_probe(struct i2c_client *client, const struct i2c_device_id *id) { -- return pmbus_do_probe(client, id, &dps1900_info); +- return pmbus_do_probe(client, &dps1900_info); + struct pmbus_driver_info *info; + + info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), @@ -146,7 +146,7 @@ index f737f6480..d60c48e2c 100644 + return -ENODEV; + } + -+ return pmbus_do_probe(client, id, info); ++ return pmbus_do_probe(client, info); } static const struct i2c_device_id dps1900_id[] = { diff --git a/patch/driver-hwmon-pmbus-ucd9200-mlnx.patch b/patches-sonic/driver-hwmon-pmbus-ucd9200-mlnx.patch similarity index 100% rename from patch/driver-hwmon-pmbus-ucd9200-mlnx.patch rename to patches-sonic/driver-hwmon-pmbus-ucd9200-mlnx.patch diff --git a/patch/driver-i2c-bus-intel-ismt-add-delay-param.patch b/patches-sonic/driver-i2c-bus-intel-ismt-add-delay-param.patch similarity index 88% rename from patch/driver-i2c-bus-intel-ismt-add-delay-param.patch rename to patches-sonic/driver-i2c-bus-intel-ismt-add-delay-param.patch index 31e9394a6..8cf1f3689 100644 --- a/patch/driver-i2c-bus-intel-ismt-add-delay-param.patch +++ b/patches-sonic/driver-i2c-bus-intel-ismt-add-delay-param.patch @@ -10,7 +10,7 @@ timed out' error message. 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-ismt.c b/drivers/i2c/busses/i2c-ismt.c -index 0d1c3ec8c..5f78de34d 100644 +index a35a27c32..b89fbb60d 100644 --- a/drivers/i2c/busses/i2c-ismt.c +++ b/drivers/i2c/busses/i2c-ismt.c @@ -66,6 +66,7 @@ @@ -21,7 +21,7 @@ index 0d1c3ec8c..5f78de34d 100644 #include -@@ -189,9 +190,12 @@ static const struct pci_device_id ismt_ids[] = { +@@ -190,9 +191,12 @@ static const struct pci_device_id ismt_ids[] = { MODULE_DEVICE_TABLE(pci, ismt_ids); /* Bus speed control bits for slow debuggers - refer to the docs for usage */ @@ -36,7 +36,7 @@ index 0d1c3ec8c..5f78de34d 100644 /** * __ismt_desc_dump() - dump the contents of a specific descriptor -@@ -397,6 +401,9 @@ static int ismt_access(struct i2c_adapter *adap, u16 addr, +@@ -400,6 +404,9 @@ static int ismt_access(struct i2c_adapter *adap, u16 addr, struct device *dev = &priv->pci_dev->dev; u8 *dma_buffer = PTR_ALIGN(&priv->buffer[0], 16); @@ -46,7 +46,7 @@ index 0d1c3ec8c..5f78de34d 100644 desc = &priv->hw[priv->head]; /* Initialize the DMA buffer */ -@@ -761,7 +768,7 @@ static void ismt_hw_init(struct ismt_priv *priv) +@@ -759,7 +766,7 @@ static void ismt_hw_init(struct ismt_priv *priv) bus_speed = 1000; break; } diff --git a/patches-sonic/driver-i2c-smbus-add-disable_spd-module-parameter.patch b/patches-sonic/driver-i2c-smbus-add-disable_spd-module-parameter.patch new file mode 100644 index 000000000..af04ff828 --- /dev/null +++ b/patches-sonic/driver-i2c-smbus-add-disable_spd-module-parameter.patch @@ -0,0 +1,48 @@ +From f929033d2ce54b4cb5072f9d594efa24ab3d4ea9 Mon Mar 09 00:00:00 2026 +From: Aravind Mani +Date: Mon, 3 Mar 2026 00:00:00 +0000 +Subject: [PATCH] i2c: smbus: Add disable_spd module parameter to skip automatic SPD EEPROM instantiation + +In some of the Dell switch platforms, the system EEPROM resides at +address 0x50 on the DDR SMBUS. The automatic SPD scanning grabs this address +with the ee1004 driver before the platform driver can claim it with the at24 driver +and it breaks system EEPROM access. + +Added a module parameter 'disable_spd' (default 0) in i2c-smbus driver. +By default, it allows automatic SPD EEPROM instantiation. +When disable_spd flag is set to 1,then the automatic SPD EEPROM instantiation will be disabled. + +Usage: + modprobe i2c-smbus disable_spd=1 + or: echo "options i2c-smbus disable_spd=1" > /etc/modprobe.d/i2c-smbus.conf + +Signed-off-by: Aravind Mani +--- + drivers/i2c/i2c-smbus.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/drivers/i2c/i2c-smbus.c 2026-03-03 21:27:46.766199714 +0530 ++++ b/drivers/i2c/i2c-smbus.c 2026-03-03 21:28:00.099400219 +0530 +@@ -362,6 +362,10 @@ + * - Only works on systems with 1 to 8 memory slots + */ + #if IS_ENABLED(CONFIG_DMI) ++static bool disable_spd; ++module_param(disable_spd, bool, 0644); ++MODULE_PARM_DESC(disable_spd, "Disable automatic SPD EEPROM instantiation (default: false)"); ++ + void i2c_register_spd(struct i2c_adapter *adap) + { + int n, slot_count = 0, dimm_count = 0; +@@ -370,6 +374,9 @@ + u64 mem_size; + const char *name; + ++ if (disable_spd) ++ return; ++ + while ((handle = dmi_memdev_handle(slot_count)) != 0xffff) { + slot_count++; + +-- +2.39.2 diff --git a/patch/driver-net-tg3-add-param-short-preamble-and-reset.patch b/patches-sonic/driver-net-tg3-add-param-short-preamble-and-reset.patch similarity index 85% rename from patch/driver-net-tg3-add-param-short-preamble-and-reset.patch rename to patches-sonic/driver-net-tg3-add-param-short-preamble-and-reset.patch index fc42963ad..8f91d2eb6 100644 --- a/patch/driver-net-tg3-add-param-short-preamble-and-reset.patch +++ b/patches-sonic/driver-net-tg3-add-param-short-preamble-and-reset.patch @@ -1,5 +1,7 @@ Subject: [PATCH] Add 2 more parameter for tg3.c. +From: Roy Lee + 1. short_preamble: Enable mdio traffic to external PHYs(i.g. BCM54616). 2. bcm5718s_reset: Enable BCM5718S reset support. @@ -9,10 +11,10 @@ Signed-off-by: roy_lee 1 file changed, 18 insertions(+) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c -index 8b543a27b..7bc30a48b 100644 +index 1d985ebf1..af09bad75 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c -@@ -242,6 +242,14 @@ static int tg3_debug = -1; /* -1 == use TG3_DEF_MSG_ENABLE as value */ +@@ -231,6 +231,14 @@ static int tg3_debug = -1; /* -1 == use TG3_DEF_MSG_ENABLE as value */ module_param(tg3_debug, int, 0); MODULE_PARM_DESC(tg3_debug, "Tigon3 bitmapped debugging message enable value"); @@ -27,7 +29,7 @@ index 8b543a27b..7bc30a48b 100644 #define TG3_DRV_DATA_FLAG_10_100_ONLY 0x0001 #define TG3_DRV_DATA_FLAG_5705_10_100 0x0002 -@@ -1520,6 +1528,11 @@ static void tg3_mdio_config_5785(struct tg3 *tp) +@@ -1508,6 +1516,11 @@ static void tg3_mdio_config_5785(struct tg3 *tp) static void tg3_mdio_start(struct tg3 *tp) { tp->mi_mode &= ~MAC_MI_MODE_AUTO_POLL; @@ -39,7 +41,7 @@ index 8b543a27b..7bc30a48b 100644 tw32_f(MAC_MI_MODE, tp->mi_mode); udelay(80); -@@ -2722,6 +2735,11 @@ static int tg3_phy_reset(struct tg3 *tp) +@@ -2707,6 +2720,11 @@ static int tg3_phy_reset(struct tg3 *tp) } } diff --git a/patches-sonic/driver-net-tg3-change-dma-mask-for-57766.patch b/patches-sonic/driver-net-tg3-change-dma-mask-for-57766.patch new file mode 100644 index 000000000..8f2adce12 --- /dev/null +++ b/patches-sonic/driver-net-tg3-change-dma-mask-for-57766.patch @@ -0,0 +1,51 @@ +From 821f6d79ad2773e0ff1537c0bb3c7af93a694709 Mon Sep 17 00:00:00 2001 +From: Boyang Yu +Date: Fri, 9 Aug 2024 17:03:51 +0000 +Subject: [PATCH] tg3: fix broadcom NIC 57766 staying down issue + +Set consistent dma mask to 31 in the tg3 driver for broadcom NIC +ASIC_REV_57766. Before the change, when a previous DMA end with +lower 16 bits 0xffff, and a new dma starts with upper bits 0xffff, +the hardware seems to think we're at address 0xffffffff and triggers +an overflow. The change asks the kernel to only address 31 bits for +coherent allocations. This will ensure that upper bits are at most +0x7fff thus avoiding the bug. + +Signed-off-by: Boyang Yu +--- + drivers/net/ethernet/broadcom/tg3.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c +index 6a1179935..b1bb1dc55 100644 +--- a/drivers/net/ethernet/broadcom/tg3.c ++++ b/drivers/net/ethernet/broadcom/tg3.c +@@ -17794,7 +17794,7 @@ static int tg3_init_one(struct pci_dev *pdev, + * On 64-bit systems without IOMMU, use 64-bit dma_mask and + * do DMA address check in __tg3_start_xmit(). + */ +- if (tg3_flag(tp, 4G_DMA_ONLY)) ++ if (tg3_flag(tp, 4G_DMA_ONLY) || (tg3_asic_rev(tp) == ASIC_REV_57766)) + persist_dma_mask = dma_mask = DMA_BIT_MASK(32); + else if (tg3_flag(tp, 40BIT_DMA_BUG)) { + persist_dma_mask = dma_mask = DMA_BIT_MASK(40); +@@ -17825,6 +17825,16 @@ static int tg3_init_one(struct pci_dev *pdev, + "No usable DMA configuration, aborting\n"); + goto err_out_apeunmap; + } ++ ++ if (tg3_asic_rev(tp) == ASIC_REV_57766) { ++ err = dma_set_coherent_mask(&pdev->dev, ++ DMA_BIT_MASK(31)); ++ if (err < 0) { ++ dev_err(&pdev->dev, ++ "Unable to obtain 31 bit DMA for consistent allocations\n"); ++ goto err_out_apeunmap; ++ } ++ } + } + + tg3_init_bufmgr_config(tp); +-- +2.41.0 + diff --git a/patch/driver-sff-8436-use-nvmem-framework.patch b/patches-sonic/driver-sff-8436-use-nvmem-framework.patch similarity index 98% rename from patch/driver-sff-8436-use-nvmem-framework.patch rename to patches-sonic/driver-sff-8436-use-nvmem-framework.patch index 086a6615a..779761d87 100644 --- a/patch/driver-sff-8436-use-nvmem-framework.patch +++ b/patches-sonic/driver-sff-8436-use-nvmem-framework.patch @@ -10,10 +10,10 @@ that the 'eeprom' file in sys is provided by the framework. 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig -index e3acc9b2a..05bcf1e1f 100644 +index c7d5cc8ad..b1a9be835 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig -@@ -114,6 +114,7 @@ config EEPROM_IDT_89HPESX +@@ -132,6 +132,7 @@ config EEPROM_EE1004 config EEPROM_SFF_8436 tristate "SFF-8436 QSFP EEPROMs support" depends on I2C && SYSFS diff --git a/patch/driver-sff-8436-use-nvmem_device_read.patch b/patches-sonic/driver-sff-8436-use-nvmem_device_read.patch similarity index 98% rename from patch/driver-sff-8436-use-nvmem_device_read.patch rename to patches-sonic/driver-sff-8436-use-nvmem_device_read.patch index fefe9e052..8e11ff6c8 100644 --- a/patch/driver-sff-8436-use-nvmem_device_read.patch +++ b/patches-sonic/driver-sff-8436-use-nvmem_device_read.patch @@ -49,7 +49,7 @@ index 9e6a7bf43..4688597eb 100644 - -/*-------------------------------------------------------------------------*/ - - static int sff_8436_remove(struct i2c_client *client) + static void sff_8436_remove(struct i2c_client *client) { struct sff_8436_data *sff_8436; @@ -861,8 +836,6 @@ static int sff_8436_eeprom_probe(struct i2c_client *client, diff --git a/patch/driver-support-optoe-EOF_fix.patch b/patches-sonic/driver-support-optoe-EOF_fix.patch similarity index 99% rename from patch/driver-support-optoe-EOF_fix.patch rename to patches-sonic/driver-support-optoe-EOF_fix.patch index 0ca8119c7..68f8879bd 100644 --- a/patch/driver-support-optoe-EOF_fix.patch +++ b/patches-sonic/driver-support-optoe-EOF_fix.patch @@ -14,7 +14,7 @@ Signed-off-by: Don Bollinger 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c -index 7425bf60e..b3064f02b 100644 +index 6813e46d7..bb33be2a2 100644 --- a/drivers/misc/eeprom/optoe.c +++ b/drivers/misc/eeprom/optoe.c @@ -158,6 +158,7 @@ struct optoe_platform_data { diff --git a/patch/driver-support-optoe-QSFP_DD.patch b/patches-sonic/driver-support-optoe-QSFP_DD.patch similarity index 95% rename from patch/driver-support-optoe-QSFP_DD.patch rename to patches-sonic/driver-support-optoe-QSFP_DD.patch index 5be80f953..1f3fdaec4 100644 --- a/patch/driver-support-optoe-QSFP_DD.patch +++ b/patches-sonic/driver-support-optoe-QSFP_DD.patch @@ -15,7 +15,7 @@ Signed-off-by: Don Bollinger 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c -index c22b92a29..16287fdc5 100644 +index f31c409ed..6b80397b8 100644 --- a/drivers/misc/eeprom/optoe.c +++ b/drivers/misc/eeprom/optoe.c @@ -97,6 +97,13 @@ @@ -108,13 +108,13 @@ index c22b92a29..16287fdc5 100644 + if (dev_class == TWO_ADDR) { + /* SFP family */ + /* if it doesn't exist, create 0x51 i2c address */ -+ if (!optoe->client[1]) { -+ optoe->client[1] = i2c_new_dummy(client->adapter, 0x51); -+ if (!optoe->client[1]) { ++ if (!optoe->client[1] || IS_ERR(optoe->client[1])) { ++ optoe->client[1] = i2c_new_dummy_device(client->adapter, 0x51); ++ if (IS_ERR(optoe->client[1])) { + dev_err(&client->dev, + "address 0x51 unavailable\n"); + mutex_unlock(&optoe->lock); -+ return -EADDRINUSE; ++ return PTR_ERR(optoe->client[1]); + } + } + optoe->bin.size = TWO_ADDR_EEPROM_SIZE; diff --git a/patches-sonic/driver-support-optoe-bank-support.patch b/patches-sonic/driver-support-optoe-bank-support.patch new file mode 100644 index 000000000..65ac60cbc --- /dev/null +++ b/patches-sonic/driver-support-optoe-bank-support.patch @@ -0,0 +1,346 @@ +From baef70561c79d0e34d79bfc011edfbec585e5d24 Mon Sep 17 00:00:00 2001 +From: Wataru Ishida +Date: Sun, 15 Feb 2026 22:34:29 +0900 +Subject: [PATCH] optoe: Add CMIS Bank support for transceivers with >8 lanes + +This patch adds CMIS Bank support to the 'optoe3' device class in order +to enable access to CMIS transceivers with more than 8 lanes (e.g., OSFP-XD, CPO OEs). + +- Bank support can be enabled only for the 'optoe3' dev class. +- 'bank_size' sysfs entry is added to enable and configure the size of the bank. +- By default, bank size is set to 0. + +Signed-off-by: Wataru Ishida +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -101,9 +101,48 @@ + * considerably more pages (at least to page 0xAF), which this driver + * supports. + * +- * NOTE: This version of the driver ONLY SUPPORTS BANK 0 PAGES on CMIS +- * devices. ++ * For CMIS transceivers that support Banked Pages, access to these pages ++ * is also supported. To access the banked pages, set the number of banks ++ * to access via the `max_bank_size` sysfs entry. ++ * By default, `max_bank_size` is set to 0, which disables this feature. + * ++ * When access to the Banked Pages is enabled, they are mapped into a linear ++ * address space. The mapping starts right after the Non-Banked Page area, ++ * as shown below. ++ * ++ * Note that support for memory pages varies by device. If the device does not ++ * support a requested page, it should overwrite the PageSelect register to 0 and ++ * return the data from page 0 following the behavior defined in CMIS 5.3 8.2.15. ++ * ++ * Since this driver does not validate the PageSelect register value after each ++ * write, applications should verify page and bank support through device ++ * advertisement registers or the datasheet. ++ * ++ * +-------------------------------+ ++ * | Lower Page | ++ * +-------------------------------+ ++ * | Upper Page (Bank 0, Page 0h) | ++ * +-------------------------------+ ++ * | Upper Page (Bank 0, Page 1h) | ++ * +-------------------------------+ ++ * | ... | ++ * +-------------------------------+ ++ * | Upper Page (Bank 0, Page FFh) | ++ * +-------------------------------+ ++ * | Upper Page (Bank 1, Page 0h) | ++ * +-------------------------------+ ++ * | ... | ++ * +-------------------------------+ ++ * | Upper Page (Bank 1, Page FFh) | ++ * +-------------------------------+ ++ * | Upper Page (Bank 2, Page 0h) | ++ * +-------------------------------+ ++ * | ... | ++ * +-------------------------------+ ++ * | Upper Page (Bank 2, Page FFh) | ++ * +-------------------------------+ ++ * | ... | ++ * (continued for more banks) + **/ + + /* #define DEBUG 1 */ +@@ -150,6 +189,15 @@ struct optoe_platform_data { + + /* fundamental unit of addressing for EEPROM */ + #define OPTOE_PAGE_SIZE 128 ++ ++/* ++ * By default, banked pages are not supported (max_bank_size = 0), which limits ++ * the maximum linear address space to 256 pages (32KB). When max_bank_size is ++ * greater than 0, the driver supports access to additional banked pages, with ++ * the total address space increasing by 256 pages (32KB) for each additional bank. ++ */ ++#define OPTOE_DEFAULT_BANK_SIZE 0 ++ + /* + * Single address devices (eg QSFP) have 256 pages, plus the unpaged + * low 128 bytes. If the device does not support paging, it is +@@ -168,6 +216,7 @@ struct optoe_platform_data { + #define TWO_ADDR_NO_0X51_SIZE (2 * OPTOE_PAGE_SIZE) + + /* a few constants to find our way around the EEPROM */ ++#define OPTOE_BANK_SELECT_REG 0x7E + #define OPTOE_PAGE_SELECT_REG 0x7F + #define ONE_ADDR_PAGEABLE_REG 0x02 + #define QSFP_NOT_PAGEABLE (1<<2) +@@ -195,6 +244,13 @@ struct optoe_data { + u8 *writebuf; + unsigned int write_max; + unsigned int write_timeout; ++ unsigned int max_bank_size; /* 0 means banked pages are not supported, otherwise the maximum supported bank size */ ++ ++ /* Indicates if page restore has failed. ++ * If true, the driver doesn't skip writing to page select register ++ * even for accesses to page 0. ++ */ ++ bool page_restore_failed; + + unsigned int num_addresses; + +@@ -245,13 +301,19 @@ static const struct i2c_device_id optoe_ + }; + MODULE_DEVICE_TABLE(i2c, optoe_ids); + ++static uint32_t one_addr_eeprom_size_with_bank(uint32_t max_bank_size) ++{ ++ max_bank_size = max_bank_size == 0 ? 1 : max_bank_size; ++ return (max_bank_size * OPTOE_ARCH_PAGES + 1) * OPTOE_PAGE_SIZE; ++} ++ + /*-------------------------------------------------------------------------*/ + /* + * This routine computes the addressing information to be used for + * a given r/w request. + * + * Task is to calculate the client (0 = i2c addr 50, 1 = i2c addr 51), +- * the page, and the offset. ++ * the bank, the page, and the offset. + * + * Handles both single address (eg QSFP) and two address (eg SFP). + * For SFP, offset 0-255 are on client[0], >255 is on client[1] +@@ -274,7 +336,7 @@ MODULE_DEVICE_TABLE(i2c, optoe_ids); + */ + + static uint8_t optoe_translate_offset(struct optoe_data *optoe, +- loff_t *offset, struct i2c_client **client) ++ loff_t *offset, struct i2c_client **client, uint8_t *bank) + { + unsigned int page = 0; + +@@ -297,12 +359,17 @@ static uint8_t optoe_translate_offset(st + if (*offset < OPTOE_PAGE_SIZE) + return page; + +- /* note, page will always be positive since *offset >= 128 */ +- page = (*offset >> 7)-1; +- /* 0x80 places the offset in the top half, offset is last 7 bits */ +- *offset = OPTOE_PAGE_SIZE + (*offset & 0x7f); ++ /* ++ * Calculate bank, page, and physical offset for paged EEPROM access. ++ * Memory layout: multiple banks, each containing 256 pages of 128 bytes. ++ */ ++ loff_t offset_in_paged_area = *offset - OPTOE_PAGE_SIZE; ++ const size_t bytes_per_bank = OPTOE_ARCH_PAGES * OPTOE_PAGE_SIZE; // 256 * 128 = 32KB ++ *bank = offset_in_paged_area / bytes_per_bank; ++ page = (offset_in_paged_area / OPTOE_PAGE_SIZE) % OPTOE_ARCH_PAGES; ++ *offset = OPTOE_PAGE_SIZE + (offset_in_paged_area % OPTOE_PAGE_SIZE); + +- return page; /* note also returning client and offset */ ++ return page; /* note also returning client, bank and offset */ + } + + static ssize_t optoe_eeprom_read(struct optoe_data *optoe, +@@ -511,23 +578,44 @@ static ssize_t optoe_eeprom_update_clien + { + struct i2c_client *client; + ssize_t retval = 0; +- uint8_t page = 0; ++ uint8_t page = 0, bank = 0; + loff_t phy_offset = off; + int ret = 0; + +- page = optoe_translate_offset(optoe, &phy_offset, &client); ++ page = optoe_translate_offset(optoe, &phy_offset, &client, &bank); + dev_dbg(&client->dev, +- "%s off %lld page:%d phy_offset:%lld, count:%ld, opcode:%d\n", +- __func__, off, page, phy_offset, (long int) count, opcode); +- if (page > 0) { +- ret = optoe_eeprom_write(optoe, client, &page, +- OPTOE_PAGE_SELECT_REG, 1); ++ "%s off %lld bank:%d page:%d phy_offset:%lld, count:%ld, opcode:%d\n", ++ __func__, off, bank, page, phy_offset, (long int) count, opcode); ++ if (optoe->page_restore_failed || page > 0) { ++ /* ++ * CMIS 5.3 8.2.15 Page Mapping ++ * ++ * For an arbitrary Page Address change or for just a Bank Index change, ++ * a host must write both BankSelect and PageSelect in one WRITE access, ++ * even if the PageSelect value does not change. ++ * The module does not begin processing the BankSelect value until after the PageSelect register has been written. ++ * ++ * For just a Page Index change (mapping another Page in the current Bank), ++ * or for mapping an arbitrary unbanked Page to Upper Memory, a host may WRITE only the PageSelect Byte. ++ */ ++ if (bank > 0) { ++ char buf[2] = {bank, page}; ++ unsigned int write_max_orig = optoe->write_max; ++ optoe->write_max = 2; /* ensure both bank and page are written together */ ++ ret = optoe_eeprom_write(optoe, client, buf, ++ OPTOE_BANK_SELECT_REG, 2); ++ optoe->write_max = write_max_orig; ++ } else { ++ ret = optoe_eeprom_write(optoe, client, &page, ++ OPTOE_PAGE_SELECT_REG, 1); ++ } + if (ret < 0) { + dev_dbg(&client->dev, +- "Write page register for page %d failed ret:%d!\n", +- page, ret); ++ "Write page register for bank %d, page %d failed ret:%d!\n", ++ bank, page, ret); + return ret; + } ++ optoe->page_restore_failed = false; + } + + while (count) { +@@ -553,13 +641,23 @@ static ssize_t optoe_eeprom_update_clien + + + if (page > 0) { +- /* return the page register to page 0 (why?) */ +- page = 0; +- ret = optoe_eeprom_write(optoe, client, &page, +- OPTOE_PAGE_SELECT_REG, 1); ++ if (bank > 0) { ++ char buf[2] = {0, 0}; ++ unsigned int write_max_orig = optoe->write_max; ++ optoe->write_max = 2; /* ensure both bank and page are written together */ ++ ret = optoe_eeprom_write(optoe, client, buf, ++ OPTOE_BANK_SELECT_REG, 2); ++ optoe->write_max = write_max_orig; ++ } else { ++ page = 0; ++ ret = optoe_eeprom_write(optoe, client, &page, ++ OPTOE_PAGE_SELECT_REG, 1); ++ } + if (ret < 0) { + dev_err(&client->dev, +- "Restore page register to 0 failed:%d!\n", ret); ++ "Restore bank, page register to (0, 0) failed:%d!\n", ret); ++ optoe->page_restore_failed = true; ++ + /* error only if nothing has been transferred */ + if (retval == 0) + retval = ret; +@@ -622,8 +720,10 @@ static ssize_t optoe_page_legal(struct o + /* if no pages needed, we're good */ + if ((off + len) <= ONE_ADDR_EEPROM_UNPAGED_SIZE) + return len; ++ ++ maxlen = one_addr_eeprom_size_with_bank(optoe->max_bank_size); + /* if offset exceeds possible pages, we're not good */ +- if (off >= ONE_ADDR_EEPROM_SIZE) ++ if (off >= maxlen) + return OPTOE_EOF; + /* in between, are pages supported? */ + status = optoe_eeprom_read(optoe, client, ®val, +@@ -665,7 +765,7 @@ static ssize_t optoe_page_legal(struct o + maxlen = ONE_ADDR_EEPROM_UNPAGED_SIZE - off; + } else { + /* Pages supported, trim len to the end of pages */ +- maxlen = ONE_ADDR_EEPROM_SIZE - off; ++ maxlen = maxlen - off; + } + len = (len > maxlen) ? maxlen : len; + dev_dbg(&client->dev, +@@ -993,6 +1093,51 @@ static ssize_t set_port_name(struct devi + return count; + } + ++static ssize_t show_max_bank_size(struct device *dev, ++ struct device_attribute *dattr, char *buf) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct optoe_data *optoe = i2c_get_clientdata(client); ++ ssize_t count; ++ ++ mutex_lock(&optoe->lock); ++ count = sprintf(buf, "%u\n", optoe->max_bank_size); ++ mutex_unlock(&optoe->lock); ++ ++ return count; ++} ++ ++static ssize_t set_max_bank_size(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct optoe_data *optoe = i2c_get_clientdata(client); ++ unsigned int max_bank_size; ++ ++ /* setting max bank size is only supported for the CMIS device */ ++ if (optoe->dev_class != CMIS_ADDR) { ++ return -EINVAL; ++ } ++ ++ if (kstrtouint(buf, 0, &max_bank_size) != 0) { ++ return -EINVAL; ++ } ++ ++ /* Re-register the sysfs eeprom bin file with the updated size so that ++ * the kernel inode size (used by sysfs_kf_bin_read) reflects the new ++ * accessible address space. ++ */ ++ sysfs_remove_bin_file(&client->dev.kobj, &optoe->bin); ++ mutex_lock(&optoe->lock); ++ optoe->max_bank_size = max_bank_size; ++ optoe->bin.size = one_addr_eeprom_size_with_bank(max_bank_size); ++ mutex_unlock(&optoe->lock); ++ sysfs_create_bin_file(&client->dev.kobj, &optoe->bin); ++ ++ return count; ++} ++ + static DEVICE_ATTR(port_name, 0644, show_port_name, set_port_name); + #endif /* if NOT defined EEPROM_CLASS, the common case */ + +@@ -1001,6 +1146,7 @@ static DEVICE_ATTR(write_timeout, 0644, + static DEVICE_ATTR(write_max, 0644, show_dev_write_max_size, + set_dev_write_max_size); + static DEVICE_ATTR(dev_class, 0644, show_dev_class, set_dev_class); ++static DEVICE_ATTR(max_bank_size, 0644, show_max_bank_size, set_max_bank_size); + + static struct attribute *optoe_attrs[] = { + #ifndef EEPROM_CLASS +@@ -1009,6 +1155,7 @@ static struct attribute *optoe_attrs[] = + &dev_attr_write_timeout.attr, + &dev_attr_write_max.attr, + &dev_attr_dev_class.attr, ++ &dev_attr_max_bank_size.attr, + NULL, + }; + +@@ -1107,7 +1254,7 @@ static int optoe_probe(struct i2c_client + } else if (strcmp(client->name, "optoe3") == 0) { + /* CMIS spec */ + optoe->dev_class = CMIS_ADDR; +- chip.byte_len = ONE_ADDR_EEPROM_SIZE; ++ chip.byte_len = one_addr_eeprom_size_with_bank(OPTOE_DEFAULT_BANK_SIZE); + num_addresses = 1; + } else { /* those were the only choices */ + err = -EINVAL; +@@ -1119,6 +1266,7 @@ static int optoe_probe(struct i2c_client + optoe->chip = chip; + optoe->num_addresses = num_addresses; + optoe->write_timeout = OPTOE_DEFAULT_WRITE_TIMEOUT; ++ optoe->max_bank_size = OPTOE_DEFAULT_BANK_SIZE; + memcpy(optoe->port_name, port_name, MAX_PORT_NAME_LEN); + + /* diff --git a/patch/driver-support-optoe-chunk-offset-fix.patch b/patches-sonic/driver-support-optoe-chunk-offset-fix.patch similarity index 98% rename from patch/driver-support-optoe-chunk-offset-fix.patch rename to patches-sonic/driver-support-optoe-chunk-offset-fix.patch index 252737cbe..4a8a70f2a 100644 --- a/patch/driver-support-optoe-chunk-offset-fix.patch +++ b/patches-sonic/driver-support-optoe-chunk-offset-fix.patch @@ -10,7 +10,7 @@ Subject: [PATCH] Correct a panic inducing defect which is triggered on a read 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c -index b3064f02b..c22b92a29 100644 +index bb33be2a2..f31c409ed 100644 --- a/drivers/misc/eeprom/optoe.c +++ b/drivers/misc/eeprom/optoe.c @@ -660,6 +660,7 @@ static ssize_t optoe_read_write(struct optoe_data *optoe, diff --git a/patches-sonic/driver-support-optoe-dynamic-write-timeout.patch b/patches-sonic/driver-support-optoe-dynamic-write-timeout.patch new file mode 100644 index 000000000..4a76b77ed --- /dev/null +++ b/patches-sonic/driver-support-optoe-dynamic-write-timeout.patch @@ -0,0 +1,129 @@ +From 4d5ae45bdd2623d65b6d3bac13c66663e24448c5 Mon Sep 17 00:00:00 2001 +From: Mihir Patel +Date: Fri, 15 Dec 2023 22:41:34 +0000 +Subject: [PATCH] Dynamic write timeout support for optoe driver + +The optoe kernel driver currently has a fixed 25ms write timeout value. We need to provide an interface to support overwriting the write_timeout value from userspace. +The overwriting of the write timeout value is needed for some non-standard optics which are not compliant with the timeout value in spec and will help in resolving issues related to EEPROM access. +The overwriting of the write_timeout value can be done based on platform or vendor of the transceiver. +Following sysfs will be created with this change +/sys/bus/i2c/devices/-0050/write_timeout + +Testing +root@sonic:/sys/bus/i2c/devices/31-0050# ls +dev_class driver eeprom modalias name port_name power subsystem uevent write_max write_timeout +root@sonic:/sys/bus/i2c/devices/31-0050# + +Signed-off-by: Mihir Patel +--- + ...driver-support-dynamic-write-timeout.patch | 105 ++++++++++++++++++ + 1 file changed, 105 insertions(+) + +diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c +index 394c19944..a54a9ee1b 100644 +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -194,6 +194,7 @@ struct optoe_data { + + u8 *writebuf; + unsigned int write_max; ++ unsigned int write_timeout; + + unsigned int num_addresses; + +@@ -223,7 +224,8 @@ static unsigned int io_limit = OPTOE_PAGE_SIZE; + * specs often allow 5 msec for a page write, sometimes 20 msec; + * it's important to recover from write timeouts. + */ +-static unsigned int write_timeout = 25; ++#define OPTOE_DEFAULT_WRITE_TIMEOUT 25 ++#define OPTOE_MAX_SUPPORTED_WRITE_TIMEOUT 500 + + /* + * flags to distinguish one-address (QSFP family) from two-address (SFP family) +@@ -352,7 +354,7 @@ static ssize_t optoe_eeprom_read(struct optoe_data *optoe, + * loop a few times until this one succeeds, waiting at least + * long enough for one entire page write to work. + */ +- timeout = jiffies + msecs_to_jiffies(write_timeout); ++ timeout = jiffies + msecs_to_jiffies(optoe->write_timeout); + do { + read_time = jiffies; + +@@ -453,7 +455,7 @@ static ssize_t optoe_eeprom_write(struct optoe_data *optoe, + * loop a few times until this one succeeds, waiting at least + * long enough for one entire page write to work. + */ +- timeout = jiffies + msecs_to_jiffies(write_timeout); ++ timeout = jiffies + msecs_to_jiffies(optoe->write_timeout); + do { + write_time = jiffies; + +@@ -855,6 +857,39 @@ static ssize_t set_dev_write_max_size(struct device *dev, + return count; + } + ++static ssize_t show_dev_write_timeout_size(struct device *dev, ++ struct device_attribute *dattr, char *buf) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct optoe_data *optoe = i2c_get_clientdata(client); ++ ssize_t count; ++ ++ mutex_lock(&optoe->lock); ++ count = sprintf(buf, "%u\n", optoe->write_timeout); ++ mutex_unlock(&optoe->lock); ++ ++ return count; ++} ++ ++static ssize_t set_dev_write_timeout_size(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct optoe_data *optoe = i2c_get_clientdata(client); ++ unsigned int write_timeout_size; ++ ++ if (kstrtouint(buf, 0, &write_timeout_size) != 0 || ++ write_timeout_size < 0 || write_timeout_size > OPTOE_MAX_SUPPORTED_WRITE_TIMEOUT) ++ return -EINVAL; ++ ++ mutex_lock(&optoe->lock); ++ optoe->write_timeout = write_timeout_size; ++ mutex_unlock(&optoe->lock); ++ ++ return count; ++} ++ + static ssize_t show_dev_class(struct device *dev, + struct device_attribute *dattr, char *buf) + { +@@ -961,6 +996,8 @@ static ssize_t set_port_name(struct device *dev, + static DEVICE_ATTR(port_name, 0644, show_port_name, set_port_name); + #endif /* if NOT defined EEPROM_CLASS, the common case */ + ++static DEVICE_ATTR(write_timeout, 0644, show_dev_write_timeout_size, ++ set_dev_write_timeout_size); + static DEVICE_ATTR(write_max, 0644, show_dev_write_max_size, + set_dev_write_max_size); + static DEVICE_ATTR(dev_class, 0644, show_dev_class, set_dev_class); +@@ -969,6 +1006,7 @@ static struct attribute *optoe_attrs[] = { + #ifndef EEPROM_CLASS + &dev_attr_port_name.attr, + #endif ++ &dev_attr_write_timeout.attr, + &dev_attr_write_max.attr, + &dev_attr_dev_class.attr, + NULL, +@@ -1079,6 +1117,7 @@ static int optoe_probe(struct i2c_client *client, + optoe->use_smbus = use_smbus; + optoe->chip = chip; + optoe->num_addresses = num_addresses; ++ optoe->write_timeout = OPTOE_DEFAULT_WRITE_TIMEOUT; + memcpy(optoe->port_name, port_name, MAX_PORT_NAME_LEN); + + /* +-- +2.25.1 + diff --git a/patches-sonic/driver-support-optoe-oneaddr-pageable.patch b/patches-sonic/driver-support-optoe-oneaddr-pageable.patch new file mode 100644 index 000000000..8e8696e3a --- /dev/null +++ b/patches-sonic/driver-support-optoe-oneaddr-pageable.patch @@ -0,0 +1,64 @@ +From cb4523bb02cb6228e3e8e5f3333e6af9a1b15466 Mon Sep 17 00:00:00 2001 +From: Mihir Patel +Date: Fri, 10 Mar 2023 06:52:27 +0000 +Subject: [PATCH] Read ID register to find pageable bit in optoe driver + +The current optoe driver looks at bit 2 for all optoe1 +(dev_class as ONE_ADDR) transceivers to detect if it's pageable or not. +However, for QSFP28 w/ CMIS optics, some platforms use it as optoe1 +and not optoe3. With CMIS, the pageable bit has now changed to bit 7 for +the same register. This causes incorrect behavior when the driver checks +for pageability on QSFP28 w/ CMIS transceiver and hence, we need to +read the transceiver ID to see if the transceiver is CMIS based and then +find the relevant pageable bit. + +Test result summary +Tested the changes on a switch with a 100G CMIS and non-CMIS transceiver + +No significant time difference is seen related to EEPROM read after adding +the current changes. Below stats were taken for a 100G CMIS based +transceiver with making it as optoe3 v/s optoe1 + +Test stats (average time taken after 3 dumps) +Time to dump first 4096B from EEPROM with transceiver as optoe3 - 914ms +Time to dump first 4096B from EEPROM with transceiver as optoe1 - 911ms + +Signed-off-by: Mihir Patel +--- + drivers/misc/eeprom/optoe.c | 20 +++++++++++++++++++- + 1 file changed, 19 insertions(+), 1 deletion(-) + +diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c +index 62294392c..f34bfe88b 100644 +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -630,7 +630,25 @@ static ssize_t optoe_page_legal(struct optoe_data *optoe, + return status; /* error out (no module?) */ + + if (optoe->dev_class == ONE_ADDR) { +- not_pageable = QSFP_NOT_PAGEABLE; ++ u8 idRegVal; ++ ++ status = optoe_eeprom_read(optoe, client, &idRegVal, ++ OPTOE_ID_REG, 1); ++ if (status < 0) ++ return status; /* error out (no module?) */ ++ ++ /* ++ * For QSFP28 with CMIS optic, if userspace has dev_class as ONE_ADDR, ++ * the driver looks at the incorrect bit to find if it is pageable. ++ * Below check ensures we read the appropriate bit for such QSFP28 CMIS ++ * compliant optics with dev_class as ONE_ADDR ++ * The ID values below are based on the SFF-8024 spec (Page 0, byte 0) ++ * for CMIS optics ++ */ ++ if (idRegVal == 0x18 || idRegVal == 0x19 || idRegVal == 0x1e) ++ not_pageable = CMIS_NOT_PAGEABLE; ++ else ++ not_pageable = QSFP_NOT_PAGEABLE; + } else { + not_pageable = CMIS_NOT_PAGEABLE; + } +-- +2.25.1 + diff --git a/patches-sonic/driver-support-optoe-twoaddr-a2h-access.patch b/patches-sonic/driver-support-optoe-twoaddr-a2h-access.patch new file mode 100644 index 000000000..e25a0665e --- /dev/null +++ b/patches-sonic/driver-support-optoe-twoaddr-a2h-access.patch @@ -0,0 +1,70 @@ +From 23a58d78c8c12b0092e1dfded96e6d648bffe1de Mon Sep 17 00:00:00 2001 +From: Jemston Fernando +Date: Thu, 25 Nov 2021 04:30:04 -0800 +Subject: [PATCH] A2h access for optoe2 even when DDM is unsupported + +Enable 0x51 (A2h) access for optoe2 even when DDM is unsupported, for +accessing vendor specific fields. Some SFP optics that do not support DDM +can have vendor specific implementation via 0x51 in A2h register space +(eg. Mailbox Registers). + +Removed the DDM support check for optoe2 (TWO_ADDR) and now its EEPROM +size is 512 bytes minimum. Return error when accessing beyond 256 bytes +for optics that do not support I2C address 0x51. + +Signed-off-by: Jemston Fernando +--- + drivers/misc/eeprom/optoe.c | 23 +++-------------------- + 1 file changed, 3 insertions(+), 20 deletions(-) + +diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c +index a7554e9..6229439 100644 +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -174,8 +174,6 @@ struct optoe_platform_data { + #define CMIS_NOT_PAGEABLE (1<<7) + #define TWO_ADDR_PAGEABLE_REG 0x40 + #define TWO_ADDR_PAGEABLE (1<<4) +-#define TWO_ADDR_0X51_REG 92 +-#define TWO_ADDR_0X51_SUPP (1<<6) + #define OPTOE_ID_REG 0 + #define OPTOE_READ_OP 0 + #define OPTOE_WRITE_OP 1 +@@ -593,8 +591,8 @@ static ssize_t optoe_page_legal(struct optoe_data *optoe, + return -EINVAL; + if (optoe->dev_class == TWO_ADDR) { + /* SFP case */ +- /* if only using addr 0x50 (first 256 bytes) we're good */ +- if ((off + len) <= TWO_ADDR_NO_0X51_SIZE) ++ /* if access is within addr 0x50 or first page of 0x51 (first 512 bytes) we're good */ ++ if ((off + len) <= TWO_ADDR_EEPROM_UNPAGED_SIZE) + return len; + /* if offset exceeds possible pages, we're not good */ + if (off >= TWO_ADDR_EEPROM_SIZE) +@@ -611,22 +609,7 @@ static ssize_t optoe_page_legal(struct optoe_data *optoe, + /* pages not supported, trim len to unpaged size */ + if (off >= TWO_ADDR_EEPROM_UNPAGED_SIZE) + return OPTOE_EOF; +- +- /* will be accessing addr 0x51, is that supported? */ +- /* byte 92, bit 6 implies DDM support, 0x51 support */ +- status = optoe_eeprom_read(optoe, client, ®val, +- TWO_ADDR_0X51_REG, 1); +- if (status < 0) +- return status; +- if (regval & TWO_ADDR_0X51_SUPP) { +- /* addr 0x51 is OK */ +- maxlen = TWO_ADDR_EEPROM_UNPAGED_SIZE - off; +- } else { +- /* addr 0x51 NOT supported, trim to 256 max */ +- if (off >= TWO_ADDR_NO_0X51_SIZE) +- return OPTOE_EOF; +- maxlen = TWO_ADDR_NO_0X51_SIZE - off; +- } ++ maxlen = TWO_ADDR_EEPROM_UNPAGED_SIZE - off; + } + len = (len > maxlen) ? maxlen : len; + dev_dbg(&client->dev, +-- +2.7.4 + diff --git a/patches-sonic/driver-support-optoe-update-to-linux-6.1.patch b/patches-sonic/driver-support-optoe-update-to-linux-6.1.patch new file mode 100644 index 000000000..582172ea3 --- /dev/null +++ b/patches-sonic/driver-support-optoe-update-to-linux-6.1.patch @@ -0,0 +1,30 @@ +Update optoe support for Linux 6.1 + +From: Saikrishna Arcot + + +--- + drivers/misc/eeprom/optoe.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c +index f34bfe88b..394c19944 100644 +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -802,7 +802,7 @@ static ssize_t optoe_bin_write(struct file *filp, struct kobject *kobj, + return optoe_read_write(optoe, buf, off, count, OPTOE_WRITE_OP); + } + +-static int optoe_remove(struct i2c_client *client) ++static void optoe_remove(struct i2c_client *client) + { + struct optoe_data *optoe; + int i; +@@ -820,7 +820,6 @@ static int optoe_remove(struct i2c_client *client) + + kfree(optoe->writebuf); + kfree(optoe); +- return 0; + } + + static ssize_t show_dev_write_max_size(struct device *dev, diff --git a/patches-sonic/driver-support-optoe-update-to-linux-6.10.patch b/patches-sonic/driver-support-optoe-update-to-linux-6.10.patch new file mode 100644 index 000000000..9acb0da5b --- /dev/null +++ b/patches-sonic/driver-support-optoe-update-to-linux-6.10.patch @@ -0,0 +1,28 @@ +Index: source_sonic/drivers/misc/eeprom/optoe.c +=================================================================== +--- source_sonic.orig/drivers/misc/eeprom/optoe.c ++++ source_sonic/drivers/misc/eeprom/optoe.c +@@ -1016,13 +1016,13 @@ static struct attribute_group optoe_attr + .attrs = optoe_attrs, + }; + +-static int optoe_probe(struct i2c_client *client, +- const struct i2c_device_id *id) ++static int optoe_probe(struct i2c_client *client) + { + int err; + int use_smbus = 0; + struct optoe_platform_data chip; + struct optoe_data *optoe; ++ const struct i2c_device_id *id; + int num_addresses = 0; + char port_name[MAX_PORT_NAME_LEN]; + +@@ -1045,6 +1045,7 @@ static int optoe_probe(struct i2c_client + "probe, chip provided, flags:0x%x; name: %s\n", + chip.flags, client->name); + } else { ++ id = i2c_match_id(optoe_ids, client); + if (!id->driver_data) { + err = -ENODEV; + goto exit; diff --git a/patches-sonic/driver-support-optoe-write-max.patch b/patches-sonic/driver-support-optoe-write-max.patch new file mode 100644 index 000000000..7cfe07eda --- /dev/null +++ b/patches-sonic/driver-support-optoe-write-max.patch @@ -0,0 +1,86 @@ +From ca9daf437f0910493a6eb8ced900662223ea3cf7 Mon Sep 17 00:00:00 2001 +From: Prince George +Date: Mon, 16 Aug 2021 16:04:15 +0000 +Subject: [PATCH] Dynamic write_max support for optoe driver + In current optoe driver, the value of write_max is hardcoded to one byte. + CMIS spec supports firmware upgrade on QSFP-DD transceivers which means + there is significant overhead due to this one byte write limit when a + platform wants to upgrade the firmware over an i2c bus at 100Khz. + The overhead is more pronounced when a platform has shared master and + platform needs to perform firmware upgrade on multiple transceivers. + + Added sysfs file 'write_max' for each i2c device (that use optoe driver) + so that the platform can override the default write max which is one byte. + + Test result:- + Firmware file of size 1.9MB takes ~ 33 mins with write-max size = 1 Byte + Firmware file of size 1.9MB takes ~ 8 mins with write-max size = 64 Bytes + +Signed-off-by: Prince George +--- + drivers/misc/eeprom/optoe.c | 36 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c +index 16287fdc5..cacfb1848 100644 +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -822,6 +822,39 @@ static int optoe_remove(struct i2c_client *client) + return 0; + } + ++static ssize_t show_dev_write_max_size(struct device *dev, ++ struct device_attribute *dattr, char *buf) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct optoe_data *optoe = i2c_get_clientdata(client); ++ ssize_t count; ++ ++ mutex_lock(&optoe->lock); ++ count = sprintf(buf, "%u\n", optoe->write_max); ++ mutex_unlock(&optoe->lock); ++ ++ return count; ++} ++ ++static ssize_t set_dev_write_max_size(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct optoe_data *optoe = i2c_get_clientdata(client); ++ int write_max_size; ++ ++ if (kstrtouint(buf, 0, &write_max_size) != 0 || ++ write_max_size < 1 || write_max_size > OPTOE_PAGE_SIZE) ++ return -EINVAL; ++ ++ mutex_lock(&optoe->lock); ++ optoe->write_max = write_max_size; ++ mutex_unlock(&optoe->lock); ++ ++ return count; ++} ++ + static ssize_t show_dev_class(struct device *dev, + struct device_attribute *dattr, char *buf) + { +@@ -928,12 +961,15 @@ static ssize_t set_port_name(struct device *dev, + static DEVICE_ATTR(port_name, 0644, show_port_name, set_port_name); + #endif /* if NOT defined EEPROM_CLASS, the common case */ + ++static DEVICE_ATTR(write_max, 0644, show_dev_write_max_size, ++ set_dev_write_max_size); + static DEVICE_ATTR(dev_class, 0644, show_dev_class, set_dev_class); + + static struct attribute *optoe_attrs[] = { + #ifndef EEPROM_CLASS + &dev_attr_port_name.attr, + #endif ++ &dev_attr_write_max.attr, + &dev_attr_dev_class.attr, + NULL, + }; +-- +2.17.1 + diff --git a/patches-sonic/driver-support-optoe-write-max_fix.patch b/patches-sonic/driver-support-optoe-write-max_fix.patch new file mode 100644 index 000000000..3071b0dd5 --- /dev/null +++ b/patches-sonic/driver-support-optoe-write-max_fix.patch @@ -0,0 +1,34 @@ +From f1250392a9dff619b147d8aee0c73c0f6d550713 Mon Sep 17 00:00:00 2001 +From: Prince George +Date: Thu, 13 Jun 2024 14:18:22 +0000 +Subject: [PATCH] Fix optoe's write_max when using native i2c driver + +Signed-off-by: Prince George +--- + drivers/misc/eeprom/optoe.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c +index 5a2e71be5..1efd18040 100644 +--- a/drivers/misc/eeprom/optoe.c ++++ b/drivers/misc/eeprom/optoe.c +@@ -1114,7 +1114,7 @@ static int optoe_probe(struct i2c_client *client, + goto exit; + } + +- dev_dbg(&client->dev, "dev_class: %d\n", optoe->dev_class); ++ dev_dbg(&client->dev, "dev_class: %d use_smbus=%d\n", optoe->dev_class, use_smbus); + optoe->use_smbus = use_smbus; + optoe->chip = chip; + optoe->num_addresses = num_addresses; +@@ -1157,7 +1157,7 @@ static int optoe_probe(struct i2c_client *client, + optoe->write_max = write_max; + + /* buffer (data + address at the beginning) */ +- optoe->writebuf = kmalloc(write_max + 2, GFP_KERNEL); ++ optoe->writebuf = kmalloc(OPTOE_PAGE_SIZE + 2, GFP_KERNEL); + if (!optoe->writebuf) { + err = -ENOMEM; + goto exit_kfree; +-- +2.25.1 diff --git a/patch/driver-support-optoe.patch b/patches-sonic/driver-support-optoe.patch similarity index 98% rename from patch/driver-support-optoe.patch rename to patches-sonic/driver-support-optoe.patch index 861286663..3d2dd38b3 100644 --- a/patch/driver-support-optoe.patch +++ b/patches-sonic/driver-support-optoe.patch @@ -18,10 +18,10 @@ Signed-off-by: Don Bollinger create mode 100644 drivers/misc/eeprom/optoe.c diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig -index 05bcf1e1f..9f444adae 100644 +index b1a9be835..795b05fbd 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig -@@ -124,4 +124,22 @@ config EEPROM_SFF_8436 +@@ -142,4 +142,22 @@ config EEPROM_SFF_8436 This driver can also be built as a module. If so, the module will be called sff_8436. @@ -45,17 +45,17 @@ index 05bcf1e1f..9f444adae 100644 + endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile -index 6a6d90ffe..80afa837e 100644 +index 647ffac5f..d95b4860d 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile -@@ -8,3 +8,4 @@ obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o - obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o +@@ -9,3 +9,4 @@ obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o + obj-$(CONFIG_EEPROM_EE1004) += ee1004.o obj-$(CONFIG_EEPROM_SFF_8436) += sff_8436_eeprom.o +obj-$(CONFIG_EEPROM_OPTOE) += optoe.o diff --git a/drivers/misc/eeprom/optoe.c b/drivers/misc/eeprom/optoe.c new file mode 100644 -index 000000000..7425bf60e +index 000000000..6813e46d7 --- /dev/null +++ b/drivers/misc/eeprom/optoe.c @@ -0,0 +1,1126 @@ @@ -1084,10 +1084,10 @@ index 000000000..7425bf60e + + /* SFF-8472 spec requires that the second I2C address be 0x51 */ + if (num_addresses == 2) { -+ optoe->client[1] = i2c_new_dummy(client->adapter, 0x51); -+ if (!optoe->client[1]) { ++ optoe->client[1] = i2c_new_dummy_device(client->adapter, 0x51); ++ if (IS_ERR(optoe->client[1])) { + dev_err(&client->dev, "address 0x51 unavailable\n"); -+ err = -EADDRINUSE; ++ err = PTR_ERR(optoe->client[1]); + goto err_struct; + } + } diff --git a/patch/driver-support-sff-8436-eeprom-update.patch b/patches-sonic/driver-support-sff-8436-eeprom-update.patch similarity index 93% rename from patch/driver-support-sff-8436-eeprom-update.patch rename to patches-sonic/driver-support-sff-8436-eeprom-update.patch index d2d7f6e49..1a2fcb964 100644 --- a/patch/driver-support-sff-8436-eeprom-update.patch +++ b/patches-sonic/driver-support-sff-8436-eeprom-update.patch @@ -71,12 +71,12 @@ index 0b6bf312e..f5627bf85 100644 (page ? "Upper" : "Lower"), (page ? (page-1) : page), page_offset, page_len, ret); } } -@@ -780,15 +777,13 @@ static ssize_t sff_8436_macc_write(struct memory_accessor *macc, const char *buf +@@ -780,21 +777,17 @@ static ssize_t sff_8436_macc_write(struct memory_accessor *macc, const char *buf /*-------------------------------------------------------------------------*/ -static int __devexit sff_8436_remove(struct i2c_client *client) -+static int sff_8436_remove(struct i2c_client *client) ++static void sff_8436_remove(struct i2c_client *client) { struct sff_8436_data *sff_8436; @@ -87,7 +87,14 @@ index 0b6bf312e..f5627bf85 100644 - kfree(sff_8436->writebuf); kfree(sff_8436); - return 0; +- return 0; + } +-static int sff_8436_eeprom_probe(struct i2c_client *client, +- const struct i2c_device_id *id) ++static int sff_8436_eeprom_probe(struct i2c_client *client) + { + int err; + int use_smbus = 0; @@ -821,7 +816,6 @@ static int sff_8436_eeprom_probe(struct i2c_client *client, chip.setup = NULL; diff --git a/patch/driver-support-sff-8436-eeprom.patch b/patches-sonic/driver-support-sff-8436-eeprom.patch similarity index 99% rename from patch/driver-support-sff-8436-eeprom.patch rename to patches-sonic/driver-support-sff-8436-eeprom.patch index bd8f40a93..8a6d3eb2d 100644 --- a/patch/driver-support-sff-8436-eeprom.patch +++ b/patches-sonic/driver-support-sff-8436-eeprom.patch @@ -13,12 +13,12 @@ From: Cumulus Networks create mode 100644 include/linux/i2c/sff-8436.h diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig -index d382b13c2..e3acc9b2a 100644 +index 0f791bfdc..c7d5cc8ad 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig -@@ -111,4 +111,16 @@ config EEPROM_IDT_89HPESX - This driver can also be built as a module. If so, the module - will be called idt_89hpesx. +@@ -129,4 +129,16 @@ config EEPROM_EE1004 + This driver can also be built as a module. If so, the module + will be called ee1004. +config EEPROM_SFF_8436 + tristate "SFF-8436 QSFP EEPROMs support" @@ -34,13 +34,13 @@ index d382b13c2..e3acc9b2a 100644 + endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile -index 2aab60ef3..6a6d90ffe 100644 +index a9b4b6579..647ffac5f 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile -@@ -7,3 +7,4 @@ obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o - obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o +@@ -8,3 +8,4 @@ obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o + obj-$(CONFIG_EEPROM_EE1004) += ee1004.o +obj-$(CONFIG_EEPROM_SFF_8436) += sff_8436_eeprom.o diff --git a/drivers/misc/eeprom/sff_8436_eeprom.c b/drivers/misc/eeprom/sff_8436_eeprom.c new file mode 100644 diff --git a/patch/driver-support-sff-8436-read-write-fix.patch b/patches-sonic/driver-support-sff-8436-read-write-fix.patch similarity index 100% rename from patch/driver-support-sff-8436-read-write-fix.patch rename to patches-sonic/driver-support-sff-8436-read-write-fix.patch diff --git a/patches-sonic/driver-tty-serial-8250-add-support-for-fintek-F81214E.patch b/patches-sonic/driver-tty-serial-8250-add-support-for-fintek-F81214E.patch new file mode 100644 index 000000000..6b37aefab --- /dev/null +++ b/patches-sonic/driver-tty-serial-8250-add-support-for-fintek-F81214E.patch @@ -0,0 +1,92 @@ +From c8f3ac729f827b655e0a239a3967b47c9dfce606 Mon Sep 17 00:00:00 2001 +From: Ravi Rama +Date: Fri, 13 Mar 2026 14:47:27 -0500 +Subject: [PATCH] serial: 8250_fintek: Add support for F81214E + +The F81214E is a LPC/eSPI to 2 UART Super I/O chip. + +Functionally, it is the same as the F81216E. The only difference +is that the F81216E has 4 UART ports, whereas the F81214E has 2 +UART ports. + +Signed-off-by: Ravi Rama +--- + drivers/tty/serial/8250/8250_fintek.c | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/drivers/tty/serial/8250/8250_fintek.c b/drivers/tty/serial/8250/8250_fintek.c +index b4461a89b8d0..976c5748905c 100644 +--- a/drivers/tty/serial/8250/8250_fintek.c ++++ b/drivers/tty/serial/8250/8250_fintek.c +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0 + /* +- * Probe for F81216A LPC to 4 UART ++ * Probe for F81216A LPC to 4 UART and F81214E LPC/eSPI to 2 UART + * + * Copyright (C) 2014-2016 Ricardo Ribalda, Qtechnology A/S + */ +@@ -23,6 +23,7 @@ + #define CHIP_ID_F81216AD 0x1602 + #define CHIP_ID_F81216E 0x1617 + #define CHIP_ID_F81216H 0x0501 ++#define CHIP_ID_F81214E 0x1417 + #define CHIP_ID_F81216 0x0802 + #define VENDOR_ID1 0x23 + #define VENDOR_ID1_VAL 0x19 +@@ -161,6 +162,7 @@ static int fintek_8250_check_id(struct fintek_8250 *pdata) + case CHIP_ID_F81216AD: + case CHIP_ID_F81216E: + case CHIP_ID_F81216H: ++ case CHIP_ID_F81214E: + case CHIP_ID_F81216: + break; + default: +@@ -185,6 +187,7 @@ static int fintek_8250_get_ldn_range(struct fintek_8250 *pdata, int *min, + case CHIP_ID_F81216AD: + case CHIP_ID_F81216E: + case CHIP_ID_F81216H: ++ case CHIP_ID_F81214E: + case CHIP_ID_F81216: + *min = F81216_LDN_LOW; + *max = F81216_LDN_HIGH; +@@ -255,6 +258,7 @@ static void fintek_8250_set_irq_mode(struct fintek_8250 *pdata, bool is_level) + case CHIP_ID_F81216AD: + case CHIP_ID_F81216E: + case CHIP_ID_F81216H: ++ case CHIP_ID_F81214E: + case CHIP_ID_F81216: + sio_write_mask_reg(pdata, FINTEK_IRQ_MODE, IRQ_SHARE, + IRQ_SHARE); +@@ -269,6 +273,7 @@ static void fintek_8250_set_max_fifo(struct fintek_8250 *pdata) + switch (pdata->pid) { + case CHIP_ID_F81216E: /* 128Bytes FIFO */ + case CHIP_ID_F81216H: ++ case CHIP_ID_F81214E: + case CHIP_ID_F81966: + case CHIP_ID_F81866: + sio_write_mask_reg(pdata, FIFO_CTRL, +@@ -304,6 +309,7 @@ static void fintek_8250_set_termios(struct uart_port *port, + switch (pdata->pid) { + case CHIP_ID_F81216E: + case CHIP_ID_F81216H: ++ case CHIP_ID_F81214E: + reg = RS485; + break; + case CHIP_ID_F81966: +@@ -354,6 +360,7 @@ static void fintek_8250_set_termios_handler(struct uart_8250_port *uart) + switch (pdata->pid) { + case CHIP_ID_F81216E: + case CHIP_ID_F81216H: ++ case CHIP_ID_F81214E: + case CHIP_ID_F81966: + case CHIP_ID_F81866: + uart->port.set_termios = fintek_8250_set_termios; +@@ -446,6 +453,7 @@ static void fintek_8250_set_rs485_handler(struct uart_8250_port *uart) + break; + + case CHIP_ID_F81216E: /* F81216E does not support RS485 delays */ ++ case CHIP_ID_F81214E: /* F81214E does not support RS485 delays */ + uart->port.rs485_config = fintek_8250_rs485_config; + uart->port.rs485_supported = fintek_8250_rs485_supported; + break; diff --git a/patch/kconfig-exclusions b/patches-sonic/kconfig-exclusions similarity index 64% rename from patch/kconfig-exclusions rename to patches-sonic/kconfig-exclusions index 6d4f0e622..756b4ab08 100644 --- a/patch/kconfig-exclusions +++ b/patches-sonic/kconfig-exclusions @@ -6,18 +6,39 @@ CONFIG_CGROUP_NET_CLASSID CONFIG_NET_CLS_CGROUP CONFIG_NETFILTER_XT_MATCH_CGROUP CONFIG_CGROUP_NET_PRIO +# Unset to enable UBIFS on flash +CONFIG_MTD_SPI_NOR_USE_4K_SECTORS +###-> mellanox_common-start +###-> mellanox_common-end +CONFIG_DRM +CONFIG_VIDEO_DEV +CONFIG_MEDIA_SUPPORT +CONFIG_MEDIA_CONTROLLER +CONFIG_DVB_CORE +CONFIG_MEDIA_TUNER +CONFIG_MEDIA_ATTACH [amd64] +CONFIG_AMD_MEM_ENCRYPT # Unset X86_PAT according to Broadcom's requirement CONFIG_X86_PAT CONFIG_MLXSW_PCI -CONFIG_THERMAL_STATISTICS +###-> mellanox_amd64-start +###-> mellanox_amd64-end [arm64] +CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU + +[marvell-prestera-arm64] +CONFIG_UBIFS_FS_ZSTD +CONFIG_CRYPTO_DEV_SAFEXCEL + +[pensando] +CONFIG_ARM64_VA_BITS_48 [armhf] -[marvell-armhf] +[marvell-prestera-armhf] CONFIG_ARCH_VIRT CONFIG_ARCH_BCM CONFIG_ARCH_EXYNOS @@ -44,3 +65,6 @@ CONFIG_EFI CONFIG_LOCK_DOWN_KERNEL CONFIG_SECONDARY_TRUSTED_KEYRING CONFIG_SYSTEM_BLACKLIST_KEYRING + +[mellanox-arm64] + diff --git a/patches-sonic/kconfig-force-inclusions b/patches-sonic/kconfig-force-inclusions new file mode 100644 index 000000000..e14876637 --- /dev/null +++ b/patches-sonic/kconfig-force-inclusions @@ -0,0 +1,3 @@ +[arm64] +CONFIG_COMPAT_VDSO=y +CONFIG_THUMB2_COMPAT_VDSO=y diff --git a/patches-sonic/kconfig-inclusions b/patches-sonic/kconfig-inclusions new file mode 100644 index 000000000..f36b82e1e --- /dev/null +++ b/patches-sonic/kconfig-inclusions @@ -0,0 +1,249 @@ +[common] +CONFIG_LOG_BUF_SHIFT=20 +CONFIG_RTC_INTF_DEV_UIE_EMUL=y +###-> mellanox_common-start +###-> mellanox_common-end + +[amd64] +CONFIG_SATA_MOBILE_LPM_POLICY=1 +# For Arista +CONFIG_I2C_MUX_PCA9541=m +CONFIG_SENSORS_MAX6697=m +#CONFIG_SENSORS_DPS1900=m # TODO: enable once the DPS1900 patch is re-enabled +CONFIG_PCI_REASSIGN_PREF_MEM=y +# For cig-cs6436 +CONFIG_SERIAL_8250_LPSS=m +# SFF_8436 +CONFIG_EEPROM_SFF_8436=m +# For Dell S6000 +CONFIG_SENSORS_MAX6620=m +CONFIG_PMBUS=m +CONFIG_SENSORS_PMBUS=m +#CONFIG_SENSORS_DNI_DPS460=m # TODO: enable once the DPS460 patch is re-enabled +CONFIG_I2C_MUX_GPIO=m +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SCH=m +# For Dell Z9100 +CONFIG_I2C_MUX_PCA954x=m +# For Ingrasys S9100 +CONFIG_I2C_GPIO=m +CONFIG_GPIO_PCA953X=m +# For Inventec d7032 +CONFIG_GPIO_ICH=m +# For mitac ly1200 +CONFIG_SENSORS_MAX31790=m +# For optoe +CONFIG_EEPROM_OPTOE=m +###-> mellanox_amd64-start +CONFIG_X86_AMD_PLATFORM_DEVICE=y +CONFIG_MCTP=y +CONFIG_DMI_SYSFS=y +CONFIG_OF=y +CONFIG_EEPROM_AT24=m +CONFIG_AMD_XGBE=m +CONFIG_IGB=m +CONFIG_MLXSW_CORE=m +CONFIG_SERIAL_8250_DW=y +CONFIG_HW_RANDOM_AMD=m +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX=m +CONFIG_I2C_SMBUS=m +CONFIG_I2C_I801=m +CONFIG_I2C_DESIGNWARE_PCI=m +CONFIG_I2C_MLXCPLD=m +CONFIG_I2C_SLAVE=y +CONFIG_PINCTRL=y +CONFIG_GPIOLIB=y +CONFIG_SENSORS_K10TEMP=m +CONFIG_SENSORS_DRIVETEMP=m +CONFIG_SENSORS_CORETEMP=m +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_POWR1220=m +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_EMC1403=m +CONFIG_SENSORS_EMC2305=m +CONFIG_SENSORS_STTS751=m +CONFIG_SENSORS_TMP102=m +CONFIG_SENSORS_TMP421=m +CONFIG_THERMAL_NETLINK=y +CONFIG_INTEL_PCH_THERMAL=m +CONFIG_WATCHDOG_SYSFS=y +CONFIG_LPC_ICH=m +CONFIG_MFD_INTEL_LPSS_PCI=y +CONFIG_LEDS_MLXREG=m +CONFIG_LEDS_TRIGGER_TIMER=m +CONFIG_DW_DMAC_PCI=y +CONFIG_MELLANOX_PLATFORM=y +CONFIG_MLX_PLATFORM=m +CONFIG_NET_DEVLINK=y +CONFIG_I2C_MUX_REG=m +CONFIG_I2C_MUX_MLXCPLD=m +CONFIG_REGMAP_I2C=m +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_NVME_HWMON=y +CONFIG_MLXSW_CORE_HWMON=y +CONFIG_MLXSW_I2C=m +CONFIG_MLXSW_MINIMAL=m +CONFIG_MLXSW_CORE_THERMAL=n +CONFIG_MLXREG_HOTPLUG=m +CONFIG_MLXREG_IO=m +CONFIG_MLX_WDT=m +CONFIG_MLXREG_LC=m +#CONFIG_MLXREG_DPU=m +CONFIG_SENSORS_MLXREG_FAN=m +CONFIG_GPIO_GENERIC=m +CONFIG_MAX1363=m +CONFIG_SENSORS_TPS53679=m +CONFIG_SENSORS_XDPE122=m +CONFIG_SENSORS_MP2975=m +#CONFIG_SENSORS_MP2855=m +CONFIG_SENSORS_MP2888=m +CONFIG_SENSORS_MP2891=m +CONFIG_CPU_THERMAL=y +CONFIG_IGB_HWMON=y +CONFIG_MFD_CORE=y +CONFIG_MFD_INTEL_LPSS=y +CONFIG_TI_ADS1015=m +CONFIG_PINCTRL_INTEL=y +CONFIG_PINCTRL_CANNONLAKE=m +CONFIG_PINCTRL_DENVERTON=m +CONFIG_NVSW_SN2201=m +CONFIG_I2C_DESIGNWARE_PLATFORM=m +CONFIG_I2C_DESIGNWARE_BAYTRAIL=y +CONFIG_I2C_DESIGNWARE_CORE=m +CONFIG_SPI_PXA2XX=m +CONFIG_SENSORS_XDPE152=m +CONFIG_SENSORS_IIO_HWMON=m +CONFIG_SENSORS_LM25066=m +CONFIG_SENSORS_UCD9000=m +CONFIG_SENSORS_UCD9200=m +CONFIG_THERMAL_OF=y +CONFIG_MCTP_FLOWS=y +CONFIG_MCTP_TRANSPORT_I2C=m +CONFIG_I2C_SLAVE_EEPROM=m +#CONFIG_I2C_ASF=m +CONFIG_SPI_AMD=m +CONFIG_PINCTRL_AMD=y +CONFIG_EDAC_AMD64=m +CONFIG_AMD_XGBE_DCB=y +CONFIG_SENSORS_ISL68137=m +###-> mellanox_amd64-end +# For Cisco 8000 +CONFIG_PHYLIB=m +CONFIG_OF_GPIO=y +CONFIG_OF_MDIO=m +CONFIG_MDIO_BUS_MUX_GPIO=m +CONFIG_MDIO_BUS_MUX=m +CONFIG_REGMAP_MMIO=y +CONFIG_TI_TLC4541=m +CONFIG_SENSORS_LTC2978=m +CONFIG_SENSORS_LTC2945=m +CONFIG_SENSORS_TPS40422=m +CONFIG_SENSORS_ADM1275=m +CONFIG_SPI_INTEL_PLATFORM=m +CONFIG_SPI_INTEL=m +# For pensando +CONFIG_IONIC=m +# For AMD Ryzen Embedded processors +CONFIG_PCI_ENDPOINT=y +CONFIG_PCIE_DW=y +CONFIG_PCIE_DW_HOST=y +CONFIG_PCIE_DW_EP=y +CONFIG_PCIE_DW_PLAT=y +CONFIG_PCIE_DW_PLAT_HOST=y +CONFIG_PCIE_DW_PLAT_EP=y +# For Micas +CONFIG_MDIO_BITBANG=m +CONFIG_MDIO_GPIO=m +CONFIG_SPI_GPIO=m +CONFIG_SENSORS_INA3221=m +# For Nokia +CONFIG_RTC_DRV_DS1307=m +# For Nexthop +CONFIG_SENSORS_Q54SJ108A2=m + +[arm64] +# For enabling SDEI +CONFIG_ARM_SDE_INTERFACE=y +# For Marvell +CONFIG_EEPROM_SFF_8436=m +CONFIG_EEPROM_OPTOE=m +CONFIG_I2C_MUX_GPIO=m +CONFIG_MTD_RAW_NAND=m +CONFIG_MTD_NAND_ECC_SW_BCH=y +CONFIG_MTD_NAND_MARVELL=m +CONFIG_SENSORS_EMC2305=m +CONFIG_PHY_MVEBU_CP110_COMPHY=y +CONFIG_SPI_ORION=m +CONFIG_ARM_SMC_WATCHDOG=m +# For pensando +CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_EMBEDDED=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_GPIO_DWAPB=m +CONFIG_IP_PNP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IRQSOFF_TRACER=y +CONFIG_KALLSYMS_ALL=y +CONFIG_TRACER_SNAPSHOT_PER_CPU_SWAP=y +CONFIG_SCHED_TRACER=y +CONFIG_MEMTEST=y +CONFIG_MMC_DEBUG=y +CONFIG_MMC_SDHCI_CADENCE=m +CONFIG_PMBUS=m +CONFIG_SENSORS_PMBUS=m +CONFIG_SENSORS_LTC2978=m +CONFIG_SENSORS_TPS53679=m +CONFIG_RTC_DRV_PCF85363=m +CONFIG_RTC_CLASS=y +CONFIG_SPI_CADENCE_QUADSPI=m +CONFIG_SPI_DESIGNWARE=m +CONFIG_SPI_DW_MMIO=m +CONFIG_SQUASHFS_DECOMP_MULTI=y +CONFIG_PREEMPTIRQ_TRACEPOINTS=y +CONFIG_RING_BUFFER_ALLOW_SWAP=y +CONFIG_KEXEC_FILE=y + +[marvell-prestera-arm64] + +[pensando] +CONFIG_ARCH_PENSANDO=y +CONFIG_ARCH_PENSANDO_ELBA_SOC=y +CONFIG_PENSANDO_SOC_PENFW=y +CONFIG_EDAC_ELBA=y +CONFIG_IRQ_PENSANDO=y +CONFIG_MFD_PENSANDO_ELBASR=y +CONFIG_RESET_ELBASR=y +CONFIG_UIO_PCIEMAC=m +CONFIG_UIO_PENMSI1=m +CONFIG_UIO_PENGIC=m +CONFIG_UIO_PENMSI=m +CONFIG_ARM64_VA_BITS_39=y +CONFIG_COMMON_CLK_CS2000_CP=y +CONFIG_I2C_RD1173=y +CONFIG_CONTEXT_TRACKING=y +CONFIG_NO_HZ_FULL=y +CONFIG_RCU_NOCB_CPU=y +CONFIG_VIRT_CPU_ACCOUNTING=y +CONFIG_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_RTC_DRV_ELBA_CPLD=y + +[armhf] +CONFIG_EEPROM_SFF_8436=m +CONFIG_EEPROM_OPTOE=m +CONFIG_I2C_MUX_GPIO=m + +[marvell-prestera-armhf] +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_OF_PARTS=y + +[nvidia-bluefield] +CONFIG_MMC_DW=y +CONFIG_MMC_DW_PLTFM=y +CONFIG_MMC_DW_BLUEFIELD=y +CONFIG_EFIVAR_FS=y + +[mellanox-arm64] + diff --git a/patches-sonic/kconfig-secure-boot-exclusions b/patches-sonic/kconfig-secure-boot-exclusions new file mode 100644 index 000000000..68fb8c58f --- /dev/null +++ b/patches-sonic/kconfig-secure-boot-exclusions @@ -0,0 +1,20 @@ +[common] + +[amd64] +CONFIG_MODULE_SIG_SHA256 +# For mellanox +CONFIG_SECURITY_LOCKDOWN_LSM +CONFIG_SECURITY_LOCKDOWN_LSM_EARLY +CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE +CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT + +[arm64] +CONFIG_MODULE_SIG_SHA256 +# For mellanox +CONFIG_SECURITY_LOCKDOWN_LSM +CONFIG_SECURITY_LOCKDOWN_LSM_EARLY +CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE +CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT + +[armhf] + diff --git a/patches-sonic/kconfig-secure-boot-inclusions b/patches-sonic/kconfig-secure-boot-inclusions new file mode 100644 index 000000000..cfd9e900c --- /dev/null +++ b/patches-sonic/kconfig-secure-boot-inclusions @@ -0,0 +1,18 @@ +[common] + +[amd64] +CONFIG_SYSTEM_TRUSTED_KEYS="debian/certs/debian-uefi-certs.pem" +CONFIG_MODULE_SIG_HASH="sha512" +CONFIG_MODULE_SIG_SHA512=y +CONFIG_KEXEC_SIG_FORCE=y +CONFIG_MODULE_SIG_FORCE=y + +[arm64] +CONFIG_SYSTEM_TRUSTED_KEYS="debian/certs/debian-uefi-certs.pem" +CONFIG_MODULE_SIG_HASH="sha512" +CONFIG_MODULE_SIG_SHA512=y +CONFIG_KEXEC_SIG=y +CONFIG_MODULE_SIG_FORCE=y + +[armhf] + diff --git a/patch/kernel-sched-core-fix-cgroup-fork-race.patch b/patches-sonic/kernel-sched-core-fix-cgroup-fork-race.patch similarity index 100% rename from patch/kernel-sched-core-fix-cgroup-fork-race.patch rename to patches-sonic/kernel-sched-core-fix-cgroup-fork-race.patch diff --git a/patches-sonic/micas-m2-w6950-128oc-dts.patch b/patches-sonic/micas-m2-w6950-128oc-dts.patch new file mode 100644 index 000000000..ae203ed65 --- /dev/null +++ b/patches-sonic/micas-m2-w6950-128oc-dts.patch @@ -0,0 +1,2573 @@ +diff --git a/arch/arm64/boot/dts/aspeed/Makefile b/arch/arm64/boot/dts/aspeed/Makefile +index 410778816..0460964 100644 +--- a/arch/arm64/boot/dts/aspeed/Makefile ++++ b/arch/arm64/boot/dts/aspeed/Makefile +@@ -34,2 +34,3 @@ dtb-$(CONFIG_ARCH_ASPEED) += \ + aspeed-nvidia-spc6-a1-bmc.dtb \ +- aspeed-nvidia-spc6-bmc.dtb ++ aspeed-nvidia-spc6-bmc.dtb \ ++ micas-m2-w6950-128oc.dtb +diff --git a/arch/arm64/boot/dts/aspeed/micas-m2-w6950-128oc.dts b/arch/arm64/boot/dts/aspeed/micas-m2-w6950-128oc.dts +new file mode 100644 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/micas-m2-w6950-128oc.dts +@@ -0,0 +1,2522 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++#include ++#include ++ ++#define DUAL_NODE 0 // 1: DUAL_NODE, 0: SINGLE_NODE ++#define PCIE0_EP 1 // 1: EP, 0: RC ++#define PCIE1_EP 0 // 1: EP, 0: RC ++#define PCIE2_RC 0 // 1: RC, 0: SGMII ++ ++/ { ++ model = "Micas M2-W6950-128OC BMC"; ++ compatible = "micas,m2-w6950-128oc", "aspeed,ast2700"; ++ ++ aliases { ++ spi4 = &spi_gpio; /* 53134 */ ++ spi6 = &spi_gpio1; /* MGMT CPLD */ ++ i2c401 = &fpga0_i2c0; ++ i2c402 = &fpga0_i2c1; ++ i2c403 = &fpga0_i2c2; ++ i2c404 = &fpga0_i2c3; ++ i2c405 = &fpga0_i2c4; ++ i2c406 = &fpga0_i2c5; ++ i2c407 = &fpga0_i2c6; ++ i2c408 = &fpga0_i2c7; ++ i2c409 = &fpga0_i2c8; ++ i2c410 = &fpga0_i2c9; ++ i2c427 = &fpga1_i2c0; ++ i2c436 = &fpga2_i2c0; ++ i2c445 = &cpld_i2c0; ++ }; ++ ++ chosen { ++ stdout-path = "serial12:115200n8"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ video_engine_memory0: video0 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ video_engine_memory1: video1 { ++ size = <0x0 0x02000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++#if 0 ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++#endif ++ ++ espi0_mcyc_memory: mcyc0 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ }; ++ ++#if 0 ++ fan0: pwm-fan0 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 0 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan1: pwm-fan1 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 1 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan2: pwm-fan2 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 2 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan3: pwm-fan3 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 3 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan4: pwm-fan4 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 4 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan5: pwm-fan5 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 5 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan6: pwm-fan6 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 6 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan7: pwm-fan7 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 7 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ fan8: pwm-fan8 { ++ compatible = "pwm-fan"; ++ pwms = <&pwm_tach 8 40000 0>; /* Target freq:25 kHz */ ++ cooling-min-state = <0>; ++ cooling-max-state = <3>; ++ #cooling-cells = <2>; ++ cooling-levels = <0 15 128 255>; ++ }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++#endif ++ ++ /* mac fpga i2c master */ ++ fpga0_i2c0: fpga0-i2c0 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2000>; ++ i2c_filter = <0x2004>; ++ i2c_stretch = <0x2008>; ++ i2c_ext_9548_exits_flag = <0x200c>; ++ i2c_ext_9548_addr = <0x2010>; ++ i2c_ext_9548_chan = <0x2014>; ++ i2c_in_9548_chan = <0x2018>; ++ i2c_slave = <0x201c>; ++ i2c_reg = <0x2020>; ++ i2c_reg_len = <0x2030>; ++ i2c_data_len = <0x2034>; ++ i2c_ctrl = <0x2038>; ++ i2c_status = <0x203c>; ++ i2c_err_vec = <0x2048>; ++ i2c_data_buf = <0x2100>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x207c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; /* delay time before reset(us) */ ++ i2c_rst_delay = <1>; /* reset time(us) */ ++ i2c_rst_delay_a = <1>; /* delay time after reset(us) */ ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <451>; ++ fpga_9548_flag = <1>; /* 1: internal 9548 2: external 9548 */ ++ fpga_9548_reset_flag = <0>; /* 1: reset, 0: no bit */ ++ i2c@0 { ++ reg = <0>; ++ pca9548@77 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x77>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <51>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ pca9548@75 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x75>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <59>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c1: fpga0-i2c1 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2200>; ++ i2c_filter = <0x2204>; ++ i2c_stretch = <0x2208>; ++ i2c_ext_9548_exits_flag = <0x220c>; ++ i2c_ext_9548_addr = <0x2210>; ++ i2c_ext_9548_chan = <0x2214>; ++ i2c_in_9548_chan = <0x2218>; ++ i2c_slave = <0x221c>; ++ i2c_reg = <0x2220>; ++ i2c_reg_len = <0x2230>; ++ i2c_data_len = <0x2234>; ++ i2c_ctrl = <0x2238>; ++ i2c_status = <0x223c>; ++ i2c_err_vec = <0x2248>; ++ i2c_data_buf = <0x2300>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x227c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <452>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@76 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x76>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <67>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c2: fpga0-i2c2 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2400>; ++ i2c_filter = <0x2404>; ++ i2c_stretch = <0x2408>; ++ i2c_ext_9548_exits_flag = <0x240c>; ++ i2c_ext_9548_addr = <0x2410>; ++ i2c_ext_9548_chan = <0x2414>; ++ i2c_in_9548_chan = <0x2418>; ++ i2c_slave = <0x241c>; ++ i2c_reg = <0x2420>; ++ i2c_reg_len = <0x2430>; ++ i2c_data_len = <0x2434>; ++ i2c_ctrl = <0x2438>; ++ i2c_status = <0x243c>; ++ i2c_err_vec = <0x2448>; ++ i2c_data_buf = <0x2500>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x247c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <453>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@77 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x77>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <75>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c3: fpga0-i2c3 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2600>; ++ i2c_filter = <0x2604>; ++ i2c_stretch = <0x2608>; ++ i2c_ext_9548_exits_flag = <0x260c>; ++ i2c_ext_9548_addr = <0x2610>; ++ i2c_ext_9548_chan = <0x2614>; ++ i2c_in_9548_chan = <0x2618>; ++ i2c_slave = <0x261c>; ++ i2c_reg = <0x2620>; ++ i2c_reg_len = <0x2630>; ++ i2c_data_len = <0x2634>; ++ i2c_ctrl = <0x2638>; ++ i2c_status = <0x263c>; ++ i2c_err_vec = <0x2648>; ++ i2c_data_buf = <0x2700>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x267c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <454>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@77 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x77>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <83>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c4: fpga0-i2c4 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2800>; ++ i2c_filter = <0x2804>; ++ i2c_stretch = <0x2808>; ++ i2c_ext_9548_exits_flag = <0x280c>; ++ i2c_ext_9548_addr = <0x2810>; ++ i2c_ext_9548_chan = <0x2814>; ++ i2c_in_9548_chan = <0x2818>; ++ i2c_slave = <0x281c>; ++ i2c_reg = <0x2820>; ++ i2c_reg_len = <0x2830>; ++ i2c_data_len = <0x2834>; ++ i2c_ctrl = <0x2838>; ++ i2c_status = <0x283c>; ++ i2c_err_vec = <0x2848>; ++ i2c_data_buf = <0x2900>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x287c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <455>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@71 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x71>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <91>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ pca9548@72 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x72>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <99>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c5: fpga0-i2c5 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2a00>; ++ i2c_filter = <0x2a04>; ++ i2c_stretch = <0x2a08>; ++ i2c_ext_9548_exits_flag = <0x2a0c>; ++ i2c_ext_9548_addr = <0x2a10>; ++ i2c_ext_9548_chan = <0x2a14>; ++ i2c_in_9548_chan = <0x2a18>; ++ i2c_slave = <0x2a1c>; ++ i2c_reg = <0x2a20>; ++ i2c_reg_len = <0x2a30>; ++ i2c_data_len = <0x2a34>; ++ i2c_ctrl = <0x2a38>; ++ i2c_status = <0x2a3c>; ++ i2c_err_vec = <0x2a48>; ++ i2c_data_buf = <0x2b00>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x2a7c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <456>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@71 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x71>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <107>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c6: fpga0-i2c6 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2c00>; ++ i2c_filter = <0x2c04>; ++ i2c_stretch = <0x2c08>; ++ i2c_ext_9548_exits_flag = <0x2c0c>; ++ i2c_ext_9548_addr = <0x2c10>; ++ i2c_ext_9548_chan = <0x2c14>; ++ i2c_in_9548_chan = <0x2c18>; ++ i2c_slave = <0x2c1c>; ++ i2c_reg = <0x2c20>; ++ i2c_reg_len = <0x2c30>; ++ i2c_data_len = <0x2c34>; ++ i2c_ctrl = <0x2c38>; ++ i2c_status = <0x2c3c>; ++ i2c_err_vec = <0x2c48>; ++ i2c_data_buf = <0x2d00>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x2c7c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <457>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@72 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x72>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <115>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c7: fpga0-i2c7 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x2e00>; ++ i2c_filter = <0x2e04>; ++ i2c_stretch = <0x2e08>; ++ i2c_ext_9548_exits_flag = <0x2e0c>; ++ i2c_ext_9548_addr = <0x2e10>; ++ i2c_ext_9548_chan = <0x2e14>; ++ i2c_in_9548_chan = <0x2e18>; ++ i2c_slave = <0x2e1c>; ++ i2c_reg = <0x2e20>; ++ i2c_reg_len = <0x2e30>; ++ i2c_data_len = <0x2e34>; ++ i2c_ctrl = <0x2e38>; ++ i2c_status = <0x2e3c>; ++ i2c_err_vec = <0x2e48>; ++ i2c_data_buf = <0x2f00>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x2e7c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <458>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@73 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x73>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <123>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c8: fpga0-i2c8 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x3000>; ++ i2c_filter = <0x3004>; ++ i2c_stretch = <0x3008>; ++ i2c_ext_9548_exits_flag = <0x300c>; ++ i2c_ext_9548_addr = <0x3010>; ++ i2c_ext_9548_chan = <0x3014>; ++ i2c_in_9548_chan = <0x3018>; ++ i2c_slave = <0x301c>; ++ i2c_reg = <0x3020>; ++ i2c_reg_len = <0x3030>; ++ i2c_data_len = <0x3034>; ++ i2c_ctrl = <0x3038>; ++ i2c_status = <0x303c>; ++ i2c_err_vec = <0x3048>; ++ i2c_data_buf = <0x3100>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x307c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9541"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <459>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ i2c@0 { ++ reg = <0>; ++ pca9548@74 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x74>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <131>; ++ fpga_9548_flag = <2>; ++ fpga_9548_reset_flag = <1>; ++ }; ++ }; ++ }; ++ }; ++ ++ fpga0_i2c9: fpga0-i2c9 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x3200>; ++ i2c_filter = <0x3204>; ++ i2c_stretch = <0x3208>; ++ i2c_ext_9548_exits_flag = <0x320c>; ++ i2c_ext_9548_addr = <0x3210>; ++ i2c_ext_9548_chan = <0x3214>; ++ i2c_in_9548_chan = <0x3218>; ++ i2c_slave = <0x321c>; ++ i2c_reg = <0x3220>; ++ i2c_reg_len = <0x3230>; ++ i2c_data_len = <0x3234>; ++ i2c_ctrl = <0x3238>; ++ i2c_status = <0x323c>; ++ i2c_err_vec = <0x3248>; ++ i2c_data_buf = <0x3300>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x327c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <139>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ }; ++ }; ++ ++ /* uport fpga i2c master */ ++ fpga1_i2c0: fpga1-i2c0 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x42000>; ++ i2c_filter = <0x42004>; ++ i2c_stretch = <0x42008>; ++ i2c_ext_9548_exits_flag = <0x4200c>; ++ i2c_ext_9548_addr = <0x42010>; ++ i2c_ext_9548_chan = <0x42014>; ++ i2c_in_9548_chan = <0x42018>; ++ i2c_slave = <0x4201c>; ++ i2c_reg = <0x42020>; ++ i2c_reg_len = <0x42030>; ++ i2c_data_len = <0x42034>; ++ i2c_ctrl = <0x42038>; ++ i2c_status = <0x4203c>; ++ i2c_err_vec = <0x42048>; ++ i2c_data_buf = <0x42100>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x4207c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <147>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ }; ++ }; ++ ++ /* dport fpga i2c master */ ++ fpga2_i2c0: fpga2-i2c0 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x82000>; ++ i2c_filter = <0x82004>; ++ i2c_stretch = <0x82008>; ++ i2c_ext_9548_exits_flag = <0x8200c>; ++ i2c_ext_9548_addr = <0x82010>; ++ i2c_ext_9548_chan = <0x82014>; ++ i2c_in_9548_chan = <0x82018>; ++ i2c_slave = <0x8201c>; ++ i2c_reg = <0x82020>; ++ i2c_reg_len = <0x82030>; ++ i2c_data_len = <0x82034>; ++ i2c_ctrl = <0x82038>; ++ i2c_status = <0x8203c>; ++ i2c_err_vec = <0x82048>; ++ i2c_data_buf = <0x82100>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/fpga0"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x8207c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; ++ i2c_rst_delay = <1>; ++ i2c_rst_delay_a = <1>; ++ pca9548@70 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <155>; ++ fpga_9548_flag = <1>; ++ fpga_9548_reset_flag = <0>; ++ }; ++ }; ++ ++ /* CPU_CPLD */ ++ firmware_cpld0 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <1>; ++ chip_index = <1>; ++ }; ++ ++ /* MGMT_CPLD */ ++ firmware_cpld1 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <2>; ++ chip_index = <1>; ++ }; ++ ++ /* MAC_CPLD_AB */ ++ firmware_cpld2 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <3>; ++ chip_index = <1>; ++ }; ++ ++ /* MAC_CPLD_C */ ++ firmware_cpld3 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <4>; ++ chip_index = <1>; ++ }; ++ ++ /* UPORT_CPLD */ ++ firmware_cpld4 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <5>; ++ chip_index = <1>; ++ }; ++ ++ /* DPORT_CPLD */ ++ firmware_cpld5 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <6>; ++ chip_index = <1>; ++ }; ++ ++ /* UFAN_CPLD */ ++ firmware_cpld6 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <7>; ++ chip_index = <1>; ++ }; ++ ++ /* DFAN_CPLD */ ++ firmware_cpld7 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <8>; ++ chip_index = <1>; ++ }; ++ ++ /* MAC_CPLDD */ ++ firmware_cpld8 { ++ compatible = "firmware_cpld_ispvme"; ++ type = "JTAG"; ++ tdi = <713>; ++ tck = <710>; ++ tms = <711>; ++ tdo = <712>; ++ chain = <9>; ++ chip_index = <1>; ++ }; ++ ++ /* MAC FPGA upgrade link */ ++ firmware_mac_fpga_up { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <1>; ++ chip_index = <1>; ++ mtd_name = "spi5.0"; ++ flash_base = <0x2f0000>; ++ test_base = <0x7F0000>; ++ test_size = <0x10000>; ++ }; ++ ++ /* MAC FPGA upgrade link */ ++ firmware_mac_fpga_shaopian { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <2>; ++ chip_index = <1>; ++ mtd_name = "spi5.0"; ++ flash_base = <0x0>; ++ test_base = <0x7F0000>; ++ test_size = <0x10000>; ++ }; ++ ++ /* UPORT FPGA upgrade link */ ++ firmware_uport_fpga_up { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <3>; ++ chip_index = <1>; ++ mtd_name = "spi5.0"; ++ flash_base = <0x2f0000>; ++ test_base = <0x7F0000>; ++ test_size = <0x10000>; ++ }; ++ ++ /* UPORT FPGA upgrade link */ ++ firmware_uport_fpga_shaopian { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <4>; ++ chip_index = <1>; ++ mtd_name = "spi5.0"; ++ flash_base = <0x0>; ++ test_base = <0x7F0000>; ++ test_size = <0x10000>; ++ }; ++ ++ /* DPORT FPGA upgrade link */ ++ firmware_dport_fpga_up { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <5>; ++ chip_index = <1>; ++ mtd_name = "spi5.0"; ++ flash_base = <0x2f0000>; ++ test_base = <0x7F0000>; ++ test_size = <0x10000>; ++ }; ++ ++ /* DPORT FPGA upgrade link */ ++ firmware_dport_fpga_shaopian { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <6>; ++ chip_index = <1>; ++ mtd_name = "spi5.0"; ++ flash_base = <0x0>; ++ test_base = <0x7F0000>; ++ test_size = <0x10000>; ++ }; ++ ++ /* for 53134 firmware upgrade */ ++ spi_gpio: spi-gpio { ++ compatible = "spi-gpio"; ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ num-chipselects = <1>; ++ ++ cs-gpios = <&gpio1 ASPEED_GPIO(P, 0) GPIO_ACTIVE_HIGH>; ++ gpio-sck = <&gpio1 ASPEED_GPIO(P, 1) GPIO_ACTIVE_HIGH>; ++ gpio-mosi = <&gpio1 ASPEED_GPIO(P, 2) GPIO_ACTIVE_HIGH>; ++ gpio-miso = <&gpio1 ASPEED_GPIO(P, 3) GPIO_ACTIVE_HIGH>; ++ }; ++ ++ /* 53134 upgrade */ ++ firmware_bcm53134 { ++ compatible = "firmware_sysfs"; ++ type = "SYSFS"; ++ chain = <8>; ++ chip_index = <1>; ++ sysfs_name = "/sys/bus/spi/devices/spi4.0/eeprom"; ++ en_gpio_0 = <555>; ++ en_level_0 = <0>; ++ en_logic_dev_0 = "/dev/cpld1"; ++ en_logic_addr_0 = <0x54>; ++ en_logic_mask_0 = <0xfc>; ++ en_logic_en_val_0 = <0x2>; ++ en_logic_dis_val_0 = <0x0>; ++ en_logic_width_0 = <1>; ++ en_logic_dev_1 = "/dev/cpld1"; ++ en_logic_addr_1 = <0x55>; ++ en_logic_mask_1 = <0xf8>; ++ en_logic_en_val_1 = <0x5>; ++ en_logic_dis_val_1 = <0x0>; ++ en_logic_width_1 = <1>; ++ en_logic_dev_2 = "/dev/cpld1"; ++ en_logic_addr_2 = <0x4c>; ++ en_logic_mask_2 = <0>; ++ en_logic_en_val_2 = <0xb2>; ++ en_logic_dis_val_2 = <0xb3>; ++ en_logic_width_2 = <1>; ++ }; ++ ++ /* mac pcie upgrade */ ++ firmware_mac_pcie { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <9>; ++ chip_index = <1>; ++ mtd_name = "spi5.0"; ++ flash_base = <0>; ++ }; ++ ++ /* BIOS */ ++ firmware_bios { ++ compatible = "firmware_sysfs"; ++ type = "MTD_DEV"; ++ chain = <7>; ++ chip_index = <1>; ++ mtd_name = "BIOS"; ++ flash_base = <0x0>; ++ }; ++ ++ ++ led_wdt: led_wdt { ++ compatible = "wb_wdt"; ++ feed_wdt_type = /bits/ 8 <0x2>; /* 0: does not feed the dog, 1:hrtimer feeds the dog, 2: threads feed the dog */ ++ hw_margin_ms = <180000>; /* Timeout period */ ++ feed_time = <30000>; /* Dog feeding time */ ++ config_dev_name = "/dev/cpld1"; /* Configuring CPLD */ ++ config_mode = /bits/ 8 <2>; /* 1:GPIO feed the dog, 2: logic device feed the dog */ ++ priv_func_mode = /bits/ 8 <1>; /* 1:i2c, 2:pcie, 3:IO, 4:Device file */ ++ enable_reg = <0xc8>; /* Enable register. Note: This led_wdt has no enable register and is compatible with driver configuration as a dog feed register */ ++ enable_val = /bits/ 8 <0x1>; /* Enable value */ ++ disable_val = /bits/ 8 <0x0>; /* Disabled value */ ++ enable_mask = /bits/ 8 <0x1>; /* Enable the mask */ ++ timeout_cfg_reg = <0xca>; /* Time setting register */ ++ timeleft_cfg_reg = <0xcb>; /* Residual time register */ ++ hw_algo = "eigenvalues"; /* write 1 to feed wdt */ ++ feed_dev_name = "/dev/cpld1"; /* Logic device feed dog register */ ++ feed_reg = <0xcc>; /* Dog feed register */ ++ active_val = /bits/ 8 <0x1>; /* Dog feed value */ ++ logic_func_mode = /bits/ 8 <0x1>; /* 1:i2c, 2:pcie, 3:IO, 4:Device file */ ++ timer_accuracy = <1600>; /* 1.6s */ ++ }; ++ wb_mmc_resume@0 { ++ compatible = "dev_resume_drv"; ++ dev_type = <1>; /* 1: MMC; 2: SSD */ ++ slot_id = <0>; /* emmc controller0 */ ++ bit_mask = <0x01>; ++ reg_intf_name = "mmc_resume_register"; ++ unreg_intf_name = "mmc_resume_unregister"; ++ ++ resume_way0 { ++ resume_way = <6>; /* DEV_RESUME_WAY_AST2700 */ ++ rst_gpio_flag = <1>; /* if have no rst_gpio, set flag 0 */ ++ rst_gpio_num = <524>; ++ rst_active_level = <0>; ++ power_gpio_flag = <1>; /* if have no power_gpio, set flag 0 */ ++ power_gpio_num = <545>; ++ power_active_level = <0>; ++ reset_delay = <200000>; ++ dereset_delay = <10000>; ++ }; ++ }; ++ ++ spi_gpio1: spi_gpio@1 { ++ compatible = "spi-gpio"; ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ num-chipselects = <1>; ++ ++ cs-gpios = <&gpio1 ASPEED_GPIO(D, 5) GPIO_ACTIVE_HIGH>; ++ gpio-sck = <&gpio1 ASPEED_GPIO(D, 6) GPIO_ACTIVE_HIGH>; ++ gpio-mosi = <&gpio1 ASPEED_GPIO(D, 4) GPIO_ACTIVE_HIGH>; ++ gpio-miso = <&gpio1 ASPEED_GPIO(D, 3) GPIO_ACTIVE_HIGH>; ++ ++ spi@0 { ++ compatible = "wb-spi-dev"; ++ reg = <0>; ++ spi-max-frequency = <6250000>; ++ spi-cpha; ++ spi-cpol; ++ spi_dev_name = "cpld10"; ++ data_bus_width = <4>; ++ addr_bus_width = <2>; ++ per_rd_len = <4>; ++ per_wr_len = <4>; ++ spi_len = <0x200>; ++ }; ++ }; ++#if 0 ++ cpld-i2c-master-0 { ++ compatible = "wb-indirect-dev"; ++ logic_func_mode = <5>; ++ dev_name = "cpld11"; ++ logic_dev_name = "/dev/cpld10"; ++ data_bus_width = <4>; ++ wr_data = <0x00>; ++ addr_low = <0x04>; ++ addr_high = <0x05>; ++ rd_data = <0x06>; ++ opt_ctl = <0x0a>; ++ indirect_len = <0x200>; ++ lock_mode = <0x2>; ++ }; ++#endif ++ cpld_i2c0: cpld-i2c0 { ++ compatible = "wb-fpga-i2c"; ++ i2c_timeout = <3000>; ++ i2c_scale = <0x00>; ++ i2c_filter = <0x04>; ++ i2c_stretch = <0x08>; ++ i2c_ext_9548_exits_flag = <0x0c>; ++ i2c_ext_9548_addr = <0x10>; ++ i2c_ext_9548_chan = <0x14>; ++ i2c_in_9548_chan = <0x18>; ++ i2c_slave = <0x1c>; ++ i2c_reg = <0x20>; ++ i2c_reg_len = <0x30>; ++ i2c_data_len = <0x34>; ++ i2c_ctrl = <0x38>; ++ i2c_status = <0x3c>; ++ i2c_err_vec = <0x48>; ++ i2c_data_buf = <0x100>; ++ i2c_data_buf_len = <256>; ++ dev_name = "/dev/cpld10"; ++ i2c_scale_value = <0x4e>; ++ i2c_filter_value = <0x7c>; ++ i2c_stretch_value = <0x7c>; ++ i2c_func_mode = <5>; /* SYMBOL_SPI_DEV */ ++ i2c_adap_reset_flag = <0>; ++ i2c_reset_addr = <0x7c>; ++ i2c_reset_on = <0x00000001>; ++ i2c_reset_off = <0x00000000>; ++ i2c_rst_delay_b = <0>; /* delay time before reset(us) */ ++ i2c_rst_delay = <1>; /* reset time(us) */ ++ i2c_rst_delay_a = <1>; /* delay time after reset(us) */ ++ pca9548@70 { ++ compatible = "wb_fpga_pca9548"; ++ reg = <0x70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pca9548_base_nr = <329>; ++ fpga_9548_flag = <1>; /* 1: internal 9548 2: external 9548 */ ++ fpga_9548_reset_flag = <0>; /* 1: reset, 0: no bit */ ++ }; ++ }; ++ gpio_init { ++ compatible = "gpio_init"; ++ /* start ast2720 common */ ++ gpioport@0 { ++ gpio = <564>; /* GPIOF0: BASE_CPLD_INT_R#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@1 { ++ gpio = <539>; /* GPIOB7: BMC NMI#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@2 { ++ gpio = <739>; /* GPIOAA7: BMC_INT_OUT#, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <1>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@3 { ++ gpio = <735>; /* GPIOAA3: BMC_SMI#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@4 { ++ gpio = <738>; /* GPIOAA6: BMC_WDO, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <0>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@5 { ++ gpio = <544>; /* GPIOC4: CATERR#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@6 { ++ gpio = <536>; /* GPIOB4: CPU_ERROR0#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@7 { ++ gpio = <538>; /* GPIOB6: CPU_ERROR1#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@8 { ++ gpio = <540>; /* GPIOC0: CPU_ERROR2#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@9 { ++ gpio = <733>; /* GPIOAA1: CPU_RST_IN_R#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ //gpioport@10 { ++ // gpio = <578>; /* GPIOG6: PWRBT#, Output,Operations under U-Boot */ ++ // init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ // init_level = <1>; /* 0: LOW, 1: HIGH */ ++ // free_flag = <1>; ++ //}; ++ gpioport@10 { ++ gpio = <537>; /* GPIOB5: THERMTRIP#, Input */ ++ init_direction = <1>; /* 0: OUTPUT 1: INPUT */ ++ free_flag = <1>; ++ }; ++ gpioport@11 { ++ gpio = <732>; /* GPIOAA0: CPU_RST_OUT#, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <1>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@12 { ++ gpio = <582>; /* GPIOH2: ONLINE_JTAG_EN, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <0>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@13 { ++ gpio = <580>; /* GPIOH0: ONLINE_JTAG_SEL2, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <0>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@14 { ++ gpio = <579>; /* GPIOG7: ONLINE_JTAG_SEL1, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <0>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@15 { ++ gpio = <567>; /* GPIOF3: BIOS_SEL, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <0>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@16 { ++ gpio = <555>; /* GPIOD7: BMC_UPGRADE_EN# 53134, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <1>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ /* end ast2720 common */ ++ gpioport@17 { ++ gpio = <565>; /* GPIOF1: BMC_I2C_EN#, Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <1>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ gpioport@18 { ++ gpio = <736>; /* GPIOAA4: , Output */ ++ init_direction = <0>; /* 0: OUTPUT 1: INPUT */ ++ init_level = <1>; /* 0: LOW, 1: HIGH */ ++ free_flag = <1>; ++ }; ++ }; ++}; ++ ++#if 0 ++&pwm_tach { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default ++ &pinctrl_pwm2_default &pinctrl_pwm3_default ++ &pinctrl_pwm4_default &pinctrl_pwm5_default ++ &pinctrl_pwm6_default &pinctrl_pwm7_default ++ &pinctrl_pwm8_default ++ &pinctrl_tach0_default &pinctrl_tach1_default ++ &pinctrl_tach2_default &pinctrl_tach3_default ++ &pinctrl_tach4_default &pinctrl_tach5_default ++ &pinctrl_tach6_default &pinctrl_tach7_default ++ &pinctrl_tach8_default &pinctrl_tach9_default ++ &pinctrl_tach10_default &pinctrl_tach11_default ++ &pinctrl_tach12_default &pinctrl_tach13_default ++ &pinctrl_tach14_default &pinctrl_tach15_default>; ++ fan-0 { ++ tach-ch = /bits/ 8 <0x0>; ++ }; ++ fan-1 { ++ tach-ch = /bits/ 8 <0x1>; ++ }; ++ fan-2 { ++ tach-ch = /bits/ 8 <0x2>; ++ }; ++ fan-3 { ++ tach-ch = /bits/ 8 <0x3>; ++ }; ++ fan-4 { ++ tach-ch = /bits/ 8 <0x4>; ++ }; ++ fan-5 { ++ tach-ch = /bits/ 8 <0x5>; ++ }; ++ fan-6 { ++ tach-ch = /bits/ 8 <0x6>; ++ }; ++ fan-7 { ++ tach-ch = /bits/ 8 <0x7>; ++ }; ++ fan-8 { ++ tach-ch = /bits/ 8 <0x8>; ++ }; ++ fan-9 { ++ tach-ch = /bits/ 8 <0x9>; ++ }; ++ fan-10 { ++ tach-ch = /bits/ 8 <0xA>; ++ }; ++ fan-11 { ++ tach-ch = /bits/ 8 <0xB>; ++ }; ++ fan-12 { ++ tach-ch = /bits/ 8 <0xC>; ++ }; ++ fan-13 { ++ tach-ch = /bits/ 8 <0xD>; ++ }; ++ fan-14 { ++ tach-ch = /bits/ 8 <0xE>; ++ }; ++ fan-15 { ++ tach-ch = /bits/ 8 <0xF>; ++ }; ++}; ++#endif ++ ++&edac { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "disabled"; ++ memory-region = <&mctp0_reserved>; ++}; ++ ++&mctp1 { ++ status = "disabled"; ++ memory-region = <&mctp1_reserved>; ++}; ++ ++&mctp2 { ++ status = "disabled"; ++ memory-region = <&mctp2_reserved>; ++}; ++ ++&sgpiom0 { ++ status = "disabled"; ++}; ++ ++&sgpiom1 { ++ status = "disabled"; ++}; ++ ++&jtag1 { ++ status = "disabled"; ++}; ++ ++&adc0 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "disabled"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc0_default &pinctrl_adc1_default ++ &pinctrl_adc2_default &pinctrl_adc3_default ++ &pinctrl_adc4_default &pinctrl_adc5_default ++ &pinctrl_adc6_default &pinctrl_adc7_default>; ++}; ++ ++&adc1 { ++ aspeed,int-vref-microvolt = <2500000>; ++ status = "disabled"; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_adc8_default &pinctrl_adc9_default ++ &pinctrl_adc10_default &pinctrl_adc11_default ++ &pinctrl_adc12_default &pinctrl_adc13_default ++ &pinctrl_adc14_default &pinctrl_adc15_default>; ++}; ++ ++&pinctrl0 { ++ pinctrl_emmcclk_driving: emmcclk-driving { ++ pins = "AC14"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_emmccmd_driving: emmccmd-driving { ++ pins = "AE15"; ++ drive-strength = <1>; ++ }; ++ pinctrl_emmcdat_driving: emmcdat-driving { ++ pins = "AD14", "AE14", "AF14", "AB13"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c12_15_hv_voltage: i3chv-voltage { ++ pins = "W25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c12_driving: i3c12-driving { ++ pins = "W25", "Y23"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c13_driving: i3c13-driving { ++ pins = "Y24", "W21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c14_driving: i3c14-driving { ++ pins = "AA23", "AC22"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c15_driving: i3c15-driving { ++ pins = "AB22", "Y21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_rgmii1_driving: rgmii1-driving { ++ pins = "D19", "C19", "D15", "B12", "B10", "P13", ++ "C18", "C6", "C7", "D7", "N13", "C8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&gpio1 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage &pinctrl_i3c12_15_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving ++ &pinctrl_i3c12_driving &pinctrl_i3c13_driving ++ &pinctrl_i3c14_driving &pinctrl_i3c15_driving>; ++ pinctrl-names = "default"; ++}; ++ ++&i3c0 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06010000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c1 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&i3c2 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06012000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c3 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&i3c4 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06014000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c5 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&i3c6 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06016000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c7 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&i3c8 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x06018000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c9 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&i3c10 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601A000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c11 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&i3c12 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601C000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c13 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&i3c14 { ++ initial-role = "target"; ++ pid = <0x000007ec 0x0601E000>; ++ dcr = /bits/ 8 <0xcc>; ++ status = "disabled"; ++}; ++ ++&i3c15 { ++ initial-role = "primary"; ++ status = "disabled"; ++}; ++ ++&uart0 { ++ status = "disabled"; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart2 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ status = "okay"; ++}; ++ ++&vuart0 { ++ virtual; ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ status = "disabled"; ++}; ++ ++&vuart1 { ++ virtual; ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ status = "disabled"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <5000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc-alt"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-g7-alt-flash-layout-64.dtsi" ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi0 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ spi-aspeed-full-duplex; ++ ++ tpm0: tpmdev@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <17000000>; ++ reg = <0>; ++ status = "okay"; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "BIOS"; ++ spi-max-frequency = <5000000>; ++ reg = <1>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++}; ++ ++&spi1 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1_cs1_default>; ++ pinctrl-names = "default"; ++ ++ fpga@0 { ++ status = "okay"; ++ compatible = "wb-spi-dev"; ++ reg = <0>; ++ spi-max-frequency = <6250000>; ++ /* spi-cpha; */ ++ /* spi-cpol; */ ++ spi_dev_name = "fpga0"; ++ data_bus_width = <4>; ++ addr_bus_width = <4>; ++ per_rd_len = <4>; ++ per_wr_len = <4>; ++ spi_len = <0xc0000>; ++ }; ++}; ++ ++#if 1 ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "disabled"; ++ ++ spi-aspeed-full-duplex; ++}; ++#else ++&spi2 { ++ compatible = "aspeed,ast2700-spi"; ++ pinctrl-0 = <&pinctrl_spi2_default &pinctrl_spi2_cs1_default>; ++ pinctrl-names = "default"; ++ status = "disabled"; ++ ++ flash@0 { ++ status = "okay"; ++ reg = < 0 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:0"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ reg = < 1 >; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ label = "spi2:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <2>; ++ spi-rx-bus-width = <2>; ++ }; ++}; ++#endif ++ ++&can0 { ++ status = "disabled"; ++}; ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++ pinctrl-names = "default"; ++ max-frequency = <50000000>; ++ bus-width = <4>; ++ pinctrl-0 = <&pinctrl_emmc_default &pinctrl_emmcclk_driving &pinctrl_emmccmd_driving &pinctrl_emmcdat_driving>; ++ ++ non-removable; ++ no-sd; ++ no-sdio; ++ ++ cap-mmc-hw-reset; ++ reset_gpio = <524>; /* reset emmc gpio GPIOA0 */ ++ reset_on = <0>; ++ reset_off = <1>; ++ fix_clk_phase = <0x001000BF>; ++}; ++ ++&ufs_controller { ++ status = "disabled"; ++}; ++ ++&ufs { ++ status = "disabled"; ++ lanes-per-direction = <2>; ++ ref-clk-freq = <26000000>; ++}; ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy0: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&mdio1 { ++ status = "okay"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy1: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ /* For DDR5 board */ ++ ti,rx-internal-delay = ; ++ ti,tx-internal-delay = ; ++ }; ++}; ++ ++&mac0 { ++ status = "disabled"; ++ ++ phy-mode = "rgmii-id"; ++ phy-handle = <ðphy0>; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++}; ++ ++&mac1 { ++ status = "okay"; ++ ++ phy-mode = "rgmii"; ++ phy-handle; ++ ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii1_default &pinctrl_rgmii1_driving>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++}; ++ ++#if PCIE2_RC ++&pcie2 { ++ status = "disabled"; ++}; ++#else ++&mdio2 { ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ }; ++}; ++ ++&sgmii { ++ status = "disabled"; ++}; ++ ++&mac2 { ++ status = "disabled"; ++ ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++}; ++#endif ++ ++&syscon1 { ++ assigned-clocks = <&syscon1 SCU1_CLK_MACHCLK>, ++ <&syscon1 SCU1_CLK_RGMII>, ++ <&syscon1 SCU1_CLK_RMII>; ++ assigned-clock-rates = <200000000>, <125000000>, <50000000>; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ memory-region = <&espi0_mcyc_memory>; ++ perif-rtc-enable; ++ oob-dma-mode; ++ flash-dma-mode; ++#if 0 // eDAF mode: Change 1 to enable MIX mode in Linux, but HW mode in SPL would be overwritten ++ flash-edaf-mode = <0x0>; ++ flash-edaf-tgt-addr = <&edaf0>; ++ flash-edaf-size = <0x0 0x4000000>; ++#endif ++}; ++ ++&rtc_over_espi0 { ++ status = "disabled"; ++}; ++ ++#if DUAL_NODE ++&espi1 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi1_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ perif-rtc-enable; ++ oob-dma-mode; ++ flash-dma-mode; ++#if 0 // eDAF mode: Change 1 to enable MIX mode in Linux, but HW mode in SPL would be overwritten ++ flash-edaf-mode = <0x0>; ++ flash-edaf-tgt-addr = <&edaf1>; ++ flash-edaf-size = <0x0 0x4000000>; ++#endif ++}; ++ ++&rtc_over_espi1 { ++ status = "okay"; ++}; ++#endif /* DUAL_NODE */ ++ ++&lpc0_kcs0 { ++ status = "disabled"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <0>; ++}; ++ ++&lpc0_kcs1 { ++ status = "disabled"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <1>; ++}; ++ ++&lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_kcs3 { ++ status = "disabled"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <3>; ++}; ++ ++&lpc0_ibt { ++ status = "disabled"; ++}; ++ ++&lpc0_mbox { ++ status = "okay"; ++}; ++ ++#if 0 ++&lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++#else ++&lpc0_pcc { ++ status = "okay"; ++ pcc-ports = <0x80>; ++}; ++#endif ++ ++&lpc0_uart_routing { ++ status = "okay"; ++}; ++ ++&lpc1_kcs0 { ++ status = "disabled"; ++ kcs-io-addr = <0xca0>; ++ kcs-channel = <4>; ++}; ++ ++&lpc1_kcs1 { ++ status = "disabled"; ++ kcs-io-addr = <0xca8>; ++ kcs-channel = <5>; ++}; ++ ++&lpc1_kcs2 { ++ status = "disabled"; ++ kcs-io-addr = <0xca2>; ++ kcs-channel = <6>; ++}; ++ ++&lpc1_kcs3 { ++ status = "disabled"; ++ kcs-io-addr = <0xca4>; ++ kcs-channel = <7>; ++}; ++ ++&lpc1_ibt { ++ status = "disabled"; ++}; ++ ++&lpc1_mbox { ++ status = "okay"; ++}; ++ ++#if 0 ++&lpc1_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>, <0x81>; ++}; ++#else ++&lpc1_pcc { ++ status = "okay"; ++ pcc-ports = <0x80>; ++}; ++#endif ++ ++&lpc1_uart_routing { ++ status = "okay"; ++}; ++ ++&video0 { ++ status = "disabled"; ++ memory-region = <&video_engine_memory0>; ++}; ++ ++&video1 { ++ status = "disabled"; ++ memory-region = <&video_engine_memory1>; ++}; ++ ++&disp_intf { ++ status = "disabled"; ++}; ++ ++&rtc { ++ status = "disabled"; ++}; ++ ++&rsss { ++ status = "disabled"; ++}; ++ ++&ecdsa { ++ status = "disabled"; ++}; ++ ++&hace { ++ status = "disabled"; ++}; ++ ++#if PCIE0_EP ++&bmc_dev0 { ++ status = "okay"; ++ memory-region = <&bmc_dev0_memory>; ++}; ++ ++&xdma0 { ++ status = "okay"; ++ memory-region = <&xdma_memory0>; ++}; ++ ++&pcie_vuart0 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart1 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <8>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <9>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <10>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <11>; ++}; ++ ++&pcie_lpc0_ibt { ++ status = "okay"; ++ bt-channel = <2>; ++}; ++ ++&pcie0_mmbi0 { ++ status = "okay"; ++ memory-region = <&pcie0_mmbi0_memory>; ++ ++ bmc-int-value = /bits/ 8 <0x00>; ++ bmc-int-location = <0>; ++}; ++#else /* !PCIE0_EP */ ++&pcie0 { ++ status = "okay"; ++}; ++#endif /* PCIE0_EP */ ++ ++#if PCIE1_EP ++&bmc_dev1 { ++ status = "okay"; ++ memory-region = <&bmc_dev1_memory>; ++}; ++ ++&xdma1 { ++ status = "okay"; ++ memory-region = <&xdma_memory1>; ++}; ++ ++&pcie_vuart2 { ++ port = <0x3f8>; ++ sirq = <4>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_vuart3 { ++ port = <0x2f8>; ++ sirq = <3>; ++ sirq-polarity = <0>; ++ ++ status = "okay"; ++}; ++ ++&pcie_lpc1_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <12>; ++}; ++ ++&pcie_lpc1_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <13>; ++}; ++ ++&pcie_lpc1_kcs2 { ++ status = "okay"; ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <14>; ++}; ++ ++&pcie_lpc1_kcs3 { ++ status = "okay"; ++ kcs-io-addr = <0x3a4>; ++ kcs-channel = <15>; ++}; ++ ++&pcie_lpc1_ibt { ++ status = "okay"; ++ bt-channel = <3>; ++}; ++ ++&pcie1_mmbi4 { ++ status = "okay"; ++ memory-region = <&pcie1_mmbi4_memory>; ++ ++ bmc-int-value = /bits/ 8 <0x00>; ++ bmc-int-location = <0>; ++}; ++#else /* !PCIE1_EP */ ++&pcie1 { ++ status = "okay"; ++}; ++#endif /* PCIE1_EP */ ++ ++#if 0 ++&i3c0 { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++#endif ++ ++&sdio_controller { ++ status = "disabled"; ++ mmc-hs200-1_8v; ++ ++ vcc_sdhci0: regulator-vcc-sdhci0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "SDHCI0 Vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 6) GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; ++ ++ vccq_sdhci0: regulator-vccq-sdhci0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "SDHCI0 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 7) GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1>, ++ <1800000 0>; ++ }; ++ ++}; ++ ++&sdhci { ++ status = "disabled"; ++ bus-width = <4>; ++ max-frequency = <125000000>; ++ /* DDR50 bits in CAPA2 are not supported */ ++ sdhci-caps-mask = <0x6 0x0>; ++ sdhci-drive-type = /bits/ 8 <3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sd_default>; ++ vmmc-supply = <&vcc_sdhci0>; ++ vqmmc-supply = <&vccq_sdhci0>; ++ sd-uhs-sdr104; /* enable sdr104 to execute tuning */ ++}; ++ ++&i2c0 { ++ status = "okay"; ++ mgmt_cpld@3b { ++ compatible = "wb-i2c-dev"; ++ reg = <0x3b>; ++ i2c_name = "cpld1"; ++ i2c_alias = "MGMT_CPLD"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++}; ++ ++&i2c1 { ++ status = "okay"; ++ cpu_cpld@0d { ++ compatible = "wb-i2c-dev"; ++ reg = <0x0d>; ++ i2c_name = "cpld0"; ++ i2c_alias = "CPU_CPLD"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++}; ++ ++&i2c2 { ++ status = "okay"; ++ mac_cpldd@4b { ++ compatible = "wb-i2c-dev"; ++ reg = <0x4b>; ++ i2c_name = "cpld9"; ++ i2c_alias = "MAC_CPLDD"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++ i2cswitch@72 { ++ compatible = "nxp,wb_pca9548"; ++ reg = <0x72>; ++ probe_disable; ++ probe_hw_init; ++ pca9548_base_nr = <14>; ++ status = "okay"; ++ pca9548_reset_type = <5>; /* 1:I2C 2:GPIO 3:IO 4:FILE 5:LOGIC*/ ++ rst_delay_b = <0>; /* delay time before reset(us) */ ++ rst_delay = <1000>; /* reset time(us) */ ++ rst_delay_a = <1000>; /* delay time after reset(us) */ ++ reset_reg_mode = <1>; /* REG_MODE_REGKEY */ ++ dev_name = "/dev/cpld1"; ++ offset = <0x2d>; ++ reset_on = <0x0>; ++ reset_off = <0x1>; ++ logic_func_mode = <0x1>; /* I2C */ ++ i2c@0 { ++ reg = <0>; ++ mac-cpldc@3b { ++ compatible = "wb-i2c-dev"; ++ reg = <0x3b>; ++ i2c_name = "cpld4"; ++ i2c_alias = "MAC_CPLDC"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++ }; ++ ++ i2c@1 { ++ reg = <1>; ++ mac-cplda@1b { ++ compatible = "wb-i2c-dev"; ++ reg = <0x1b>; ++ i2c_name = "cpld2"; ++ i2c_alias = "MAC_CPLDA"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++ }; ++ ++ i2c@2 { ++ reg = <2>; ++ mac-cpldb@2b { ++ compatible = "wb-i2c-dev"; ++ reg = <0x2b>; ++ i2c_name = "cpld3"; ++ i2c_alias = "MAC_CPLDB"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++ }; ++ ++ i2c@4 { ++ reg = <4>; ++ uport-cpld@3b { ++ compatible = "wb-i2c-dev"; ++ reg = <0x3b>; ++ i2c_name = "cpld5"; ++ i2c_alias = "UPORT_CPLD"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++ }; ++ ++ i2c@6 { ++ reg = <6>; ++ dport-cpld@3b { ++ compatible = "wb-i2c-dev"; ++ reg = <0x3b>; ++ i2c_name = "cpld6"; ++ i2c_alias = "DPORT_CPLD"; ++ data_bus_width = <1>; ++ addr_bus_width = <1>; ++ per_rd_len = <256>; ++ per_wr_len = <256>; ++ i2c_len = <0x100>; ++ }; ++ }; ++ }; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&i2c4 { ++ status = "okay"; ++}; ++ ++&i2c5 { ++ status = "okay"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++ rtc@51 { ++ compatible = "nxp,pcf8563"; ++ reg = <0x51>; ++ status = "okay"; ++ cap-mode = /bits/ 8 <0x00>; ++ clock-freq = /bits/ 8 <0x07>; ++ accuracy-value = /bits/ 8 <0x09>; ++ }; ++ sbrmi@3c { ++ compatible = "amd,sbrmi"; ++ reg = <0x3c>; ++ }; ++ ++ sbtsi@4c { ++ compatible = "amd,sbtsi"; ++ reg = <0x4c>; ++ }; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++}; ++ ++&i2c13 { ++ status = "okay"; ++}; ++ ++&peci0 { ++ status = "okay"; ++}; ++ ++#if 0 ++&ehci0 { ++ status = "okay"; ++}; ++ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&uhci0 { ++ status = "okay"; ++ memory-region = <&uhci0_reserved>; ++}; ++ ++#endif ++ ++#if 1 ++&uphy3a { ++ status = "disabled"; ++}; ++ ++&uphy3b { ++ status = "disabled"; ++}; ++#endif ++ ++#if 0 ++&xhci0 { ++ status = "okay"; ++}; ++ ++&xhci1 { ++ status = "okay"; ++}; ++#endif ++ ++&vhuba0 { ++ status = "okay"; ++}; ++ ++&usb3ahp { ++ status = "disabled"; ++ pinctrl-0 = <&pinctrl_usb3axhp_default &pinctrl_usb2axhp_default>; ++}; ++ ++&usb3bhp { ++ status = "disabled"; ++}; ++ ++&uphy2b { ++ status = "disabled"; ++}; ++ ++&vhubb1 { ++ status = "disabled"; ++}; ++ ++&vhubc { ++ status = "disabled"; ++#if 0 ++ pinctrl-0 = <&pinctrl_usb2cud_default>; ++ aspeed,uart-ports = <12>; ++#endif ++}; ++ ++&ehci3 { ++ status = "disabled"; ++}; ++ ++&uhci1 { ++ status = "disabled"; ++ memory-region = <&uhci1_reserved>; ++}; ++ ++&wdt0 { ++ status = "okay"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++&otp { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-g7-alt-flash-layout-64.dtsi b/arch/arm64/boot/dts/aspeed/aspeed-g7-alt-flash-layout-64.dtsi +new file mode 100644 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/aspeed-g7-alt-flash-layout-64.dtsi +@@ -0,0 +1,32 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x400000>; // 4MB ++ label = "u-boot-alt"; ++ }; ++ ++ u-boot-env@400000 { ++ reg = <0x400000 0x20000>; // 128KB ++ label = "u-boot-env-alt"; ++ }; ++ ++ kernel@420000 { ++ reg = <0x420000 0x900000>; // 9MB ++ label = "kernel-alt"; ++ }; ++ ++ rofs@d20000 { ++ reg = <0xd20000 0x2AE0000>; // 42.875MB ++ label = "rofs-alt"; ++ }; ++ ++ data@3800000 { ++ reg = <0x3800000 0x800000>; // 8MB ++ label = "data-alt"; ++ }; ++}; diff --git a/patches-sonic/nexthop-b27-dts.patch b/patches-sonic/nexthop-b27-dts.patch new file mode 100644 index 000000000..4ccce2685 --- /dev/null +++ b/patches-sonic/nexthop-b27-dts.patch @@ -0,0 +1,585 @@ +From 00a401bfbf89926ecc24882259826427cfd32b7d Mon Sep 17 00:00:00 2001 +From: Chandrasekaran Swaminathan +Date: Thu, 2 Jul 2026 16:53:15 +0000 +Subject: [PATCH] Add NextHop B27 R0 device tree + +This adds nexthop-b27-r0.dts for NextHop B27 R0 hardware. + +Hardware differences from ast2700-evb: +- MAC0: Fixed-link (1000Mbps) instead of PHY - direct RGMII to BCM switch +- MAC1: Disabled (not connected) +- MDIO0/MDIO1: Disabled (no PHY chips) + +The device tree is based on ast2700-evb.dts with modifications for +production hardware that does not have PHY chips. + +Signed-off-by: Chandrasekaran Swaminathan +--- + arch/arm64/boot/dts/aspeed/Makefile | 1 + + arch/arm64/boot/dts/aspeed/nexthop-b27-r0.dts | 542 ++++++++++++++++++ + 2 files changed, 543 insertions(+) + create mode 100644 arch/arm64/boot/dts/aspeed/nexthop-b27-r0.dts + +diff --git a/arch/arm64/boot/dts/aspeed/Makefile b/arch/arm64/boot/dts/aspeed/Makefile +index 7c31f31..3e904b2 100644 +--- a/arch/arm64/boot/dts/aspeed/Makefile ++++ b/arch/arm64/boot/dts/aspeed/Makefile +@@ -2,6 +2,7 @@ + + dtb-$(CONFIG_ARCH_ASPEED) += \ + ast2700-evb.dtb \ ++ nexthop-b27-r0.dtb \ + ast2700-evb_ast1700-evb.dtb \ + ast2700-evb_ast1700-evb-dual.dtb \ + ast2700-raw.dtb \ +diff --git a/arch/arm64/boot/dts/aspeed/nexthop-b27-r0.dts b/arch/arm64/boot/dts/aspeed/nexthop-b27-r0.dts +new file mode 100644 +index 0000000..7d1f339 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nexthop-b27-r0.dts +@@ -0,0 +1,542 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// ++// NextHop AST2700 based B27 R0 Device Tree ++// Based on ast2700-evb.dts with modifications for NextHop B27 card ++ ++/dts-v1/; ++ ++#include "aspeed-g7.dtsi" ++#include ++ ++#define PCIE0_EP 0 // 1: EP, 0: RC ++#define PCIE1_EP 0 // 1: EP, 0: RC ++#define PCIE2_RC 0 // 1: RC, 0: SGMII ++ ++/ { ++ model = "Nexthop B27 R0"; ++ compatible = "nexthop,nexthop-b27-r0", "aspeed,ast2700"; ++ ++ chosen { ++ stdout-path = &uart12; ++ }; ++ ++ firmware { ++ optee: optee { ++ compatible = "linaro,optee-tz"; ++ method = "smc"; ++ }; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ gfx_memory: framebuffer { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ ++ xdma_memory0: xdma0 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++ }; ++ ++ xdma_memory1: xdma1 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x01000000>; ++ compatible = "shared-dma-pool"; ++ no-map; ++ }; ++ ++ espi0_mcyc_memory: mcyc0 { ++ size = <0x0 0x01000000>; ++ alignment = <0x0 0x00010000>; ++ compatible = "shared-dma-pool"; ++ reusable; ++ }; ++ }; ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ status = "okay"; ++ io-channels = <&adc0 0>, <&adc0 1>, <&adc0 2>, <&adc0 3>, ++ <&adc0 4>, <&adc0 5>, <&adc0 6>, <&adc0 7>, ++ <&adc1 0>, <&adc1 1>, <&adc1 2>, <&adc1 3>, ++ <&adc1 4>, <&adc1 5>, <&adc1 6>, <&adc1 7>; ++ }; ++}; ++ ++&edac { ++ status = "okay"; ++}; ++ ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++ ++&pinctrl0 { ++ pinctrl_emmcclk_driving: emmcclk-driving { ++ pins = "AC14"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_emmccmd_driving: emmccmd-driving { ++ pins = "AE15"; ++ drive-strength = <1>; ++ }; ++ pinctrl_emmcdat_driving: emmcdat-driving { ++ pins = "AD14", "AE14", "AF14", "AB13"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&pinctrl1 { ++ pinctrl_i3c0_3_hv_voltage: i3chv-voltage { ++ pins = "U25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c0_driving: i3c0-driving { ++ pins = "U25", "U26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c1_driving: i3c1-driving { ++ pins = "Y26", "AA24"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c2_driving: i3c2-driving { ++ pins = "R25", "AA26"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c3_driving: i3c3-driving { ++ pins = "R26", "Y25"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c12_15_hv_voltage: i3chv-voltage { ++ pins = "W25"; ++ power-source = <1800>; ++ }; ++ ++ pinctrl_i3c12_driving: i3c12-driving { ++ pins = "W25", "Y23"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c13_driving: i3c13-driving { ++ pins = "Y24", "W21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c14_driving: i3c14-driving { ++ pins = "AA23", "AC22"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_i3c15_driving: i3c15-driving { ++ pins = "AB22", "Y21"; ++ drive-strength = <2>; ++ }; ++ ++ pinctrl_rgmii0_driving: rgmii0-driving { ++ pins = "C20", "C19", "A8", "R14", "A7", "P14", ++ "D20", "A6", "B6", "N14", "B7", "B8"; ++ drive-strength = <1>; ++ }; ++ ++ pinctrl_rgmii1_driving: rgmii1-driving { ++ pins = "D19", "C19", "D15", "B12", "B10", "P13", ++ "C18", "C6", "C7", "D7", "N13", "C8"; ++ drive-strength = <1>; ++ }; ++}; ++ ++&gpio1 { ++ pinctrl-0 = <&pinctrl_i3c0_3_hv_voltage &pinctrl_i3c12_15_hv_voltage ++ &pinctrl_i3c0_driving &pinctrl_i3c1_driving ++ &pinctrl_i3c2_driving &pinctrl_i3c3_driving ++ &pinctrl_i3c12_driving &pinctrl_i3c13_driving ++ &pinctrl_i3c14_driving &pinctrl_i3c15_driving>; ++ pinctrl-names = "default"; ++ ++ gpio-line-names = ++ /*A0-A7*/ "","","","","","","","", ++ /*B0-B7*/ "","","","","","","","", ++ /*C0-C7*/ "","","","","","","","", ++ /*D0-D7*/ "","","","","","","","", ++ /*E0-E7*/ "","bmc_mode","cpe_ctrl","fpga_prog", ++ "eeprom_wp","","","", ++ /*F0-F7*/ "pwr_cyc_req","cpu_alert_l","cpu_rst_l","", ++ "","","","", ++ /*G0-G7*/ "","","","", ++ "tpm_rst_l","tpm_pirq","tpm_gpio_0","tpm_gpio_1", ++ /*H0-H7*/ "tpm_gpio_2","","","","","","","", ++ /*I0-I7*/ "","","","","","","","", ++ /*J0-J7*/ "","","","","","","","", ++ /*K0-K7*/ "","","","","","","","", ++ /*L0-L7*/ "","","","","","","","", ++ /*M0-M7*/ "","","","","","","","", ++ /*N0-N7*/ "","","","","","","","", ++ /*O0-O7*/ "","","","","","","","", ++ /*P0-P7*/ "","","","","","","","", ++ /*Q0-Q7*/ "","","","","","","","emmc_rst", ++ /*R0-R7*/ "","","","","","","","", ++ /*S0-S7*/ "","","","","","","","", ++ /*T0-T7*/ "","","","","","","","", ++ /*U0-U7*/ "","","","","","","","", ++ /*V0-V7*/ "","","","","","","","", ++ /*W0-W7*/ "","","","","","","","", ++ /*X0-X7*/ "","","","","","","","", ++ /*Y0-Y7*/ "","","","","","","","", ++ /*Z0-Z7*/ "","","","","","","","", ++ /*AA0-AA7*/ "","","","","","","","", ++ /*AB0-AB7*/ "","","","","","","","", ++ /*AC0-AC7*/ "","","","","","","",""; ++ ++ /* TODO: Add more GPIOs hogs as needed, rename & add labels for clarity */ ++ ++ bmc-gpio2-hog { ++ gpio-hog; ++ gpios = ; ++ output-low; ++ }; ++ ++ bmc-gpio3-hog { ++ gpio-hog; ++ gpios = ; ++ input; ++ }; ++ ++ bmc-gpio6-hog { ++ gpio-hog; ++ gpios = ; ++ input; ++ }; ++}; ++ ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ dma-mode; ++ status = "okay"; ++}; ++ ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++#include "aspeed-evb-flash-layout-128.dtsi" ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++ ++&spi0 { ++ compatible = "aspeed,ast2700-spi-txrx", "aspeed,ast2700-spi"; ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_quad_default &pinctrl_spi0_cs1_default>; ++ pinctrl-names = "default"; ++ ++ spi-aspeed-full-duplex; ++ ++ fpga@0 { ++ status = "okay"; ++ compatible = "nh,fpga-spi-bridge"; ++ spi-max-frequency = <16000000>; ++ reg = <0>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ compatible = "jedec,spi-nor"; ++ m25p,fast-read; ++ reg = <1>; ++ spi-max-frequency = <50000000>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ host_bios@0 { ++ label = "pnor"; ++ reg = <0x0 0x4000000>; // Adjust size (0x4000000 = 64MB) ++ }; ++ }; ++ }; ++}; ++ ++ ++&spi2 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ pinctrl-0 = <&pinctrl_spi2_default>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ spi-aspeed-full-duplex; ++ ++ tpm0: tpmdev@0 { ++ compatible = "tcg,tpm_tis-spi"; ++ spi-max-frequency = <25000000>; ++ reg = <0>; ++ status = "okay"; ++ ++ reset-gpios = <&gpio1 ASPEED_GPIO(G, 4) GPIO_ACTIVE_LOW>; ++ }; ++}; ++ ++ ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++ bus-width = <4>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_emmc_default &pinctrl_emmcclk_driving &pinctrl_emmccmd_driving &pinctrl_emmcdat_driving>; ++ ++ non-removable; ++ max-frequency = <200000000>; ++}; ++ ++ ++&chassis { ++ status = "okay"; ++}; ++ ++&mac0 { ++ status = "okay"; ++ phy-mode = "rgmii-id"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_rgmii0_default &pinctrl_rgmii0_driving>; ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ pause; ++ }; ++}; ++ ++&mdio0 { ++ status = "okay"; ++}; ++ ++&syscon1 { ++ assigned-clocks = <&syscon1 SCU1_CLK_MACHCLK>, ++ <&syscon1 SCU1_CLK_RGMII>, ++ <&syscon1 SCU1_CLK_RMII>; ++ assigned-clock-rates = <200000000>, <125000000>, <50000000>; ++}; ++ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ // perif-mmbi-enable; ++ // perif-mmbi-src-addr = <0x0 0xa8000000>; ++ // perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ // perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ memory-region = <&espi0_mcyc_memory>; ++ // perif-rtc-enable; ++ vw-pltrst-monitor; ++ oob-dma-mode; ++}; ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++ ++&sdio_controller { ++ status = "disabled"; ++ mmc-hs200-1_8v; ++ ++ vcc_sdhci0: regulator-vcc-sdhci0 { ++ compatible = "regulator-fixed"; ++ regulator-name = "SDHCI0 Vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 6) GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; ++ ++ vccq_sdhci0: regulator-vccq-sdhci0 { ++ compatible = "regulator-gpio"; ++ regulator-name = "SDHCI0 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ gpios = <&gpio1 ASPEED_GPIO(G, 7) GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1>, ++ <1800000 0>; ++ }; ++ ++}; ++ ++&sdhci { ++ status = "disabled"; ++ bus-width = <4>; ++ max-frequency = <100000000>; ++ /* DDR50 bits in CAPA2 are not supported */ ++ sdhci-caps-mask = <0x6 0x0>; ++ sdhci-drive-type = /bits/ 8 <3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sd_default>; ++ vmmc-supply = <&vcc_sdhci0>; ++ vqmmc-supply = <&vccq_sdhci0>; ++ sd-uhs-sdr104; /* enable sdr104 to execute tuning */ ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ status = "okay"; ++}; ++ ++&i2c2 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c2_default>; ++ ++ adm1266: adm1266@41 { ++ compatible = "adi,adm1266"; ++ reg = <0x41>; ++ }; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; ++ ++&i2c4 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_i2c4_default>; ++ ++ eeprom@50 { ++ compatible = "atmel,24c64"; ++ reg = <0x50>; ++ pagesize = <32>; ++ }; ++}; ++ ++&i2c5 { ++ status = "disabled"; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++}; ++ ++&i2c8 { ++ status = "okay"; ++}; ++ ++&i2c9 { ++ status = "okay"; ++}; ++ ++&i2c10 { ++ status = "okay"; ++}; ++ ++&i2c11 { ++ status = "okay"; ++}; ++ ++&i2c12 { ++ status = "okay"; ++}; ++ ++&i2c13 { ++ status = "disabled"; ++}; ++ ++ ++&vhubb0 { ++ status = "disabled"; ++}; ++ ++&vhubb1 { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_usb2bd1_default>; ++}; ++ ++&wdt0 { ++ status = "okay"; ++ aspeed,reset-type = "soc"; ++}; ++ ++&wdt1 { ++ status = "okay"; ++}; ++ ++/ { ++ aliases { ++ watchdog0 = &wdt0; ++ watchdog1 = &wdt1; ++ }; ++}; ++ ++&otp { ++ status = "okay"; ++}; ++ +-- +2.34.1 + diff --git a/patches-sonic/platform-x86-int0002-remove-irqf-oneshot.patch b/patches-sonic/platform-x86-int0002-remove-irqf-oneshot.patch new file mode 100644 index 000000000..689ddab80 --- /dev/null +++ b/patches-sonic/platform-x86-int0002-remove-irqf-oneshot.patch @@ -0,0 +1,53 @@ +From: Sebastian Andrzej Siewior +Date: 2026-01-28 +Subject: platform/x86: int0002: Remove IRQF_ONESHOT from request_irq() + +Backport of stable commit 5938fb0e328508a50c334852e30f02ecc28b74d6 +[ Upstream commit f6bc712877f24dc89bdfd7bdbf1a32f3b9960b34 ] + +Passing IRQF_ONESHOT ensures that the interrupt source is masked until the +secondary (threaded) handler is done. If only a primary handler is used +then the flag makes no sense because the interrupt cannot fire (again) +while its handler is running. + +The flag also prevents force-threading of the primary handler and the +irq-core will warn about this. + +The flag was added to match the flag on the shared handler which uses a +threaded handler and therefore IRQF_ONESHOT. This is no longer needed +because devm_request_irq() now passes IRQF_COND_ONESHOT for this case. + +Revert adding IRQF_ONESHOT to irqflags. + +Applied here on top of revert-acpi-osl-use-threaded-irq-for-sci.patch, +which requests the ACPI SCI with plain IRQF_SHARED again. The INT0002 +GPIO IRQ is typically shared with the SCI, so dropping IRQF_ONESHOT here +keeps the two request_irq() flag sets in agreement and avoids: + + genirq: Flags mismatch irq 9. 00000084 (INT0002) vs. 00002080 (acpi) + +Fixes: 8f812373d1958 ("platform/x86: intel: int0002_vgpio: Pass IRQF_ONESHOT to request_irq()") +Reported-by: Borah, Chaitanya Kumar +Signed-off-by: Sebastian Andrzej Siewior +Signed-off-by: Thomas Gleixner +Reviewed-by: Hans de Goede +Acked-by: Ilpo Järvinen +Signed-off-by: Sasha Levin +--- + drivers/platform/x86/intel/int0002_vgpio.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c +--- a/drivers/platform/x86/intel/int0002_vgpio.c ++++ b/drivers/platform/x86/intel/int0002_vgpio.c +@@ -195,8 +195,8 @@ + * FIXME: augment this if we managed to pull handling of shared + * IRQs into gpiolib. + */ +- ret = devm_request_irq(dev, irq, int0002_irq, +- IRQF_ONESHOT | IRQF_SHARED, "INT0002", chip); ++ ret = devm_request_irq(dev, irq, int0002_irq, IRQF_SHARED, "INT0002", ++ chip); + if (ret) { + dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret); + return ret; diff --git a/patches-sonic/revert-acpi-osl-use-threaded-irq-for-sci.patch b/patches-sonic/revert-acpi-osl-use-threaded-irq-for-sci.patch new file mode 100644 index 000000000..567d6dc5c --- /dev/null +++ b/patches-sonic/revert-acpi-osl-use-threaded-irq-for-sci.patch @@ -0,0 +1,80 @@ +From: Mohan Yelugoti +Date: 2026-05-11 +Subject: Revert "ACPI: OSL: Use a threaded interrupt handler for SCI" + +This reverts commit 7a36b901a6eb0e9945341db71ed3c45c7721cfa9. + +After upgrading from Debian bookworm to trixie on modular systems, +the kdump kernel started hitting a soft lockup while capturing a +crash dump. The issue is reproducible by triggering a panic in the +production kernel with: + +echo c | sudo tee /proc/sysrq-trigger + +Once the kdump kernel boots, CPU0 gets stuck in the ACPI SCI handling +path and the soft lockup watchdog eventually panics the kdump kernel, +so no vmcore is produced. + +The trace below was obtained by adding the following to the kdump +command line: debug=1, loglevel=7, softlockup_all_cpu_backtrace=1 and +softlockup_panic=1: + + watchdog: BUG: soft lockup - CPU#0 stuck for 26s! [irq/9-acpi:39] + CPU: 0 UID: 0 PID: 39 Comm: irq/9-acpi Not tainted + 6.12.41+deb13-sonic-amd64 #1 Debian 6.12.41-1 + Hardware name: Intel Camelback Mountain CRB, BIOS + Aboot-norcal7-7.1.6-generic-22971530 06/30/2021 + RIP: 0010:acpi_os_read_port+0x30/0xa0 + Call Trace: + + acpi_hw_gpe_read+0x61/0x80 + acpi_ev_detect_gpe+0x74/0x180 + acpi_ev_gpe_detect+0xe1/0x130 + acpi_ev_sci_xrupt_handler+0x1d/0x40 + acpi_irq+0x1c/0x40 + irq_thread_fn+0x23/0x60 + irq_thread+0x1b3/0x2f0 + kthread+0xd2/0x100 + ret_from_fork+0x34/0x50 + ret_from_fork_asm+0x1a/0x30 + + Kernel panic - not syncing: softlockup: hung tasks + +Comparing the bookworm and trixie kernels, the SCI handler was moved +from a hardirq handler to a threaded handler by the commit being +reverted. Moving to a threaded IRQ regressed kdump on this hardware; +reverting that commit restores the previous hardirq-based SCI handling +and the kdump kernel completes the crash dump without triggering the +soft lockup watchdog. + +Signed-off-by: Mohan Yelugoti +--- + drivers/acpi/osl.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c +--- a/drivers/acpi/osl.c ++++ b/drivers/acpi/osl.c +@@ -544,7 +544,11 @@ acpi_os_predefined_override(const struct acpi_predefined_names *init_val, + + static irqreturn_t acpi_irq(int irq, void *dev_id) + { +- if ((*acpi_irq_handler)(acpi_irq_context)) { ++ u32 handled; ++ ++ handled = (*acpi_irq_handler) (acpi_irq_context); ++ ++ if (handled) { + acpi_irq_handled++; + return IRQ_HANDLED; + } else { +@@ -578,8 +582,7 @@ acpi_os_install_interrupt_handler(u32 gsi, acpi_osd_handler handler, + + acpi_irq_handler = handler; + acpi_irq_context = context; +- if (request_threaded_irq(irq, NULL, acpi_irq, IRQF_SHARED | IRQF_ONESHOT, +- "acpi", acpi_irq)) { ++ if (request_irq(irq, acpi_irq, IRQF_SHARED, "acpi", acpi_irq)) { + pr_err("SCI (IRQ%d) allocation failed\n", irq); + acpi_irq_handler = NULL; + return AE_NOT_ACQUIRED; diff --git a/patch/rtnetlink-catch-EOPNOTSUPP-errors.patch b/patches-sonic/rtnetlink-catch-EOPNOTSUPP-errors.patch similarity index 100% rename from patch/rtnetlink-catch-EOPNOTSUPP-errors.patch rename to patches-sonic/rtnetlink-catch-EOPNOTSUPP-errors.patch diff --git a/patches-sonic/series b/patches-sonic/series new file mode 100644 index 000000000..f2ea0be54 --- /dev/null +++ b/patches-sonic/series @@ -0,0 +1,312 @@ +# This series applies on GIT commit 18c5597832fcf6988111b05a9a1607ae148723c +# Note: Markers should start with "###->". +# Header should end with .*-start eg: any_description-start +# Footer must end with .*-end eg: any_description-end +# Markers should not be nested + +driver-arista-net-tg3-dma-mask-4g-sb800.patch +driver-arista-net-tg3-disallow-broadcom-default-mac.patch +driver-arista-net-tg3-access-regs-indirectly.patch +driver-arista-net-tg3-napi-enable-called-flag.patch +driver-arista-pci-reassign-pref-mem.patch +driver-arista-mmcblk-not-working-on-AMD-platforms.patch +driver-arista-restrict-eMMC-drive-to-50Mhz-from-userland.patch +driver-arista-i2c-designware-shutdown.patch +driver-arista-pci-aer-disable-recovery.patch +driver-arista-pci-dpc-disable.patch +driver-arista-pci-clear-aer-error-on-recovery-failure.patch +driver-support-sff-8436-eeprom.patch +driver-support-sff-8436-eeprom-update.patch +driver-sff-8436-use-nvmem-framework.patch +driver-sff-8436-use-nvmem_device_read.patch +driver-support-sff-8436-read-write-fix.patch +driver-i2c-bus-intel-ismt-add-delay-param.patch +#driver-hwmon-max6658-fix-write-convrate.patch # TODO: update for current version +#driver-hwmon-pmbus-dni_dps460.patch # TODO: build failure +#driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch # TODO: disabling for dependency +#driver-hwmon-pmbus-dni_dps460-add-status-attributes.patch # TODO: disabling for depedency +#driver-hwmon-pmbus-dps1900.patch # TODO: build failure +#driver-hwmon-pmbus-dps200.patch # TODO: disabling for dependency +driver-support-optoe.patch +driver-support-optoe-EOF_fix.patch +driver-support-optoe-chunk-offset-fix.patch +driver-support-optoe-QSFP_DD.patch +driver-support-optoe-write-max.patch +driver-support-optoe-write-max_fix.patch +driver-support-optoe-twoaddr-a2h-access.patch +driver-support-optoe-oneaddr-pageable.patch +driver-support-optoe-update-to-linux-6.1.patch +driver-support-optoe-dynamic-write-timeout.patch +driver-support-optoe-update-to-linux-6.10.patch +driver-support-optoe-bank-support.patch +driver-net-tg3-add-param-short-preamble-and-reset.patch +driver-net-tg3-change-dma-mask-for-57766.patch +0004-dt-bindings-hwmon-Add-missing-documentation-for-lm75.patch +0005-dt-bindings-hwmon-Add-tmp75b-to-lm75.txt.patch +0006-device-tree-bindinds-add-NXP-PCT2075-as-compatible-d.patch +revert-acpi-osl-use-threaded-irq-for-sci.patch +# Upstreamed in 6.12.94 (stable commit f6bc712877f2); backport is now a no-op. +#platform-x86-int0002-remove-irqf-oneshot.patch +#Support-for-fullcone-nat.patch # TODO(trixie): update for current version + +# +# This series applies on GIT commit 1451b36b2b0d62178e42f648d8a18131af18f7d8 +# Tkernel-sched-core-fix-cgroup-fork-race.patch +# Tconfig-mlnx-sn2700.patch +# Tdriver-at24-fix-odd-length-two-byte-access.patch +# Tdriver-hwmon-lm75b-update.patch +# Tdriver-hwmon-pmbus-add-dps460-support.patch +# Tdriver-hwmon-pmbus-ucd9200-mlnx.patch +# Trtnetlink-catch-EOPNOTSUPP-errors.patch +# Tbridge-per-port-multicast-broadcast-flood-flags.patch +# + +driver-i2c-smbus-add-disable_spd-module-parameter.patch +0001-efi-memattr-Ignore-table-if-the-size-is-clearly-bogus.patch + +# Mellanox patches for 5.10 +# TODO(trixie): update for current version +###-> mellanox_sdk-start +0001-psample-define-the-macro-PSAMPLE_MD_EXTENDED_ATTR.patch +0002-drop_monitor-Extend-WJH-buffer-linux-channel.patch +0003-drop_monitor-Allow-running-multiple-instances-of-dropwatch.patch +0004-drop_monitor-Send-hardware-drop-notifications-to-the.patch +0005-drop_monitor-Do-not-block-a-configuration-change-if-it-is-a.patch +###-> mellanox_sdk-end + +###-> mellanox_hw_mgmt-start +0002-platform-mellanox-mlxbf-pmc-Add-support-for-monitori.patch +0003-platform-mellanox-mlxbf-pmc-Add-support-for-clock_me.patch +0004-platform-mellanox-mlxbf-bootctl-use-sysfs_emit-inste.patch +0005-platform-mellanox-mlxreg-hotplug-use-sysfs_emit-inst.patch +0006-platform-mellanox-mlxreg-io-use-sysfs_emit-instead-o.patch +0007-mlxbf-bootctl-Support-sysfs-entries-for-RTC-battery-.patch +0008-mlxbf-bootctl-use-sysfs_emit_at-in-secure_boot_fuse_.patch +0009-platform-mellanox-mlxbf-pmc-Support-additional-PMC-b.patch +0010-platform-mellanox-nvsw-sn2200-Fix-.items-in-nvsw_sn2.patch +0011-platform-mellanox-Fix-spelling-and-comment-clarity-i.patch +0015-i2c-mlxcpld-Support-PCIe-mapped-register-space.patch +0016-hwmon-mlxreg-fan-Add-support-for-new-flavour-of-capa.patch +0017-leds-mlxreg-Add-support-for-new-flavour-of-capabilit.patch +0018-leds-mlxreg-Remove-code-for-amber-LED-colour.patch +0019-Extend-driver-to-support-Infineon-Digital-Multi-phas.patch +0020-dt-bindings-trivial-devices-Add-infineon-xdpe1a2g7.patch +0021-leds-mlxreg-Skip-setting-LED-color-during-initializa.patch +0022-i2c-mux-Add-register-map-based-mux-driver.patch +0023-platform-mellanox-Add-support-for-dynamic-I2C-channe.patch +0024-mellanox-Relocate-mlx-platform-driver.patch +0025-platform-mellanox-mlx-platform-Cosmetic-changes.patch +0026-platform-mellanox-Rename-field-to-improve-code-reada.patch +0027-platform-mellanox-mlx-platform-Change-register-name.patch +0028-platform-mellanox-mlxreg-dpu-Add-initial-support-for.patch +0029-platform-mellanox-Introduce-support-of-Nvidia-smart-.patch +#0030-hwmon-mlxreg-fan-Separate-methods-of-fan-setting-com.patch +0031-platform-mellanox-indicate-deferred-I2C-bus-creation.patch +0032-platform_data-mlxreg-Add-capability-bit-and-mask-fie.patch +0033-platform-mellanox-mlxreg-hotplug-Add-support-for-new.patch +0034-platform-mellanox-Cosmetic-changes-to-improve-code-s.patch +0035-platform-mellanox-mlx-platform-Add-support-for-new-N.patch +0036-platform-mellanox-mlxreg-dpu-Fix-smatch-warnings.patch +0037-platform-mellanox-mlxreg-io-Extend-number-of-hwmon-a.patch +0038-platform_data-mlxreg-Add-fields-for-interrupt-storm-.patch +0039-platform-mellanox-mlxreg-hotplug-Add-support-for-han.patch +0040-platform-mellanox-mlxreg-dpu-Introduce-completion-ca.patch +0041-hwmon-add-MP2869-MP29608-MP29612-and-MP29816-series-.patch +0042-hwmon-add-MP29502-driver.patch +0043-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2855-c.patch +0044-platform_data-mlxreg-Add-non-sticky-bit-indication-f.patch +0045-platform-mellanox-mlxreg-hotplug-Add-handling-of-non.patch +0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch +#0047-hwmon-mlxreg-fan-Prevent-fans-from-getting-stuck-at-.patch +0052-platform-mellanox-Introduce-support-for-switches-equ.patch +0053-platform-mellanox-mlx-platform-Add-support-for-new-X.patch +0054-i2c-asf-Introduce-MCTP-support-over-ASF-controller.patch +0054-1-platform-mellanox-Downstream-Add-support-for-new-Nvi.patch +0055-platform-mellanox-Downstream-Add-support-DGX-flavor-.patch +0055-1-platform-mellanox-mlx-platform-Add-support-DGX-flavo.patch +0056-hwmon-pmbus-mp2845-Add-support-for-MP2845-device.patch +0057-hwmon-pmbus-mp5926-Add-support-for-MP5926-device.patch +0058-PCI-DOE-Poll-DOE-Busy-bit-for-up-to-1-second-in-pci.patch +0059-hwmon-pmbus-xdpe1a2g7b-Add-support-for-XDPE1A2G7B-5B.patch +0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch +0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch +0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch +0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch +0063-reset-phy-using-polling-function-not-register-write.patch +0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch +8000-mlxsw-Use-weak-reverse-dependencies-for-firmware-fla.patch +8002-mlxsw-i2c-SONIC-ISSU-Prevent-transaction-execution-f.patch +8003-mlxsw-minimal-Downstream-Ignore-error-reading-SPAD-r.patch +8004-leds-leds-mlxreg-Downstream-Send-udev-event-from-led.patch +8005-i2c-mlxcpld-Downstream-WA-to-avoid-error-for-SMBUS-r.patch +8006-hwmon-mlxreg-fan-Downstream-Allow-fan-speed-setting-.patch +8007-hwmon-emc2305-Downstream-Allow-fan-speed-setting-gra.patch +8008-hwmon-mlxsw-Downstream-Allow-fan-speed-setting-granu.patch +8009-mlxsw-i2c-Downstream-Add-retry-mechanism-for-failed-.patch +8010-mlxsw-minimal-Downstream-Disable-ethtool-interface.patch +8011-hwmon-pmbus-Downstream-Workaround-for-psu-attributes.patch +8013-hwmon-pmbus-mp2975-Clear-interrupts-at-probe.patch +8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch +###-> mellanox_hw_mgmt-end + +# Cisco patches for 5.10 kernel +cisco-mtd-part.patch +cisco-mdio-mux-support-acpi.patch +cisco-acpi-spi-nor.patch +cisco-Enable-static-memory-reservation-for-OIRable-PCIe-de.patch +cisco-npu-disable-other-bars.patch +0001-regmap-i2c-add-SMBus-byte-word-reg16-bus-for-adapter.patch +0002-regmap-i2c-fix-sparse-warning-in-regmap_smbus_word_w.patch + +# Nokia patches for 5.10 kernel +#0001-hwmon-emc2305-Fix-unable-to-probe-emc2301-2-3.patch # Upstreamed + +# +# Marvell platform patches for 6.1 kernel +0001-armhf_secondary_boot_online.patch +0005-dts-ac5-marvell-Add-switching-watchdog-node.patch +0007-usb-ehci-Add-support-for-ac5-with-DMA-mask-64-bit.patch +0008-mv6xxx-Fix-i2c-lock-due-to-arb-loss.patch +0009-dt-bindings-marvell-Add-ARMADA-7K-properties.patch +0010-dts-marvell-Add-support-for-7020-comexpress.patch +0011-arm64-dts-marvell-Add-Nokia-7215-IXS-A1-board.patch +0015-arm64-dts-marvell-Add-Supermicro-SSE-G3748-board.patch +0016-arm64-dts-marvell-Add-Wistron-ES-1227-54TS-board.patch +0017-Extend-driver-to-support-XMC-XM25QH256C-device.patch +0018-drivers-i2c-fix-after-kdump-crash.patch +0019-irqchip-mvebu-gicp-Clear-pending-interrupts-on-init.patch +0022-arm64-dts-marvell-Add-DTS-for-cn9131-db-comexpress-trixie.patch + +# Marvell platform patches for 6.12 kernel +0001-mmc-sdhci-cadence-Add-CN10K-eMMC-support.patch +0002-armhf-armada38x-disable-HW-UIE.patch + +# amd-pensando elba support +0001-Add-support-for-the-TI-TPS53659.patch +0002-Adding-support-LTC3888.patch +0003-i2c-designware-Support-stuck-SDA-line-recovery.patch +0004-i2c-Add-Lattice-RD1173-I2C-controller-driver.patch +0005-arm64-traps-Call-platform-handler-for-do_serror.patch +0006-Initial-support-for-the-Pensando-Elba-SoC.patch +0007-drivers-reset-Add-emmc-hardware-reset.patch +0008-dts-pensnado-Elba-flash-partitions.patch +0009-drivers-edac-Add-Elba-EDAC-support.patch +0010-drivers-soc-pensando-dev-capmem-driver.patch +0011-Interrupt-domain-controllers-for-Elba-ASIC.patch +0012-drivers-uio-UIO-drivers-for-Elba.patch +0013-drivers-pensando-soc-Boot-State-Machine-integrtion.patch +0014-drivers-soc-pensando-crash-dump-driver.patch +0015-drivers-soc-pensando-Add-the-Reset-Cause-driver.patch +0016-soc-pensando-pcie-driver.patch +0017-drivers-soc-pensando-kpcimgr-driver.patch +0018-dts-pensando-add-mnet-and-mcrypt-devices-with-reserv.patch +0019-drivers-soc-pensando-sbus-driver.patch +0020-drivers-soc-pensando-boot_count-to-sysfs-for-kdump-l.patch +0021-arch-arm64-boot-dts-psci-support.patch +0022-drivers-soc-pensando-penfw-driver.patch +0023-rtc-elba-cpld-support.patch +0024-dts-changes-for-mtfuji-and-lipari.patch +0025-Add-support-for-new-qspi-macronix-flash-mx66u1g45g.patch +0026-Create-miniaturised-diafgw-partition-for-128MB-flash.patch +0027-pcie-if-hotplug-enabled-do-immediate-reset-on-panic.patch + +# amd sfh hid +#0001-amd_sfh_hid_client_init_log_no_error_for_EOPNOTSUPP.patch + +# Security patch +#0001-Change-the-system.map-file-permission-only-readable-.patch + +# Micas patches +0001-fix-os-crash-caused-by-optoe-when-class-switch.patch +0001-tty-8250-HSUART-DMA-be-deactivated-for-DNV-CPU.patch + +# Nexthop patches +0001-x86-CPU-AMD-Print-the-reason-for-the-last-reset.patch +0002-x86-CPU-AMD-Ignore-invalid-reset-reason-value.patch +0003-xilinx-spi-make-interrupt-optional.patch +0004-xilinx-spi-platform_get_irq_optional.patch +0005-xilinx-spi-use-device-property.patch +driver-tty-serial-8250-add-support-for-fintek-F81214E.patch # Linux upstream pending +#0001-PM-runtime-Add-new-devm-functions.patch +0007-i2c-xiic-Relocate-xiic_i2c_runtime_suspend-and-xiic_.patch +0008-i2c-xiic-Add-atomic-transfer-support.patch +0009-i2c-i2c-xiic-Replace-dev_err-with-dev_err_probe-in-p.patch +0010-i2c-xiic-switch-to-devres-managed-APIs.patch +0011-i2c-xiic-remove-duplicate-error-message.patch +0012-i2c-xiic-switch-to-generic-device-property-accessors.patch +0013-i2c-xiic-cosmetic-cleanup.patch +0014-i2c-xiic-cosmetic-use-resource-format-specifier-in-d.patch +0015-i2c-xiic-use-numbered-adapter-registration.patch +0016-i2c-xiic-skip-input-clock-setup-on-non-OF-systems.patch +0017-dt-bindings-trivial-devices-Add-Delta-Q54SN120A1-and-Q54SW120A7.patch +0018-hwmon-pmbus-Add-Delta-Q54SN120A1-Q54SW120A7-chip.patch + +# +0001-PCI-AER-Simplify-pci_print_aer.patch +0002-PCI-AER-Update-statistics-before-ratelimiting.patch +0003-PCI-AER-Trace-error-event-before-ratelimiting.patch +0004-PCI-AER-Simplify-add_error_device.patch +0005-PCI-AER-Check-log-level-once-and-remember-it.patch +0006-PCI-AER-Convert-aer_get_device_error_info-aer_print_.patch +0007-PCI-AER-Factor-COR-UNCOR-error-handling-out-from-aer.patch +0008-PCI-AER-Ratelimit-correctable-and-non-fatal-error-lo.patch +0009-PCI-AER-Add-sysfs-attributes-for-log-ratelimits.patch +0010-fix-aer-ratelimiting-sysfs-output-units.patch +0011-PCI-AER-Fix-NULL-pointer-access-by-aer_info.patch +0012-PCI-AER-Avoid-NULL-pointer-dereference-in-aer_rateli.patch + +# Patches for EVPN MH +0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch +0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch +# Drop 0003 when kernel >= 6.12 (merged upstream: torvalds/linux 03dc03fa0432) +0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch + +############################################################ +# +# Let us keep Aspeed patches at the end +# +############################################################ +###-> aspeed +###-> aspeed-upstream +# Full regen against pristine 6.12.94 at Aspeed tag v00.07.04, split three ways: +# 0001 - all Aspeed-namespace files (new + updated) +# 0002 - new non-Aspeed dependency subsystems (i3c/mctp, jtag, dwc trng, ...) +# 0003 - modifications to shared/common kernel files (review surface) +# The mmc-sdhci-of-aspeed CMD6-timing and tuning backports are subsumed by +# v00.07.04 (folded into 0001), so they are no longer carried separately. +0001-aspeed-ast2700-support.patch +0002-aspeed-ast2700-non-aspeed-deps-update-to-6.12.94-00.07.04.patch +0003-aspeed-ast2700-shared-kernel-changes-update-to-6.12.94-00.07.04.patch +###-> aspeed-upstream-end +aspeed-spi-txrx-fix-full-duplex-capture.patch +nexthop-b27-dts.patch +0001-Add-device-tree-for-Nokia-BMC-H6-128-platform.patch +arista_goldfinch-dts.patch +0001-DTS-Aspeed-Nvidia-spc6-a1-bmc-dts.patch +0002-DTS-Aspeed-Nvidia-spc6-bmc-dts.patch +micas-m2-w6950-128oc-dts.patch +###-> aspeed-end + +###-> nvidia_aspeed_bmc-start +#0001-mctp-driver-net-Extend-MCTP-support.patch +#0002-mctp-aspeed-IRoT-Add-initial-support.patch +#0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch +#0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch +#0005-mctp-driver-net-Fix-MCTP-over-USB.patch +#0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch +#0007-JTAG-Aspeed-Fix-kernel-configuration.patch +#0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch +#0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch +#0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch +#0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch +#0012-usb-ehci-Add-Aspeed-AST2700-support.patch +###-> nvidia_aspeed_bmc-end + +# +# +############################################################ +# +# Internal patches will be added below (placeholder) +# Do not add any public patch below this marker +# +############################################################ diff --git a/scripts/aspeed_content_scan.py b/scripts/aspeed_content_scan.py new file mode 100644 index 000000000..91f4448f3 --- /dev/null +++ b/scripts/aspeed_content_scan.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Scan a file or directory for keyword matches, ignoring comments. + +Used by generate-aspeed-patch.sh to decide whether a new file/directory is +genuinely Aspeed-related. A plain `grep -ri aspeed` also matches attribution +comments such as "Based on aspeed-socinfo.c", which pulls in unrelated files +(e.g. drivers/soc/vt8500). This helper strips comments before matching so that +only real code/config references count. + +Usage: + aspeed_content_scan.py [regex] + +Exit status: + 0 - at least one non-comment match found + 1 - no non-comment match found (or path missing) +""" + +import os +import re +import sys + +DEFAULT_REGEX = r"aspeed|ast2[567]00|ast1[78]00" + +# Extensions / filenames that use C-style comments (/* */ and //). +C_STYLE_EXT = {".c", ".h", ".dts", ".dtsi", ".dtso", ".S", ".s"} +# Extensions / filenames that use hash (#) comments. +HASH_STYLE_EXT = {".yaml", ".yml", ".txt", ".rst", ".mk", ".defconfig", + ".config", ".sh", ".py"} +HASH_STYLE_NAMES = {"Kconfig", "Makefile", "Kbuild"} + +_C_BLOCK = re.compile(r"/\*.*?\*/", re.DOTALL) +_C_LINE = re.compile(r"//[^\n]*") +_HASH_LINE = re.compile(r"#[^\n]*") + + +def _strip_c_comments(text): + text = _C_BLOCK.sub(" ", text) + text = _C_LINE.sub(" ", text) + return text + + +def _strip_hash_comments(text): + return _HASH_LINE.sub(" ", text) + + +def _style_for(path): + base = os.path.basename(path) + if base in HASH_STYLE_NAMES or base.startswith("Kconfig") or \ + base.startswith("Makefile"): + return "hash" + ext = os.path.splitext(base)[1] + if ext in C_STYLE_EXT: + return "c" + if ext in HASH_STYLE_EXT: + return "hash" + # Unknown: strip C-style only. This is the safe default because stripping + # '#' from an unknown C-like file would remove #include/#define lines that + # may legitimately reference an aspeed header. + return "c" + + +def _strip_comments(path, text): + style = _style_for(path) + if style == "hash": + return _strip_hash_comments(text) + return _strip_c_comments(text) + + +def _file_matches(path, pattern): + try: + with open(path, "r", encoding="utf-8", errors="ignore") as fh: + text = fh.read() + except (OSError, IOError): + return False + stripped = _strip_comments(path, text) + return pattern.search(stripped) is not None + + +def main(argv): + if len(argv) < 2: + sys.stderr.write("usage: aspeed_content_scan.py [regex]\n") + return 2 + target = argv[1] + regex = argv[2] if len(argv) > 2 else DEFAULT_REGEX + pattern = re.compile(regex, re.IGNORECASE) + + if os.path.isfile(target): + return 0 if _file_matches(target, pattern) else 1 + + if os.path.isdir(target): + for root, _dirs, files in os.walk(target): + for name in files: + if _file_matches(os.path.join(root, name), pattern): + return 0 + return 1 + + return 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/scripts/generate-aspeed-patch.sh b/scripts/generate-aspeed-patch.sh new file mode 100755 index 000000000..4bc0cf283 --- /dev/null +++ b/scripts/generate-aspeed-patch.sh @@ -0,0 +1,1138 @@ +#!/bin/bash +# Generates an Aspeed AST2700 patch for the SONiC kernel. Two modes: +# +# incremental (default): diffs (kernel + all currently-committed series +# patches) against (kernel + a fresh Aspeed upstream merge). The output +# captures whatever a newer Aspeed tag adds on top of the existing +# patches-sonic/ series. Use for routine Aspeed tag bumps while the SONiC +# kernel version is unchanged. +# +# baseline (--baseline): diffs pristine sonic-src (the committed +# ###-> aspeed-upstream patches are NOT applied) against a fresh Aspeed +# merge, yielding the FULL Aspeed support for the current SONiC kernel. +# Use to reset the baseline when the SONiC kernel version itself changes, +# then feed the result to split-aspeed-patch.py to regenerate the +# 0001/0002/0003 patch set. +# +# See the design comment further below for details. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KERNEL_DIR="$(dirname "$SCRIPT_DIR")" +BUILD_DIR="$(dirname "$(dirname "$KERNEL_DIR")")" + +# Mode selection (see the header comment for the full description): +# (default) incremental - baseline tree includes the committed +# ###-> aspeed-upstream patches; output is the delta only. +# --baseline baseline reset - baseline tree is pristine sonic-src; output is +# the full Aspeed support. Pair with split-aspeed-patch.py when +# the SONiC kernel version changes. +BASELINE_MODE=0 +usage() { + cat <] $0 [--baseline] + + --baseline Generate the full baseline patch: do NOT apply the committed + ###-> aspeed-upstream patches to the baseline tree. Use when the + SONiC kernel version changes, then split the result with + scripts/split-aspeed-patch.py to regenerate 0001/0002/0003. + -h, --help Show this help and exit. + +Without --baseline the script produces an incremental patch: the delta between +the currently-committed series state and a fresh Aspeed upstream merge. +EOF +} +while [ $# -gt 0 ]; do + case "$1" in + --baseline) BASELINE_MODE=1 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage; exit 1 ;; + esac + shift +done + +# Configuration +# By default the script auto-detects the latest Aspeed release tag on the kernel +# line that matches the SONiC kernel (e.g. 6.12 -> aspeed-master-v6.12). To pin a +# specific ref, override it explicitly: +# ASPEED_TAG=v00.07.04 ./scripts/generate-aspeed-patch.sh +ASPEED_REPO="https://github.com/AspeedTech-BMC/linux.git" +WORK_DIR="${TMPDIR:-/tmp}/aspeed-patch-gen" +ASPEED_SRC="$WORK_DIR/aspeed-src" +SONIC_SRC="$WORK_DIR/sonic-src" +BASELINE_TREE="$WORK_DIR/baseline" +SONIC_ASPEED="$WORK_DIR/sonic-src-aspeed" +# AST2700 (g7) defconfig. Used by Step 5c2 to decide whether a genuinely-new +# source directory is actually needed by the Aspeed image (dual-gate: content +# scan OR defconfig membership). +ASPEED_DEFCONFIG="$ASPEED_SRC/arch/arm64/configs/aspeed_g7_defconfig" +if [ "$BASELINE_MODE" -eq 1 ]; then + OUTPUT_PATCH="$WORK_DIR/aspeed-ast2700-baseline.patch" +else + OUTPUT_PATCH="$WORK_DIR/aspeed-ast2700-incremental.patch" +fi + +# Read kernel version from Makefile +KERNEL_VERSION=$(sed -nE 's/^KERNEL_VERSION[[:space:]]*[?:+]?=[[:space:]]*//p' "$KERNEL_DIR/Makefile") +SONIC_KERNEL_URL="https://packages.trafficmanager.net/public/debian-security/pool/updates/main/l/linux/linux_${KERNEL_VERSION}.orig.tar.xz" + +# Derive the Aspeed kernel-line branch from the SONiC kernel version (e.g. 6.12). +ASPEED_KERNEL_LINE=$(printf '%s' "$KERNEL_VERSION" | grep -oE '^[0-9]+\.[0-9]+') +ASPEED_LINE_BRANCH="aspeed-master-v${ASPEED_KERNEL_LINE}" + +# Resolve which ref to use. An explicit ASPEED_TAG always wins; otherwise find +# the latest release tag published at the tip of the matching kernel line. +# Aspeed tags releases at the tip of each aspeed-master-v branch, so we +# match tags by the branch-tip commit and pick the highest version when several +# point at the same commit. +if [ -n "$ASPEED_TAG" ]; then + ASPEED_REF="$ASPEED_TAG" + echo "Using explicitly requested Aspeed ref: $ASPEED_REF" +else + echo "Resolving latest Aspeed release tag on '$ASPEED_LINE_BRANCH'..." + LINE_TIP=$(git ls-remote "$ASPEED_REPO" "refs/heads/$ASPEED_LINE_BRANCH" 2>/dev/null | awk 'END{print $1}') + LATEST_TAG="" + if [ -n "$LINE_TIP" ]; then + LATEST_TAG=$(git ls-remote --tags "$ASPEED_REPO" 2>/dev/null \ + | awk -v tip="$LINE_TIP" '$1==tip {print $2}' \ + | sed -e 's,refs/tags/,,' -e 's,\^{},,' \ + | sort -u | sort -V | tail -n1) + fi + if [ -n "$LATEST_TAG" ]; then + echo " Latest tag for ${ASPEED_KERNEL_LINE}: $LATEST_TAG (branch tip $LINE_TIP)" + ASPEED_REF="$LATEST_TAG" + else + echo " No release tag found at tip of '$ASPEED_LINE_BRANCH'; falling back to the branch." + ASPEED_REF="$ASPEED_LINE_BRANCH" + fi +fi + +# Patch author for the generated From:/Signed-off-by: lines. Resolution order: +# 1. $PATCH_AUTHOR if provided ("Name ") +# 2. the invoking user's git identity (git config user.name/user.email) +# 3. a generic fallback if git config is unset +if [ -z "$PATCH_AUTHOR" ]; then + git_name=$(git -C "$KERNEL_DIR" config user.name 2>/dev/null) + git_email=$(git -C "$KERNEL_DIR" config user.email 2>/dev/null) + if [ -n "$git_name" ] && [ -n "$git_email" ]; then + PATCH_AUTHOR="$git_name <$git_email>" + else + echo "WARNING: no PATCH_AUTHOR arg and git user.name/user.email unset;" + exit 1 + fi +fi + +# Accumulated across all apply_patches invocations. +TOTAL_APPLY_FAILURES=0 + +echo "=========================================" +if [ "$BASELINE_MODE" -eq 1 ]; then +echo "Aspeed Patch Generation Script (baseline / full)" +else +echo "Aspeed Patch Generation Script (incremental)" +fi +echo "=========================================" +echo "Kernel Version: $KERNEL_VERSION" +echo "Aspeed Upstream Ref: $ASPEED_REF" +echo "Patch Author: $PATCH_AUTHOR" +echo "Work Directory: $WORK_DIR" +echo "Output Patch: $OUTPUT_PATCH" +echo "=========================================" + +# Clean up previous run (optional - comment out to preserve for analysis) +# if [ -d "$WORK_DIR" ]; then +# echo "Cleaning up previous work directory..." +# rm -rf "$WORK_DIR" +# fi + +# Create work directory if it doesn't exist +if [ ! -d "$WORK_DIR" ]; then + echo "Creating work directory..." + mkdir -p "$WORK_DIR" +else + echo "Work directory already exists, will reuse/overwrite files..." +fi + +# Step 1: Download Aspeed kernel source +echo "" +echo "Step 1: Downloading Aspeed kernel source..." +if [ -d "$ASPEED_SRC/.git" ]; then + echo "Aspeed source already exists; verifying it matches '$ASPEED_REF'..." + # Resolve the requested ref (branch or annotated/lightweight tag) on the + # remote. For an annotated tag the peeled "^{}" line carries the commit; + # take the last matching line so we always compare against the commit a + # checkout would land on. Auto-tag detection can resolve to a newer tag + # than a cached tree, so re-fetch rather than silently reuse stale content. + want_sha=$(git ls-remote "$ASPEED_REPO" "$ASPEED_REF" "refs/tags/$ASPEED_REF^{}" 2>/dev/null | awk 'END{print $1}') + have_sha=$(git -C "$ASPEED_SRC" rev-parse HEAD 2>/dev/null) + if [ -n "$want_sha" ] && [ "$want_sha" != "$have_sha" ]; then + echo " Cached tree is at ${have_sha:-} but '$ASPEED_REF' is $want_sha; re-fetching..." + git -C "$ASPEED_SRC" fetch --depth 1 "$ASPEED_REPO" "$ASPEED_REF" + git -C "$ASPEED_SRC" checkout --quiet --detach FETCH_HEAD + git -C "$ASPEED_SRC" reset --hard --quiet FETCH_HEAD + git -C "$ASPEED_SRC" clean -fdxq + else + echo "Aspeed source already exists at $ASPEED_SRC and is at $ASPEED_REF, reusing" + fi +else + rm -rf "$ASPEED_SRC" + echo "Cloning $ASPEED_REPO @ $ASPEED_REF -> $ASPEED_SRC" + git clone --depth 1 --branch "$ASPEED_REF" "$ASPEED_REPO" "$ASPEED_SRC" +fi + +echo "Aspeed source ready (size on disk: $(du -sh "$ASPEED_SRC" | cut -f1))" + +# Step 2: Download SONiC kernel source +echo "" +echo "Step 2: Downloading SONiC kernel source..." +SONIC_TARBALL="$WORK_DIR/linux_${KERNEL_VERSION}.orig.tar.xz" +if [ -d "$SONIC_SRC" ]; then + echo "SONiC source already exists, removing and re-extracting..." + rm -rf "$SONIC_SRC" +fi +if [ -f "$SONIC_TARBALL" ]; then + echo "SONiC kernel tarball already cached at $SONIC_TARBALL, skipping download..." +else + echo "Downloading SONiC kernel tarball to $SONIC_TARBALL..." + wget -q -O "$SONIC_TARBALL" "$SONIC_KERNEL_URL" +fi +mkdir -p "$SONIC_SRC" +cd "$SONIC_SRC" +tar -xf "$SONIC_TARBALL" --strip-components=1 +echo "SONiC kernel source ready (size on disk: $(du -sh "$SONIC_SRC" | cut -f1))" + +# Series sections (delimited by "###-> NAME-start" / "###-> NAME-end") whose +# patches this script must NOT apply to either tree. Such blocks are managed +# elsewhere (e.g. the NVIDIA hw-mgmt integration rewrites nvidia_aspeed_bmc +# wholesale between its markers in sonic-buildimage) and/or depend on Aspeed +# content this script never builds; they also cancel in the diff since they'd +# land on both sides. To exclude a new section, add its base-name here — no +# other code change needed. (The ###-> aspeed section is handled separately.) +SKIP_SECTIONS=("nvidia_aspeed_bmc") + +# _is_skip_section NAME -> returns 0 if NAME is listed in SKIP_SECTIONS, else 1. +_is_skip_section() { + local needle="$1" s + for s in "${SKIP_SECTIONS[@]}"; do + [ "$s" = "$needle" ] && return 0 + done + return 1 +} + +# validate_series_ordering: assert that every hand-written aspeed-section patch +# (one inside ###-> aspeed but outside any ###-> aspeed-upstream sub-section) +# comes AFTER all ###-> aspeed-upstream sub-sections. See above for motivation +validate_series_ordering() { + local series_file="$KERNEL_DIR/patches-sonic/series" + local in_aspeed=0 in_upstream=0 in_skip=0 seen_handwritten=0 + local handwritten_example="" sect="" + + # Reads via redirection (not a pipe), so this loop runs in the current + # shell — exiting on the first violation aborts the whole script directly. + while IFS= read -r line; do + case "$line" in + "###-> aspeed") in_aspeed=1; continue ;; + "###-> aspeed-end") in_aspeed=0; continue ;; + "###-> aspeed-upstream-end") in_upstream=0; continue ;; + "###-> aspeed-upstream") + if [ $seen_handwritten -eq 1 ]; then + echo " ERROR: ###-> aspeed-upstream sub-section opens after hand-written" >&2 + echo " patch '$handwritten_example' — all upstream sub-sections must" >&2 + echo " precede every hand-written aspeed-section patch." >&2 + echo "ABORT: patches-sonic/series ordering invariant violated." >&2 + exit 1 + fi + in_upstream=1 + continue + ;; + "###-> "*"-start") + sect="${line#"###-> "}"; sect="${sect%-start}" + if _is_skip_section "$sect"; then in_skip=1; fi + continue + ;; + "###-> "*"-end") + sect="${line#"###-> "}"; sect="${sect%-end}" + if _is_skip_section "$sect"; then in_skip=0; fi + continue + ;; + esac + + # Comments and blanks don't affect ordering. + [[ "$line" =~ ^#.*$ ]] && continue + [[ -z "$line" ]] && continue + + # A real patch entry inside the aspeed section, outside any upstream + # sub-section (and outside any skip section), is hand-written. + if [ $in_aspeed -eq 1 ] && [ $in_upstream -eq 0 ] && [ $in_skip -eq 0 ]; then + seen_handwritten=1 + local stripped="${line%%#*}" + stripped="${stripped%"${stripped##*[![:space:]]}"}" + [ -n "$stripped" ] && handwritten_example="$stripped" + fi + done < "$series_file" +} + +# apply_patches: iterate patches-sonic/series in order and apply patches into +# $target_dir, filtered by $mode. +# +# Modes: +# non-aspeed - apply entries OUTSIDE the ###-> aspeed section AND outside +# any SKIP_SECTIONS block (see that list above). Skip-section +# patches are vendor-managed and/or Aspeed-dependent and cancel +# in the diff regardless, so they are never applied. +# aspeed-upstream - apply entries INSIDE the ###-> aspeed section AND inside an +# ###-> aspeed-upstream sub-section. Patches outside any +# ###-> aspeed-upstream sub-section (i.e. handwritten patc) +# are deliberately NOT applied — see the design comment at +# the top of this file. +apply_patches() { + local target_dir="$1" + local mode="$2" + local label="$3" + + echo "" + echo "Applying patches into $target_dir [$label, mode=$mode]" + + local applied=0 + local skipped=0 + local failed=0 + local in_aspeed=0 + local in_upstream=0 + local in_skip=0 + local sect="" + local series_file="$KERNEL_DIR/patches-sonic/series" + + pushd "$target_dir" > /dev/null + set +e + + while IFS= read -r line; do + if [[ "$line" == "###-> aspeed" ]]; then + in_aspeed=1 + continue + elif [[ "$line" == "###-> aspeed-end" ]]; then + in_aspeed=0 + continue + elif [[ "$line" == "###-> aspeed-upstream" ]]; then + in_upstream=1 + continue + elif [[ "$line" == "###-> aspeed-upstream-end" ]]; then + in_upstream=0 + continue + elif [[ "$line" == "###-> "*"-start" ]]; then + sect="${line#"###-> "}"; sect="${sect%-start}" + if _is_skip_section "$sect"; then in_skip=1; fi + continue + elif [[ "$line" == "###-> "*"-end" ]]; then + sect="${line#"###-> "}"; sect="${sect%-end}" + if _is_skip_section "$sect"; then in_skip=0; fi + continue + fi + + if [[ "$line" =~ ^#.*$ ]] || [[ -z "$line" ]]; then + continue + fi + + # Quilt-style trailing comments: ".patch # some note". + # Strip the comment and surrounding whitespace before we look up + # the patch file. (Marker lines are matched above by full equality, + # so this stripping doesn't affect them.) + line="${line%%#*}" + line="${line%"${line##*[![:space:]]}"}" + line="${line#"${line%%[![:space:]]*}"}" + if [[ -z "$line" ]]; then + continue + fi + + local should_apply=0 + case "$mode" in + non-aspeed) + [ $in_aspeed -eq 0 ] && [ $in_skip -eq 0 ] && should_apply=1 + ;; + aspeed-upstream) + if [ $in_aspeed -eq 1 ] && [ $in_upstream -eq 1 ]; then + should_apply=1 + fi + ;; + *) + echo "apply_patches: unknown mode '$mode'" >&2 + set -e + popd > /dev/null + return 2 + ;; + esac + + if [ $should_apply -eq 0 ]; then + skipped=$((skipped + 1)) + continue + fi + + local patch_file="$KERNEL_DIR/patches-sonic/$line" + if [ -f "$patch_file" ]; then + echo " Applying: $line" + local patch_output patch_rc + patch_output=$(patch -p1 < "$patch_file" 2>&1) + patch_rc=$? + echo "$patch_output" | sed 's/^/ /' + + if [ $patch_rc -ne 0 ]; then + echo " ERROR: $line FAILED to apply (exit $patch_rc)" + failed=$((failed + 1)) + else + applied=$((applied + 1)) + fi + else + echo " ERROR: patch file not found: $line" + failed=$((failed + 1)) + fi + done < "$series_file" + + set -e + popd > /dev/null + + echo " Result: applied=$applied skipped=$skipped failed=$failed" + + TOTAL_APPLY_FAILURES=$((TOTAL_APPLY_FAILURES + failed)) +} + +# Validate series ordering before building any trees (cheap, fail-fast). +echo "" +echo "Validating patches-sonic/series ordering..." +validate_series_ordering +echo "Series ordering OK" + +# Step 3: Apply non-aspeed patches into sonic-src (shared by both trees). +echo "" +echo "Step 3: Applying non-Aspeed SONiC patches into sonic-src..." +apply_patches "$SONIC_SRC" "non-aspeed" "sonic-src (shared base)" + +# Step 3b: Build the baseline tree the output diff is taken against. +# incremental mode: sonic-src + the committed ###-> aspeed-upstream patches +# (the current committed upstream-merge state, so the diff +# captures only what a newer Aspeed tag adds). +# baseline mode : pristine sonic-src only (the aspeed-upstream patches are +# NOT applied), so the diff is the FULL Aspeed support for a +# SONiC kernel baseline reset. +# Hand-written aspeed-section patches are intentionally skipped in both modes +# (they cancel by absence — they're applied to neither tree). +echo "" +echo "Step 3b: Building baseline tree..." +if [ -d "$BASELINE_TREE" ]; then + echo "Baseline tree already exists, removing and recreating..." + rm -rf "$BASELINE_TREE" +fi +cp -a "$SONIC_SRC" "$BASELINE_TREE" +if [ "$BASELINE_MODE" -eq 1 ]; then + echo " baseline mode: NOT applying ###-> aspeed-upstream patches (full patch)" +else + apply_patches "$BASELINE_TREE" "aspeed-upstream" "baseline" +fi +echo "Baseline tree ready: $(du -sh $BASELINE_TREE | cut -f1)" + +# Steps 4 + 5: build the target tree by merging fresh Aspeed upstream content +# into a copy of sonic-src. Wrapped in a function purely to keep the merge +# logic isolated from the control flow above. +merge_aspeed_files() { + local merge_label="$1" + echo "" + echo "=========================================" + echo "Building target tree: $merge_label" + echo " ASPEED_SRC: $ASPEED_SRC" + echo " SONIC_ASPEED: $SONIC_ASPEED" + echo "=========================================" + +# Step 4: Create sonic-src-aspeed by copying sonic-src +echo "" +echo "Step 4: Creating sonic-src-aspeed..." +if [ -d "$SONIC_ASPEED" ]; then + echo "sonic-src-aspeed already exists, removing and recreating..." + rm -rf "$SONIC_ASPEED" +fi +cp -a "$SONIC_SRC" "$SONIC_ASPEED" +echo "Copy complete: $(du -sh $SONIC_ASPEED | cut -f1)" + +# Step 5: Merge Aspeed files +echo "" +echo "Step 5: Merging Aspeed AST2700 files..." + +# Step 5a: Copy all DTS files +echo "Step 5a: Copying ARM64 DTS files..." +mkdir -p "$SONIC_ASPEED/arch/arm64/boot/dts/aspeed" +cp -rv "$ASPEED_SRC/arch/arm64/boot/dts/aspeed/"* "$SONIC_ASPEED/arch/arm64/boot/dts/aspeed/" | wc -l + +# Step 5a2: Preserve EXISTING ARM32 DTS files unchanged (don't add new ones) +echo "Step 5a2: Preserving existing ARM32 DTS files unchanged..." +# We need to preserve existing ARM32 DTS files exactly as they are in SONiC kernel +# We don't want to modify them OR add new ARM32 DTS files from Aspeed kernel because +# they have DTC syntax (/bits/) that's incompatible with SONiC kernel's DTC version +if [ -d "$SONIC_SRC/arch/arm/boot/dts/aspeed" ]; then + mkdir -p "$SONIC_ASPEED/arch/arm/boot/dts/aspeed" + + # Copy ALL existing files from SONiC kernel unchanged + ARM32_PRESERVED=0 + for sonic_file in "$SONIC_SRC/arch/arm/boot/dts/aspeed/"*; do + if [ -f "$sonic_file" ]; then + filename=$(basename "$sonic_file") + # Always copy from SONiC source to preserve existing files unchanged + cp -v "$sonic_file" "$SONIC_ASPEED/arch/arm/boot/dts/aspeed/$filename" + ARM32_PRESERVED=$((ARM32_PRESERVED + 1)) + fi + done + echo " Preserved $ARM32_PRESERVED existing ARM32 DTS files unchanged" +else + echo " No ARM32 DTS directory in SONiC source" +fi + +# Step 5b: Find and intelligently merge Kconfig files that have Aspeed-related changes +echo "Step 5b: Intelligently merging modified Kconfig files..." +KCONFIG_COUNT=0 + +SMART_MERGE="$SCRIPT_DIR/smart_merge.py" + +find "$ASPEED_SRC" -name "Kconfig" -type f > "$WORK_DIR/kconfig-files.txt" +while IFS= read -r aspeed_kconfig; do + rel_path="${aspeed_kconfig#$ASPEED_SRC/}" + sonic_kconfig="$SONIC_SRC/$rel_path" + target_kconfig="$SONIC_ASPEED/$rel_path" + + # Only process if the file exists in SONiC source + if [ -f "$sonic_kconfig" ]; then + # Check if there are differences + if ! diff -q "$sonic_kconfig" "$aspeed_kconfig" > /dev/null 2>&1; then + set +e # Temporarily disable exit on error for grep + + # Check if file is in Aspeed-related directory + echo "$rel_path" | grep -qi "aspeed\|ast2[567]00\|ast1[78]00" + in_aspeed_dir=$? + + # Check if diff has added lines with Aspeed content + diff -u "$sonic_kconfig" "$aspeed_kconfig" | grep "^+" | grep -v "^+++" | grep -qi "aspeed\|ast2[567]00\|ast1[78]00" + has_aspeed_additions=$? + + if [ $in_aspeed_dir -eq 0 ]; then + # File is in Aspeed-specific directory - copy entirely + echo " Aspeed-specific file: $rel_path" + mkdir -p "$SONIC_ASPEED/$(dirname $rel_path)" + cp "$aspeed_kconfig" "$target_kconfig" + KCONFIG_COUNT=$((KCONFIG_COUNT + 1)) + elif [ $has_aspeed_additions -eq 0 ]; then + # Shared file with Aspeed additions - smart merge + echo " Shared file with Aspeed changes: $rel_path (smart merge)" + mkdir -p "$SONIC_ASPEED/$(dirname $rel_path)" + + # Try smart merge, fall back to full copy if it fails + if ! python3 "$SMART_MERGE" "$sonic_kconfig" "$aspeed_kconfig" "$target_kconfig" 2>/dev/null; then + echo " Smart merge failed, using full file" + cp "$aspeed_kconfig" "$target_kconfig" + fi + + KCONFIG_COUNT=$((KCONFIG_COUNT + 1)) + fi + set -e # Re-enable exit on error + fi + fi +done < "$WORK_DIR/kconfig-files.txt" +echo " Processed $KCONFIG_COUNT Kconfig files with Aspeed changes" + +# Step 5c: Find and intelligently merge Makefile files that have Aspeed-related changes +echo "Step 5c: Intelligently merging modified Makefile files..." +MAKEFILE_COUNT=0 +find "$ASPEED_SRC" -name "Makefile" -type f > "$WORK_DIR/makefile-files.txt" +while IFS= read -r aspeed_makefile; do + rel_path="${aspeed_makefile#$ASPEED_SRC/}" + sonic_makefile="$SONIC_SRC/$rel_path" + target_makefile="$SONIC_ASPEED/$rel_path" + + # Only process if the file exists in SONiC source + if [ -f "$sonic_makefile" ]; then + # Check if there are differences + if ! diff -q "$sonic_makefile" "$aspeed_makefile" > /dev/null 2>&1; then + set +e # Temporarily disable exit on error for grep + + # Check if file is in Aspeed-related directory + echo "$rel_path" | grep -qi "aspeed\|ast2[567]00\|ast1[78]00" + in_aspeed_dir=$? + + # Check if diff has added lines with Aspeed content + diff -u "$sonic_makefile" "$aspeed_makefile" | grep "^+" | grep -v "^+++" | grep -qi "aspeed\|ast2[567]00\|ast1[78]00" + has_aspeed_additions=$? + + # Skip ALL ARM32 Makefiles (we only need ARM64 for AST2700) + if echo "$rel_path" | grep -q "^arch/arm/"; then + echo " Skipping ARM32 Makefile: $rel_path (not needed for ARM64)" + elif [ $in_aspeed_dir -eq 0 ]; then + # File is in Aspeed-specific directory - copy entirely + echo " Aspeed-specific file: $rel_path" + mkdir -p "$SONIC_ASPEED/$(dirname $rel_path)" + cp "$aspeed_makefile" "$target_makefile" + MAKEFILE_COUNT=$((MAKEFILE_COUNT + 1)) + elif [ $has_aspeed_additions -eq 0 ]; then + # Shared file with Aspeed additions - smart merge + echo " Shared file with Aspeed changes: $rel_path (smart merge)" + mkdir -p "$SONIC_ASPEED/$(dirname $rel_path)" + + # Use smart merge, fall back to full copy if it fails + if ! python3 "$SMART_MERGE" "$sonic_makefile" "$aspeed_makefile" "$target_makefile" 2>/dev/null; then + echo " Smart merge failed, using full file" + cp "$aspeed_makefile" "$target_makefile" + fi + + MAKEFILE_COUNT=$((MAKEFILE_COUNT + 1)) + fi + set -e # Re-enable exit on error + fi + fi +done < "$WORK_DIR/makefile-files.txt" +echo " Processed $MAKEFILE_COUNT Makefile files with Aspeed changes" + +# Step 5c2: Find and copy directories referenced by new Kconfig source statements +echo "Step 5c2: Copying directories referenced by new Kconfig source statements..." +KCONFIG_DIR_COUNT=0 +# Find all Kconfig files that were modified +find "$ASPEED_SRC" -name "Kconfig" -type f > "$WORK_DIR/kconfig-check-source.txt" +while IFS= read -r aspeed_kconfig; do + rel_path="${aspeed_kconfig#$ASPEED_SRC/}" + sonic_kconfig="$SONIC_SRC/$rel_path" + + # Only process if the file exists in SONiC source + if [ -f "$sonic_kconfig" ]; then + # Check if there are new source statements added + set +e + # Find lines that start with +source (added lines) + new_sources=$(diff -u "$sonic_kconfig" "$aspeed_kconfig" | grep "^+source" | sed 's/^+source "\(.*\)"/\1/') + set -e + + # For each new source statement, copy the referenced directory + if [ -n "$new_sources" ]; then + while IFS= read -r source_path; do + if [ -n "$source_path" ]; then + # The source path is relative to kernel root (e.g., "drivers/char/hw_random/dwc/Kconfig") + source_file="$ASPEED_SRC/$source_path" + source_dir=$(dirname "$source_file") + + # Check if the source directory exists + if [ -d "$source_dir" ]; then + rel_source_dir="${source_dir#$ASPEED_SRC/}" + # Only copy directories that are genuinely NEW (absent from the + # SONiC base). A new +source statement can reference a sibling + # Kconfig file inside a pre-existing shared directory (e.g. the + # Aspeed tree adds `source "drivers/gpu/drm/Kconfig.debug"`), in + # which case dirname resolves to a large shared directory such as + # drivers/gpu/drm. Wholesale-copying that newer-kernel directory + # clobbers unrelated files and drops non-Aspeed obj-/subdir-y + # entries. Such shared directories are handled by the smart-merge + # steps (5b/5c) and the targeted file copies (5d), so skip them here. + if [ -d "$SONIC_SRC/$rel_source_dir" ]; then + echo " Skipping pre-existing shared directory: $rel_source_dir (handled by smart merge / targeted copy)" + else + # Genuinely new directory (absent from the SONiC base) + # referenced by a new +source statement. "New to SONiC" + # is NOT the same as "Aspeed addition": the SONiC/Debian + # tree strips many vanilla drivers (e.g. staging/rtl8712) + # that the near-vanilla Aspeed tree still carries. Only + # bring the directory in if it is genuinely Aspeed-relevant: + # (a) its code/config references Aspeed (content scan), or + # (b) a Kconfig symbol it defines is enabled in the Aspeed + # g7 defconfig -- this catches Intel-authored but + # Aspeed-required stacks like drivers/i3c/mctp + # (CONFIG_I3C_MCTP=y) whose code carries no "aspeed" + # marker. + set +e + python3 "$SCRIPT_DIR/aspeed_content_scan.py" "$source_dir" 2>/dev/null + wanted=$? + if [ $wanted -ne 0 ] && [ -f "$ASPEED_DEFCONFIG" ]; then + dir_syms=$(grep -hoE '^[[:space:]]*(menu)?config [A-Z0-9_]+' "$source_dir/Kconfig" 2>/dev/null | awk '{print $NF}') + for sym in $dir_syms; do + if grep -qE "^CONFIG_${sym}=(y|m)\$" "$ASPEED_DEFCONFIG"; then + wanted=0 + break + fi + done + fi + set -e + + if [ $wanted -eq 0 ]; then + echo " Found new source directory: $rel_source_dir" + mkdir -p "$SONIC_ASPEED/$rel_source_dir" + cp -rv "$source_dir/"* "$SONIC_ASPEED/$rel_source_dir/" | wc -l + KCONFIG_DIR_COUNT=$((KCONFIG_DIR_COUNT + 1)) + else + echo " Skipping new non-Aspeed directory: $rel_source_dir (no Aspeed content, not enabled by aspeed_g7_defconfig)" + fi + fi + fi + fi + done <<< "$new_sources" + fi + fi +done < "$WORK_DIR/kconfig-check-source.txt" +echo " Copied $KCONFIG_DIR_COUNT directories referenced by Kconfig source statements" + +# Step 5d: Find and copy all Aspeed-related .c and .h files +echo "Step 5d: Copying Aspeed-related source files..." +SOURCE_COUNT=0 +find "$ASPEED_SRC/drivers" "$ASPEED_SRC/crypto" "$ASPEED_SRC/arch/arm64" -type f \( -name "*aspeed*.c" -o -name "*aspeed*.h" -o -name "*ast2[567]00*.c" -o -name "*ast2[567]00*.h" -o -name "*ast1[78]00*.c" -o -name "*ast1[78]00*.h" \) 2>/dev/null > "$WORK_DIR/aspeed-source-files.txt" +while IFS= read -r aspeed_file; do + rel_path="${aspeed_file#$ASPEED_SRC/}" + sonic_file="$SONIC_SRC/$rel_path" + + # Copy if file doesn't exist in SONiC or is different + if [ ! -f "$sonic_file" ] || ! diff -q "$sonic_file" "$aspeed_file" > /dev/null 2>&1; then + mkdir -p "$SONIC_ASPEED/$(dirname $rel_path)" + cp -v "$aspeed_file" "$SONIC_ASPEED/$rel_path" + SOURCE_COUNT=$((SOURCE_COUNT + 1)) + fi +done < "$WORK_DIR/aspeed-source-files.txt" +echo " Copied $SOURCE_COUNT Aspeed source files" + +# Step 5e: Copy specific driver directories that contain Aspeed support +# NOTE: Do NOT include drivers/edac here - it's a shared directory with non-Aspeed content +# drivers/edac/Kconfig and Makefile are handled by Steps 5b/5c (smart merge) +# Aspeed-specific .c files are handled by Step 5d +echo "Step 5e: Copying Aspeed-specific driver directories..." +ASPEED_DIRS=( + "drivers/soc/aspeed" + "drivers/media/platform/aspeed" + "drivers/pinctrl/aspeed" + "drivers/crypto/aspeed" + "drivers/net/ethernet/faraday" + # "drivers/edac" - REMOVED: This is a shared directory, handled by Steps 5b/5c/5d + "drivers/usb/gadget/udc/aspeed-vhub" +) + +for dir in "${ASPEED_DIRS[@]}"; do + if [ -d "$ASPEED_SRC/$dir" ]; then + echo " Copying directory: $dir" + mkdir -p "$SONIC_ASPEED/$dir" + cp -rv "$ASPEED_SRC/$dir/"* "$SONIC_ASPEED/$dir/" 2>/dev/null | wc -l + fi +done + +# Step 5e1: Copy only Aspeed-specific irqchip drivers (not entire directory) +echo "Step 5e1: Copying Aspeed-specific irqchip drivers..." +IRQCHIP_COUNT=0 +if [ -d "$ASPEED_SRC/drivers/irqchip" ]; then + mkdir -p "$SONIC_ASPEED/drivers/irqchip" + # Only copy files with "aspeed" in the name + for irqchip_file in "$ASPEED_SRC/drivers/irqchip/"*aspeed*; do + if [ -f "$irqchip_file" ]; then + filename=$(basename "$irqchip_file") + echo " Copying Aspeed irqchip: $filename" + cp -v "$irqchip_file" "$SONIC_ASPEED/drivers/irqchip/$filename" + IRQCHIP_COUNT=$((IRQCHIP_COUNT + 1)) + fi + done + echo " Copied $IRQCHIP_COUNT Aspeed irqchip drivers" +fi + +# Step 5e2: Remove ARM32-only drivers that use ARM32-specific symbols +echo "Step 5e2: Removing ARM32-only drivers..." +# aspeed-sbc.c is AST2600-only and uses arch_debugfs_dir (ARM32-specific symbol) +if [ -f "$SONIC_ASPEED/drivers/soc/aspeed/aspeed-sbc.c" ]; then + echo " Removing aspeed-sbc.c (AST2600-only, uses ARM32-specific arch_debugfs_dir)" + rm -f "$SONIC_ASPEED/drivers/soc/aspeed/aspeed-sbc.c" + + # Also remove the Makefile entry for aspeed-sbc + if [ -f "$SONIC_ASPEED/drivers/soc/aspeed/Makefile" ]; then + echo " Removing aspeed-sbc.o from Makefile" + sed -i '/obj-\$(CONFIG_ASPEED_SBC)/d' "$SONIC_ASPEED/drivers/soc/aspeed/Makefile" + fi +fi + +# Step 5f: Copy include files +echo "Step 5f: Copying include files..." +find "$ASPEED_SRC/include" -type f \( -name "*aspeed*" -o -name "*ast2700*" -o -name "*ast1700*" -o -name "*ast1800*" -o -name "*ast2600*" -o -name "*ast2500*" \) > "$WORK_DIR/include-files.txt" +while IFS= read -r f; do + rel_path="${f#$ASPEED_SRC/}" + mkdir -p "$SONIC_ASPEED/$(dirname $rel_path)" + cp -v "$f" "$SONIC_ASPEED/$rel_path" +done < "$WORK_DIR/include-files.txt" + +# Step 5g: Skip scripts/dtc/include-prefixes (they are symlinks to arch/*/boot/dts) +echo "Step 5g: Skipping DTC include prefixes (symlinks to arch/*/boot/dts)..." +# scripts/dtc/include-prefixes/arm64 -> ../../../arch/arm64/boot/dts +# scripts/dtc/include-prefixes/arm -> ../../../arch/arm/boot/dts +# These are symlinks, so files copied to arch/*/boot/dts will automatically appear here +# Copying them separately would create duplicate entries in the patch + +# Step 5h: Smart merge arch/arm64 Kconfig files with Aspeed changes +echo "Step 5h: Smart merging arch/arm64 Kconfig files..." +if [ -f "$ASPEED_SRC/arch/arm64/Kconfig.platforms" ]; then + if ! diff -q "$SONIC_SRC/arch/arm64/Kconfig.platforms" "$ASPEED_SRC/arch/arm64/Kconfig.platforms" > /dev/null 2>&1; then + if diff -u "$SONIC_SRC/arch/arm64/Kconfig.platforms" "$ASPEED_SRC/arch/arm64/Kconfig.platforms" | grep -qi "aspeed"; then + echo " Smart merging arch/arm64/Kconfig.platforms with Aspeed changes" + if ! python3 "$SMART_MERGE" "$SONIC_SRC/arch/arm64/Kconfig.platforms" \ + "$ASPEED_SRC/arch/arm64/Kconfig.platforms" \ + "$SONIC_ASPEED/arch/arm64/Kconfig.platforms" 2>/dev/null; then + echo " Smart merge failed, using full file" + cp "$ASPEED_SRC/arch/arm64/Kconfig.platforms" "$SONIC_ASPEED/arch/arm64/Kconfig.platforms" + fi + fi + fi +fi + +# Step 5i: Smart merge arch/arm64/boot/dts Makefile +echo "Step 5i: Smart merging DTS Makefile..." +if [ -f "$ASPEED_SRC/arch/arm64/boot/dts/Makefile" ]; then + if ! diff -q "$SONIC_SRC/arch/arm64/boot/dts/Makefile" "$ASPEED_SRC/arch/arm64/boot/dts/Makefile" > /dev/null 2>&1; then + if diff -u "$SONIC_SRC/arch/arm64/boot/dts/Makefile" "$ASPEED_SRC/arch/arm64/boot/dts/Makefile" | grep -qi "aspeed"; then + echo " Smart merging arch/arm64/boot/dts/Makefile with Aspeed changes" + if ! python3 "$SMART_MERGE" "$SONIC_SRC/arch/arm64/boot/dts/Makefile" \ + "$ASPEED_SRC/arch/arm64/boot/dts/Makefile" \ + "$SONIC_ASPEED/arch/arm64/boot/dts/Makefile" 2>/dev/null; then + echo " Smart merge failed, using full file" + cp "$ASPEED_SRC/arch/arm64/boot/dts/Makefile" "$SONIC_ASPEED/arch/arm64/boot/dts/Makefile" + fi + fi + fi +fi + +# Step 5j: Detect and copy dependencies of Aspeed files +echo "Step 5j: Detecting and copying dependencies..." +DEPENDENCY_COUNT=0 + +# Create a list of all Aspeed .c files that were copied +find "$SONIC_ASPEED" -type f -name "*.c" \( -path "*/aspeed/*" -o -name "*aspeed*.c" -o -name "*ast2[567]00*.c" -o -name "*ast1[78]00*.c" \) > "$WORK_DIR/aspeed-c-files.txt" + +# For each Aspeed .c file, find #include statements and check if those files are different in Aspeed source +while IFS= read -r aspeed_c_file; do + # Extract #include statements (both "file.h" and ) + grep -h '^#include' "$aspeed_c_file" 2>/dev/null | sed -n 's/^#include [<"]\(.*\)[>"]/\1/p' > "$WORK_DIR/includes-temp.txt" || true + + while IFS= read -r include_file; do + if [ -n "$include_file" ]; then + # Try to find the include file in common locations + for base_dir in "include/linux" "include/trace/events" "include/dt-bindings" "drivers/net/mctp" "include/uapi/linux"; do + aspeed_include="$ASPEED_SRC/$base_dir/$include_file" + sonic_include="$SONIC_SRC/$base_dir/$include_file" + + # If the file exists in Aspeed source and is different from SONiC source + if [ -f "$aspeed_include" ]; then + # Check if file doesn't exist in SONiC or is different + if [ ! -f "$sonic_include" ] || ! diff -q "$sonic_include" "$aspeed_include" > /dev/null 2>&1; then + # Check if the file has Aspeed-related changes or is a dependency + set +e + # Check if it's a new file or has Aspeed-related content + if [ ! -f "$sonic_include" ]; then + # New file - check if it has Aspeed references or is in our dependency list + if grep -qi "aspeed\|ast2[567]00\|ast1[78]00\|mctp.*pcie.*vdm\|xdma" "$aspeed_include" 2>/dev/null; then + echo " Found dependency (new file): $base_dir/$include_file" + mkdir -p "$SONIC_ASPEED/$base_dir" + cp -v "$aspeed_include" "$SONIC_ASPEED/$base_dir/$include_file" + DEPENDENCY_COUNT=$((DEPENDENCY_COUNT + 1)) + fi + else + # Existing file - check if diff has Aspeed-related additions or new flags/definitions + # For UAPI headers, also check for new flags, enums, or definitions that might be needed + if echo "$base_dir" | grep -q "uapi"; then + # For UAPI headers, copy if there are ANY additions (they're API definitions) + diff -u "$sonic_include" "$aspeed_include" | grep "^+" | grep -v "^+++" | grep -q "." 2>/dev/null + if [ $? -eq 0 ]; then + echo " Found dependency (modified UAPI): $base_dir/$include_file" + mkdir -p "$SONIC_ASPEED/$base_dir" + cp -v "$aspeed_include" "$SONIC_ASPEED/$base_dir/$include_file" + DEPENDENCY_COUNT=$((DEPENDENCY_COUNT + 1)) + fi + else + # For non-UAPI headers, check if diff has Aspeed-related additions + diff -u "$sonic_include" "$aspeed_include" | grep "^+" | grep -v "^+++" | grep -qi "aspeed\|ast2[567]00\|ast1[78]00\|devm_clk_hw_register_gate_parent_hw\|mctp.*pcie.*vdm\|xdma" 2>/dev/null + if [ $? -eq 0 ]; then + echo " Found dependency (modified file): $base_dir/$include_file" + mkdir -p "$SONIC_ASPEED/$base_dir" + cp -v "$aspeed_include" "$SONIC_ASPEED/$base_dir/$include_file" + DEPENDENCY_COUNT=$((DEPENDENCY_COUNT + 1)) + fi + fi + fi + set -e + fi + fi + done + fi + done < "$WORK_DIR/includes-temp.txt" +done < "$WORK_DIR/aspeed-c-files.txt" + +# Also check for specific known dependencies +echo " Checking for known dependencies..." +KNOWN_DEPS=( + "include/linux/mctp-pcie-vdm.h" + "drivers/net/mctp/mctp-pcie-vdm.c" + "include/trace/events/xdma.h" + "include/linux/clk-provider.h" + "include/uapi/linux/videodev2.h" + "drivers/pwm/core.c" + "include/linux/pwm.h" +) + +for dep in "${KNOWN_DEPS[@]}"; do + aspeed_dep="$ASPEED_SRC/$dep" + sonic_dep="$SONIC_SRC/$dep" + + if [ -f "$aspeed_dep" ]; then + # Check if file doesn't exist in SONiC or is different + if [ ! -f "$sonic_dep" ] || ! diff -q "$sonic_dep" "$aspeed_dep" > /dev/null 2>&1; then + set +e + # Check if this is a UAPI file + is_uapi=0 + echo "$dep" | grep -q "uapi" && is_uapi=1 + + # For new files, check if they have relevant content + if [ ! -f "$sonic_dep" ]; then + if grep -qi "aspeed\|ast2[567]00\|ast1[78]00\|mctp.*pcie.*vdm\|xdma\|devm_clk_hw_register_gate_parent_hw" "$aspeed_dep" 2>/dev/null; then + echo " Found known dependency (new): $dep" + mkdir -p "$SONIC_ASPEED/$(dirname $dep)" + cp -v "$aspeed_dep" "$SONIC_ASPEED/$dep" + DEPENDENCY_COUNT=$((DEPENDENCY_COUNT + 1)) + fi + else + # For existing files, check if diff has relevant additions + if [ $is_uapi -eq 1 ]; then + # For UAPI files, copy if there are ANY additions (they're API definitions) + diff -u "$sonic_dep" "$aspeed_dep" | grep "^+" | grep -v "^+++" | grep -q "." 2>/dev/null + if [ $? -eq 0 ]; then + echo " Found known dependency (modified UAPI): $dep" + mkdir -p "$SONIC_ASPEED/$(dirname $dep)" + cp -v "$aspeed_dep" "$SONIC_ASPEED/$dep" + DEPENDENCY_COUNT=$((DEPENDENCY_COUNT + 1)) + fi + else + # For non-UAPI files, check for Aspeed-related additions or PWM API exports + diff -u "$sonic_dep" "$aspeed_dep" | grep "^+" | grep -v "^+++" | grep -qi "aspeed\|ast2[567]00\|ast1[78]00\|devm_clk_hw_register_gate_parent_hw\|mctp.*pcie.*vdm\|xdma\|pwm_request_from_chip" 2>/dev/null + if [ $? -eq 0 ]; then + echo " Found known dependency (modified): $dep" + mkdir -p "$SONIC_ASPEED/$(dirname $dep)" + cp -v "$aspeed_dep" "$SONIC_ASPEED/$dep" + DEPENDENCY_COUNT=$((DEPENDENCY_COUNT + 1)) + fi + fi + fi + set -e + fi + fi +done + +echo " Copied $DEPENDENCY_COUNT dependency files" + +# Step 5k: Summary of what was copied +echo "" +echo "=========================================" +echo "Merge Summary:" +echo "=========================================" +echo "DTS files: Copied all from arch/arm64/boot/dts/aspeed/ and arch/arm/boot/dts/aspeed/" +echo "Kconfig files: Scanned and copied files with Aspeed changes" +echo "Makefile files: Scanned and copied files with Aspeed changes" +echo "Kconfig source dirs: Copied $KCONFIG_DIR_COUNT directories referenced by new source statements" +echo "Source files: Copied all Aspeed-related .c and .h files" +echo "Driver directories: Copied soc, media, pinctrl, crypto, faraday, edac, aspeed-vhub" +echo "Irqchip drivers: Copied $IRQCHIP_COUNT Aspeed-specific irqchip drivers (not entire directory)" +echo "Include files: Copied all Aspeed/AST* header files" +echo "Dependencies: Copied $DEPENDENCY_COUNT dependency files (headers, drivers, trace events)" +echo "DTC includes: Skipped (symlinks excluded from diff to avoid duplicates)" +echo "=========================================" +} # end merge_aspeed_files + +# Build the target tree: +# - copy sonic-src (already has non-aspeed patches applied) +# - merge in upstream Aspeed source (replaces what the ###-> aspeed-upstream +# patches do in baseline) +# Hand-written aspeed-section patches are NOT applied here — they're skipped in +# baseline too, so they cancel by absence. +merge_aspeed_files "target" + +# Bail out if any patch failed while building the baseline (or the shared +# non-aspeed base). Those patches feed one side of the diff, so a half-applied +# tree would encode their missing changes as a giant phantom delta. +if [ "$TOTAL_APPLY_FAILURES" -gt 0 ]; then + echo "" + echo "=========================================" + echo "ABORT: $TOTAL_APPLY_FAILURES patch(es) failed to apply" + echo "=========================================" + echo "Tree state is invalid; refusing to produce $OUTPUT_PATCH" + echo "Look above for 'ERROR:' lines, fix the underlying issue, then re-run." + rm -f "$OUTPUT_PATCH" + exit 1 +fi + +# Step 6: Generate diff (excluding symlinked directories) +DIFF_FROM_DIR="$(basename "$BASELINE_TREE")" +DIFF_TO_DIR="$(basename "$SONIC_ASPEED")" + +echo "" +echo "Step 6: Generating diff: $DIFF_FROM_DIR -> $DIFF_TO_DIR" +echo "Note: Excluding scripts/dtc/include-prefixes (symlinks to arch/*/boot/dts)" +cd "$WORK_DIR" +diff -Naur --exclude='.git' --exclude='include-prefixes' "$DIFF_FROM_DIR" "$DIFF_TO_DIR" > "$WORK_DIR/aspeed-changes.diff" || true +echo "Diff created: $(du -sh $WORK_DIR/aspeed-changes.diff | cut -f1)" + +# Bail out early on empty diff — nothing to ship. +if [ ! -s "$WORK_DIR/aspeed-changes.diff" ]; then + rm -f "$OUTPUT_PATCH" + echo "" + echo "=========================================" + echo "Diff is empty — baseline already matches the fresh upstream merge." + echo "Nothing new in $ASPEED_REF relative to the current series state." + echo "No patch file written." + echo "=========================================" + exit 0 +fi + +# Step 7: Convert diff to git format (a/ and b/ prefixes) +echo "" +echo "Step 7: Converting to git patch format..." +sed -e "s|^--- ${DIFF_FROM_DIR}/|--- a/|" \ + -e "s|^+++ ${DIFF_TO_DIR}/|+++ b/|" \ + -e "s|^diff -Naur .* ${DIFF_FROM_DIR}/\(.*\) ${DIFF_TO_DIR}/\1|diff --git a/\1 b/\1|" \ + "$WORK_DIR/aspeed-changes.diff" > "$WORK_DIR/aspeed-changes-git.diff" +echo "Converted to git format: $(du -sh $WORK_DIR/aspeed-changes-git.diff | cut -f1)" + +# Step 8: Create git patch with header +echo "" +echo "Step 8: Creating git patch with header..." +if [ "$BASELINE_MODE" -eq 1 ]; then +cat > "$OUTPUT_PATCH" << PATCH_HEADER +From: ${PATCH_AUTHOR} +Date: $(date -R) +Subject: [PATCH] Aspeed AST2700 full support (baseline for ${KERNEL_VERSION}) + +Full Aspeed AST2700 support generated against the pristine ${KERNEL_VERSION} +SONiC kernel at Aspeed ${ASPEED_REF}. Baseline reset: the committed +###-> aspeed-upstream patches were NOT applied. Split with split-aspeed-patch.py +into 0001/0002/0003 to replace the currently-committed Aspeed support set. + +Signed-off-by: ${PATCH_AUTHOR} +--- +PATCH_HEADER +else +cat > "$OUTPUT_PATCH" << PATCH_HEADER +From: ${PATCH_AUTHOR} +Date: $(date -R) +Subject: [PATCH] Aspeed AST2700 incremental update + +Incremental patch capturing the delta between the currently-committed +patches-sonic/ series state and a fresh Aspeed upstream merge at +${ASPEED_REF}. Stage by appending inside the existing ###-> aspeed-upstream +sub-section in patches-sonic/series (after the prior upstream patches, before +###-> aspeed-upstream-end), so it stays ahead of the hand-written patches. + +Signed-off-by: ${PATCH_AUTHOR} +--- +PATCH_HEADER +fi + +cat "$WORK_DIR/aspeed-changes-git.diff" >> "$OUTPUT_PATCH" +echo "Git patch created: $(du -sh $OUTPUT_PATCH | cut -f1)" + +# Step 9: Generate statistics +echo "" +echo "=========================================" +echo "Patch Generation Complete!" +echo "=========================================" +echo "Output patch: $OUTPUT_PATCH" +echo "Patch size: $(du -sh $OUTPUT_PATCH | cut -f1)" +echo "Total lines: $(wc -l < $OUTPUT_PATCH)" +echo "Files changed: $(grep -c '^diff --git' $OUTPUT_PATCH)" +echo "" +echo "Sample of changed files:" +grep '^diff --git' "$OUTPUT_PATCH" | head -20 +echo "" + +# Step 10: Sanity check for non-Aspeed deletions +echo "=========================================" +echo "Step 10: Sanity Check for Non-Aspeed Deletions" +echo "=========================================" +echo "Scanning patch for suspicious deletions in Kconfig/Makefile files..." +echo "" + +SUSPICIOUS_DELETIONS_FOUND=0 + +# Check for deletions in Kconfig files (config entries) +echo "Checking Kconfig deletions..." +KCONFIG_DELETIONS=$(grep -B2 "^-config " "$OUTPUT_PATCH" | grep -v "^--$" | grep "^diff.*Kconfig" || true) + +if [ -n "$KCONFIG_DELETIONS" ]; then + echo "⚠️ WARNING: Found config deletions in Kconfig files:" + grep -B1 -A5 "^-config " "$OUTPUT_PATCH" | grep -v "aspeed\|ast2[567]00\|ast1[78]00" | head -50 + SUSPICIOUS_DELETIONS_FOUND=1 + echo "" +fi + +# Check for deletions in Makefile files (obj- entries) +echo "Checking Makefile deletions..." +MAKEFILE_OBJ_DELETIONS=$(grep "^-obj-\|^-subdir-y" "$OUTPUT_PATCH" | grep -v "aspeed\|ast2[567]00\|ast1[78]00" || true) + +if [ -n "$MAKEFILE_OBJ_DELETIONS" ]; then + echo "⚠️ WARNING: Found non-Aspeed obj/subdir deletions in Makefile files:" + echo "$MAKEFILE_OBJ_DELETIONS" | head -50 + SUSPICIOUS_DELETIONS_FOUND=1 + echo "" + + # Show specific problematic patterns + echo "Known problematic deletions to review:" + grep "^-.*pensando\|^-.*elba\|^-.*IRQ_PENSANDO\|^-.*EDAC_ELBA\|^-.*RESET_ELBASR\|^-.*STM32MP_EXTI\|^-.*I2C_RD1173" "$OUTPUT_PATCH" || echo " (None of the known problematic patterns found)" + echo "" +fi + +# Check for deletions in arch/arm64/boot/dts/Makefile specifically +echo "Checking arch/arm64/boot/dts/Makefile deletions..." +AARCH64_DTS_DELETIONS=$(grep -A20 "^diff.*arch/arm64/boot/dts/Makefile" "$OUTPUT_PATCH" | grep "^-subdir-y" | grep -v "aspeed" || true) + +if [ -n "$AARCH64_DTS_DELETIONS" ]; then + echo "⚠️ WARNING: Found subdirectory deletions in arch/arm64/boot/dts/Makefile:" + echo "$AARCH64_DTS_DELETIONS" + SUSPICIOUS_DELETIONS_FOUND=1 + echo "" +fi + +# Summary +echo "=========================================" +if [ $SUSPICIOUS_DELETIONS_FOUND -eq 1 ]; then + echo "❌ SANITY CHECK FAILED!" + echo "Found suspicious non-Aspeed deletions in the patch." + echo "Please review the patch carefully before applying." + echo "Look for deletions of:" + echo " - config entries (pensando, elba, etc.)" + echo " - Makefile obj- entries" + echo " - subdir-y entries" + echo "" + echo "To review deletions:" + echo " grep '^-config ' $OUTPUT_PATCH | grep -v aspeed" + echo " grep '^-obj-\\|^-subdir-y' $OUTPUT_PATCH | grep -v aspeed" +else + echo "✅ SANITY CHECK PASSED!" + echo "No suspicious non-Aspeed deletions found." +fi +echo "=========================================" +echo "" + +if [ "$BASELINE_MODE" -eq 1 ]; then +echo "To stage the baseline patch set (SONiC kernel version reset):" +echo " # 1. Split the full patch into the Rule-B 3-part set, writing directly" +echo " # into patches-sonic/ (replaces the prior 0001/0002/0003):" +echo " INPUT_PATCH=$OUTPUT_PATCH ASPEED_REF=$ASPEED_REF \\" +echo " OUTDIR=$KERNEL_DIR/patches-sonic $SCRIPT_DIR/split-aspeed-patch.py" +echo "" +echo " # 2. Refresh the ###-> aspeed-upstream block in patches-sonic/series so" +echo " # it lists the regenerated 0001/0002/0003 files, then re-run this" +echo " # script without --baseline to confirm the incremental delta is empty." +else +echo "To stage the incremental patch:" +echo " # 1. Rename to reflect what the bump represents, e.g." +echo " # aspeed-ast2700-v00.07.02-to-v00.07.03.patch" +echo " cp $OUTPUT_PATCH $KERNEL_DIR/patches-sonic/.patch" +echo "" +echo " # 2. Append the new patch INSIDE the existing ###-> aspeed-upstream" +echo " # sub-section in patches-sonic/series (after the prior upstream" +echo " # patches, before ###-> aspeed-upstream-end). It must stay ahead of" +echo " # the hand-written patches, and it applies on top of the upstream" +echo " # patches the diff was generated against. Example:" +echo " #" +echo " # ###-> aspeed" +echo " # ###-> aspeed-upstream" +echo " # aspeed-ast2700-support.patch" +echo " # .patch <-- new" +echo " # ###-> aspeed-upstream-end" +echo " # -board-dts.patch" +echo " # ###-> aspeed-end" +fi +echo "" + diff --git a/scripts/smart_merge.py b/scripts/smart_merge.py new file mode 100755 index 000000000..197353193 --- /dev/null +++ b/scripts/smart_merge.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Smart merge for Kconfig/Makefile files. +Uses a common ancestor approach: extract Aspeed additions from diff, +add them to SONiC base at correct locations while preserving order. +""" +import sys +import re +import difflib + +def smart_merge(sonic_file, aspeed_file, target_file): + # Aspeed-related patterns + aspeed_patterns = [ + re.compile(r'aspeed', re.IGNORECASE), + re.compile(r'ast2[567]00', re.IGNORECASE), + re.compile(r'ast1[78]00', re.IGNORECASE), + re.compile(r'AST2700_IRQ'), + re.compile(r'ARCH_ASPEED'), + ] + + def is_aspeed_related(text): + return any(pattern.search(text) for pattern in aspeed_patterns) + + # Read files + with open(sonic_file, 'r') as f: + sonic_lines = f.readlines() + + with open(aspeed_file, 'r') as f: + aspeed_lines = f.readlines() + + # Use difflib to get a proper sequence matcher + matcher = difflib.SequenceMatcher(None, sonic_lines, aspeed_lines) + + result = [] + + for tag, i1, i2, j1, j2 in matcher.get_opcodes(): + if tag == 'equal': + # Common lines - keep them + result.extend(sonic_lines[i1:i2]) + elif tag == 'delete': + # Lines deleted in aspeed - KEEP THEM (preserve SONiC content) + result.extend(sonic_lines[i1:i2]) + elif tag == 'insert': + # Lines added in aspeed - check if Aspeed-related + new_lines = aspeed_lines[j1:j2] + aspeed_related = any(is_aspeed_related(line) for line in new_lines) + if aspeed_related: + # Add these lines + result.extend(new_lines) + # Otherwise skip (non-Aspeed additions) + elif tag == 'replace': + # Lines changed - keep SONiC version, then add Aspeed additions if any + result.extend(sonic_lines[i1:i2]) + new_lines = aspeed_lines[j1:j2] + aspeed_related = any(is_aspeed_related(line) for line in new_lines) + if aspeed_related: + # Also add the Aspeed replacements + result.extend(new_lines) + + # Write result + with open(target_file, 'w') as f: + f.writelines(result) + +if __name__ == '__main__': + smart_merge(sys.argv[1], sys.argv[2], sys.argv[3]) diff --git a/scripts/split-aspeed-patch.py b/scripts/split-aspeed-patch.py new file mode 100755 index 000000000..5f8fde14f --- /dev/null +++ b/scripts/split-aspeed-patch.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# Splits the monolithic Aspeed incremental patch produced by +# generate-aspeed-patch.sh into the Rule-B 3-part set. Intended as the +# post-generation step of a baseline reset (SONiC kernel version bump), where a +# fresh full merge replaces 0001/0002/0003 rather than appending an increment. +# +# Rule B (per changed file): +# 0001 - anything under the Aspeed namespace (path matches aspeed/astNN00), +# both new files and modifications to Aspeed drivers already upstream. +# 0002 - brand-new files outside the Aspeed namespace (self-contained +# dependency subsystems, e.g. i3c/mctp, jtag, dwc trng). +# 0003 - modifications to existing shared/common kernel files (the +# vendor-independent review surface, e.g. pwm/core.c, Kconfig merges). +# +# All configuration mirrors generate-aspeed-patch.sh and is overridable via the +# environment so the two scripts share one source of truth: +# INPUT_PATCH, WORK_DIR, OUTDIR, KERNEL_VERSION, ASPEED_REF/ASPEED_TAG, +# PATCH_AUTHOR, PATCH_DATE +import os +import re +import subprocess +import sys +from email.utils import formatdate + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +KERNEL_DIR = os.path.dirname(SCRIPT_DIR) + +ASPEED_RE = re.compile(r"aspeed|ast1[0-9]00|ast2[0-9]00", re.I) + + +def _git_config(key): + try: + out = subprocess.run(["git", "-C", KERNEL_DIR, "config", key], + capture_output=True, text=True, check=False) + return out.stdout.strip() + except OSError: + return "" + + +def _kernel_version(): + v = os.environ.get("KERNEL_VERSION", "").strip() + if v: + return v + mk = os.path.join(KERNEL_DIR, "Makefile") + with open(mk, "r", errors="surrogateescape") as fh: + for line in fh: + m = re.match(r"^KERNEL_VERSION[ \t]*[?:+]?=[ \t]*(.+?)[ \t]*$", line) + if m: + return m.group(1).strip() + return "unknown" + + +def _author(): + a = os.environ.get("PATCH_AUTHOR", "").strip() + if a: + return a + name, email = _git_config("user.name"), _git_config("user.email") + if name and email: + return f"{name} <{email}>" + sys.exit("ERROR: no PATCH_AUTHOR and git user.name/user.email unset") + + +def _config(): + work = os.environ.get("WORK_DIR") or os.path.join( + os.environ.get("TMPDIR", "/tmp"), "aspeed-patch-gen") + ref = (os.environ.get("ASPEED_REF") or os.environ.get("ASPEED_TAG") or "").strip() + if not ref: + ref = "unknown-ref" + sys.stderr.write("WARNING: ASPEED_REF/ASPEED_TAG unset; using 'unknown-ref'\n") + return { + "src": os.environ.get("INPUT_PATCH") or os.path.join( + work, "aspeed-ast2700-incremental.patch"), + "outdir": os.environ.get("OUTDIR") or os.path.join(work, "aspeed-split"), + "kver": _kernel_version(), + "ref": ref, + "sfx": ref.lstrip("v"), + "author": _author(), + "date": os.environ.get("PATCH_DATE") or formatdate(localtime=True), + } + + +def _buckets(cfg): + kver, sfx, ref = cfg["kver"], cfg["sfx"], cfg["ref"] + return { + "0001": { + "file": f"{cfg['outdir']}/0001-aspeed-ast2700-support.patch", + "subject": "Aspeed AST2700 support: Aspeed-namespace files (new and updated)", + "body": ("All files under the Aspeed namespace (new files plus updates to\n" + "Aspeed drivers already present upstream), from a fresh Aspeed\n" + f"merge at {ref} on the {kver} SONiC kernel."), + "lines": [], + }, + "0002": { + "file": f"{cfg['outdir']}/0002-aspeed-ast2700-non-aspeed-deps-update-to-{kver}-{sfx}.patch", + "subject": "Aspeed AST2700 support: new non-Aspeed dependency subsystems", + "body": ("New, self-contained non-Aspeed subsystems the Aspeed image depends\n" + "on (i3c/mctp, jtag, dwc TRNG, mctp-pcie-vdm, xdma trace), from a\n" + f"fresh Aspeed merge at {ref} on the {kver} SONiC kernel."), + "lines": [], + }, + "0003": { + "file": f"{cfg['outdir']}/0003-aspeed-ast2700-shared-kernel-changes-update-to-{kver}-{sfx}.patch", + "subject": "Aspeed AST2700 support: modifications to shared/common kernel files", + "body": ("Modifications to existing vendor-independent (shared/common) kernel\n" + "files - the common-code review surface (Kconfig/Makefile merges,\n" + "ftgmac100, pwm/core, clk-provider, pwm.h, videodev2.h), from a\n" + f"fresh Aspeed merge at {ref} on the {kver} SONiC kernel."), + "lines": [], + }, + } + + +def _header(cfg, b): + return (f"From: {cfg['author']}\n" + f"Date: {cfg['date']}\n" + f"Subject: [PATCH] {b['subject']}\n\n" + f"{b['body']}\n\n" + f"Signed-off-by: {cfg['author']}\n" + "---\n") + + +def _classify(path, is_new): + if ASPEED_RE.search(path): + return "0001" + return "0002" if is_new else "0003" + + +def main(): + cfg = _config() + if not os.path.isfile(cfg["src"]): + sys.exit(f"ERROR: input patch not found: {cfg['src']}") + os.makedirs(cfg["outdir"], exist_ok=True) + buckets = _buckets(cfg) + with open(cfg["src"], "r", errors="surrogateescape") as fh: + lines = fh.readlines() + start = next((i for i, l in enumerate(lines) if l.startswith("diff --git ")), None) + if start is None: + sys.exit(f"ERROR: no 'diff --git' blocks found in {cfg['src']}") + blocks, cur = [], None + for l in lines[start:]: + if l.startswith("diff --git "): + if cur is not None: + blocks.append(cur) + cur = [l] + else: + cur.append(l) + if cur is not None: + blocks.append(cur) + counts = {"0001": 0, "0002": 0, "0003": 0} + for blk in blocks: + path = re.match(r"diff --git a/(.*?) b/", blk[0]).group(1) + is_new = next((x for x in blk if x.startswith("@@ ")), "").startswith("@@ -0,0") + key = _classify(path, is_new) + buckets[key]["lines"].extend(blk) + counts[key] += 1 + for key, b in buckets.items(): + with open(b["file"], "w", errors="surrogateescape") as out: + out.write(_header(cfg, b)) + out.writelines(b["lines"]) + print(f"{key}: {counts[key]:3d} files -> {b['file']}") + print(f"TOTAL: {sum(counts.values())} files") + print(f"\nCopy the three files into {KERNEL_DIR}/patches-sonic/ and refresh the\n" + "###-> aspeed-upstream block in patches-sonic/series.") + + +if __name__ == "__main__": + main()